feat(client): 所有表格列头加筛选,图标更大靠右对齐

FilterableColumnHeader:
- mainAxisAlignment.spaceBetween 将图标推到列头右侧
- 漏斗图标从 13px 增大到 16px,清除图标 14px

各页面改动:
- batch_tracking:状态/仓库/供应商筛选移入列头,工具栏合并为一行
- finance:类型/往来单位筛选移入列头,工具栏保留月份选择和显示字段
- inventory:仓库筛选从 API 端单选改为客户端多选,移入"仓库"列头
- products:品牌列头加筛选,从加载数据中动态派生选项

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-04-18 19:00:03 +08:00
parent c5c74d59ab
commit 5a3907e3f9
5 changed files with 170 additions and 191 deletions
@@ -5,6 +5,7 @@ import '../../core/theme/app_theme.dart';
import '../../models/product.dart';
import '../../providers/product_provider.dart';
import '../../widgets/data_table_card.dart';
import '../../widgets/multi_select_dropdown.dart' show FilterableColumnHeader;
import '../../widgets/page_scaffold.dart';
class ProductsScreen extends ConsumerStatefulWidget {
@@ -17,6 +18,7 @@ class ProductsScreen extends ConsumerStatefulWidget {
class _ProductsScreenState extends ConsumerState<ProductsScreen> {
final _searchCtrl = TextEditingController();
Timer? _debounce;
Set<String> _filterBrand = {};
@override
void dispose() {
@@ -133,6 +135,17 @@ class _ProductsScreenState extends ConsumerState<ProductsScreen> {
Widget _buildProductList(
List<Product> products, int totalCount, int page) {
// 品牌选项从当前数据中派生,客户端筛选
final brandOptions = products
.map((p) => p.brand ?? '')
.where((s) => s.isNotEmpty)
.toSet()
.toList()
..sort();
final filteredProducts = _filterBrand.isEmpty
? products
: products.where((p) => _filterBrand.contains(p.brand ?? '')).toList();
return DataTableCard(
totalCount: totalCount,
page: page,
@@ -160,18 +173,25 @@ class _ProductsScreenState extends ConsumerState<ProductsScreen> {
),
],
),
columns: const [
DataColumn(label: Text('商品编码')),
DataColumn(label: Text('商品名称')),
DataColumn(label: Text('品牌')),
DataColumn(label: Text('规格')),
DataColumn(label: Text('单位')),
DataColumn(label: Text('进价'), numeric: true),
DataColumn(label: Text('售价'), numeric: true),
DataColumn(label: Text('安全库存'), numeric: true),
DataColumn(label: Text('操作')),
columns: [
const DataColumn(label: Text('商品编码')),
const DataColumn(label: Text('商品名称')),
DataColumn(
label: FilterableColumnHeader(
text: '品牌',
options: brandOptions,
selected: _filterBrand,
onChanged: (v) => setState(() => _filterBrand = v),
),
),
const DataColumn(label: Text('规格')),
const DataColumn(label: Text('单位')),
const DataColumn(label: Text('进价'), numeric: true),
const DataColumn(label: Text('售价'), numeric: true),
const DataColumn(label: Text('安全库存'), numeric: true),
const DataColumn(label: Text('操作')),
],
rows: products.isEmpty
rows: filteredProducts.isEmpty
? [
const DataRow(cells: [
DataCell(SizedBox()),
@@ -186,7 +206,7 @@ class _ProductsScreenState extends ConsumerState<ProductsScreen> {
DataCell(SizedBox()),
])
]
: products
: filteredProducts
.map((p) => DataRow(
cells: [
DataCell(Text(p.code,