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/config/license_copy.dart';
|
||||||
import '../../core/responsive/responsive.dart';
|
import '../../core/responsive/responsive.dart';
|
||||||
import '../../core/theme/context_tokens.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/number_rule.dart';
|
||||||
import '../../models/user.dart';
|
import '../../models/user.dart';
|
||||||
import '../../providers/license_provider.dart';
|
import '../../providers/license_provider.dart';
|
||||||
@@ -228,7 +231,7 @@ class _SettingsScreenState extends ConsumerState<SettingsScreen> {
|
|||||||
),
|
),
|
||||||
data: (users) => SingleChildScrollView(
|
data: (users) => SingleChildScrollView(
|
||||||
child: DataTable(
|
child: DataTable(
|
||||||
headingRowColor: WidgetStateProperty.all(const Color(0xFFF0F4FF)),
|
headingRowColor: WidgetStateProperty.all(context.tokens.thBg),
|
||||||
columns: const [
|
columns: const [
|
||||||
DataColumn(label: Text('姓名')),
|
DataColumn(label: Text('姓名')),
|
||||||
DataColumn(label: Text('用户名')),
|
DataColumn(label: Text('用户名')),
|
||||||
@@ -600,7 +603,7 @@ class _SettingsScreenState extends ConsumerState<SettingsScreen> {
|
|||||||
Card(
|
Card(
|
||||||
child: DataTable(
|
child: DataTable(
|
||||||
headingRowColor:
|
headingRowColor:
|
||||||
WidgetStateProperty.all(const Color(0xFFF0F4FF)),
|
WidgetStateProperty.all(context.tokens.thBg),
|
||||||
columns: const [
|
columns: const [
|
||||||
DataColumn(label: Text('单据类型')),
|
DataColumn(label: Text('单据类型')),
|
||||||
DataColumn(label: Text('前缀')),
|
DataColumn(label: Text('前缀')),
|
||||||
@@ -725,6 +728,8 @@ class _SettingsScreenState extends ConsumerState<SettingsScreen> {
|
|||||||
const Text('系统参数',
|
const Text('系统参数',
|
||||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600)),
|
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600)),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
const _ThemePickerCard(),
|
||||||
|
const SizedBox(height: 16),
|
||||||
Card(
|
Card(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(20),
|
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 预览 ────────────────────────────────────────
|
// ── 门店 Logo 预览 ────────────────────────────────────────
|
||||||
class _ShopLogoPreview extends StatelessWidget {
|
class _ShopLogoPreview extends StatelessWidget {
|
||||||
final String logoUrl;
|
final String logoUrl;
|
||||||
|
|||||||
Reference in New Issue
Block a user