feat(client): 登录/注册页照原型重建,ds 真相源组件族统一全部屏

- 登录/注册(login.html/register.html 1:1):两栏卡片+品牌渐变面板+主题小衣服
  pill(onSurface 变体)+记住我(记录并预填最近账号)+原型式 toast 校验;
  登录 fidelity 1.4–2.1% 三主题全绿;注册暂不入闸(少 门店编号/兑换券 字段,
  已记 CONTRACT,screens.mjs 留存根)
- ds 原子补齐:DsToast(.toast 单例)/DsCheck(.check/.agree)/DsButton lg 档/
  DsSelect 替换全部旧 DropdownButton/DsField label 在上/DsInput 后缀与密码形态
- 全屏统一:对话框按钮全 DsButton、盒式输入主题钉死(visualDensity.standard、
  h38、InputDecorationTheme 渗漏修复)、图标全 lucide、JetBrains Mono 三端同源、
  BrandMark 真相源 logo、只读模式写操作全量守卫(WriteGuard+DsToast)
- 出入库列表:版式对齐原型(卡片对齐+搜索框居中)、KPI 近30天滚动、结清后
  失效财务应收应付表;商品编辑抽屉介绍库改搜索下拉、图片双击全屏预览
- 删除旧 UI 死代码:DataTableCard/FormDialog/PageScaffold/SearchChip/
  SelectProductDialog/tabStateProvider
- golden/fidelity:stock-in/out 补注册(9%)、login 入册(8%)、goldens 全量重打;
  修复 pubspec flutter_web_plugins 非法声明(CI pub get 阻断)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JJ1g8XV1YhhmHRzhwWEW7o
