feat(client): 设置页新增外观主题选择器(A/B/C 实时切换)
系统参数 Tab 顶部加「外观主题」卡片:三枚带微缩预览的主题色板 (经典蓝/琥珀/酒窖),点选即调用 themeController.set 实时换色并持久化。 表格列头 F0F4FF → thBg 一并清理。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TSKEiHsvauyxYUW2itzUXX
This commit is contained in:
@@ -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<SettingsScreen> {
|
||||
),
|
||||
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<SettingsScreen> {
|
||||
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<SettingsScreen> {
|
||||
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;
|
||||
|
||||
Reference in New Issue
Block a user