feat(client): 库存查询新增规格/系列/入库时间;筛选组件支持搜索+滚动+标签
- 库存查询加规格、系列服务端筛选(后端 IN 条件,前端从 product-options API 加载全量选项) - 库存查询新增「入库时间」列,导出 Excel 同步带上 - 入库单商品明细:隐藏商品编码列,生产日期从选填折叠区移至主行常显 - 筛选弹窗重构:顶部搜索框实时过滤 + 滚动列表(ConstrainedBox maxHeight)根治溢出 - 已选项以 Chip 标签置顶展示,每个标签带 × 可单独取消 - FilterableColumnHeader 筛选图标改为常驻,不再需要 hover 才显示 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -17,6 +17,7 @@ import '../../widgets/page_scaffold.dart';
|
||||
import '../../core/utils/export_util.dart';
|
||||
import '../../core/utils/print_util.dart';
|
||||
import '../../providers/product_provider.dart';
|
||||
import '../../providers/product_option_provider.dart';
|
||||
import '../../providers/tab_state_provider.dart';
|
||||
import '../../providers/shop_provider.dart' show shopInfoProvider;
|
||||
|
||||
@@ -31,6 +32,9 @@ class InventoryListScreen extends ConsumerStatefulWidget {
|
||||
class _InventoryListScreenState extends ConsumerState<InventoryListScreen> {
|
||||
final _searchCtrl = TextEditingController();
|
||||
Set<String> _filterWarehouse = {};
|
||||
Set<String> _filterSpec = {};
|
||||
Set<String> _filterSeries = {};
|
||||
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
@@ -365,18 +369,24 @@ class _InventoryListScreenState extends ConsumerState<InventoryListScreen> {
|
||||
items.where((i) => i.minStock != null && i.quantity < i.minStock!).length;
|
||||
final emptyCount = items.where((i) => i.quantity == 0).length;
|
||||
|
||||
// 仓库选项从数据中派生,客户端筛选
|
||||
// 仓库选项从当前页派生(客户端筛选),系列/规格从 product-options API 加载(服务端筛选)
|
||||
final warehouseOptions = items
|
||||
.map((i) => i.warehouseName)
|
||||
.where((s) => s.isNotEmpty)
|
||||
.toSet()
|
||||
.toList()
|
||||
..sort();
|
||||
final seriesOptions = ref.watch(productSeriesListProvider).valueOrNull
|
||||
?.map((s) => s.name)
|
||||
.toList() ??
|
||||
[];
|
||||
final specOptions = ref.watch(productSpecListProvider).valueOrNull
|
||||
?.map((s) => s.name)
|
||||
.toList() ??
|
||||
[];
|
||||
final filteredItems = _filterWarehouse.isEmpty
|
||||
? items
|
||||
: items
|
||||
.where((i) => _filterWarehouse.contains(i.warehouseName))
|
||||
.toList();
|
||||
: items.where((i) => _filterWarehouse.contains(i.warehouseName)).toList();
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
@@ -440,7 +450,7 @@ class _InventoryListScreenState extends ConsumerState<InventoryListScreen> {
|
||||
OutlinedButton.icon(
|
||||
onPressed: () => exportExcel(
|
||||
filename: '库存查询',
|
||||
headers: ['商品编码', '商品名称', '规格', '批次号', '仓库', '库存量', '单价', '生产日期', '供应商', '安全库存', '状态'],
|
||||
headers: ['商品编码', '商品名称', '规格', '系列', '批次号', '仓库', '库存量', '单价', '生产日期', '入库时间', '供应商', '安全库存', '状态'],
|
||||
rows: filteredItems.map((item) {
|
||||
final status = item.quantity == 0
|
||||
? '缺货'
|
||||
@@ -451,11 +461,13 @@ class _InventoryListScreenState extends ConsumerState<InventoryListScreen> {
|
||||
item.productCode.isEmpty ? '' : item.productCode,
|
||||
item.productName.isEmpty ? '' : item.productName,
|
||||
item.spec.isEmpty ? '' : item.spec,
|
||||
item.series.isEmpty ? '' : item.series,
|
||||
item.batchNo.isEmpty ? '' : item.batchNo,
|
||||
item.warehouseName.isEmpty ? '' : item.warehouseName,
|
||||
'${item.quantity.toStringAsFixed(0)} ${item.unit}'.trim(),
|
||||
item.unitPrice != null ? item.unitPrice!.toStringAsFixed(2) : '',
|
||||
item.productionDate ?? '',
|
||||
item.createdAt != null && item.createdAt!.length >= 10 ? item.createdAt!.substring(0, 10) : '',
|
||||
item.supplierName.isEmpty ? '' : item.supplierName,
|
||||
item.minStock ?? '',
|
||||
status,
|
||||
@@ -490,8 +502,28 @@ class _InventoryListScreenState extends ConsumerState<InventoryListScreen> {
|
||||
columns: [
|
||||
const DataColumn(label: Text('商品编码')),
|
||||
const DataColumn(label: Text('商品名称')),
|
||||
const DataColumn(label: Text('规格')),
|
||||
const DataColumn(label: Text('系列')),
|
||||
DataColumn(
|
||||
label: FilterableColumnHeader(
|
||||
text: '规格',
|
||||
options: specOptions,
|
||||
selected: _filterSpec,
|
||||
onChanged: (v) {
|
||||
setState(() => _filterSpec = v);
|
||||
ref.read(inventoryListProvider.notifier).setSpec(v.toList());
|
||||
},
|
||||
),
|
||||
),
|
||||
DataColumn(
|
||||
label: FilterableColumnHeader(
|
||||
text: '系列',
|
||||
options: seriesOptions,
|
||||
selected: _filterSeries,
|
||||
onChanged: (v) {
|
||||
setState(() => _filterSeries = v);
|
||||
ref.read(inventoryListProvider.notifier).setSeries(v.toList());
|
||||
},
|
||||
),
|
||||
),
|
||||
const DataColumn(label: Text('批次号')),
|
||||
DataColumn(
|
||||
label: FilterableColumnHeader(
|
||||
@@ -504,6 +536,7 @@ class _InventoryListScreenState extends ConsumerState<InventoryListScreen> {
|
||||
const DataColumn(label: Text('库存量'), numeric: true),
|
||||
const DataColumn(label: Text('单价'), numeric: true),
|
||||
const DataColumn(label: Text('生产日期')),
|
||||
const DataColumn(label: Text('入库时间')),
|
||||
const DataColumn(label: Text('供应商')),
|
||||
const DataColumn(label: Text('备注')),
|
||||
const DataColumn(label: Text('状态')),
|
||||
@@ -527,6 +560,7 @@ class _InventoryListScreenState extends ConsumerState<InventoryListScreen> {
|
||||
DataCell(SizedBox()),
|
||||
DataCell(SizedBox()),
|
||||
DataCell(SizedBox()),
|
||||
DataCell(SizedBox()),
|
||||
])
|
||||
]
|
||||
: filteredItems
|
||||
@@ -590,6 +624,12 @@ class _InventoryListScreenState extends ConsumerState<InventoryListScreen> {
|
||||
: '-',
|
||||
)),
|
||||
DataCell(Text(item.productionDate ?? '-')),
|
||||
DataCell(Text(
|
||||
item.createdAt != null && item.createdAt!.length >= 10
|
||||
? item.createdAt!.substring(0, 10)
|
||||
: '-',
|
||||
style: const TextStyle(fontSize: 12, color: AppTheme.textSecondary),
|
||||
)),
|
||||
DataCell(Text(item.supplierName.isEmpty ? '-' : item.supplierName)),
|
||||
DataCell(
|
||||
Tooltip(
|
||||
|
||||
Reference in New Issue
Block a user