From 648c00434c93079d5fe3178025a9dc9ca18d1b94 Mon Sep 17 00:00:00 2001 From: wangjia <809946525@qq.com> Date: Thu, 25 Jun 2026 06:21:35 +0800 Subject: [PATCH] =?UTF-8?q?feat(client):=20=E8=AE=BE=E7=BD=AE=E9=A1=B5?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=A4=96=E8=A7=82=E4=B8=BB=E9=A2=98=E9=80=89?= =?UTF-8?q?=E6=8B=A9=E5=99=A8=EF=BC=88A/B/C=20=E5=AE=9E=E6=97=B6=E5=88=87?= =?UTF-8?q?=E6=8D=A2=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 系统参数 Tab 顶部加「外观主题」卡片:三枚带微缩预览的主题色板 (经典蓝/琥珀/酒窖),点选即调用 themeController.set 实时换色并持久化。 表格列头 F0F4FF → thBg 一并清理。 Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01TSKEiHsvauyxYUW2itzUXX --- .../lib/screens/settings/settings_screen.dart | 166 +++++++++++++++++- 1 file changed, 164 insertions(+), 2 deletions(-) diff --git a/client/lib/screens/settings/settings_screen.dart b/client/lib/screens/settings/settings_screen.dart index 9f490dd..10a4bb5 100644 --- a/client/lib/screens/settings/settings_screen.dart +++ b/client/lib/screens/settings/settings_screen.dart @@ -13,6 +13,9 @@ import '../../core/config/app_info.dart'; import '../../core/config/license_copy.dart'; import '../../core/responsive/responsive.dart'; import '../../core/theme/context_tokens.dart'; +import '../../core/theme/app_tokens.dart'; +import '../../core/theme/app_tokens.g.dart'; +import '../../core/theme/theme_controller.dart'; import '../../models/number_rule.dart'; import '../../models/user.dart'; import '../../providers/license_provider.dart'; @@ -228,7 +231,7 @@ class _SettingsScreenState extends ConsumerState { ), data: (users) => SingleChildScrollView( child: DataTable( - headingRowColor: WidgetStateProperty.all(const Color(0xFFF0F4FF)), + headingRowColor: WidgetStateProperty.all(context.tokens.thBg), columns: const [ DataColumn(label: Text('姓名')), DataColumn(label: Text('用户名')), @@ -600,7 +603,7 @@ class _SettingsScreenState extends ConsumerState { Card( child: DataTable( headingRowColor: - WidgetStateProperty.all(const Color(0xFFF0F4FF)), + WidgetStateProperty.all(context.tokens.thBg), columns: const [ DataColumn(label: Text('单据类型')), DataColumn(label: Text('前缀')), @@ -725,6 +728,8 @@ class _SettingsScreenState extends ConsumerState { const Text('系统参数', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600)), const SizedBox(height: 16), + const _ThemePickerCard(), + const SizedBox(height: 16), Card( child: Padding( padding: const EdgeInsets.all(20), @@ -1184,6 +1189,163 @@ class _ParamRow extends StatelessWidget { } +// ── 外观主题选择 ────────────────────────────────────────── +class _ThemePickerCard extends ConsumerWidget { + const _ThemePickerCard(); + + // (key, 名称, 说明) + static const List<(String, String, String)> _themes = [ + ('a', '经典蓝', '浅色 · 默认'), + ('b', '琥珀', '深色'), + ('c', '酒窖', '暖色'), + ]; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final current = ref.watch(themeControllerProvider); + return Card( + child: Padding( + padding: const EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('外观主题', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: context.tokens.primaryDark)), + const SizedBox(height: 4), + Text('切换配色方案,立即生效并记住选择', + style: TextStyle(fontSize: 13, color: context.tokens.muted)), + const SizedBox(height: 16), + Wrap( + spacing: 12, + runSpacing: 12, + children: [ + for (final (key, name, desc) in _themes) + _ThemeSwatch( + preview: appTokensOf(key), + name: name, + desc: desc, + selected: current == key, + onTap: () => + ref.read(themeControllerProvider.notifier).set(key), + ), + ], + ), + ], + ), + ), + ); + } +} + +class _ThemeSwatch extends StatelessWidget { + final AppTokens preview; + final String name; + final String desc; + final bool selected; + final VoidCallback onTap; + const _ThemeSwatch({ + required this.preview, + required this.name, + required this.desc, + required this.selected, + required this.onTap, + }); + + @override + Widget build(BuildContext context) { + return InkWell( + borderRadius: BorderRadius.circular(10), + onTap: onTap, + child: Container( + width: 150, + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: context.tokens.surface, + border: Border.all( + color: selected ? context.tokens.primary : context.tokens.border, + width: selected ? 2 : 1, + ), + borderRadius: BorderRadius.circular(10), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // 微缩预览:用该主题真实 token 渲染一小块 chrome + Container( + height: 46, + padding: const EdgeInsets.all(6), + decoration: BoxDecoration( + color: preview.page, + borderRadius: BorderRadius.circular(6), + border: Border.all(color: preview.border), + ), + child: Row( + children: [ + Container( + width: 22, + height: 34, + decoration: BoxDecoration( + color: preview.surface, + borderRadius: BorderRadius.circular(3), + border: Border.all(color: preview.border), + ), + ), + const SizedBox(width: 6), + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: 36, + height: 6, + decoration: BoxDecoration( + color: preview.primary, + borderRadius: BorderRadius.circular(3))), + const SizedBox(height: 5), + Container( + width: 24, + height: 5, + decoration: BoxDecoration( + color: preview.muted, + borderRadius: BorderRadius.circular(3))), + ], + ), + ), + ], + ), + ), + const SizedBox(height: 8), + Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(name, + style: const TextStyle( + fontSize: 13, fontWeight: FontWeight.w600)), + Text(desc, + style: TextStyle( + fontSize: 11, color: context.tokens.muted)), + ], + ), + ), + if (selected) + Icon(Icons.check_circle, + size: 18, color: context.tokens.primary), + ], + ), + ], + ), + ), + ); + } +} + // ── 门店 Logo 预览 ──────────────────────────────────────── class _ShopLogoPreview extends StatelessWidget { final String logoUrl;