refactor(client): 库存屏移除预警/流水死 tab 及关联死代码

build() 只渲染库存视图(预警并入 KPI 缺货卡点击筛选、流水移到商品详情),
_buildWarningTab/_buildLogTab/_warningCard/_logCard/_DirectionBadge 已成死代码,
连同未用的 data_table_card/tab_state_provider/page_scaffold import 一并移除(约 -290 行)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-07-01 20:46:41 +08:00
parent ce18ed3fc9
commit 8a8043573c
@@ -13,8 +13,7 @@ import '../../models/inventory.dart';
import '../../core/auth/auth_state.dart';
import '../../widgets/write_guard.dart';
import '../../providers/inventory_provider.dart';
import '../../widgets/data_table_card.dart';
import '../../widgets/kpi_card.dart' show StatusPill; // 旧 tab/移动卡片用,待统一 DsBadge
import '../../widgets/kpi_card.dart' show StatusPill; // 移动卡片状态药丸
import 'package:lucide_icons_flutter/lucide_icons.dart';
import '../../widgets/ds/ds_atoms.dart';
import '../../widgets/ds/ds_kpi.dart';
@@ -23,14 +22,12 @@ import '../../widgets/mobile_list_card.dart';
import '../../core/theme/app_dims.g.dart';
import '../../widgets/multi_select_dropdown.dart' show ColDef, ColumnToggleButton, FilterableColumnHeader;
import '../../core/storage/column_prefs.dart';
import '../../widgets/page_scaffold.dart';
import '../../core/utils/export_util.dart';
import '../../core/utils/print_util.dart';
import '../../core/utils/dialog_util.dart';
import '../../widgets/label_preview_dialog.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;
class InventoryListScreen extends ConsumerStatefulWidget {
@@ -469,63 +466,6 @@ class _InventoryListScreenState extends ConsumerState<InventoryListScreen> {
);
}
/// 库存预警:窄屏卡片
Widget _warningCard(Inventory item) {
return MobileListCard(
onTap: item.productId != null
? () => context.push('/products/${item.productId}')
: null,
title: Text(item.productName.isEmpty ? '-' : item.productName),
subtitle: item.productCode.isEmpty ? null : Text(item.productCode),
trailing: _InventoryStatusBadge(item),
fields: [
MobileCardField('仓库', item.warehouseName.isEmpty ? '-' : item.warehouseName),
MobileCardField('当前库存', null,
valueWidget: Text(
item.quantity.toStringAsFixed(0),
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w700,
color: item.quantity == 0 ? context.tokens.danger : context.tokens.accent),
)),
MobileCardField('安全库存', '${item.minStock}'),
MobileCardField('缺口', null,
valueWidget: Text(
'${item.minStock! - item.quantity.toInt()}',
style: TextStyle(
fontSize: 13,
color: context.tokens.danger,
fontWeight: FontWeight.w600),
)),
],
);
}
/// 流水记录:窄屏卡片
Widget _logCard(dynamic log) {
return MobileListCard(
title: Text(log.productName ?? '-'),
subtitle: Text(log.createdAt?.substring(0, 19) ?? '-'),
trailing: _DirectionBadge(log.direction),
fields: [
MobileCardField('仓库', log.warehouseName ?? '-'),
MobileCardField('数量', null,
valueWidget: Text(
log.quantity.toStringAsFixed(0),
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w600,
color: log.direction == 'in'
? context.tokens.success
: context.tokens.danger),
)),
MobileCardField('变前', log.qtyBefore?.toStringAsFixed(0) ?? '-'),
MobileCardField('变后', log.qtyAfter?.toStringAsFixed(0) ?? '-'),
if (log.refType != null) MobileCardField('来源', log.refType),
],
);
}
@override
Widget build(BuildContext context) {
// 照原型 inventory.html:单视图(无 tab)。库存预警并入 KPI 缺货卡点击筛选,
@@ -931,267 +871,6 @@ class _InventoryListScreenState extends ConsumerState<InventoryListScreen> {
);
}
Widget _buildWarningTab() {
final asyncInventory = ref.watch(inventoryListProvider);
return asyncInventory.when(
loading: () => const Center(child: CircularProgressIndicator()),
error: (e, _) => Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.error_outline, size: 40, color: context.tokens.muted),
const SizedBox(height: 12),
Text('加载失败:$e',
style: TextStyle(color: context.tokens.muted),
textAlign: TextAlign.center),
const SizedBox(height: 12),
ElevatedButton(
onPressed: () =>
ref.read(inventoryListProvider.notifier).reload(),
child: const Text('重试'),
),
],
),
),
data: (result) {
final warnings = result.data
.where((item) =>
item.minStock != null && item.quantity < item.minStock!)
.toList();
return DataTableCard(
totalCount: warnings.length,
page: 1,
toolbar: Row(
children: [
Icon(Icons.warning_amber,
color: context.tokens.accent, size: 18),
const SizedBox(width: 8),
Text(
'${warnings.length} 个商品库存低于安全库存',
style: TextStyle(
fontSize: 13, color: context.tokens.accent),
),
const Spacer(),
OutlinedButton.icon(
onPressed: () => exportExcel(
filename: '库存预警',
headers: ['商品编码', '商品名称', '仓库', '当前库存', '安全库存', '缺口', '状态'],
rows: warnings.map((item) => [
item.productCode.isEmpty ? '' : item.productCode,
item.productName.isEmpty ? '' : item.productName,
item.warehouseName.isEmpty ? '' : item.warehouseName,
item.quantity.toInt(),
item.minStock ?? 0,
item.minStock! - item.quantity.toInt(),
item.quantity == 0 ? '缺货' : '库存不足',
]).toList(),
),
icon: const Icon(Icons.download, size: 16),
label: const Text('导出'),
),
],
),
mobileCards: warnings.map(_warningCard).toList(),
columns: const [
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('状态')),
],
rows: warnings.isEmpty
? [
DataRow(cells: [
const DataCell(SizedBox()),
DataCell(Text('暂无预警商品',
style: TextStyle(
color: context.tokens.muted))),
const DataCell(SizedBox()),
const DataCell(SizedBox()),
const DataCell(SizedBox()),
const DataCell(SizedBox()),
const DataCell(SizedBox()),
])
]
: warnings
.map((item) => DataRow(
color: WidgetStateProperty.all(
item.quantity == 0
? context.tokens.danger.withOpacity(0.05)
: context.tokens.accent.withOpacity(0.04)),
cells: [
DataCell(Text(
item.productCode.isEmpty ? '-' : item.productCode,
style: const TextStyle(
fontFamily: 'monospace',
fontSize: 12))),
DataCell(item.productId != null
? GestureDetector(
onTap: () => context.push('/products/${item.productId}'),
child: SizedBox(
width: 180,
child: Text(
item.productName.isEmpty ? '-' : item.productName,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: context.tokens.primary,
decoration: TextDecoration.underline,
decorationColor: context.tokens.primary,
)),
),
)
: Text(item.productName.isEmpty ? '-' : item.productName,
overflow: TextOverflow.ellipsis)),
DataCell(Text(item.warehouseName.isEmpty ? '-' : item.warehouseName)),
DataCell(Text(
item.quantity.toStringAsFixed(0),
style: TextStyle(
fontWeight: FontWeight.w700,
color: item.quantity == 0
? context.tokens.danger
: context.tokens.accent),
)),
DataCell(Text('${item.minStock}')),
DataCell(Text(
'${item.minStock! - item.quantity.toInt()}',
style: TextStyle(
color: context.tokens.danger,
fontWeight: FontWeight.w600),
)),
DataCell(_InventoryStatusBadge(item)),
],
))
.toList(),
);
},
);
}
Widget _buildLogTab() {
final asyncLogs = ref.watch(inventoryLogProvider);
return asyncLogs.when(
loading: () => const Center(child: CircularProgressIndicator()),
error: (e, _) => Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.error_outline, size: 40, color: context.tokens.muted),
const SizedBox(height: 12),
Text('加载失败:$e',
style: TextStyle(color: context.tokens.muted),
textAlign: TextAlign.center),
const SizedBox(height: 12),
ElevatedButton(
onPressed: () =>
ref.read(inventoryLogProvider.notifier).reload(),
child: const Text('重试'),
),
],
),
),
data: (result) {
final logs = result.data;
return DataTableCard(
totalCount: result.total,
page: result.page,
pageSize: result.pageSize,
onPageChanged: (p) =>
ref.read(inventoryLogProvider.notifier).setPage(p),
onPageSizeChanged: (s) =>
ref.read(inventoryLogProvider.notifier).setPageSize(s),
toolbar: Row(
children: [
Icon(Icons.history, color: context.tokens.primary, size: 18),
const SizedBox(width: 8),
const Text('库存流水记录',
style: TextStyle(
fontSize: 14, fontWeight: FontWeight.w500)),
const Spacer(),
OutlinedButton.icon(
onPressed: () => exportExcel(
filename: '库存流水',
headers: ['商品名称', '仓库', '方向', '数量', '变前', '变后', '来源', '时间'],
rows: logs.map((log) => [
log.productName ?? '',
log.warehouseName ?? '',
log.direction == 'in' ? '入库' : '出库',
log.quantity.toInt(),
log.qtyBefore?.toInt() ?? 0,
log.qtyAfter?.toInt() ?? 0,
log.refType ?? '',
log.createdAt?.substring(0, 19) ?? '',
]).toList(),
),
icon: const Icon(Icons.download, size: 16),
label: const Text('导出'),
),
],
),
mobileCards: logs.map(_logCard).toList(),
columns: const [
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('来源')),
DataColumn(label: Text('时间')),
],
rows: logs.isEmpty
? [
DataRow(cells: [
const DataCell(SizedBox()),
DataCell(Text('暂无流水记录',
style: TextStyle(
color: context.tokens.muted))),
const DataCell(SizedBox()),
const DataCell(SizedBox()),
const DataCell(SizedBox()),
const DataCell(SizedBox()),
const DataCell(SizedBox()),
const DataCell(SizedBox()),
])
]
: logs
.map((log) => DataRow(cells: [
DataCell(SizedBox(
width: 160,
child: Text(log.productName ?? '-',
overflow: TextOverflow.ellipsis),
)),
DataCell(Text(log.warehouseName ?? '-')),
DataCell(_DirectionBadge(log.direction)),
DataCell(Text(
log.quantity.toStringAsFixed(0),
style: TextStyle(
fontWeight: FontWeight.w600,
color: log.direction == 'in'
? context.tokens.success
: context.tokens.danger),
)),
DataCell(Text(
log.qtyBefore?.toStringAsFixed(0) ?? '-')),
DataCell(Text(
log.qtyAfter?.toStringAsFixed(0) ?? '-')),
DataCell(Text(log.refType ?? '-',
style: TextStyle(
fontSize: 12,
color: context.tokens.muted))),
DataCell(Text(
log.createdAt?.substring(0, 19) ?? '-',
style: TextStyle(
fontSize: 12,
color: context.tokens.muted),
)),
]))
.toList(),
);
},
);
}
}
class _InventoryStatusBadge extends StatelessWidget {
@@ -1212,22 +891,6 @@ class _InventoryStatusBadge extends StatelessWidget {
}
}
class _DirectionBadge extends StatelessWidget {
final String direction;
const _DirectionBadge(this.direction);
@override
Widget build(BuildContext context) {
final t = context.tokens;
final isIn = direction == 'in';
return StatusPill(
label: isIn ? '入库' : '出库',
color: isIn ? t.success : t.danger,
background: isIn ? t.okSoft : t.dangerBg,
);
}
}
// ── 导入进度对话框 ──────────────────────────────────────────
enum _ImportStage { uploading, processing, done, error }