feat(client): 库存/盘点/财务窄屏对齐移动原型(KPI 网格/卡片流/图标徽章/sheet)
- 库存列表:窄屏隐藏页内大标题头;KPI 2×2 MKpiGrid(缺货卡 warn+点击筛选); MSearchRow(详搜钮开筛选 sheet:状态/规格/系列);卡片徽章换图标变体 - 库存盘点:窄屏卡片流(单号/范围·项数/进行中徽章带图标/差异/日期)+ 详情 sheet(drow 键值行)+ 底部操作条;DateTime.now→appNow 可注入时钟 - 财务:窄屏隐藏大标题头;KPI 2×2(收入 up 绿/支出 down 红/应收 warn); DsSeg 应收/应付/流水三分段 + 卡片流(类型徽章带图标、金额正负色) - golden:新增 m_inventory / m_inventory_check / m_finance 三屏×三主题 (390×844@2x);重生成既有 inventory_list_mobile / finance_mobile 基准 (桌面 golden 零改动) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@@ -22,6 +22,8 @@ import '../../widgets/ds/ds_atoms.dart';
|
|||||||
import '../../widgets/ds/ds_bar_chart.dart';
|
import '../../widgets/ds/ds_bar_chart.dart';
|
||||||
import '../../widgets/ds/ds_kpi.dart';
|
import '../../widgets/ds/ds_kpi.dart';
|
||||||
import '../../widgets/ds/ds_table.dart';
|
import '../../widgets/ds/ds_table.dart';
|
||||||
|
import '../../widgets/ds/m_kpi_grid.dart';
|
||||||
|
import '../../widgets/ds/status_icon_map.dart';
|
||||||
import '../../widgets/finance_entry_dialog.dart';
|
import '../../widgets/finance_entry_dialog.dart';
|
||||||
import '../../widgets/finance_partner_drawer.dart';
|
import '../../widgets/finance_partner_drawer.dart';
|
||||||
import '../../widgets/mobile_list_card.dart';
|
import '../../widgets/mobile_list_card.dart';
|
||||||
@@ -42,6 +44,8 @@ class _FinanceScreenState extends ConsumerState<FinanceScreen> {
|
|||||||
String _rangeLabel = '本月';
|
String _rangeLabel = '本月';
|
||||||
// 流水类型筛选(原型 3 chips 扩到 5)
|
// 流水类型筛选(原型 3 chips 扩到 5)
|
||||||
String _flowChip = '全部';
|
String _flowChip = '全部';
|
||||||
|
// 窄屏三分段(原型 m-finance .seg:应收 / 应付 / 流水)
|
||||||
|
int _mSeg = 0;
|
||||||
|
|
||||||
static const _flowChips = ['全部', '应收', '应付', '收款', '付款'];
|
static const _flowChips = ['全部', '应收', '应付', '收款', '付款'];
|
||||||
static const _chipToType = {
|
static const _chipToType = {
|
||||||
@@ -139,6 +143,40 @@ class _FinanceScreenState extends ConsumerState<FinanceScreen> {
|
|||||||
final flowsAsync = ref.watch(financeListProvider);
|
final flowsAsync = ref.watch(financeListProvider);
|
||||||
final flows = flowsAsync.valueOrNull?.data ?? const <FinanceRecord>[];
|
final flows = flowsAsync.valueOrNull?.data ?? const <FinanceRecord>[];
|
||||||
|
|
||||||
|
// 窄屏(原型 m-finance):隐藏页内大标题头,KPI 2×2 网格 + 三分段 + 卡片流。
|
||||||
|
if (mobile) {
|
||||||
|
final content = Container(
|
||||||
|
color: t.bg,
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
padding: const EdgeInsets.all(AppDims.sp4),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
_timeRow(t, true),
|
||||||
|
_mKpis(t),
|
||||||
|
const SizedBox(height: 14),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
child: DsSeg(
|
||||||
|
items: const ['应收', '应付', '流水'],
|
||||||
|
index: _mSeg,
|
||||||
|
onChanged: (i) => setState(() => _mSeg = i)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
..._mSegBody(t, flows),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return Stack(children: [
|
||||||
|
content,
|
||||||
|
if (flowsAsync.isLoading)
|
||||||
|
const Positioned.fill(child: DsLoadingScrim()),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
final content = Container(
|
final content = Container(
|
||||||
color: t.bg,
|
color: t.bg,
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
@@ -645,4 +683,184 @@ class _FinanceScreenState extends ConsumerState<FinanceScreen> {
|
|||||||
: null,
|
: null,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── 窄屏形态(原型 m-finance)──────────────────────────────────────
|
||||||
|
|
||||||
|
/// delta 箭头/色调映射(与 DsKpi 同规则:▲/▼ 文本字符)。
|
||||||
|
String _mDeltaText(String text, DsKpiDelta tone) => switch (tone) {
|
||||||
|
DsKpiDelta.up => '▲ $text',
|
||||||
|
DsKpiDelta.down => '▼ $text',
|
||||||
|
DsKpiDelta.neutral => text,
|
||||||
|
};
|
||||||
|
|
||||||
|
MKpiDeltaTone _mTone(DsKpiDelta tone) => switch (tone) {
|
||||||
|
DsKpiDelta.up => MKpiDeltaTone.up,
|
||||||
|
DsKpiDelta.down => MKpiDeltaTone.down,
|
||||||
|
DsKpiDelta.neutral => MKpiDeltaTone.normal,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// 窄屏 KPI 2×2(原型 .m-kpi:本月收入 / 本月支出 / 应收合计 / 应付合计)。
|
||||||
|
Widget _mKpis(dynamic t) {
|
||||||
|
final outSum = ref.watch(stockOutSummaryProvider).valueOrNull;
|
||||||
|
final inSum = ref.watch(stockInSummaryProvider).valueOrNull;
|
||||||
|
final rows = ref.watch(financePartnerRowsProvider).valueOrNull ??
|
||||||
|
const <PartnerFinanceRow>[];
|
||||||
|
var recv = 0.0, pay = 0.0, openCount = 0;
|
||||||
|
for (final r in rows) {
|
||||||
|
recv += r.recv;
|
||||||
|
pay += r.pay;
|
||||||
|
openCount += r.openCount;
|
||||||
|
}
|
||||||
|
final (saleDelta, saleTone) = outSum != null
|
||||||
|
? _momDelta(outSum.monthAmount, outSum.lastMonthAmount)
|
||||||
|
: ('较上月 —', DsKpiDelta.neutral);
|
||||||
|
final (buyDelta, buyTone) = inSum != null
|
||||||
|
? _momDelta(inSum.monthAmount, inSum.lastMonthAmount)
|
||||||
|
: ('较上月 —', DsKpiDelta.neutral);
|
||||||
|
return MKpiGrid(items: [
|
||||||
|
MKpiItem(
|
||||||
|
label: '本月收入',
|
||||||
|
value: outSum != null ? _yuanWan(outSum.monthAmount) : '—',
|
||||||
|
delta: _mDeltaText(saleDelta, saleTone),
|
||||||
|
deltaTone: _mTone(saleTone),
|
||||||
|
),
|
||||||
|
MKpiItem(
|
||||||
|
label: '本月支出',
|
||||||
|
value: inSum != null ? _yuanWan(inSum.monthAmount) : '—',
|
||||||
|
delta: _mDeltaText(buyDelta, buyTone),
|
||||||
|
deltaTone: _mTone(buyTone),
|
||||||
|
),
|
||||||
|
// 原型 delta「12 笔未结」:应收/应付分侧笔数无数据源 → 用未结清总笔数(已知差异)
|
||||||
|
MKpiItem(
|
||||||
|
label: '应收合计',
|
||||||
|
value: _yuanWan(recv),
|
||||||
|
delta: '未结清 $openCount 笔',
|
||||||
|
deltaTone: MKpiDeltaTone.warn,
|
||||||
|
),
|
||||||
|
MKpiItem(
|
||||||
|
label: '应付合计',
|
||||||
|
value: _yuanWan(pay),
|
||||||
|
delta: '按到期及时结清',
|
||||||
|
deltaTone: MKpiDeltaTone.normal,
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 三分段列表体:m-section 计数标题 + 卡片流(应收/应付=往来汇总,流水=收支记录)。
|
||||||
|
List<Widget> _mSegBody(dynamic t, List<FinanceRecord> flows) {
|
||||||
|
Widget section(String title, int count) => Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(2, 16, 2, 8),
|
||||||
|
child: Text('$title · 共 $count',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: AppDims.fsSm,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
letterSpacing: .4,
|
||||||
|
color: t.muted)),
|
||||||
|
);
|
||||||
|
Widget empty() => Padding(
|
||||||
|
padding: const EdgeInsets.all(24),
|
||||||
|
child: Center(
|
||||||
|
child: Text('暂无数据',
|
||||||
|
style: TextStyle(color: t.muted, fontSize: AppDims.fsSm))),
|
||||||
|
);
|
||||||
|
List<Widget> stacked(List<Widget> cards) => [
|
||||||
|
for (var i = 0; i < cards.length; i++) ...[
|
||||||
|
if (i > 0) const SizedBox(height: 10),
|
||||||
|
cards[i],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
if (_mSeg == 2) {
|
||||||
|
return [
|
||||||
|
section('资金流水', flows.length),
|
||||||
|
if (flows.isEmpty)
|
||||||
|
empty()
|
||||||
|
else
|
||||||
|
...stacked([for (final r in flows) _mFlowCard(t, r)]),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
final async = ref.watch(financePartnerRowsProvider);
|
||||||
|
return async.when(
|
||||||
|
loading: () => [
|
||||||
|
const Padding(
|
||||||
|
padding: EdgeInsets.all(24),
|
||||||
|
child: Center(child: CircularProgressIndicator())),
|
||||||
|
],
|
||||||
|
error: (e, _) => [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(24),
|
||||||
|
child: Center(
|
||||||
|
child: Text('汇总加载失败',
|
||||||
|
style: TextStyle(color: t.muted, fontSize: AppDims.fsSm))),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
data: (rows) {
|
||||||
|
final ar = _mSeg == 0;
|
||||||
|
final list = rows.where((r) => ar ? r.recv > 0 : r.pay > 0).toList();
|
||||||
|
return [
|
||||||
|
section(ar ? '应收账款' : '应付账款', list.length),
|
||||||
|
if (list.isEmpty)
|
||||||
|
empty()
|
||||||
|
else
|
||||||
|
...stacked(
|
||||||
|
[for (final r in list) _mPartnerCard(t, r, ar: ar)]),
|
||||||
|
];
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 应收/应付卡(原型 .m-card:往来单位 + 金额 + 徽章带图标)。
|
||||||
|
Widget _mPartnerCard(dynamic t, PartnerFinanceRow r, {required bool ar}) {
|
||||||
|
return MobileListCard(
|
||||||
|
onTap: () => showFinancePartnerDrawer(context, row: r),
|
||||||
|
title: Text(r.name),
|
||||||
|
subtitle: Text('未结清 ${r.openCount} 笔'),
|
||||||
|
trailing: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
DsBadge('未结清',
|
||||||
|
tone: DsBadgeTone.warn, icon: statusIcon('未结清')),
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
Text(_yuan(ar ? r.recv : r.pay),
|
||||||
|
style: TextStyle(
|
||||||
|
fontFamily: AppFonts.mono,
|
||||||
|
fontFamilyFallback: AppFonts.monoFallback,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
fontSize: 13,
|
||||||
|
color: t.heading)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 流水卡(原型 .m-card:类型徽章带图标 收款/付款 + 金额正负色)。
|
||||||
|
Widget _mFlowCard(dynamic t, FinanceRecord r) {
|
||||||
|
final isIn = r.type == 'receipt' || r.type == 'receivable';
|
||||||
|
return MobileListCard(
|
||||||
|
title: Text(
|
||||||
|
(r.partnerName?.isNotEmpty == true) ? r.partnerName! : r.typeLabel),
|
||||||
|
subtitle: Text((r.recordDate ?? '').split('T').first),
|
||||||
|
trailing: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
DsBadge(r.typeLabel,
|
||||||
|
tone: switch (r.type) {
|
||||||
|
'receipt' => DsBadgeTone.ok,
|
||||||
|
'payment' => DsBadgeTone.danger,
|
||||||
|
'receivable' => DsBadgeTone.warn,
|
||||||
|
_ => DsBadgeTone.info,
|
||||||
|
},
|
||||||
|
icon: statusIcon(r.typeLabel)),
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
Text('${isIn ? '+' : '-'}${_yuan(r.amount.abs())}',
|
||||||
|
style: TextStyle(
|
||||||
|
fontFamily: AppFonts.mono,
|
||||||
|
fontFamilyFallback: AppFonts.monoFallback,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
fontSize: 13,
|
||||||
|
color: isIn ? t.success : t.danger)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,10 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
import '../../core/responsive/responsive.dart';
|
||||||
|
import '../../core/theme/app_dims.g.dart';
|
||||||
import '../../core/theme/context_tokens.dart';
|
import '../../core/theme/context_tokens.dart';
|
||||||
|
import '../../core/utils/clock.dart';
|
||||||
import '../../models/inventory.dart';
|
import '../../models/inventory.dart';
|
||||||
import '../../models/warehouse.dart';
|
import '../../models/warehouse.dart';
|
||||||
import '../../core/config/app_constants.dart';
|
import '../../core/config/app_constants.dart';
|
||||||
@@ -12,6 +15,9 @@ import '../../providers/warehouse_provider.dart';
|
|||||||
import '../../core/theme/app_fonts.dart';
|
import '../../core/theme/app_fonts.dart';
|
||||||
import '../../widgets/ds/ds_atoms.dart';
|
import '../../widgets/ds/ds_atoms.dart';
|
||||||
import '../../widgets/ds/ds_toast.dart';
|
import '../../widgets/ds/ds_toast.dart';
|
||||||
|
import '../../widgets/ds/m_sheet.dart';
|
||||||
|
import '../../widgets/ds/status_icon_map.dart';
|
||||||
|
import '../../widgets/mobile_list_card.dart';
|
||||||
|
|
||||||
class InventoryCheckScreen extends ConsumerStatefulWidget {
|
class InventoryCheckScreen extends ConsumerStatefulWidget {
|
||||||
const InventoryCheckScreen({super.key});
|
const InventoryCheckScreen({super.key});
|
||||||
@@ -31,12 +37,18 @@ class _InventoryCheckScreenState extends ConsumerState<InventoryCheckScreen> {
|
|||||||
final List<_CheckItem> _checkItems = [];
|
final List<_CheckItem> _checkItems = [];
|
||||||
|
|
||||||
String get _checkNo {
|
String get _checkNo {
|
||||||
final now = DateTime.now();
|
final now = appNow();
|
||||||
final date =
|
final date =
|
||||||
'${now.year}${now.month.toString().padLeft(2, '0')}${now.day.toString().padLeft(2, '0')}';
|
'${now.year}${now.month.toString().padLeft(2, '0')}${now.day.toString().padLeft(2, '0')}';
|
||||||
return 'PD$date${_selectedWarehouse?.id.toString().padLeft(3, '0') ?? '001'}';
|
return 'PD$date${_selectedWarehouse?.id.toString().padLeft(3, '0') ?? '001'}';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 盘点日期(appNow 可注入时钟,golden 冻结确定化)。
|
||||||
|
String get _dateStr {
|
||||||
|
final now = appNow();
|
||||||
|
return '${now.year}-${now.month.toString().padLeft(2, '0')}-${now.day.toString().padLeft(2, '0')}';
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
for (final item in _checkItems) {
|
for (final item in _checkItems) {
|
||||||
@@ -81,9 +93,7 @@ class _InventoryCheckScreenState extends ConsumerState<InventoryCheckScreen> {
|
|||||||
}
|
}
|
||||||
setState(() => _submitting = true);
|
setState(() => _submitting = true);
|
||||||
try {
|
try {
|
||||||
final now = DateTime.now();
|
final dateStr = _dateStr;
|
||||||
final dateStr =
|
|
||||||
'${now.year}-${now.month.toString().padLeft(2, '0')}-${now.day.toString().padLeft(2, '0')}';
|
|
||||||
|
|
||||||
final items = _checkItems.map((item) {
|
final items = _checkItems.map((item) {
|
||||||
final actual =
|
final actual =
|
||||||
@@ -125,6 +135,10 @@ class _InventoryCheckScreenState extends ConsumerState<InventoryCheckScreen> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final asyncWarehouses = ref.watch(warehouseListProvider);
|
final asyncWarehouses = ref.watch(warehouseListProvider);
|
||||||
|
|
||||||
|
// 窄屏(原型 m-inventory-check):壳顶栏带标题/返回 → 隐藏页内大标题头,
|
||||||
|
// 盘点单以卡片流呈现(点卡开详情 sheet),底部操作条提交。
|
||||||
|
if (context.isMobile) return _buildMobile(asyncWarehouses);
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: context.tokens.bg,
|
backgroundColor: context.tokens.bg,
|
||||||
body: Column(
|
body: Column(
|
||||||
@@ -173,90 +187,7 @@ class _InventoryCheckScreenState extends ConsumerState<InventoryCheckScreen> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
// Basic info
|
// Basic info
|
||||||
Card(
|
_basicInfoCard(asyncWarehouses),
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text('盘点基本信息',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 14,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
color: context.tokens.primaryDark)),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Wrap(
|
|
||||||
spacing: 16,
|
|
||||||
runSpacing: 16,
|
|
||||||
children: [
|
|
||||||
_InfoField(
|
|
||||||
label: '盘点单号',
|
|
||||||
child: InputDecorator(
|
|
||||||
decoration: const InputDecoration(),
|
|
||||||
child: Text(
|
|
||||||
_checkNo,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontFamily: AppFonts.mono,
|
|
||||||
fontFamilyFallback:
|
|
||||||
AppFonts.monoFallback,
|
|
||||||
fontSize: 13),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
_InfoField(
|
|
||||||
label: '盘点仓库',
|
|
||||||
child: asyncWarehouses.when(
|
|
||||||
loading: () =>
|
|
||||||
const LinearProgressIndicator(),
|
|
||||||
error: (e, _) => Text('$e',
|
|
||||||
style: TextStyle(
|
|
||||||
color: context.tokens.danger,
|
|
||||||
fontSize: 12)),
|
|
||||||
data: (warehouses) => DsSelect<Warehouse>(
|
|
||||||
value: _selectedWarehouse,
|
|
||||||
hint: '请选择仓库',
|
|
||||||
options: [
|
|
||||||
for (final w in warehouses) (w, w.name),
|
|
||||||
],
|
|
||||||
onChanged: (w) {
|
|
||||||
setState(() => _selectedWarehouse = w);
|
|
||||||
_loadInventory(w);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
_InfoField(
|
|
||||||
label: '盘点类型',
|
|
||||||
child: DsSelect<String>(
|
|
||||||
value: _checkType,
|
|
||||||
options: const [
|
|
||||||
('全盘', '全盘'),
|
|
||||||
('抽盘', '抽盘'),
|
|
||||||
('循环盘点', '循环盘点'),
|
|
||||||
],
|
|
||||||
onChanged: (v) =>
|
|
||||||
setState(() => _checkType = v),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
_InfoField(
|
|
||||||
label: '盘点日期',
|
|
||||||
child: InputDecorator(
|
|
||||||
decoration: const InputDecoration(),
|
|
||||||
child: Builder(builder: (ctx) {
|
|
||||||
final now = DateTime.now();
|
|
||||||
return Text(
|
|
||||||
'${now.year}-${now.month.toString().padLeft(2, '0')}-${now.day.toString().padLeft(2, '0')}',
|
|
||||||
style: const TextStyle(fontSize: 13),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
// Check items table
|
// Check items table
|
||||||
Card(
|
Card(
|
||||||
@@ -408,6 +339,256 @@ class _InventoryCheckScreenState extends ConsumerState<InventoryCheckScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 盘点基本信息卡(桌面/移动共用):单号 / 仓库 / 类型 / 日期。
|
||||||
|
Widget _basicInfoCard(AsyncValue<List<Warehouse>> asyncWarehouses) {
|
||||||
|
return Card(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text('盘点基本信息',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: context.tokens.primaryDark)),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Wrap(
|
||||||
|
spacing: 16,
|
||||||
|
runSpacing: 16,
|
||||||
|
children: [
|
||||||
|
_InfoField(
|
||||||
|
label: '盘点单号',
|
||||||
|
child: InputDecorator(
|
||||||
|
decoration: const InputDecoration(),
|
||||||
|
child: Text(
|
||||||
|
_checkNo,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontFamily: AppFonts.mono,
|
||||||
|
fontFamilyFallback: AppFonts.monoFallback,
|
||||||
|
fontSize: 13),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
_InfoField(
|
||||||
|
label: '盘点仓库',
|
||||||
|
child: asyncWarehouses.when(
|
||||||
|
loading: () => const LinearProgressIndicator(),
|
||||||
|
error: (e, _) => Text('$e',
|
||||||
|
style: TextStyle(
|
||||||
|
color: context.tokens.danger, fontSize: 12)),
|
||||||
|
data: (warehouses) => DsSelect<Warehouse>(
|
||||||
|
value: _selectedWarehouse,
|
||||||
|
hint: '请选择仓库',
|
||||||
|
options: [
|
||||||
|
for (final w in warehouses) (w, w.name),
|
||||||
|
],
|
||||||
|
onChanged: (w) {
|
||||||
|
setState(() => _selectedWarehouse = w);
|
||||||
|
_loadInventory(w);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
_InfoField(
|
||||||
|
label: '盘点类型',
|
||||||
|
child: DsSelect<String>(
|
||||||
|
value: _checkType,
|
||||||
|
options: const [
|
||||||
|
('全盘', '全盘'),
|
||||||
|
('抽盘', '抽盘'),
|
||||||
|
('循环盘点', '循环盘点'),
|
||||||
|
],
|
||||||
|
onChanged: (v) => setState(() => _checkType = v),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
_InfoField(
|
||||||
|
label: '盘点日期',
|
||||||
|
child: InputDecorator(
|
||||||
|
decoration: const InputDecoration(),
|
||||||
|
child: Text(_dateStr, style: const TextStyle(fontSize: 13)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 窄屏(原型 m-inventory-check 粒度)──────────────────────────────
|
||||||
|
|
||||||
|
int get _totalDiff =>
|
||||||
|
_checkItems.fold(0, (sum, item) => sum + _getDiff(item));
|
||||||
|
|
||||||
|
String get _scopeLabel =>
|
||||||
|
'${_selectedWarehouse?.name ?? '未选仓库'} · $_checkType';
|
||||||
|
|
||||||
|
String _fmtDiff(int diff) => diff > 0 ? '+$diff' : '$diff';
|
||||||
|
|
||||||
|
Widget _buildMobile(AsyncValue<List<Warehouse>> asyncWarehouses) {
|
||||||
|
final t = context.tokens;
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: t.bg,
|
||||||
|
body: Column(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
padding: const EdgeInsets.all(AppDims.sp3),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
_basicInfoCard(asyncWarehouses),
|
||||||
|
// .m-section
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(2, 16, 2, 8),
|
||||||
|
child: Text('盘点单',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: AppDims.fsSm,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
letterSpacing: .4,
|
||||||
|
color: t.muted)),
|
||||||
|
),
|
||||||
|
if (_loadingItems)
|
||||||
|
const Padding(
|
||||||
|
padding: EdgeInsets.all(24),
|
||||||
|
child: Center(child: CircularProgressIndicator()),
|
||||||
|
)
|
||||||
|
else
|
||||||
|
_draftCard(t),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// 底部操作条(原型 .m-actionbar 形态:横排等分按钮)
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.fromLTRB(16, 12, 16, 12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: t.surface,
|
||||||
|
border: Border(top: BorderSide(color: t.borderSubtle)),
|
||||||
|
),
|
||||||
|
child: Row(children: [
|
||||||
|
Expanded(
|
||||||
|
child: DsButton('取消',
|
||||||
|
onPressed: () => context.go('/inventory')),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Expanded(
|
||||||
|
child: DsButton('提交盘点',
|
||||||
|
variant: DsBtnVariant.primary,
|
||||||
|
onPressed: (_submitting || _checkItems.isEmpty)
|
||||||
|
? null
|
||||||
|
: _submit),
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 盘点单卡(原型 .m-card:单号 / 范围·项数 / 状态徽章 + 差异 / 日期脚注)。
|
||||||
|
Widget _draftCard(dynamic t) {
|
||||||
|
final diff = _totalDiff;
|
||||||
|
return MobileListCard(
|
||||||
|
onTap: _openDraftSheet,
|
||||||
|
title: Text(_checkNo,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontFamily: AppFonts.mono,
|
||||||
|
fontFamilyFallback: AppFonts.monoFallback)),
|
||||||
|
subtitle: Text('$_scopeLabel · ${_checkItems.length} 项'),
|
||||||
|
trailing: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
DsBadge('进行中',
|
||||||
|
tone: DsBadgeTone.warn, icon: statusIcon('进行中')),
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
// 「差异」为 CJK 走默认字体,数值走 mono(JetBrains Mono 无 CJK 字形)
|
||||||
|
Text.rich(
|
||||||
|
TextSpan(children: [
|
||||||
|
TextSpan(
|
||||||
|
text: '差异 ',
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
fontSize: 13,
|
||||||
|
color: diff == 0 ? t.success : t.danger)),
|
||||||
|
TextSpan(
|
||||||
|
text: _fmtDiff(diff),
|
||||||
|
style: TextStyle(
|
||||||
|
fontFamily: AppFonts.mono,
|
||||||
|
fontFamilyFallback: AppFonts.monoFallback,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
fontSize: 13,
|
||||||
|
color: diff == 0 ? t.success : t.danger)),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
fields: [MobileCardField('日期', _dateStr)],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 盘点单详情 sheet(原型 openSheet:.drow 键值行 + 状态徽章 + 继续录入)。
|
||||||
|
void _openDraftSheet() {
|
||||||
|
final t = context.tokens;
|
||||||
|
final diff = _totalDiff;
|
||||||
|
showMSheet<void>(
|
||||||
|
context,
|
||||||
|
title: '盘点单详情',
|
||||||
|
builder: (ctx) => Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
_drow(ctx, '单号', value: _checkNo, mono: true),
|
||||||
|
_drow(ctx, '范围', value: _scopeLabel),
|
||||||
|
_drow(ctx, '商品项数', value: '${_checkItems.length} 项'),
|
||||||
|
_drow(ctx, '盈亏差异',
|
||||||
|
value: _fmtDiff(diff),
|
||||||
|
mono: true,
|
||||||
|
color: diff == 0 ? t.success : t.danger),
|
||||||
|
_drow(ctx, '状态',
|
||||||
|
child: DsBadge('进行中',
|
||||||
|
tone: DsBadgeTone.warn, icon: statusIcon('进行中')),
|
||||||
|
last: true),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
DsButton('继续录入',
|
||||||
|
variant: DsBtnVariant.primary,
|
||||||
|
onPressed: () => Navigator.of(context).pop()),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 原型 .drow:label(muted) + b(text 600),pad 11 0,下边 border-subtle。
|
||||||
|
Widget _drow(BuildContext ctx, String label,
|
||||||
|
{String? value,
|
||||||
|
Widget? child,
|
||||||
|
bool mono = false,
|
||||||
|
Color? color,
|
||||||
|
bool last = false}) {
|
||||||
|
final t = ctx.tokens;
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 11),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border:
|
||||||
|
last ? null : Border(bottom: BorderSide(color: t.borderSubtle))),
|
||||||
|
child: Row(children: [
|
||||||
|
Text(label,
|
||||||
|
style: TextStyle(fontSize: AppDims.fsBody, color: t.muted)),
|
||||||
|
const Spacer(),
|
||||||
|
child ??
|
||||||
|
Text(value ?? '',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: AppDims.fsBody,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
fontFamily: mono ? AppFonts.mono : null,
|
||||||
|
fontFamilyFallback: mono ? AppFonts.monoFallback : null,
|
||||||
|
color: color ?? t.text)),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
TableRow _buildCheckRow(int index) {
|
TableRow _buildCheckRow(int index) {
|
||||||
final item = _checkItems[index];
|
final item = _checkItems[index];
|
||||||
final diff = _getDiff(item);
|
final diff = _getDiff(item);
|
||||||
|
|||||||
@@ -21,6 +21,10 @@ import '../../widgets/ds/ds_atoms.dart';
|
|||||||
import '../../widgets/ds/ds_kpi.dart';
|
import '../../widgets/ds/ds_kpi.dart';
|
||||||
import '../../widgets/ds/ds_menu.dart';
|
import '../../widgets/ds/ds_menu.dart';
|
||||||
import '../../widgets/ds/ds_table.dart';
|
import '../../widgets/ds/ds_table.dart';
|
||||||
|
import '../../widgets/ds/m_kpi_grid.dart';
|
||||||
|
import '../../widgets/ds/m_search_row.dart';
|
||||||
|
import '../../widgets/ds/m_sheet.dart';
|
||||||
|
import '../../widgets/ds/status_icon_map.dart';
|
||||||
import '../../widgets/mobile_list_card.dart';
|
import '../../widgets/mobile_list_card.dart';
|
||||||
import '../../widgets/multi_select_dropdown.dart' show ColDef;
|
import '../../widgets/multi_select_dropdown.dart' show ColDef;
|
||||||
import '../../widgets/label_preview_dialog.dart';
|
import '../../widgets/label_preview_dialog.dart';
|
||||||
@@ -172,6 +176,129 @@ class _InventoryListScreenState extends ConsumerState<InventoryListScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── 窄屏筛选 sheet(原型移动无列头漏斗 → 详搜钮开底部 sheet)──
|
||||||
|
bool get _mobileFilterActive =>
|
||||||
|
_statusFilter != '全部' ||
|
||||||
|
_filterSpec.isNotEmpty ||
|
||||||
|
_filterSeries.isNotEmpty ||
|
||||||
|
_filterWarehouse.isNotEmpty;
|
||||||
|
|
||||||
|
void _openMobileFilterSheet(
|
||||||
|
List<String> specOptions, List<String> seriesOptions) {
|
||||||
|
showMSheet<void>(
|
||||||
|
context,
|
||||||
|
title: '筛选',
|
||||||
|
builder: (sheetCtx) => StatefulBuilder(builder: (sheetCtx, setSheet) {
|
||||||
|
Widget group(String label, List<Widget> chips) => Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 14),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(label,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: AppDims.fsSm,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: sheetCtx.tokens.muted)),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Wrap(spacing: 8, runSpacing: 8, children: chips),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
void toggleMulti(
|
||||||
|
String v,
|
||||||
|
Set<String> selected,
|
||||||
|
ValueChanged<Set<String>> apply,
|
||||||
|
) {
|
||||||
|
final next = Set.of(selected);
|
||||||
|
next.contains(v) ? next.remove(v) : next.add(v);
|
||||||
|
apply(next);
|
||||||
|
setSheet(() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
group('状态', [
|
||||||
|
for (final s in _statusOptions)
|
||||||
|
DsChip(
|
||||||
|
label: s,
|
||||||
|
selected: _statusFilter == s,
|
||||||
|
caret: false,
|
||||||
|
onTap: () {
|
||||||
|
setState(() => _statusFilter = s);
|
||||||
|
setSheet(() {});
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
if (specOptions.isNotEmpty)
|
||||||
|
group('规格', [
|
||||||
|
for (final o in specOptions)
|
||||||
|
DsChip(
|
||||||
|
label: o,
|
||||||
|
selected: _filterSpec.contains(o),
|
||||||
|
caret: false,
|
||||||
|
onTap: () => toggleMulti(o, _filterSpec, (next) {
|
||||||
|
setState(() => _filterSpec = next);
|
||||||
|
ref
|
||||||
|
.read(inventoryListProvider.notifier)
|
||||||
|
.setSpec(next.toList());
|
||||||
|
})),
|
||||||
|
]),
|
||||||
|
if (seriesOptions.isNotEmpty)
|
||||||
|
group('系列', [
|
||||||
|
for (final o in seriesOptions)
|
||||||
|
DsChip(
|
||||||
|
label: o,
|
||||||
|
selected: _filterSeries.contains(o),
|
||||||
|
caret: false,
|
||||||
|
onTap: () => toggleMulti(o, _filterSeries, (next) {
|
||||||
|
setState(() => _filterSeries = next);
|
||||||
|
ref
|
||||||
|
.read(inventoryListProvider.notifier)
|
||||||
|
.setSeries(next.toList());
|
||||||
|
})),
|
||||||
|
]),
|
||||||
|
Row(children: [
|
||||||
|
Expanded(
|
||||||
|
child: DsButton('重置', onPressed: () {
|
||||||
|
setState(() {
|
||||||
|
_statusFilter = '全部';
|
||||||
|
_filterSpec = {};
|
||||||
|
_filterSeries = {};
|
||||||
|
_filterWarehouse = {};
|
||||||
|
});
|
||||||
|
final n = ref.read(inventoryListProvider.notifier);
|
||||||
|
n.setSpec([]);
|
||||||
|
n.setSeries([]);
|
||||||
|
setSheet(() {});
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Expanded(
|
||||||
|
child: DsButton('完成',
|
||||||
|
variant: DsBtnVariant.primary,
|
||||||
|
onPressed: () => Navigator.of(sheetCtx).pop()),
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 窄屏 KPI(MKpiGrid):delta 箭头/色调映射(与 DsKpi 同规则)──
|
||||||
|
String _mDeltaText(String text, DsKpiDelta tone) => switch (tone) {
|
||||||
|
DsKpiDelta.up => '▲ $text',
|
||||||
|
DsKpiDelta.down => '▼ $text',
|
||||||
|
DsKpiDelta.neutral => text,
|
||||||
|
};
|
||||||
|
|
||||||
|
MKpiDeltaTone _mTone(DsKpiDelta tone) => switch (tone) {
|
||||||
|
DsKpiDelta.up => MKpiDeltaTone.up,
|
||||||
|
DsKpiDelta.down => MKpiDeltaTone.down,
|
||||||
|
DsKpiDelta.neutral => MKpiDeltaTone.normal,
|
||||||
|
};
|
||||||
|
|
||||||
/// 备注列展示:editable 时附带编辑图标(用于 WriteGuard 的可点子控件),
|
/// 备注列展示:editable 时附带编辑图标(用于 WriteGuard 的可点子控件),
|
||||||
/// 否则纯文本(只读角色占位)。
|
/// 否则纯文本(只读角色占位)。
|
||||||
Widget _remarkDisplay(Inventory item, {required bool editable}) {
|
Widget _remarkDisplay(Inventory item, {required bool editable}) {
|
||||||
@@ -549,13 +676,12 @@ class _InventoryListScreenState extends ConsumerState<InventoryListScreen> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
// 头部(原型 .head{margin-bottom:18px}):标题 + SKU 数 + 列设置/导出
|
// 头部(原型 .head{margin-bottom:18px}):标题 + SKU 数 + 列设置/导出
|
||||||
|
// 窄屏隐藏(原型 m-inventory:标题在壳顶栏 m-top)
|
||||||
|
if (!mobile)
|
||||||
Container(
|
Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
color: context.tokens.bg,
|
color: context.tokens.bg,
|
||||||
padding: mobile
|
padding: const EdgeInsets.only(bottom: 18),
|
||||||
? const EdgeInsets.fromLTRB(
|
|
||||||
AppDims.sp4, AppDims.sp4, AppDims.sp4, AppDims.sp2)
|
|
||||||
: const EdgeInsets.only(bottom: 18),
|
|
||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
@@ -610,6 +736,47 @@ class _InventoryListScreenState extends ConsumerState<InventoryListScreen> {
|
|||||||
final (qtyDelta, qtyTone) = summary != null
|
final (qtyDelta, qtyTone) = summary != null
|
||||||
? _momDelta(summary.inStockQty, summary.lastMonthQty)
|
? _momDelta(summary.inStockQty, summary.lastMonthQty)
|
||||||
: ('较上月 —', DsKpiDelta.neutral);
|
: ('较上月 —', DsKpiDelta.neutral);
|
||||||
|
if (context.isMobile) {
|
||||||
|
// 原型 m-inventory .m-kpi:2×2 网格(缺货卡 warn 色 + 点击筛选)
|
||||||
|
return Container(
|
||||||
|
color: context.tokens.bg,
|
||||||
|
padding: const EdgeInsets.all(AppDims.sp3),
|
||||||
|
child: MKpiGrid(items: [
|
||||||
|
MKpiItem(
|
||||||
|
label: 'SKU 总数',
|
||||||
|
value: NumberFormat.decimalPattern()
|
||||||
|
.format(summary?.skuCount ?? result.total),
|
||||||
|
delta: _mDeltaText(skuDelta, skuTone),
|
||||||
|
deltaTone: _mTone(skuTone),
|
||||||
|
onTap: _clearFilters,
|
||||||
|
),
|
||||||
|
MKpiItem(
|
||||||
|
label: '库存货值',
|
||||||
|
value: summary != null
|
||||||
|
? yuanWan(summary.stockValue)
|
||||||
|
: '—',
|
||||||
|
delta: _mDeltaText(valDelta, valTone),
|
||||||
|
deltaTone: _mTone(valTone),
|
||||||
|
),
|
||||||
|
MKpiItem(
|
||||||
|
label: '在库数量',
|
||||||
|
value: NumberFormat.decimalPattern()
|
||||||
|
.format((summary?.inStockQty ?? 0).round()),
|
||||||
|
delta: _mDeltaText(qtyDelta, qtyTone),
|
||||||
|
deltaTone: _mTone(qtyTone),
|
||||||
|
),
|
||||||
|
MKpiItem(
|
||||||
|
label: '缺货预警',
|
||||||
|
value: '${summary?.shortageCount ?? 0}',
|
||||||
|
delta: '需补货 ${summary?.warningCount ?? 0} 项',
|
||||||
|
deltaTone: MKpiDeltaTone.warn,
|
||||||
|
selected: _statusFilter == '缺货',
|
||||||
|
onTap: () =>
|
||||||
|
setState(() => _statusFilter = '缺货'),
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
}
|
||||||
final cards = <Widget>[
|
final cards = <Widget>[
|
||||||
DsKpi(
|
DsKpi(
|
||||||
title: 'SKU 总数',
|
title: 'SKU 总数',
|
||||||
@@ -651,36 +818,25 @@ class _InventoryListScreenState extends ConsumerState<InventoryListScreen> {
|
|||||||
onTap: () => setState(() => _statusFilter = '缺货'),
|
onTap: () => setState(() => _statusFilter = '缺货'),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
final mobile = context.isMobile;
|
|
||||||
final row = <Widget>[];
|
final row = <Widget>[];
|
||||||
for (var i = 0; i < cards.length; i++) {
|
for (var i = 0; i < cards.length; i++) {
|
||||||
if (i > 0) {
|
if (i > 0) {
|
||||||
// 原型 .kpis{gap:14px}
|
// 原型 .kpis{gap:14px}
|
||||||
row.add(SizedBox(width: mobile ? AppDims.sp3 : 14));
|
row.add(const SizedBox(width: 14));
|
||||||
}
|
}
|
||||||
row.add(mobile
|
row.add(Expanded(child: cards[i]));
|
||||||
? SizedBox(width: 160, child: cards[i])
|
|
||||||
: Expanded(child: cards[i]));
|
|
||||||
}
|
}
|
||||||
return Container(
|
return Container(
|
||||||
color: context.tokens.bg,
|
color: context.tokens.bg,
|
||||||
// 原型 .kpis{margin-bottom:20px};左右留白由外层 .main 统一给
|
// 原型 .kpis{margin-bottom:20px};左右留白由外层 .main 统一给
|
||||||
padding: mobile
|
padding: const EdgeInsets.only(bottom: 20),
|
||||||
? const EdgeInsets.all(AppDims.sp3)
|
child: IntrinsicHeight(
|
||||||
: const EdgeInsets.only(bottom: 20),
|
child: Row(
|
||||||
child: mobile
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
? SingleChildScrollView(
|
children: row)),
|
||||||
scrollDirection: Axis.horizontal,
|
|
||||||
child: IntrinsicHeight(child: Row(children: row)),
|
|
||||||
)
|
|
||||||
: IntrinsicHeight(
|
|
||||||
child: Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
||||||
children: row)),
|
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
// 原型 KPI 与表格卡之间无分隔线(桌面);窄屏保留视觉分隔
|
// 原型 KPI 与表格卡之间无分隔线(桌面/移动同)
|
||||||
if (mobile) const Divider(height: 1),
|
|
||||||
Expanded(
|
Expanded(
|
||||||
child: DsTable(
|
child: DsTable(
|
||||||
total: result.total,
|
total: result.total,
|
||||||
@@ -707,11 +863,21 @@ class _InventoryListScreenState extends ConsumerState<InventoryListScreen> {
|
|||||||
final canCheck = !WriteGuard.isReadonly(ref);
|
final canCheck = !WriteGuard.isReadonly(ref);
|
||||||
|
|
||||||
if (isMobile) {
|
if (isMobile) {
|
||||||
// 移动端:搜索独占一行 + 紧凑图标操作行(含 表格/列表 切换)
|
// 移动端:MSearchRow(原型 .m-search + 详搜钮)独占一行
|
||||||
|
// + 紧凑图标操作行(含 表格/列表 切换)
|
||||||
return Column(
|
return Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
searchField,
|
MSearchRow(
|
||||||
|
controller: _searchCtrl,
|
||||||
|
hint: '商品名 / 拼音 / 编码',
|
||||||
|
onSubmitted: (v) => ref
|
||||||
|
.read(inventoryListProvider.notifier)
|
||||||
|
.setKeyword(v.trim()),
|
||||||
|
filterActive: _mobileFilterActive,
|
||||||
|
onFilterTap: () => _openMobileFilterSheet(
|
||||||
|
specOptions, seriesOptions),
|
||||||
|
),
|
||||||
const SizedBox(height: AppDims.sp2),
|
const SizedBox(height: AppDims.sp2),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
@@ -901,13 +1067,26 @@ class _InventoryStatusBadge extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final t = context.tokens;
|
final t = context.tokens;
|
||||||
// 状态派生 qty vs min_stock(在售 / 预警 / 缺货),软底+圆点全走 token。
|
// 状态派生 qty vs min_stock(在售 / 预警 / 缺货),软底 + 图标变体
|
||||||
|
//(2026-07-04 拍板:圆点 → 代表图标,映射走 status_icon_map)。
|
||||||
if (item.quantity == 0) {
|
if (item.quantity == 0) {
|
||||||
return StatusPill(label: '缺货', color: t.danger, background: t.dangerBg);
|
return StatusPill(
|
||||||
|
label: '缺货',
|
||||||
|
color: t.danger,
|
||||||
|
background: t.dangerBg,
|
||||||
|
icon: statusIcon('缺货'));
|
||||||
}
|
}
|
||||||
if (item.minStock != null && item.quantity < item.minStock!) {
|
if (item.minStock != null && item.quantity < item.minStock!) {
|
||||||
return StatusPill(label: '预警', color: t.warn, background: t.warnBg);
|
return StatusPill(
|
||||||
|
label: '预警',
|
||||||
|
color: t.warn,
|
||||||
|
background: t.warnBg,
|
||||||
|
icon: statusIcon('预警'));
|
||||||
}
|
}
|
||||||
return StatusPill(label: '在售', color: t.success, background: t.okSoft);
|
return StatusPill(
|
||||||
|
label: '在售',
|
||||||
|
color: t.success,
|
||||||
|
background: t.okSoft,
|
||||||
|
icon: statusIcon('在售'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,126 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
import 'package:jiu_client/core/auth/auth_state.dart';
|
||||||
|
import 'package:jiu_client/core/models/page_result.dart';
|
||||||
|
import 'package:jiu_client/core/utils/clock.dart';
|
||||||
|
import 'package:jiu_client/models/finance.dart';
|
||||||
|
import 'package:jiu_client/models/stock_summary.dart';
|
||||||
|
import 'package:jiu_client/providers/finance_provider.dart';
|
||||||
|
import 'package:jiu_client/providers/stock_in_provider.dart';
|
||||||
|
import 'package:jiu_client/providers/stock_out_provider.dart';
|
||||||
|
import 'package:jiu_client/screens/finance/finance_screen.dart';
|
||||||
|
|
||||||
|
import '../support/golden_harness.dart';
|
||||||
|
|
||||||
|
/// 财务管理 窄屏形态 golden × 三主题(390×844 @2x)——对齐原型 m-finance.html:
|
||||||
|
/// 隐藏页内大标题头 + KPI 2×2(收入 up 绿 / 支出 down 红)+ DsSeg 三分段
|
||||||
|
/// + 应收卡片流(往来单位 + 金额 + 未结清徽章带图标)。fixtures 与桌面 golden 同套。
|
||||||
|
/// 更新基准:flutter test --update-goldens test/golden/finance_mobile_golden_test.dart
|
||||||
|
|
||||||
|
// 原型 SUMMARY 8 行
|
||||||
|
const _rows = [
|
||||||
|
PartnerFinanceRow(partnerId: 1, name: '华东酒类批发', recv: 128400, openCount: 2),
|
||||||
|
PartnerFinanceRow(partnerId: 2, name: '金樽商贸', recv: 86200, openCount: 1),
|
||||||
|
PartnerFinanceRow(partnerId: 3, name: '茅台华南经销', pay: 142800, openCount: 1),
|
||||||
|
PartnerFinanceRow(partnerId: 4, name: '鸿运餐饮连锁', recv: 74600, openCount: 1),
|
||||||
|
PartnerFinanceRow(partnerId: 5, name: '五粮液省级代理', pay: 96400, openCount: 1),
|
||||||
|
PartnerFinanceRow(
|
||||||
|
partnerId: 6, name: '盛世名酒城', recv: 52300, pay: 18600, openCount: 2),
|
||||||
|
PartnerFinanceRow(partnerId: 7, name: '御品烟酒行', recv: 31800, openCount: 1),
|
||||||
|
PartnerFinanceRow(partnerId: 8, name: '川渝糖酒集团', pay: 60400, openCount: 1),
|
||||||
|
];
|
||||||
|
|
||||||
|
// 原型 FLOWS 前 5 条(流水分段用;390×844 首屏展示应收卡片流)
|
||||||
|
const _flows = [
|
||||||
|
FinanceRecord(
|
||||||
|
id: 1,
|
||||||
|
type: 'receipt',
|
||||||
|
amount: 68000,
|
||||||
|
balance: 0,
|
||||||
|
status: 'closed',
|
||||||
|
partnerName: '华东酒类批发',
|
||||||
|
recordDate: '2026-06-20',
|
||||||
|
remark: '飞天茅台货款'),
|
||||||
|
FinanceRecord(
|
||||||
|
id: 2,
|
||||||
|
type: 'payment',
|
||||||
|
amount: 142800,
|
||||||
|
balance: 0,
|
||||||
|
status: 'closed',
|
||||||
|
partnerName: '茅台华南经销',
|
||||||
|
recordDate: '2026-06-19',
|
||||||
|
remark: '6月采购结算'),
|
||||||
|
FinanceRecord(
|
||||||
|
id: 3,
|
||||||
|
type: 'receipt',
|
||||||
|
amount: 42600,
|
||||||
|
balance: 0,
|
||||||
|
status: 'closed',
|
||||||
|
partnerName: '金樽商贸',
|
||||||
|
recordDate: '2026-06-18',
|
||||||
|
remark: '五粮液尾款'),
|
||||||
|
FinanceRecord(
|
||||||
|
id: 4,
|
||||||
|
type: 'receipt',
|
||||||
|
amount: 31500,
|
||||||
|
balance: 0,
|
||||||
|
status: 'closed',
|
||||||
|
partnerName: '鸿运餐饮连锁',
|
||||||
|
recordDate: '2026-06-17',
|
||||||
|
remark: '宴会用酒'),
|
||||||
|
FinanceRecord(
|
||||||
|
id: 5,
|
||||||
|
type: 'payment',
|
||||||
|
amount: 96400,
|
||||||
|
balance: 0,
|
||||||
|
status: 'closed',
|
||||||
|
partnerName: '五粮液省级代理',
|
||||||
|
recordDate: '2026-06-16',
|
||||||
|
remark: '普五补货'),
|
||||||
|
];
|
||||||
|
|
||||||
|
class _FakeFinanceList extends FinanceListNotifier {
|
||||||
|
@override
|
||||||
|
Future<PageResult<FinanceRecord>> build() async =>
|
||||||
|
const PageResult(data: _flows, total: 5, page: 1, pageSize: 50);
|
||||||
|
@override
|
||||||
|
void setType(String type) {}
|
||||||
|
@override
|
||||||
|
void setMonth(String month) {}
|
||||||
|
@override
|
||||||
|
void setRange(String startDate, String endDate) {}
|
||||||
|
@override
|
||||||
|
void setPage(int page) {}
|
||||||
|
@override
|
||||||
|
void setPageSize(int pageSize) {}
|
||||||
|
@override
|
||||||
|
void reload() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Override> _overrides() => [
|
||||||
|
financeListProvider.overrideWith(() => _FakeFinanceList()),
|
||||||
|
financePartnerRowsProvider.overrideWith((ref) async => _rows),
|
||||||
|
// 环比凑成原型 ▲8.4% / ▼3.1%
|
||||||
|
stockOutSummaryProvider.overrideWith((ref) async =>
|
||||||
|
const StockSummary(monthAmount: 1860000, lastMonthAmount: 1715867)),
|
||||||
|
stockInSummaryProvider.overrideWith((ref) async =>
|
||||||
|
const StockSummary(monthAmount: 1320000, lastMonthAmount: 1362229)),
|
||||||
|
isReadonlyProvider.overrideWithValue(false),
|
||||||
|
];
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
SharedPreferences.setMockInitialValues({});
|
||||||
|
appClock = () => DateTime(2026, 7, 13, 9, 30, 15);
|
||||||
|
|
||||||
|
goldenAcrossThemes(
|
||||||
|
'finance 窄屏(m-finance)',
|
||||||
|
goldenPrefix: 'm_finance',
|
||||||
|
child: () => const Scaffold(body: FinanceScreen()),
|
||||||
|
overrides: _overrides,
|
||||||
|
logical: const Size(390, 844),
|
||||||
|
dpr: 2,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Before Width: | Height: | Size: 192 KiB After Width: | Height: | Size: 108 KiB |
|
Before Width: | Height: | Size: 192 KiB After Width: | Height: | Size: 108 KiB |
|
Before Width: | Height: | Size: 196 KiB After Width: | Height: | Size: 109 KiB |
|
Before Width: | Height: | Size: 168 KiB After Width: | Height: | Size: 171 KiB |
|
Before Width: | Height: | Size: 168 KiB After Width: | Height: | Size: 171 KiB |
|
Before Width: | Height: | Size: 170 KiB After Width: | Height: | Size: 172 KiB |
|
After Width: | Height: | Size: 100 KiB |
|
After Width: | Height: | Size: 100 KiB |
|
After Width: | Height: | Size: 101 KiB |
|
After Width: | Height: | Size: 109 KiB |
|
After Width: | Height: | Size: 108 KiB |
|
After Width: | Height: | Size: 109 KiB |
|
After Width: | Height: | Size: 64 KiB |
|
After Width: | Height: | Size: 64 KiB |
|
After Width: | Height: | Size: 65 KiB |
@@ -0,0 +1,42 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:jiu_client/core/utils/clock.dart';
|
||||||
|
import 'package:jiu_client/models/warehouse.dart';
|
||||||
|
import 'package:jiu_client/providers/warehouse_provider.dart';
|
||||||
|
import 'package:jiu_client/screens/inventory/inventory_check_screen.dart';
|
||||||
|
|
||||||
|
import '../support/golden_harness.dart';
|
||||||
|
|
||||||
|
/// 库存盘点 窄屏形态 golden × 三主题(390×844 @2x)——对齐原型 m-inventory-check.html
|
||||||
|
/// 粒度:基本信息卡 + 「盘点单」卡片流(单号/范围·项数/进行中徽章带图标/差异/日期)
|
||||||
|
/// + 底部操作条(取消/提交盘点)。时钟冻结保证单号/日期确定化。
|
||||||
|
/// 更新基准:flutter test --update-goldens test/golden/inventory_check_mobile_golden_test.dart
|
||||||
|
|
||||||
|
const _warehouses = [
|
||||||
|
Warehouse(id: 1, name: '主仓', isDefault: true),
|
||||||
|
Warehouse(id: 2, name: '东库', isDefault: false),
|
||||||
|
];
|
||||||
|
|
||||||
|
class _FakeWarehouses extends WarehouseListNotifier {
|
||||||
|
@override
|
||||||
|
Future<List<Warehouse>> build() async => _warehouses;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Override> _overrides() => [
|
||||||
|
warehouseListProvider.overrideWith(() => _FakeWarehouses()),
|
||||||
|
];
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
appClock = () => DateTime(2026, 6, 20, 9, 30, 15);
|
||||||
|
|
||||||
|
goldenAcrossThemes(
|
||||||
|
'inventory check 窄屏(m-inventory-check)',
|
||||||
|
goldenPrefix: 'm_inventory_check',
|
||||||
|
child: () => const InventoryCheckScreen(),
|
||||||
|
overrides: _overrides,
|
||||||
|
logical: const Size(390, 844),
|
||||||
|
dpr: 2,
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,192 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
import 'package:jiu_client/core/models/page_result.dart';
|
||||||
|
import 'package:jiu_client/core/auth/auth_state.dart';
|
||||||
|
import 'package:jiu_client/models/inventory.dart';
|
||||||
|
import 'package:jiu_client/models/product_option.dart';
|
||||||
|
import 'package:jiu_client/providers/inventory_provider.dart';
|
||||||
|
import 'package:jiu_client/providers/product_option_provider.dart';
|
||||||
|
import 'package:jiu_client/screens/inventory/inventory_list_screen.dart';
|
||||||
|
|
||||||
|
import '../support/golden_harness.dart';
|
||||||
|
|
||||||
|
/// 库存列表 窄屏形态 golden × 三主题(390×844 @2x)——对齐原型 m-inventory.html:
|
||||||
|
/// 隐藏页内大标题头 + KPI 2×2 MKpiGrid(缺货卡 warn)+ MSearchRow + 卡片流图标徽章。
|
||||||
|
/// 更新基准:flutter test --update-goldens test/golden/inventory_list_mobile_golden_test.dart
|
||||||
|
|
||||||
|
// 原型 ITEMS 前 10 行(与桌面 golden 同套 fixtures)。
|
||||||
|
const _items = [
|
||||||
|
Inventory(
|
||||||
|
id: 1,
|
||||||
|
productId: 1,
|
||||||
|
quantity: 128,
|
||||||
|
productCode: 'MT-FT-500',
|
||||||
|
productName: '茅台 飞天 53°',
|
||||||
|
series: '飞天',
|
||||||
|
spec: '500ml×6',
|
||||||
|
unit: '件',
|
||||||
|
warehouseName: '主仓',
|
||||||
|
unitPrice: 2680,
|
||||||
|
salePrice: 2680,
|
||||||
|
productionDate: '2024-07-11',
|
||||||
|
batchNo: 'PZ240711',
|
||||||
|
supplierName: '贵州茅台经销',
|
||||||
|
minStock: 10,
|
||||||
|
createdAt: '2026-06-14',
|
||||||
|
),
|
||||||
|
Inventory(
|
||||||
|
id: 2,
|
||||||
|
productId: 2,
|
||||||
|
quantity: 64,
|
||||||
|
productCode: 'WLY-PW-500',
|
||||||
|
productName: '五粮液 普五 52°',
|
||||||
|
series: '普五',
|
||||||
|
spec: '500ml×6',
|
||||||
|
unit: '件',
|
||||||
|
warehouseName: '主仓',
|
||||||
|
unitPrice: 1050,
|
||||||
|
salePrice: 1050,
|
||||||
|
productionDate: '2024-04-28',
|
||||||
|
batchNo: 'PZ240428',
|
||||||
|
supplierName: '宜宾五粮液',
|
||||||
|
minStock: 10,
|
||||||
|
createdAt: '2026-06-14',
|
||||||
|
),
|
||||||
|
Inventory(
|
||||||
|
id: 3,
|
||||||
|
productId: 3,
|
||||||
|
quantity: 8,
|
||||||
|
productCode: 'JNC-SJ-500',
|
||||||
|
productName: '剑南春 水晶剑',
|
||||||
|
series: '水晶剑',
|
||||||
|
spec: '500ml×6',
|
||||||
|
unit: '件',
|
||||||
|
warehouseName: '主仓',
|
||||||
|
unitPrice: 438,
|
||||||
|
salePrice: 438,
|
||||||
|
productionDate: '2023-11-15',
|
||||||
|
batchNo: 'PZ231115',
|
||||||
|
supplierName: '绵竹剑南春',
|
||||||
|
minStock: 10,
|
||||||
|
createdAt: '2026-06-12',
|
||||||
|
),
|
||||||
|
Inventory(
|
||||||
|
id: 8,
|
||||||
|
productId: 8,
|
||||||
|
quantity: 0,
|
||||||
|
productCode: 'FJ-QH20-500',
|
||||||
|
productName: '汾酒 青花 20',
|
||||||
|
series: '青花20',
|
||||||
|
spec: '500ml×6',
|
||||||
|
unit: '件',
|
||||||
|
warehouseName: '主仓',
|
||||||
|
unitPrice: 420,
|
||||||
|
salePrice: 420,
|
||||||
|
productionDate: '2024-01-12',
|
||||||
|
batchNo: 'PZ240112',
|
||||||
|
supplierName: '山西杏花村',
|
||||||
|
minStock: 10,
|
||||||
|
createdAt: '2026-06-03',
|
||||||
|
),
|
||||||
|
Inventory(
|
||||||
|
id: 4,
|
||||||
|
productId: 4,
|
||||||
|
quantity: 42,
|
||||||
|
productCode: 'GJ-1573-500',
|
||||||
|
productName: '国窖 1573',
|
||||||
|
series: '1573',
|
||||||
|
spec: '500ml×6',
|
||||||
|
unit: '件',
|
||||||
|
warehouseName: '主仓',
|
||||||
|
unitPrice: 980,
|
||||||
|
salePrice: 980,
|
||||||
|
productionDate: '2024-02-08',
|
||||||
|
batchNo: 'PZ240208',
|
||||||
|
supplierName: '泸州老窖',
|
||||||
|
minStock: 10,
|
||||||
|
createdAt: '2026-06-10',
|
||||||
|
),
|
||||||
|
Inventory(
|
||||||
|
id: 5,
|
||||||
|
productId: 5,
|
||||||
|
quantity: 23,
|
||||||
|
productCode: 'YH-M6-500',
|
||||||
|
productName: '洋河 梦之蓝 M6',
|
||||||
|
series: '梦之蓝M6',
|
||||||
|
spec: '550ml×6',
|
||||||
|
unit: '件',
|
||||||
|
warehouseName: '主仓',
|
||||||
|
unitPrice: 620,
|
||||||
|
salePrice: 620,
|
||||||
|
productionDate: '2024-05-20',
|
||||||
|
batchNo: 'PZ240520',
|
||||||
|
supplierName: '洋河股份',
|
||||||
|
minStock: 30,
|
||||||
|
createdAt: '2026-06-09',
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
class _FakeInvNotifier extends InventoryListNotifier {
|
||||||
|
@override
|
||||||
|
Future<PageResult<Inventory>> build() async =>
|
||||||
|
const PageResult(data: _items, total: 6, page: 1, pageSize: 10);
|
||||||
|
@override
|
||||||
|
void setPage(int page) {}
|
||||||
|
@override
|
||||||
|
void setPageSize(int pageSize) {}
|
||||||
|
@override
|
||||||
|
void setWarehouseId(int? id) {}
|
||||||
|
@override
|
||||||
|
void setKeyword(String keyword) {}
|
||||||
|
@override
|
||||||
|
void setCode(String code) {}
|
||||||
|
@override
|
||||||
|
void setSeries(List<String> series) {}
|
||||||
|
@override
|
||||||
|
void setSpec(List<String> spec) {}
|
||||||
|
@override
|
||||||
|
void reload() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FakeSeriesNotifier extends ProductSeriesListNotifier {
|
||||||
|
@override
|
||||||
|
Future<List<ProductSeriesOption>> build() async => const [];
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FakeSpecNotifier extends ProductSpecListNotifier {
|
||||||
|
@override
|
||||||
|
Future<List<ProductSpecOption>> build() async => const [];
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Override> _overrides() => [
|
||||||
|
inventoryListProvider.overrideWith(() => _FakeInvNotifier()),
|
||||||
|
productSeriesListProvider.overrideWith(() => _FakeSeriesNotifier()),
|
||||||
|
productSpecListProvider.overrideWith(() => _FakeSpecNotifier()),
|
||||||
|
isReadonlyProvider.overrideWithValue(false),
|
||||||
|
inventorySummaryProvider.overrideWith((ref) => const InventorySummary(
|
||||||
|
skuCount: 1284,
|
||||||
|
stockValue: 2640000,
|
||||||
|
inStockQty: 18920,
|
||||||
|
shortageCount: 17,
|
||||||
|
warningCount: 6,
|
||||||
|
lastMonthSku: 1255,
|
||||||
|
lastMonthValue: 2611276,
|
||||||
|
lastMonthQty: 19034,
|
||||||
|
)),
|
||||||
|
];
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
SharedPreferences.setMockInitialValues({});
|
||||||
|
|
||||||
|
goldenAcrossThemes(
|
||||||
|
'inventory list 窄屏(m-inventory)',
|
||||||
|
goldenPrefix: 'm_inventory',
|
||||||
|
child: () => const Scaffold(body: InventoryListScreen()),
|
||||||
|
overrides: _overrides,
|
||||||
|
logical: const Size(390, 844),
|
||||||
|
dpr: 2,
|
||||||
|
);
|
||||||
|
}
|
||||||