diff --git a/client/lib/screens/stock_in/stock_in_list_screen.dart b/client/lib/screens/stock_in/stock_in_list_screen.dart index a168c2f..48a8651 100644 --- a/client/lib/screens/stock_in/stock_in_list_screen.dart +++ b/client/lib/screens/stock_in/stock_in_list_screen.dart @@ -287,7 +287,7 @@ class _StockInListScreenState extends ConsumerState { ? '¥${o.totalAmount!.toStringAsFixed(2)}' : '-')); case 'status': - final rb = returnStateBadge(o.returnState); + final rb = returnStateBadge(context, o.returnState); return DataCell(Row(mainAxisSize: MainAxisSize.min, children: [ StatusBadge(_apiStatusToEnum(o.status)), if (rb != null) ...[const SizedBox(width: 4), rb], @@ -519,6 +519,7 @@ class _StockInListScreenState extends ConsumerState { /// 入库特有的「打标签」通过 afterPrint 注入。 List _orderActions(BuildContext context, StockInOrder o) { return buildOrderRowActions( + context: context, readonly: WriteGuard.isReadonly(ref), canWithdraw: ref.watch(isAdminProvider) || (o.operatorId != null && @@ -585,9 +586,9 @@ class _StockInListScreenState extends ConsumerState { style: const TextStyle(fontFamily: 'monospace', fontSize: 14)), trailing: Row(mainAxisSize: MainAxisSize.min, children: [ StatusBadge(_apiStatusToEnum(o.status)), - if (returnStateBadge(o.returnState) != null) ...[ + if (returnStateBadge(context, o.returnState) != null) ...[ const SizedBox(width: 4), - returnStateBadge(o.returnState)!, + returnStateBadge(context, o.returnState)!, ], ]), fields: [ diff --git a/client/lib/screens/stock_out/stock_out_list_screen.dart b/client/lib/screens/stock_out/stock_out_list_screen.dart index 28f149a..df885dc 100644 --- a/client/lib/screens/stock_out/stock_out_list_screen.dart +++ b/client/lib/screens/stock_out/stock_out_list_screen.dart @@ -292,7 +292,7 @@ class _StockOutListScreenState extends ConsumerState { ? '¥${o.totalAmount!.toStringAsFixed(2)}' : '-')); case 'status': - final rb = returnStateBadge(o.returnState); + final rb = returnStateBadge(context, o.returnState); return DataCell(Row(mainAxisSize: MainAxisSize.min, children: [ StatusBadge(_apiStatusToEnum(o.status)), if (rb != null) ...[const SizedBox(width: 4), rb], @@ -549,6 +549,7 @@ class _StockOutListScreenState extends ConsumerState { /// 操作按钮列表,表格与移动端卡片共用。 List _orderActions(BuildContext context, StockOutOrder o) { return buildOrderRowActions( + context: context, readonly: WriteGuard.isReadonly(ref), canWithdraw: ref.watch(isAdminProvider) || (o.operatorId != null && @@ -585,9 +586,9 @@ class _StockOutListScreenState extends ConsumerState { style: const TextStyle(fontFamily: 'monospace', fontSize: 14)), trailing: Row(mainAxisSize: MainAxisSize.min, children: [ StatusBadge(_apiStatusToEnum(o.status)), - if (returnStateBadge(o.returnState) != null) ...[ + if (returnStateBadge(context, o.returnState) != null) ...[ const SizedBox(width: 4), - returnStateBadge(o.returnState)!, + returnStateBadge(context, o.returnState)!, ], ]), fields: [ diff --git a/client/lib/widgets/data_table_card.dart b/client/lib/widgets/data_table_card.dart index c0df242..eddd7ab 100644 --- a/client/lib/widgets/data_table_card.dart +++ b/client/lib/widgets/data_table_card.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import '../core/responsive/responsive.dart'; -import '../core/theme/app_theme.dart'; +import '../core/theme/context_tokens.dart'; class DataTableCard extends StatefulWidget { final List columns; @@ -53,7 +53,7 @@ class _DataTableCardState extends State { children: [ if (widget.toolbar != null) Container( - color: AppTheme.surface, + color: context.tokens.surface, padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), child: widget.toolbar!, ), @@ -67,14 +67,14 @@ class _DataTableCardState extends State { if (widget.totalCount != null) Container( height: 48, - color: AppTheme.surface, + color: context.tokens.surface, padding: const EdgeInsets.symmetric(horizontal: 16), child: Row( children: [ Text( '共 ${widget.totalCount} 条记录', - style: const TextStyle( - fontSize: 13, color: AppTheme.textSecondary), + style: TextStyle( + fontSize: 13, color: context.tokens.muted), ), const Spacer(), _PaginationBar( @@ -132,11 +132,11 @@ class _DataTableCardState extends State { Widget _buildMobileList() { final cards = widget.mobileCards!; if (cards.isEmpty) { - return const Center( + return Center( child: Padding( - padding: EdgeInsets.all(32), + padding: const EdgeInsets.all(32), child: Text('暂无数据', - style: TextStyle(color: AppTheme.textSecondary, fontSize: 14)), + style: TextStyle(color: context.tokens.muted, fontSize: 14)), ), ); } @@ -155,10 +155,10 @@ class _DataTableCardState extends State { return Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), child: DefaultTextStyle( - style: const TextStyle( + style: TextStyle( fontSize: 13, fontWeight: FontWeight.w600, - color: AppTheme.primaryDark, + color: context.tokens.primaryDark, ), child: col.numeric ? Align(alignment: Alignment.centerRight, child: col.label) @@ -189,8 +189,8 @@ class _DataTableCardState extends State { padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), child: DefaultTextStyle( - style: const TextStyle( - fontSize: 13, color: AppTheme.textPrimary), + style: TextStyle( + fontSize: 13, color: context.tokens.text), child: cell.child, ), ), @@ -227,13 +227,13 @@ class _PaginationBar extends StatelessWidget { children: [ // 每页条数选择器 if (onPageSizeChanged != null) ...[ - const Text('每页', style: TextStyle(fontSize: 13, color: AppTheme.textSecondary)), + Text('每页', style: TextStyle(fontSize: 13, color: context.tokens.muted)), const SizedBox(width: 4), DropdownButtonHideUnderline( child: DropdownButton( value: effectiveSize, isDense: true, - style: const TextStyle(fontSize: 13, color: AppTheme.textPrimary), + style: TextStyle(fontSize: 13, color: context.tokens.text), items: _pageSizeOptions .map((n) => DropdownMenuItem(value: n, child: Text('$n 条'))) .toList(), diff --git a/client/lib/widgets/date_picker_field.dart b/client/lib/widgets/date_picker_field.dart index 6dee86e..9860882 100644 --- a/client/lib/widgets/date_picker_field.dart +++ b/client/lib/widgets/date_picker_field.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import '../core/theme/app_theme.dart'; +import '../core/theme/context_tokens.dart'; import '../core/utils/date_util.dart'; /// 可键入日期框 + 日历图标(替代旧的「年/月/日」三下拉)。 @@ -138,8 +138,8 @@ class _DatePickerFieldState extends State { const EdgeInsets.symmetric(horizontal: 8, vertical: 8), errorText: field.errorText, suffixIcon: IconButton( - icon: const Icon(Icons.calendar_today, - size: 16, color: AppTheme.textSecondary), + icon: Icon(Icons.calendar_today, + size: 16, color: context.tokens.muted), padding: EdgeInsets.zero, constraints: const BoxConstraints(minWidth: 34, minHeight: 34), tooltip: '选择日期', diff --git a/client/lib/widgets/form_dialog.dart b/client/lib/widgets/form_dialog.dart index b1cb6f3..62cb3d3 100644 --- a/client/lib/widgets/form_dialog.dart +++ b/client/lib/widgets/form_dialog.dart @@ -1,6 +1,5 @@ -import '../core/utils/dialog_util.dart'; import 'package:flutter/material.dart'; -import '../core/theme/app_theme.dart'; +import '../core/theme/context_tokens.dart'; /// Generic dialog wrapper for forms class FormDialog extends StatelessWidget { @@ -35,9 +34,9 @@ class FormDialog extends StatelessWidget { Container( height: 48, padding: const EdgeInsets.symmetric(horizontal: 20), - decoration: const BoxDecoration( - color: AppTheme.primary, - borderRadius: BorderRadius.only( + decoration: BoxDecoration( + color: context.tokens.primary, + borderRadius: const BorderRadius.only( topLeft: Radius.circular(4), topRight: Radius.circular(4), ), diff --git a/client/lib/widgets/label_preview_dialog.dart b/client/lib/widgets/label_preview_dialog.dart index 372d285..8e0c986 100644 --- a/client/lib/widgets/label_preview_dialog.dart +++ b/client/lib/widgets/label_preview_dialog.dart @@ -2,7 +2,7 @@ import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../core/errors/error_reporter.dart'; -import '../core/theme/app_theme.dart'; +import '../core/theme/context_tokens.dart'; import '../core/responsive/responsive.dart'; import '../core/utils/print_util.dart'; import '../core/utils/label_data.dart'; @@ -164,13 +164,13 @@ class _LabelPreviewDialogState extends State { padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 6), decoration: BoxDecoration( border: Border.all( - color: isSelected ? AppTheme.primary : AppTheme.border, + color: isSelected ? context.tokens.primary : context.tokens.border, width: isSelected ? 2 : 1, ), borderRadius: BorderRadius.circular(6), color: isSelected - ? AppTheme.primary.withOpacity(0.04) - : AppTheme.surface, + ? context.tokens.primary.withOpacity(0.04) + : context.tokens.surface, ), child: Row( children: [ @@ -204,8 +204,8 @@ class _LabelPreviewDialogState extends State { style: TextStyle( fontSize: 12, color: isSelected - ? AppTheme.primary - : AppTheme.textSecondary, + ? context.tokens.primary + : context.tokens.muted, fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal, ), @@ -228,7 +228,7 @@ class _LabelPreviewDialogState extends State { child: Container( constraints: const BoxConstraints(maxWidth: 420, maxHeight: 230), decoration: BoxDecoration( - border: Border.all(color: AppTheme.border), + border: Border.all(color: context.tokens.border), borderRadius: BorderRadius.circular(4), color: Colors.white, ), @@ -297,9 +297,9 @@ class _LabelPreviewDialogState extends State { Container( padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 14), - decoration: const BoxDecoration( - color: AppTheme.primary, - borderRadius: BorderRadius.only( + decoration: BoxDecoration( + color: context.tokens.primary, + borderRadius: const BorderRadius.only( topLeft: Radius.circular(12), topRight: Radius.circular(12), ), @@ -334,24 +334,24 @@ class _LabelPreviewDialogState extends State { child: _printers.isEmpty ? Text( _printersChecked ? '无打印机可用' : '正在检测打印机...', - style: const TextStyle( + style: TextStyle( fontSize: 13, - color: AppTheme.textSecondary)) + color: context.tokens.muted)) : DropdownButtonHideUnderline( child: Container( padding: const EdgeInsets.symmetric( horizontal: 10, vertical: 4), decoration: BoxDecoration( - border: Border.all(color: AppTheme.border), + border: Border.all(color: context.tokens.border), borderRadius: BorderRadius.circular(6), ), child: DropdownButton( value: _printer, isExpanded: true, isDense: true, - style: const TextStyle( + style: TextStyle( fontSize: 13, - color: AppTheme.textPrimary), + color: context.tokens.text), items: _printers .map((p) => DropdownMenuItem( value: p, @@ -410,8 +410,8 @@ class _LabelPreviewDialogState extends State { Expanded( child: Text( _status, - style: const TextStyle( - fontSize: 12, color: AppTheme.textSecondary), + style: TextStyle( + fontSize: 12, color: context.tokens.muted), ), ) else diff --git a/client/lib/widgets/mobile_list_card.dart b/client/lib/widgets/mobile_list_card.dart index 1f20019..432f235 100644 --- a/client/lib/widgets/mobile_list_card.dart +++ b/client/lib/widgets/mobile_list_card.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import '../core/theme/app_theme.dart'; +import '../core/theme/context_tokens.dart'; /// 移动端列表卡片的单个字段(label: value)。 class MobileCardField { @@ -56,19 +56,19 @@ class MobileListCard extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ DefaultTextStyle( - style: const TextStyle( + style: TextStyle( fontSize: 15, fontWeight: FontWeight.w600, - color: AppTheme.textPrimary, + color: context.tokens.text, ), child: title, ), if (subtitle != null) ...[ const SizedBox(height: 2), DefaultTextStyle( - style: const TextStyle( + style: TextStyle( fontSize: 12, - color: AppTheme.textSecondary), + color: context.tokens.muted), child: subtitle!, ), ], @@ -83,7 +83,7 @@ class MobileListCard extends StatelessWidget { ), if (fields.isNotEmpty) ...[ const SizedBox(height: 10), - ...fields.map(_buildField), + ...fields.map((f) => _buildField(context, f)), ], if (actions != null && actions!.isNotEmpty) ...[ const Divider(height: 18), @@ -102,7 +102,7 @@ class MobileListCard extends StatelessWidget { ); } - Widget _buildField(MobileCardField f) { + Widget _buildField(BuildContext context, MobileCardField f) { return Padding( padding: const EdgeInsets.symmetric(vertical: 3), child: Row( @@ -112,16 +112,16 @@ class MobileListCard extends StatelessWidget { width: 64, child: Text( f.label, - style: const TextStyle( - fontSize: 13, color: AppTheme.textSecondary), + style: TextStyle( + fontSize: 13, color: context.tokens.muted), ), ), Expanded( child: f.valueWidget ?? Text( f.value ?? '—', - style: const TextStyle( - fontSize: 13, color: AppTheme.textPrimary), + style: TextStyle( + fontSize: 13, color: context.tokens.text), ), ), ], diff --git a/client/lib/widgets/multi_select_dropdown.dart b/client/lib/widgets/multi_select_dropdown.dart index b354988..e2be041 100644 --- a/client/lib/widgets/multi_select_dropdown.dart +++ b/client/lib/widgets/multi_select_dropdown.dart @@ -1,6 +1,6 @@ import '../core/utils/dialog_util.dart'; import 'package:flutter/material.dart'; -import '../core/theme/app_theme.dart'; +import '../core/theme/context_tokens.dart'; /// Compact button → dialog with checkboxes for multi-select filtering class MultiSelectDropdown extends StatelessWidget { @@ -23,8 +23,8 @@ class MultiSelectDropdown extends StatelessWidget { return OutlinedButton( onPressed: options.isEmpty ? null : () => _show(context), style: OutlinedButton.styleFrom( - foregroundColor: active ? AppTheme.primary : AppTheme.textSecondary, - side: BorderSide(color: active ? AppTheme.primary : AppTheme.border), + foregroundColor: active ? context.tokens.primary : context.tokens.muted, + side: BorderSide(color: active ? context.tokens.primary : context.tokens.border), padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), minimumSize: Size.zero, tapTargetSize: MaterialTapTargetSize.shrinkWrap, @@ -143,8 +143,8 @@ class _MultiSelectDialogState extends State<_MultiSelectDialog> { onDeleted: () => setState(() => _selected.remove(s)), materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, visualDensity: VisualDensity.compact, - backgroundColor: AppTheme.primary.withOpacity(0.08), - side: BorderSide(color: AppTheme.primary.withOpacity(0.3), width: 0.5), + backgroundColor: context.tokens.primary.withOpacity(0.08), + side: BorderSide(color: context.tokens.primary.withOpacity(0.3), width: 0.5), labelPadding: const EdgeInsets.symmetric(horizontal: 2), padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 0), )).toList(), @@ -168,12 +168,12 @@ class _MultiSelectDialogState extends State<_MultiSelectDialog> { ConstrainedBox( constraints: const BoxConstraints(maxHeight: 280), child: filtered.isEmpty - ? const Padding( - padding: EdgeInsets.symmetric(vertical: 16), + ? Padding( + padding: const EdgeInsets.symmetric(vertical: 16), child: Center( child: Text('无匹配项', style: TextStyle( - fontSize: 13, color: AppTheme.textSecondary)), + fontSize: 13, color: context.tokens.muted)), ), ) : ListView( @@ -256,7 +256,7 @@ class _FilterableColumnHeaderState extends State { child: Icon( active ? Icons.filter_alt : Icons.filter_alt_outlined, size: 16, - color: active ? AppTheme.primary : AppTheme.textSecondary, + color: active ? context.tokens.primary : context.tokens.muted, ), ), ), @@ -264,8 +264,8 @@ class _FilterableColumnHeaderState extends State { InkWell( onTap: () => widget.onChanged({}), borderRadius: BorderRadius.circular(4), - child: const Icon(Icons.cancel, size: 14, - color: AppTheme.primary), + child: Icon(Icons.cancel, size: 14, + color: context.tokens.primary), ), ], ), @@ -327,8 +327,8 @@ class ColumnToggleButton extends StatelessWidget { return OutlinedButton( onPressed: () => _show(context), style: OutlinedButton.styleFrom( - foregroundColor: AppTheme.textSecondary, - side: const BorderSide(color: AppTheme.border), + foregroundColor: context.tokens.muted, + side: BorderSide(color: context.tokens.border), padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), minimumSize: Size.zero, tapTargetSize: MaterialTapTargetSize.shrinkWrap, diff --git a/client/lib/widgets/network_retry_button.dart b/client/lib/widgets/network_retry_button.dart index 3865c90..7bd64b1 100644 --- a/client/lib/widgets/network_retry_button.dart +++ b/client/lib/widgets/network_retry_button.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import '../core/theme/app_theme.dart'; +import '../core/theme/context_tokens.dart'; import '../providers/connectivity_provider.dart'; /// 离线提示里的「重试」按钮: @@ -27,7 +27,7 @@ class NetworkRetryButton extends ConsumerWidget { ..hideCurrentSnackBar() ..showSnackBar(SnackBar( content: Text(ok ? '已重新连接服务器' : '仍无法连接服务器,请稍后重试'), - backgroundColor: ok ? AppTheme.success : AppTheme.danger, + backgroundColor: ok ? context.tokens.success : context.tokens.danger, duration: const Duration(seconds: 2), behavior: SnackBarBehavior.floating, )); diff --git a/client/lib/widgets/order_return_dialog.dart b/client/lib/widgets/order_return_dialog.dart index b1a2be7..760dd5e 100644 --- a/client/lib/widgets/order_return_dialog.dart +++ b/client/lib/widgets/order_return_dialog.dart @@ -1,18 +1,18 @@ import 'package:flutter/material.dart'; import '../core/responsive/responsive.dart'; -import '../core/theme/app_theme.dart'; +import '../core/theme/context_tokens.dart'; import '../core/utils/dialog_util.dart'; /// 退单状态小徽章(none 返回 null 不显示)。partial=部分退单(amber),full=已退单(红)。 -Widget? returnStateBadge(String state) { +Widget? returnStateBadge(BuildContext context, String state) { Color? c; String? t; if (state == 'partial') { - c = AppTheme.warning; + c = context.tokens.warn; t = '部分退单'; } else if (state == 'full') { - c = AppTheme.danger; + c = context.tokens.danger; t = '已退单'; } else { return null; @@ -109,7 +109,7 @@ class _OrderReturnDialogState extends State<_OrderReturnDialog> { title: const Text('退单确认', style: TextStyle(fontSize: 16)), content: RichText( text: TextSpan( - style: const TextStyle(fontSize: 13, color: AppTheme.textPrimary, height: 1.6), + style: TextStyle(fontSize: 13, color: context.tokens.text, height: 1.6), children: [ const TextSpan(text: '确认退掉以下明细?\n'), TextSpan( @@ -118,7 +118,7 @@ class _OrderReturnDialogState extends State<_OrderReturnDialog> { TextSpan(text: ' · ${l.series} · ${l.spec}\n'), TextSpan( text: '编码 ${l.code} · 数量 ${_q(l.quantity)}', - style: const TextStyle(color: AppTheme.textSecondary, fontSize: 12)), + style: TextStyle(color: context.tokens.muted, fontSize: 12)), ], ), ), @@ -129,7 +129,7 @@ class _OrderReturnDialogState extends State<_OrderReturnDialog> { ElevatedButton( onPressed: () => Navigator.pop(context, true), style: ElevatedButton.styleFrom( - backgroundColor: AppTheme.danger, foregroundColor: Colors.white), + backgroundColor: context.tokens.danger, foregroundColor: Colors.white), child: const Text('退单'), ), ], @@ -154,7 +154,7 @@ class _OrderReturnDialogState extends State<_OrderReturnDialog> { ElevatedButton( onPressed: () => Navigator.pop(context, true), style: ElevatedButton.styleFrom( - backgroundColor: AppTheme.danger, foregroundColor: Colors.white), + backgroundColor: context.tokens.danger, foregroundColor: Colors.white), child: const Text('全部退单'), ), ], @@ -181,7 +181,7 @@ class _OrderReturnDialogState extends State<_OrderReturnDialog> { ElevatedButton( onPressed: () => Navigator.pop(context, true), style: ElevatedButton.styleFrom( - backgroundColor: AppTheme.danger, foregroundColor: Colors.white), + backgroundColor: context.tokens.danger, foregroundColor: Colors.white), child: const Text('提交退单'), ), ], @@ -192,15 +192,15 @@ class _OrderReturnDialogState extends State<_OrderReturnDialog> { try { await widget.onSubmit(_staged.toList()); if (mounted) { - ScaffoldMessenger.of(context).showSnackBar(const SnackBar( - content: Text('退单成功'), backgroundColor: AppTheme.success)); + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: const Text('退单成功'), backgroundColor: context.tokens.success)); Navigator.pop(context, true); } } catch (e) { if (mounted) { setState(() => _submitting = false); ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Text('退单失败:$e'), backgroundColor: AppTheme.danger)); + content: Text('退单失败:$e'), backgroundColor: context.tokens.danger)); } } } @@ -232,8 +232,8 @@ class _OrderReturnDialogState extends State<_OrderReturnDialog> { OutlinedButton( onPressed: _submitting ? null : _confirmAll, style: OutlinedButton.styleFrom( - foregroundColor: AppTheme.danger, - side: const BorderSide(color: AppTheme.danger), + foregroundColor: context.tokens.danger, + side: BorderSide(color: context.tokens.danger), visualDensity: VisualDensity.compact, ), child: const Text('全部退单', style: TextStyle(fontSize: 12)), @@ -255,10 +255,10 @@ class _OrderReturnDialogState extends State<_OrderReturnDialog> { children: [ for (final (k, v) in widget.meta) Text.rich(TextSpan( - style: const TextStyle(fontSize: 12, color: AppTheme.textSecondary), + style: TextStyle(fontSize: 12, color: context.tokens.muted), children: [ TextSpan(text: '$k:'), - TextSpan(text: v, style: const TextStyle(color: AppTheme.textPrimary)), + TextSpan(text: v, style: TextStyle(color: context.tokens.text)), ], )), ], @@ -271,14 +271,14 @@ class _OrderReturnDialogState extends State<_OrderReturnDialog> { padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), decoration: BoxDecoration( color: const Color(0xFFF0F6FF), - border: const Border(left: BorderSide(color: AppTheme.primary, width: 3)), + border: Border(left: BorderSide(color: context.tokens.primary, width: 3)), borderRadius: BorderRadius.circular(4), ), child: Text( widget.isOut ? '提交后,已退单明细的数量将「加回库存」。退单的明细不会删除,以红色「已退单」标注。' : '提交后,已退单明细对应的库存将「从库存删除」。退单的明细不会删除,以红色「已退单」标注。', - style: const TextStyle(fontSize: 12, color: AppTheme.primaryDark), + style: TextStyle(fontSize: 12, color: context.tokens.primaryDark), ), ), // 明细列表 @@ -304,8 +304,8 @@ class _OrderReturnDialogState extends State<_OrderReturnDialog> { style: TextStyle( fontSize: 12, color: _staged.isEmpty - ? AppTheme.textSecondary - : AppTheme.danger, + ? context.tokens.muted + : context.tokens.danger, fontWeight: FontWeight.w600), ), ), @@ -317,7 +317,7 @@ class _OrderReturnDialogState extends State<_OrderReturnDialog> { ElevatedButton( onPressed: (_staged.isEmpty || _submitting) ? null : _submit, style: ElevatedButton.styleFrom( - backgroundColor: AppTheme.danger, + backgroundColor: context.tokens.danger, foregroundColor: Colors.white), child: _submitting ? const SizedBox( @@ -341,12 +341,12 @@ class _OrderReturnDialogState extends State<_OrderReturnDialog> { final nameStyle = TextStyle( fontSize: 13, fontWeight: FontWeight.w600, - color: returned ? AppTheme.danger : AppTheme.textPrimary, + color: returned ? context.tokens.danger : context.tokens.text, decoration: returned ? TextDecoration.lineThrough : null, ); final subStyle = TextStyle( fontSize: 11, - color: returned ? AppTheme.danger : AppTheme.textSecondary, + color: returned ? context.tokens.danger : context.tokens.muted, decoration: returned ? TextDecoration.lineThrough : null, ); return Container( @@ -357,8 +357,8 @@ class _OrderReturnDialogState extends State<_OrderReturnDialog> { SizedBox( width: 24, child: Text('${idx + 1}', - style: const TextStyle( - fontSize: 12, color: AppTheme.textSecondary))), + style: TextStyle( + fontSize: 12, color: context.tokens.muted))), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -370,7 +370,7 @@ class _OrderReturnDialogState extends State<_OrderReturnDialog> { Container( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 1), decoration: BoxDecoration( - color: AppTheme.danger, + color: context.tokens.danger, borderRadius: BorderRadius.circular(8)), child: const Text('已退单', style: TextStyle(fontSize: 10, color: Colors.white)), @@ -396,14 +396,14 @@ class _OrderReturnDialogState extends State<_OrderReturnDialog> { onPressed: _submitting ? null : () => setState(() => _staged.remove(l.itemId)), - child: const Text('撤销', - style: TextStyle(fontSize: 12, color: AppTheme.primary)), + child: Text('撤销', + style: TextStyle(fontSize: 12, color: context.tokens.primary)), ) : OutlinedButton( onPressed: _submitting ? null : () => _confirmLine(l), style: OutlinedButton.styleFrom( - foregroundColor: AppTheme.danger, - side: const BorderSide(color: AppTheme.danger), + foregroundColor: context.tokens.danger, + side: BorderSide(color: context.tokens.danger), visualDensity: VisualDensity.compact, padding: const EdgeInsets.symmetric(horizontal: 8), ), diff --git a/client/lib/widgets/order_row_actions.dart b/client/lib/widgets/order_row_actions.dart index 2c934d6..09dc8f8 100644 --- a/client/lib/widgets/order_row_actions.dart +++ b/client/lib/widgets/order_row_actions.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -import '../core/theme/app_theme.dart'; +import '../core/theme/context_tokens.dart'; import 'write_guard.dart'; /// 入/出库单行内操作按钮,列表表格与移动端卡片、入库与出库两屏共用。 @@ -12,6 +12,7 @@ import 'write_guard.dart'; /// - [afterPrint]:紧随「打印」之后的附加按钮(入库的「打标签」;出库传空)。 /// - 各 `on*` 回调由调用方绑定到对应单据的确认/跳转逻辑。 List buildOrderRowActions({ + required BuildContext context, required bool readonly, required String status, required int orderId, @@ -56,32 +57,32 @@ List buildOrderRowActions({ ), ); return [ - btn('详情', AppTheme.primary, onDetail), - btn('打印', AppTheme.primary, onPrint), + btn('详情', context.tokens.primary, onDetail), + btn('打印', context.tokens.primary, onPrint), ...afterPrint, if (!readonly && status == 'approved') - WriteGuard(child: btn('结清', AppTheme.accent, onSettle)), + WriteGuard(child: btn('结清', context.tokens.accent, onSettle)), // 退单(已审核单退货):红色实心按钮突出(与原型一致),按权限显示 if (!readonly && status == 'approved' && canReturn) WriteGuard( - child: filledBtn('退单', AppTheme.danger, onReturn, + child: filledBtn('退单', context.tokens.danger, onReturn, key: Key('btn_return_$orderId'))), if (!readonly && status == 'draft') ...[ - WriteGuard(child: btn('修改', AppTheme.primary, onEdit)), - WriteGuard(child: btn('删除', AppTheme.danger, onDelete)), - WriteGuard(child: btn('提交', AppTheme.primary, onSubmit)), + WriteGuard(child: btn('修改', context.tokens.primary, onEdit)), + WriteGuard(child: btn('删除', context.tokens.danger, onDelete)), + WriteGuard(child: btn('提交', context.tokens.primary, onSubmit)), ], if (!readonly && status == 'pending') ...[ WriteGuard( - child: btn('通过', AppTheme.success, onApprove, + child: btn('通过', context.tokens.success, onApprove, key: Key('btn_approve_$orderId'))), WriteGuard( - child: btn('拒绝', AppTheme.danger, onReject, + child: btn('拒绝', context.tokens.danger, onReject, key: Key('btn_reject_$orderId'))), // 撤回(审核中→草稿):管理员/超管任意单、操作员本人单可见,用 warn 样式 if (canWithdraw) WriteGuard( - child: btn('撤回', AppTheme.warning, onWithdraw, + child: btn('撤回', context.tokens.warn, onWithdraw, key: Key('btn_withdraw_$orderId'))), ], ]; diff --git a/client/lib/widgets/page_scaffold.dart b/client/lib/widgets/page_scaffold.dart index 1be33e4..fdbbd10 100644 --- a/client/lib/widgets/page_scaffold.dart +++ b/client/lib/widgets/page_scaffold.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import '../core/theme/app_theme.dart'; +import '../core/theme/context_tokens.dart'; class PageScaffold extends StatefulWidget { final String title; @@ -54,14 +54,14 @@ class _PageScaffoldState extends State crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( - color: AppTheme.surface, + color: context.tokens.surface, child: TabBar( controller: _controller, isScrollable: true, tabAlignment: TabAlignment.start, - labelColor: AppTheme.primary, - unselectedLabelColor: AppTheme.textSecondary, - indicatorColor: AppTheme.primary, + labelColor: context.tokens.primary, + unselectedLabelColor: context.tokens.muted, + indicatorColor: context.tokens.primary, indicatorWeight: 2, labelStyle: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500), diff --git a/client/lib/widgets/searchable_option_field.dart b/client/lib/widgets/searchable_option_field.dart index 69516cd..414d221 100644 --- a/client/lib/widgets/searchable_option_field.dart +++ b/client/lib/widgets/searchable_option_field.dart @@ -4,7 +4,7 @@ import '../core/utils/dialog_util.dart'; import 'package:flutter/material.dart'; import '../core/responsive/responsive.dart'; import 'package:lpinyin/lpinyin.dart'; -import '../core/theme/app_theme.dart'; +import '../core/theme/context_tokens.dart'; class OptionItem { final int id; @@ -100,18 +100,18 @@ class SearchableOptionField extends StatelessWidget { suffixIcon: selected ? GestureDetector( onTap: () => onChanged(null), - child: const Icon(Icons.close, - size: 14, color: AppTheme.textSecondary), + child: Icon(Icons.close, + size: 14, color: context.tokens.muted), ) - : const Icon(Icons.arrow_drop_down, - size: 16, color: AppTheme.textSecondary), + : Icon(Icons.arrow_drop_down, + size: 16, color: context.tokens.muted), ), isEmpty: !selected, child: Text( selected ? _displayText : hint, style: TextStyle( fontSize: 12, - color: selected ? Colors.black87 : AppTheme.textSecondary, + color: selected ? Colors.black87 : context.tokens.muted, ), overflow: TextOverflow.ellipsis, ), @@ -269,7 +269,7 @@ class _SearchDialogState extends State<_SearchDialog> { ? Center( child: Text( canCreate ? '无匹配结果,可新增到基础数据' : '无匹配结果', - style: const TextStyle(color: AppTheme.textSecondary), + style: TextStyle(color: context.tokens.muted), ), ) : ListView.builder( @@ -287,7 +287,7 @@ class _SearchDialogState extends State<_SearchDialog> { : null, selected: isSelected, selectedTileColor: - AppTheme.primary.withValues(alpha: 0.08), + context.tokens.primary.withValues(alpha: 0.08), onTap: () => Navigator.of(context).pop(opt.id), ); }, @@ -316,8 +316,8 @@ class _SearchDialogState extends State<_SearchDialog> { if (widget.selectedId != null) TextButton( onPressed: () => Navigator.of(context).pop(-1), - child: const Text('清除选择', - style: TextStyle(color: AppTheme.textSecondary)), + child: Text('清除选择', + style: TextStyle(color: context.tokens.muted)), ), TextButton( onPressed: () => Navigator.of(context).pop(null), diff --git a/client/lib/widgets/status_badge.dart b/client/lib/widgets/status_badge.dart index e5be78f..64389f2 100644 --- a/client/lib/widgets/status_badge.dart +++ b/client/lib/widgets/status_badge.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import '../core/theme/app_theme.dart'; +import '../core/theme/context_tokens.dart'; enum OrderStatus { draft, pending, approved, rejected } @@ -29,19 +29,19 @@ class StatusBadge extends StatelessWidget { switch (status) { case OrderStatus.draft: bg = const Color(0xFFF5F5F5); - fg = AppTheme.textSecondary; + fg = context.tokens.muted; break; case OrderStatus.pending: bg = const Color(0xFFFFF3E0); - fg = AppTheme.accent; + fg = context.tokens.accent; break; case OrderStatus.approved: bg = const Color(0xFFE8F5E9); - fg = AppTheme.success; + fg = context.tokens.success; break; case OrderStatus.rejected: bg = const Color(0xFFFFEBEE); - fg = AppTheme.danger; + fg = context.tokens.danger; break; } return Container( diff --git a/client/test/date_picker_field_test.dart b/client/test/date_picker_field_test.dart index 52d4d1a..7af1b8a 100644 --- a/client/test/date_picker_field_test.dart +++ b/client/test/date_picker_field_test.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:jiu_client/core/theme/themes.dart'; import 'package:jiu_client/core/utils/date_util.dart'; import 'package:jiu_client/widgets/date_picker_field.dart'; @@ -33,6 +34,7 @@ void main() { group('DatePickerField', () { testWidgets('渲染可键入框 + 日历图标', (tester) async { await tester.pumpWidget(MaterialApp( + theme: buildTheme('a'), home: Scaffold( body: DatePickerField(value: '2026-06-19', onChanged: (_) {}), ), @@ -44,6 +46,7 @@ void main() { testWidgets('初始值回显到输入框', (tester) async { await tester.pumpWidget(MaterialApp( + theme: buildTheme('a'), home: Scaffold( body: DatePickerField(value: '2026-06-19', onChanged: (_) {}), ), @@ -55,6 +58,7 @@ void main() { testWidgets('只输年份归一为 yyyy-01-01', (tester) async { String? out; await tester.pumpWidget(MaterialApp( + theme: buildTheme('a'), home: Scaffold( body: DatePickerField(onChanged: (v) => out = v), ), @@ -66,6 +70,7 @@ void main() { testWidgets('年月归一为 yyyy-MM-01', (tester) async { String? out; await tester.pumpWidget(MaterialApp( + theme: buildTheme('a'), home: Scaffold( body: DatePickerField(onChanged: (v) => out = v), ), diff --git a/client/test/partners_screen_test.dart b/client/test/partners_screen_test.dart index a9a42f3..57dd382 100644 --- a/client/test/partners_screen_test.dart +++ b/client/test/partners_screen_test.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:jiu_client/core/models/page_result.dart'; +import 'package:jiu_client/core/theme/themes.dart'; import 'package:jiu_client/models/license.dart'; import 'package:jiu_client/models/partner.dart'; import 'package:jiu_client/providers/license_provider.dart'; @@ -103,7 +104,7 @@ Widget _buildApp({ () => _FakePartnerNotifier('customer', customerState), ), ], - child: const MaterialApp(home: Scaffold(body: PartnersScreen())), + child: MaterialApp(theme: buildTheme('a'), home: const Scaffold(body: PartnersScreen())), ); } @@ -118,7 +119,7 @@ Widget _buildLoadingApp() { () => _FakeLoadingPartnerNotifier('customer'), ), ], - child: const MaterialApp(home: Scaffold(body: PartnersScreen())), + child: MaterialApp(theme: buildTheme('a'), home: const Scaffold(body: PartnersScreen())), ); } diff --git a/client/test/readonly_write_forbidden_test.dart b/client/test/readonly_write_forbidden_test.dart index 36f0022..30865cd 100644 --- a/client/test/readonly_write_forbidden_test.dart +++ b/client/test/readonly_write_forbidden_test.dart @@ -8,6 +8,7 @@ import 'package:jiu_client/core/api/api_client.dart'; import 'package:jiu_client/core/auth/auth_state.dart'; import 'package:jiu_client/core/config/license_copy.dart'; import 'package:jiu_client/core/models/page_result.dart'; +import 'package:jiu_client/core/theme/themes.dart'; import 'package:jiu_client/models/license.dart'; import 'package:jiu_client/models/partner.dart'; import 'package:jiu_client/providers/license_provider.dart'; @@ -76,7 +77,7 @@ Widget _buildApp({required bool readonly}) { ), ), ], - child: const MaterialApp(home: Scaffold(body: PartnersScreen())), + child: MaterialApp(theme: buildTheme('a'), home: const Scaffold(body: PartnersScreen())), ); } diff --git a/client/test/searchable_option_create_test.dart b/client/test/searchable_option_create_test.dart index 80ac8a8..11678e4 100644 --- a/client/test/searchable_option_create_test.dart +++ b/client/test/searchable_option_create_test.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:jiu_client/core/theme/themes.dart'; import 'package:jiu_client/widgets/searchable_option_field.dart'; void main() { @@ -8,6 +9,7 @@ void main() { Future Function(String)? onCreate, }) { return MaterialApp( + theme: buildTheme('a'), home: Scaffold( body: SearchableOptionField( options: [OptionItem(id: 1, name: '53度')], diff --git a/client/test/stock_in_screen_test.dart b/client/test/stock_in_screen_test.dart index 46a7796..6f6659a 100644 --- a/client/test/stock_in_screen_test.dart +++ b/client/test/stock_in_screen_test.dart @@ -5,6 +5,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:go_router/go_router.dart'; import 'package:jiu_client/core/models/page_result.dart'; +import 'package:jiu_client/core/theme/themes.dart'; import 'package:jiu_client/models/license.dart'; import 'package:jiu_client/models/stock_in.dart'; import 'package:jiu_client/providers/license_provider.dart'; @@ -119,7 +120,7 @@ Widget _buildApp(AsyncValue> state) { licenseProvider.overrideWith(() => _FakeLicenseNotifier()), stockInListProvider.overrideWith(() => _FakeStockInNotifier(state)), ], - child: MaterialApp.router(routerConfig: router), + child: MaterialApp.router(theme: buildTheme('a'), routerConfig: router), ); } @@ -143,7 +144,7 @@ Widget _buildLoadingApp() { licenseProvider.overrideWith(() => _FakeLicenseNotifier()), stockInListProvider.overrideWith(() => _FakeLoadingStockInNotifier()), ], - child: MaterialApp.router(routerConfig: router), + child: MaterialApp.router(theme: buildTheme('a'), routerConfig: router), ); } diff --git a/client/test/write_guard_test.dart b/client/test/write_guard_test.dart index ef0a25e..7e40d19 100644 --- a/client/test/write_guard_test.dart +++ b/client/test/write_guard_test.dart @@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:jiu_client/core/auth/auth_state.dart'; import 'package:jiu_client/core/config/license_copy.dart'; +import 'package:jiu_client/core/theme/themes.dart'; import 'package:jiu_client/models/license.dart'; import 'package:jiu_client/providers/license_provider.dart'; import 'package:jiu_client/widgets/write_guard.dart'; @@ -37,6 +38,7 @@ Widget _harness({ licenseProvider.overrideWith(() => _FakeLicenseNotifier(lic)), ], child: MaterialApp( + theme: buildTheme('a'), home: Scaffold( body: WriteGuard( child: ElevatedButton(