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:
@@ -13,8 +13,7 @@ import '../../models/inventory.dart';
|
|||||||
import '../../core/auth/auth_state.dart';
|
import '../../core/auth/auth_state.dart';
|
||||||
import '../../widgets/write_guard.dart';
|
import '../../widgets/write_guard.dart';
|
||||||
import '../../providers/inventory_provider.dart';
|
import '../../providers/inventory_provider.dart';
|
||||||
import '../../widgets/data_table_card.dart';
|
import '../../widgets/kpi_card.dart' show StatusPill; // 移动卡片状态药丸
|
||||||
import '../../widgets/kpi_card.dart' show StatusPill; // 旧 tab/移动卡片用,待统一 DsBadge
|
|
||||||
import 'package:lucide_icons_flutter/lucide_icons.dart';
|
import 'package:lucide_icons_flutter/lucide_icons.dart';
|
||||||
import '../../widgets/ds/ds_atoms.dart';
|
import '../../widgets/ds/ds_atoms.dart';
|
||||||
import '../../widgets/ds/ds_kpi.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 '../../core/theme/app_dims.g.dart';
|
||||||
import '../../widgets/multi_select_dropdown.dart' show ColDef, ColumnToggleButton, FilterableColumnHeader;
|
import '../../widgets/multi_select_dropdown.dart' show ColDef, ColumnToggleButton, FilterableColumnHeader;
|
||||||
import '../../core/storage/column_prefs.dart';
|
import '../../core/storage/column_prefs.dart';
|
||||||
import '../../widgets/page_scaffold.dart';
|
|
||||||
import '../../core/utils/export_util.dart';
|
import '../../core/utils/export_util.dart';
|
||||||
import '../../core/utils/print_util.dart';
|
import '../../core/utils/print_util.dart';
|
||||||
import '../../core/utils/dialog_util.dart';
|
import '../../core/utils/dialog_util.dart';
|
||||||
import '../../widgets/label_preview_dialog.dart';
|
import '../../widgets/label_preview_dialog.dart';
|
||||||
import '../../providers/product_provider.dart';
|
import '../../providers/product_provider.dart';
|
||||||
import '../../providers/product_option_provider.dart';
|
import '../../providers/product_option_provider.dart';
|
||||||
import '../../providers/tab_state_provider.dart';
|
|
||||||
import '../../providers/shop_provider.dart' show shopInfoProvider;
|
import '../../providers/shop_provider.dart' show shopInfoProvider;
|
||||||
|
|
||||||
class InventoryListScreen extends ConsumerStatefulWidget {
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
// 照原型 inventory.html:单视图(无 tab)。库存预警并入 KPI 缺货卡点击筛选,
|
// 照原型 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 {
|
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 }
|
enum _ImportStage { uploading, processing, done, error }
|
||||||
|
|||||||
Reference in New Issue
Block a user