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:
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':
|
||||
// 原型 .pcode(fs-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),
|
||||
]),
|
||||
),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user