This commit is contained in:
wangjia
2026-07-03 09:58:14 +08:00
parent ca7595b113
commit 6238b86dcb
286 changed files with 16831 additions and 11136 deletions
File diff suppressed because it is too large Load Diff
@@ -28,7 +28,8 @@ import '../../providers/user_provider.dart' show userListProvider;
import '../../providers/product_option_provider.dart'
show productSeriesListProvider, productSpecListProvider;
import '../../providers/product_provider.dart' show productRepositoryProvider;
import '../../providers/finance_provider.dart' show financeRepositoryProvider;
import '../../providers/finance_provider.dart'
show financeRepositoryProvider, financePartnerRowsProvider;
import '../../providers/shop_provider.dart' show shopInfoProvider;
import '../../widgets/write_guard.dart';
import '../../widgets/order_row_actions.dart';
@@ -37,6 +38,8 @@ import '../../widgets/order_detail_drawer.dart';
import '../../widgets/wheel_date_picker.dart';
import '../../core/auth/auth_state.dart'
show isAdminProvider, currentUserIdProvider;
import '../../core/theme/app_fonts.dart';
import '../../widgets/ds/ds_toast.dart';
class StockInListScreen extends ConsumerStatefulWidget {
const StockInListScreen({super.key});
@@ -79,7 +82,13 @@ class _StockInListScreenState extends ConsumerState<StockInListScreen> {
];
// 单视图已知主状态;未知值(如旧版残留的 'pending,draft' 多值)进页面时归零。
static const _knownStatuses = {'', 'draft', 'pending', 'approved', 'rejected'};
static const _knownStatuses = {
'',
'draft',
'pending',
'approved',
'rejected'
};
@override
void initState() {
@@ -218,7 +227,8 @@ class _StockInListScreenState extends ConsumerState<StockInListScreen> {
label = '近 30 天';
break;
case 'month':
range = DateTimeRange(start: DateTime(now.year, now.month, 1), end: now);
range =
DateTimeRange(start: DateTime(now.year, now.month, 1), end: now);
label = '本月';
break;
default: // all
@@ -300,10 +310,10 @@ class _StockInListScreenState extends ConsumerState<StockInListScreen> {
style: TextStyle(color: context.tokens.muted),
textAlign: TextAlign.center),
const SizedBox(height: 12),
ElevatedButton(
onPressed: () => ref.read(stockInListProvider.notifier).reload(),
child: const Text('重试'),
),
DsButton('重试',
variant: DsBtnVariant.primary,
onPressed: () =>
ref.read(stockInListProvider.notifier).reload()),
],
),
),
@@ -317,11 +327,11 @@ class _StockInListScreenState extends ConsumerState<StockInListScreen> {
.toList();
final warehouseOptions = (ref
.watch(warehouseListProvider)
.valueOrNull
?.map((w) => w.name)
.toList() ??
<String>[])
.watch(warehouseListProvider)
.valueOrNull
?.map((w) => w.name)
.toList() ??
<String>[])
..sort();
final supplierOptions = ref
.watch(allPartnersProvider)
@@ -334,51 +344,59 @@ class _StockInListScreenState extends ConsumerState<StockInListScreen> {
? int.tryParse(_detail['partner_id']!)
: null;
final summary = ref.watch(stockInSummaryProvider).valueOrNull;
final summary = ref.watch(stockInSummary30Provider).valueOrNull;
final content = Column(
children: [
_buildHeader(result.total, summary),
_buildKpis(summary, result.total),
const Divider(height: 1),
Expanded(
child: DsTable(
total: result.total,
page: result.page,
pageSize: result.pageSize,
onPageChanged: (p) =>
ref.read(stockInListProvider.notifier).setPage(p),
onPageSizeChanged: (s) =>
ref.read(stockInListProvider.notifier).setPageSize(s),
emptyText: '没有匹配的入库单 · 试试调整筛选或搜索',
toolbar: _buildToolbar(
orders: orders,
warehouseOptions: warehouseOptions,
supplierOptions: supplierOptions,
selectedSupplierId: selectedSupplierId,
final content = Container(
color: context.tokens.bg,
padding: context.isMobile
? EdgeInsets.zero
// 原型 .main{padding:22px 26px}
: const EdgeInsets.fromLTRB(26, 22, 26, 22),
child: Column(
children: [
_buildHeader(result.total, summary),
_buildKpis(summary, result.total),
Expanded(
child: DsTable(
total: result.total,
page: result.page,
pageSize: result.pageSize,
onPageChanged: (p) =>
ref.read(stockInListProvider.notifier).setPage(p),
onPageSizeChanged: (s) =>
ref.read(stockInListProvider.notifier).setPageSize(s),
emptyText: '没有匹配的入库单 · 试试调整筛选或搜索',
toolbar: _buildToolbar(
orders: orders,
warehouseOptions: warehouseOptions,
supplierOptions: supplierOptions,
selectedSupplierId: selectedSupplierId,
),
mobileCards:
orders.map((o) => _orderCard(context, o)).toList(),
columns: _colDefs
.map((c) => DsColumn(c.$1, c.$2,
numeric: c.$1 == 'amount',
action: c.$1 == 'actions'))
.toList(),
rows: orders
.map((o) => DsRow(
onTap: () => _showDetail(context, o.id),
cells: _colDefs
.map((c) => _buildCell(c.$1, o))
.toList(),
))
.toList(),
),
),
mobileCards: orders.map((o) => _orderCard(context, o)).toList(),
columns: _colDefs
.map((c) => DsColumn(c.$1, c.$2,
numeric: c.$1 == 'amount', action: c.$1 == 'actions'))
.toList(),
rows: orders
.map((o) => DsRow(
onTap: () => _showDetail(context, o.id),
cells: _colDefs
.map((c) => _buildCell(c.$1, o))
.toList(),
))
.toList(),
),
),
],
);
],
));
return Stack(
children: [
content,
// 搜索/筛选 reload 中:叠加半透明进度,保留旧内容不白屏。
if (asyncOrders.isLoading) const Positioned.fill(child: DsLoadingScrim()),
if (asyncOrders.isLoading)
const Positioned.fill(child: DsLoadingScrim()),
],
);
},
@@ -391,8 +409,11 @@ class _StockInListScreenState extends ConsumerState<StockInListScreen> {
return Container(
width: double.infinity,
color: context.tokens.bg,
padding: const EdgeInsets.fromLTRB(
AppDims.sp4, AppDims.sp4, AppDims.sp4, AppDims.sp2),
// 原型 .head{margin-bottom:18px};窄屏保持紧凑内边距
padding: context.isMobile
? const EdgeInsets.fromLTRB(
AppDims.sp4, AppDims.sp4, AppDims.sp4, AppDims.sp2)
: const EdgeInsets.only(bottom: 18),
child: Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
@@ -404,7 +425,7 @@ class _StockInListScreenState extends ConsumerState<StockInListScreen> {
const SizedBox(width: AppDims.sp3),
Padding(
padding: const EdgeInsets.only(bottom: 2),
child: Text('本月 $monthCount 笔入库单',
child: Text('近30天 $monthCount 笔入库单',
style: TextStyle(
fontSize: AppDims.fsSm, color: context.tokens.muted)),
),
@@ -443,23 +464,23 @@ class _StockInListScreenState extends ConsumerState<StockInListScreen> {
? '¥${(v / 10000).toStringAsFixed(v >= 1000000 ? 0 : 1)}'
: '¥${v.toStringAsFixed(0)}';
String pctText(double? pct) =>
pct == null ? '上月' : '${pct.abs().toStringAsFixed(1)}% 较上月';
pct == null ? '前30天' : '${pct.abs().toStringAsFixed(1)}% 较前30天';
DsKpiDelta pctTone(double? pct) => pct == null
? DsKpiDelta.neutral
: (pct >= 0 ? DsKpiDelta.up : DsKpiDelta.down);
final cards = <Widget>[
DsKpi(
title: '本月入库笔数',
value: NumberFormat.decimalPattern()
.format(summary?.monthCount ?? total),
title: '近30天入库笔数',
value:
NumberFormat.decimalPattern().format(summary?.monthCount ?? total),
icon: LucideIcons.download,
tone: DsKpiTone.info,
delta: pctText(summary?.countDeltaPct),
deltaTone: pctTone(summary?.countDeltaPct),
),
DsKpi(
title: '本月入库金额',
title: '近30天入库金额',
value: summary != null ? yuanWan(summary.monthAmount) : '',
icon: LucideIcons.wallet,
tone: DsKpiTone.ok,
@@ -489,7 +510,10 @@ class _StockInListScreenState extends ConsumerState<StockInListScreen> {
}
return Container(
color: context.tokens.bg,
padding: const EdgeInsets.all(AppDims.sp3),
// 原型 .kpis{margin-bottom:20px};窄屏保持紧凑内边距
padding: context.isMobile
? const EdgeInsets.all(AppDims.sp3)
: const EdgeInsets.only(bottom: 20),
child: mobile
? SingleChildScrollView(
scrollDirection: Axis.horizontal,
@@ -531,7 +555,8 @@ class _StockInListScreenState extends ConsumerState<StockInListScreen> {
);
final warehouseChip = PopupMenuButton<String>(
onSelected: (v) => setState(() => _warehouseName = v.isEmpty ? null : v),
onSelected: (v) =>
setState(() => _warehouseName = v.isEmpty ? null : v),
offset: const Offset(0, 40),
color: ctx.tokens.surface,
elevation: 8,
@@ -674,10 +699,12 @@ class _StockInListScreenState extends ConsumerState<StockInListScreen> {
final t = context.tokens;
switch (key) {
case 'order_no':
// 原型 .pcodefs-body 覆盖):faint + mono
return Text(o.orderNo,
style: TextStyle(
color: t.primary,
fontFamily: 'monospace',
color: t.faint,
fontFamily: AppFonts.mono,
fontFamilyFallback: AppFonts.monoFallback,
fontSize: AppDims.fsBody));
case 'supplier':
return Text(o.partnerName ?? '-');
@@ -686,9 +713,11 @@ class _StockInListScreenState extends ConsumerState<StockInListScreen> {
case 'amount':
return Text(
o.totalAmount != null
? '¥${o.totalAmount!.toStringAsFixed(2)}'
: '-',
style: const TextStyle(fontFamily: 'monospace'));
? '¥${NumberFormat('#,##0').format(o.totalAmount)}'
: '',
style: const TextStyle(
fontFamily: AppFonts.mono,
fontFamilyFallback: AppFonts.monoFallback));
case 'status':
final rb = returnStateBadge(context, o.returnState);
return Row(mainAxisSize: MainAxisSize.min, children: [
@@ -701,7 +730,7 @@ class _StockInListScreenState extends ConsumerState<StockInListScreen> {
case 'operator':
return Text(o.operatorName ?? '-');
case 'reviewer':
return Text(o.reviewerName ?? '-',
return Text(o.reviewerName ?? '',
style: o.reviewerName == null ? TextStyle(color: t.faint) : null);
case 'actions':
// 原型:操作列只放一个眼睛图标,点开右侧详情抽屉(其余操作在抽屉内)。
@@ -785,7 +814,10 @@ class _StockInListScreenState extends ConsumerState<StockInListScreen> {
return MobileListCard(
onTap: () => _showDetail(context, o.id),
title: Text(o.orderNo,
style: const TextStyle(fontFamily: 'monospace', fontSize: 14)),
style: const TextStyle(
fontFamily: AppFonts.mono,
fontFamilyFallback: AppFonts.monoFallback,
fontSize: 14)),
trailing: Row(mainAxisSize: MainAxisSize.min, children: [
StatusBadge(_apiStatusToEnum(o.status)),
if (returnStateBadge(context, o.returnState) != null) ...[
@@ -882,7 +914,8 @@ class _StockInListScreenState extends ConsumerState<StockInListScreen> {
final readonly = WriteGuard.isReadonly(ref);
final isAdmin = ref.read(isAdminProvider);
final canWithdraw = isAdmin ||
(o.operatorId != null && o.operatorId == ref.read(currentUserIdProvider));
(o.operatorId != null &&
o.operatorId == ref.read(currentUserIdProvider));
void close() => Navigator.of(context).pop();
DsButton b(String label, VoidCallback onTap,
{DsBtnVariant v = DsBtnVariant.ghost}) =>
@@ -994,7 +1027,9 @@ class _StockInListScreenState extends ConsumerState<StockInListScreen> {
final pending =
o.items.where((it) => it.unitPrice == 0 && it.id != null).toList();
if (pending.isEmpty) return;
final controllers = {for (final it in pending) it.id!: TextEditingController()};
final controllers = {
for (final it in pending) it.id!: TextEditingController()
};
final ok = await showDialog<bool>(
context: context,
builder: (ctx) => AlertDialog(
@@ -1034,12 +1069,10 @@ class _StockInListScreenState extends ConsumerState<StockInListScreen> {
),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(ctx, false),
child: const Text('取消')),
ElevatedButton(
onPressed: () => Navigator.pop(ctx, true),
child: const Text('确认')),
DsButton('取消', onPressed: () => Navigator.pop(ctx, false)),
DsButton('确认',
variant: DsBtnVariant.primary,
onPressed: () => Navigator.pop(ctx, true)),
],
),
);
@@ -1057,13 +1090,11 @@ class _StockInListScreenState extends ConsumerState<StockInListScreen> {
try {
await ref.read(stockInRepositoryProvider).confirmCost(o.id, items);
if (!mounted) return;
ScaffoldMessenger.of(context)
.showSnackBar(const SnackBar(content: Text('进价已确认')));
showDsToast(context, '进价已确认');
ref.read(stockInListProvider.notifier).reload();
} on AppException catch (e) {
if (!mounted) return;
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text('确认失败:${e.message}')));
showDsToast(context, '确认失败:${e.message}');
}
}
@@ -1075,34 +1106,25 @@ class _StockInListScreenState extends ConsumerState<StockInListScreen> {
title: const Text('结清确认'),
content: const Text('确认将该单据的账款标记为已结清?'),
actions: [
TextButton(
onPressed: () => Navigator.pop(ctx, false),
child: const Text('取消')),
TextButton(
onPressed: () => Navigator.pop(ctx, true),
child:
Text('确认结清', style: TextStyle(color: context.tokens.accent)),
),
DsButton('取消', onPressed: () => Navigator.pop(ctx, false)),
DsButton('确认结清',
variant: DsBtnVariant.accent,
onPressed: () => Navigator.pop(ctx, true)),
],
),
);
if (confirmed != true || !context.mounted) return;
try {
await ref.read(financeRepositoryProvider).closeByRef(refType, orderId);
// 结清改变单据结算状态展示,刷新列表;财务屏应收应付表非 autoDispose,需一并失效
ref.read(stockInListProvider.notifier).reload();
ref.invalidate(financePartnerRowsProvider);
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: const Text('已结清'),
backgroundColor: context.tokens.success),
);
showDsToast(context, '已结清', bg: context.tokens.success);
}
} catch (e) {
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(e.toString()),
backgroundColor: context.tokens.danger),
);
showDsToast(context, e.toString(), bg: context.tokens.danger);
}
}
}
@@ -1114,16 +1136,10 @@ class _StockInListScreenState extends ConsumerState<StockInListScreen> {
title: const Text('删除确认'),
content: Text('确认删除入库单「${o.orderNo}」?此操作不可恢复。'),
actions: [
TextButton(
onPressed: () => Navigator.of(ctx).pop(false),
child: const Text('取消')),
ElevatedButton(
onPressed: () => Navigator.of(ctx).pop(true),
style: ElevatedButton.styleFrom(
backgroundColor: context.tokens.danger,
foregroundColor: Colors.white),
child: const Text('删除'),
),
DsButton('取消', onPressed: () => Navigator.of(ctx).pop(false)),
DsButton('删除',
variant: DsBtnVariant.danger,
onPressed: () => Navigator.of(ctx).pop(true)),
],
),
);
@@ -1131,15 +1147,11 @@ class _StockInListScreenState extends ConsumerState<StockInListScreen> {
try {
await ref.read(stockInListProvider.notifier).deleteOrder(o.id);
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: const Text('已删除'),
backgroundColor: context.tokens.success));
showDsToast(context, '已删除', bg: context.tokens.success);
}
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text('删除失败:$e'),
backgroundColor: context.tokens.danger));
showDsToast(context, '删除失败:$e', bg: context.tokens.danger);
}
}
}
@@ -1152,12 +1164,10 @@ class _StockInListScreenState extends ConsumerState<StockInListScreen> {
title: const Text('提交审核'),
content: Text('确认提交入库单「${o.orderNo}」?'),
actions: [
TextButton(
onPressed: () => Navigator.of(ctx).pop(false),
child: const Text('取消')),
ElevatedButton(
onPressed: () => Navigator.of(ctx).pop(true),
child: const Text('提交')),
DsButton('取消', onPressed: () => Navigator.of(ctx).pop(false)),
DsButton('提交',
variant: DsBtnVariant.primary,
onPressed: () => Navigator.of(ctx).pop(true)),
],
),
);
@@ -1165,15 +1175,11 @@ class _StockInListScreenState extends ConsumerState<StockInListScreen> {
try {
await ref.read(stockInListProvider.notifier).submitOrder(o.id);
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: const Text('已提交审核'),
backgroundColor: context.tokens.success));
showDsToast(context, '已提交审核', bg: context.tokens.success);
}
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text('提交失败:$e'),
backgroundColor: context.tokens.danger));
showDsToast(context, '提交失败:$e', bg: context.tokens.danger);
}
}
}
@@ -1186,16 +1192,10 @@ class _StockInListScreenState extends ConsumerState<StockInListScreen> {
title: const Text('审核确认'),
content: Text('确认审核通过入库单「${o.orderNo}」?审核后将入库并增加库存。'),
actions: [
TextButton(
onPressed: () => Navigator.of(ctx).pop(false),
child: const Text('取消')),
ElevatedButton(
onPressed: () => Navigator.of(ctx).pop(true),
style: ElevatedButton.styleFrom(
backgroundColor: context.tokens.success,
foregroundColor: Colors.white),
child: const Text('通过'),
),
DsButton('取消', onPressed: () => Navigator.of(ctx).pop(false)),
DsButton('通过',
variant: DsBtnVariant.success,
onPressed: () => Navigator.of(ctx).pop(true)),
],
),
);
@@ -1203,15 +1203,11 @@ class _StockInListScreenState extends ConsumerState<StockInListScreen> {
try {
await ref.read(stockInListProvider.notifier).approveOrder(o.id);
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: const Text('审核通过'),
backgroundColor: context.tokens.success));
showDsToast(context, '审核通过', bg: context.tokens.success);
}
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text('审核失败:$e'),
backgroundColor: context.tokens.danger));
showDsToast(context, '审核失败:$e', bg: context.tokens.danger);
}
}
}
@@ -1224,16 +1220,10 @@ class _StockInListScreenState extends ConsumerState<StockInListScreen> {
title: const Text('拒绝确认'),
content: Text('确认拒绝入库单「${o.orderNo}」?'),
actions: [
TextButton(
onPressed: () => Navigator.of(ctx).pop(false),
child: const Text('取消')),
ElevatedButton(
onPressed: () => Navigator.of(ctx).pop(true),
style: ElevatedButton.styleFrom(
backgroundColor: context.tokens.danger,
foregroundColor: Colors.white),
child: const Text('拒绝'),
),
DsButton('取消', onPressed: () => Navigator.of(ctx).pop(false)),
DsButton('拒绝',
variant: DsBtnVariant.danger,
onPressed: () => Navigator.of(ctx).pop(true)),
],
),
);
@@ -1241,15 +1231,11 @@ class _StockInListScreenState extends ConsumerState<StockInListScreen> {
try {
await ref.read(stockInListProvider.notifier).rejectOrder(o.id);
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: const Text('已拒绝'),
backgroundColor: context.tokens.accent));
showDsToast(context, '已拒绝', bg: context.tokens.accent);
}
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text('操作失败:$e'),
backgroundColor: context.tokens.danger));
showDsToast(context, '操作失败:$e', bg: context.tokens.danger);
}
}
}
@@ -1262,16 +1248,10 @@ class _StockInListScreenState extends ConsumerState<StockInListScreen> {
title: const Text('撤回确认'),
content: Text('确认撤回入库单「${o.orderNo}」?撤回后单据回到草稿,可修改后重新提交审核。'),
actions: [
TextButton(
onPressed: () => Navigator.of(ctx).pop(false),
child: const Text('取消')),
ElevatedButton(
onPressed: () => Navigator.of(ctx).pop(true),
style: ElevatedButton.styleFrom(
backgroundColor: context.tokens.accent,
foregroundColor: Colors.white),
child: const Text('撤回'),
),
DsButton('取消', onPressed: () => Navigator.of(ctx).pop(false)),
DsButton('撤回',
variant: DsBtnVariant.accent,
onPressed: () => Navigator.of(ctx).pop(true)),
],
),
);
@@ -1279,15 +1259,11 @@ class _StockInListScreenState extends ConsumerState<StockInListScreen> {
try {
await ref.read(stockInListProvider.notifier).withdrawOrder(o.id);
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: const Text('已撤回为草稿'),
backgroundColor: context.tokens.accent));
showDsToast(context, '已撤回为草稿', bg: context.tokens.accent);
}
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text('撤回失败:$e'),
backgroundColor: context.tokens.danger));
showDsToast(context, '撤回失败:$e', bg: context.tokens.danger);
}
}
}
@@ -1300,16 +1276,10 @@ class _StockInListScreenState extends ConsumerState<StockInListScreen> {
title: const Text('退单'),
content: Text('确认对已审核入库单「${o.orderNo}」发起退单?将打开退单明细窗口选择要退的商品。'),
actions: [
TextButton(
onPressed: () => Navigator.of(ctx).pop(false),
child: const Text('取消')),
ElevatedButton(
onPressed: () => Navigator.of(ctx).pop(true),
style: ElevatedButton.styleFrom(
backgroundColor: context.tokens.danger,
foregroundColor: Colors.white),
child: const Text('确认'),
),
DsButton('取消', onPressed: () => Navigator.of(ctx).pop(false)),
DsButton('确认',
variant: DsBtnVariant.danger,
onPressed: () => Navigator.of(ctx).pop(true)),
],
),
);
@@ -1319,9 +1289,7 @@ class _StockInListScreenState extends ConsumerState<StockInListScreen> {
full = await ref.read(stockInRepositoryProvider).get(o.id);
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text('加载明细失败:$e'),
backgroundColor: context.tokens.danger));
showDsToast(context, '加载明细失败:$e', bg: context.tokens.danger);
}
return;
}
@@ -1347,9 +1315,12 @@ class _StockInListScreenState extends ConsumerState<StockInListScreen> {
meta: [
('供应商', o.partnerName ?? '-'),
('仓库', o.warehouseName ?? '-'),
('入库日期', o.orderDate != null && o.orderDate!.length >= 10
? o.orderDate!.substring(0, 10)
: (o.orderDate ?? '-')),
(
'入库日期',
o.orderDate != null && o.orderDate!.length >= 10
? o.orderDate!.substring(0, 10)
: (o.orderDate ?? '-')
),
],
lines: lines,
onSubmit: (ids) =>
@@ -1509,25 +1480,34 @@ class _AdvSearchDialogState extends ConsumerState<_AdvSearchDialog> {
}
List<OptionItem> _supplierOpts() =>
ref.watch(allPartnersProvider).valueOrNull?.data
ref
.watch(allPartnersProvider)
.valueOrNull
?.data
.map((p) => OptionItem(id: p.id, name: p.name))
.toList() ??
const [];
List<OptionItem> _seriesOpts() =>
ref.watch(productSeriesListProvider).valueOrNull
ref
.watch(productSeriesListProvider)
.valueOrNull
?.map((s) => OptionItem(id: s.id, name: s.name))
.toList() ??
const [];
List<OptionItem> _specOpts() =>
ref.watch(productSpecListProvider).valueOrNull
ref
.watch(productSpecListProvider)
.valueOrNull
?.map((s) => OptionItem(id: s.id, name: s.name))
.toList() ??
const [];
List<OptionItem> _userOpts() =>
ref.watch(userListProvider).valueOrNull
ref
.watch(userListProvider)
.valueOrNull
?.map((u) => OptionItem(
id: u.id,
name: (u.realName != null && u.realName!.isNotEmpty)
@@ -1576,7 +1556,11 @@ class _AdvSearchDialogState extends ConsumerState<_AdvSearchDialog> {
style: TextStyle(fontSize: AppDims.fsBody, color: t.text),
decoration: InputDecoration(
isCollapsed: true,
contentPadding: EdgeInsets.zero,
border: InputBorder.none,
filled: false,
enabledBorder: InputBorder.none,
focusedBorder: InputBorder.none,
hintText: hint,
hintStyle: TextStyle(color: t.faint, fontSize: AppDims.fsBody),
),
@@ -1663,7 +1647,7 @@ class _AdvSearchDialogState extends ConsumerState<_AdvSearchDialog> {
const Spacer(),
InkWell(
onTap: () => Navigator.of(context).pop(),
child: Icon(Icons.close, size: 20, color: t.muted),
child: Icon(LucideIcons.x, size: 20, color: t.muted),
),
]),
),
@@ -1674,66 +1658,70 @@ class _AdvSearchDialogState extends ConsumerState<_AdvSearchDialog> {
mainAxisSize: MainAxisSize.min,
children: [
row([
field('单据编号',
textField(_orderNoCtrl, '如 RK20260401001')),
field(
'供应商',
combo(
opts: _supplierOpts(),
selectedId: _partnerId,
hint: '全部供应商',
onChanged: (v) => setState(() => _partnerId = v))),
]),
row([
field('商品名', textField(_productCtrl, '输入酒名')),
field('商品编码', textField(_productCodeCtrl, '输入商品编码')),
]),
row([
field(
'系列',
combo(
opts: seriesOpts,
selectedId: _idByName(seriesOpts, _seriesName),
hint: '全部系列',
onChanged: (v) => setState(() => _seriesName = v == null
? null
: seriesOpts
.where((o) => o.id == v)
.map((o) => o.name)
.firstOrNull))),
field(
'规格',
combo(
opts: specOpts,
selectedId: _idByName(specOpts, _specName),
hint: '全部规格',
onChanged: (v) => setState(() => _specName = v == null
? null
: specOpts
.where((o) => o.id == v)
.map((o) => o.name)
.firstOrNull))),
]),
row([
field('批次号', textField(_batchCtrl, '输入批次号')),
field('状态', statusCombo()),
]),
row([
field(
'入库员',
combo(
opts: _userOpts(),
selectedId: _operatorId,
hint: '全部入库员',
onChanged: (v) => setState(() => _operatorId = v))),
field(
'审核人',
combo(
opts: _userOpts(),
selectedId: _reviewerId,
hint: '全部审核人',
onChanged: (v) => setState(() => _reviewerId = v))),
]),
field('单据编号', textField(_orderNoCtrl, '如 RK20260401001')),
field(
'供应商',
combo(
opts: _supplierOpts(),
selectedId: _partnerId,
hint: '全部供应商',
onChanged: (v) =>
setState(() => _partnerId = v))),
]),
row([
field('商品名', textField(_productCtrl, '输入酒名')),
field('商品编码', textField(_productCodeCtrl, '输入商品编码')),
]),
row([
field(
'系列',
combo(
opts: seriesOpts,
selectedId: _idByName(seriesOpts, _seriesName),
hint: '全部系列',
onChanged: (v) => setState(() => _seriesName =
v == null
? null
: seriesOpts
.where((o) => o.id == v)
.map((o) => o.name)
.firstOrNull))),
field(
'规格',
combo(
opts: specOpts,
selectedId: _idByName(specOpts, _specName),
hint: '全部规格',
onChanged: (v) => setState(() => _specName =
v == null
? null
: specOpts
.where((o) => o.id == v)
.map((o) => o.name)
.firstOrNull))),
]),
row([
field('批次号', textField(_batchCtrl, '输入批次号')),
field('状态', statusCombo()),
]),
row([
field(
'入库员',
combo(
opts: _userOpts(),
selectedId: _operatorId,
hint: '全部入库员',
onChanged: (v) =>
setState(() => _operatorId = v))),
field(
'审核人',
combo(
opts: _userOpts(),
selectedId: _reviewerId,
hint: '全部审核人',
onChanged: (v) =>
setState(() => _reviewerId = v))),
]),
row([
field('单据日期', _dateField()),
]),
@@ -1749,8 +1737,7 @@ class _AdvSearchDialogState extends ConsumerState<_AdvSearchDialog> {
child: Row(children: [
DsButton('重置', onPressed: _reset),
const Spacer(),
DsButton('取消',
onPressed: () => Navigator.of(context).pop()),
DsButton('取消', onPressed: () => Navigator.of(context).pop()),
const SizedBox(width: 10),
DsButton('确认',
variant: DsBtnVariant.primary, onPressed: _apply),
@@ -1783,16 +1770,15 @@ class _AdvSearchDialogState extends ConsumerState<_AdvSearchDialog> {
Expanded(
child: Text(label,
style: TextStyle(
fontSize: AppDims.fsBody,
color: has ? t.text : t.faint)),
fontSize: AppDims.fsBody, color: has ? t.text : t.faint)),
),
if (has)
GestureDetector(
onTap: () => setState(() => _range = null),
child: Icon(Icons.close, size: 14, color: t.muted),
child: Icon(LucideIcons.x, size: 14, color: t.muted),
)
else
Icon(Icons.date_range, size: 16, color: t.faint),
Icon(LucideIcons.calendar, size: 16, color: t.faint),
]),
),
);