feat(tools): 代码端真相源闸 check_ds_code + 外壳硬编码颜色清零

把原型 check-ds 的「单一真源」纪律延伸到 Flutter 代码:
- client/tool/check_ds_code.mjs:扫 lib/(排除.g.dart) 硬编码 Color(0x..)/Colors.具名色,
  --strict/--changed 报错挡提交,合理特例须显式 // ds-ignore: 理由
- tokens.css(单源,client+原型同步) 加 chrome 前景色 --brand-dot/--on-accent/--on-accent-faint,
  codegen 进 AppChrome;外壳 14 处硬编码(白字/品牌点/grace橙)全部改走 token
- 外壳+3 新 widget 现 0 违规;全量存量 172→158(其余屏后续逐步清)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TSKEiHsvauyxYUW2itzUXX
This commit is contained in:
wangjia
2026-06-25 19:14:47 +08:00
parent cb549ff0b4
commit 0b8c4618d6
6 changed files with 106 additions and 17 deletions
+3
View File
@@ -14,6 +14,9 @@ class AppChrome {
static const Color brandDeep = Color(0xFF0F3057);
static const Color brandMid = Color(0xFF1B4F8E);
static const Color brandWine = Color(0xFF8B2331);
static const Color brandDot = Color(0xFFC97B86);
static const Color onAccent = Color(0xFFFFFFFF);
static const Color onAccentFaint = Color(0xb8FFFFFF);
static const Color scrim = Color(0x6b0F141E);
static const Color heroGlass = Color(0x29FFFFFF);
static const Color heroGlassStrong = Color(0x3dFFFFFF);
@@ -19,7 +19,9 @@
/* 遮罩 / 玻璃叠加(主题无关:弹窗·抽屉背板、hero 上的半透明白) */
--scrim:rgba(15,20,30,.42); --hero-glass:rgba(255,255,255,.16); --hero-glass-strong:rgba(255,255,255,.24);
/* 品牌标识色(logo / 门店字标,固定不随主题) */
--brand-deep:#0F3057; --brand-mid:#1B4F8E; --brand-wine:#8B2331;
--brand-deep:#0F3057; --brand-mid:#1B4F8E; --brand-wine:#8B2331; --brand-dot:#C97B86;
/* 彩色底(横幅/徽章/品牌弹窗)上的固定前景白(主题无关 chrome 前景) */
--on-accent:#FFFFFF; --on-accent-faint:rgba(255,255,255,.72);
/* 底部状态栏(深石板灰绿,主题无关的 chrome 条) */
--statusbar-bg:#37423D; --statusbar-fg:rgba(255,255,255,.82); --statusbar-muted:rgba(255,255,255,.46); --statusbar-strong:#FFFFFF; --statusbar-div:rgba(255,255,255,.13); --statusbar-ok:#7BC49A;
/* Toast 深底(主题无关) */
+15 -14
View File
@@ -10,6 +10,7 @@ import '../../core/config/app_config.dart';
import '../../core/config/license_copy.dart';
import '../../core/responsive/responsive.dart';
import '../../core/theme/context_tokens.dart';
import '../../core/theme/app_chrome.g.dart';
import '../../providers/connectivity_provider.dart';
import '../../providers/session_heartbeat.dart';
import '../../providers/shop_provider.dart';
@@ -249,11 +250,11 @@ class _AppShellState extends ConsumerState<AppShell> {
child: Row(
mainAxisSize: MainAxisSize.min,
children: const [
Icon(Icons.visibility_outlined, size: 13, color: Colors.white),
Icon(Icons.visibility_outlined, size: 13, color: AppChrome.onAccent),
SizedBox(width: 4),
Text('只读',
style: TextStyle(
color: Colors.white,
color: AppChrome.onAccent,
fontSize: 12,
fontWeight: FontWeight.w600)),
],
@@ -366,7 +367,7 @@ class _AppShellState extends ConsumerState<AppShell> {
const SizedBox(width: 8),
const Text('岩美酒库',
style: TextStyle(
color: Colors.white,
color: AppChrome.onAccent,
fontSize: 17,
fontWeight: FontWeight.w700)),
],
@@ -486,13 +487,13 @@ class _AppShellState extends ConsumerState<AppShell> {
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6),
child: const Row(
children: [
Icon(Icons.wifi_off, size: 16, color: Colors.white),
Icon(Icons.wifi_off, size: 16, color: AppChrome.onAccent),
SizedBox(width: 8),
Expanded(
child: Text(
'网络连接已断开 · 当前显示离线缓存数据,恢复后将自动刷新',
style: TextStyle(
color: Colors.white,
color: AppChrome.onAccent,
fontSize: 13,
fontWeight: FontWeight.w500),
),
@@ -542,16 +543,16 @@ class _AppShellState extends ConsumerState<AppShell> {
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6),
child: Row(
children: [
const Icon(Icons.warning_amber_rounded, size: 16, color: Colors.white),
const Icon(Icons.warning_amber_rounded, size: 16, color: AppChrome.onAccent),
const SizedBox(width: 8),
Expanded(
child: Text(msg,
style: const TextStyle(color: Colors.white, fontSize: 13),
style: const TextStyle(color: AppChrome.onAccent, fontSize: 13),
overflow: TextOverflow.ellipsis),
),
TextButton(
onPressed: () => context.go('/settings?tab=license'),
style: TextButton.styleFrom(foregroundColor: Colors.white70),
style: TextButton.styleFrom(foregroundColor: AppChrome.onAccentFaint),
child: const Text('去激活'),
),
],
@@ -562,7 +563,7 @@ class _AppShellState extends ConsumerState<AppShell> {
void _showLicenseExpiryDialog(BuildContext ctx, LicenseInfo lic) {
final (title, body) = LicenseCopy.dialog(lic);
final Color titleColor =
lic.phase == 'grace' ? Colors.orange[800]! : ctx.tokens.danger;
lic.phase == 'grace' ? ctx.tokens.warn : ctx.tokens.danger;
showDialog(
context: ctx,
barrierDismissible: true,
@@ -584,7 +585,7 @@ class _AppShellState extends ConsumerState<AppShell> {
Navigator.pop(_);
ctx.go('/settings?tab=license');
},
child: const Text('立即前往', style: TextStyle(color: Colors.white)),
child: const Text('立即前往', style: TextStyle(color: AppChrome.onAccent)),
),
],
),
@@ -649,7 +650,7 @@ class _BrandMarkPainter extends CustomPainter {
canvas.drawPath(peak, p);
canvas.drawLine(const Offset(15, 41), const Offset(49, 41), p);
canvas.drawCircle(const Offset(32, 48), 3.4,
Paint()..color = const Color(0xFFC97B86));
Paint()..color = AppChrome.brandDot);
}
@override
@@ -911,18 +912,18 @@ void _showShopPanel(BuildContext context, AuthUser u,
),
child: Row(
children: [
const Icon(Icons.store, color: Colors.white, size: 20),
const Icon(Icons.store, color: AppChrome.onAccent, size: 20),
const SizedBox(width: 10),
const Text('门店信息',
style: TextStyle(
color: Colors.white,
color: AppChrome.onAccent,
fontSize: 16,
fontWeight: FontWeight.w600)),
const Spacer(),
IconButton(
onPressed: () => Navigator.pop(ctx),
icon: const Icon(Icons.close,
color: Colors.white70, size: 18),
color: AppChrome.onAccentFaint, size: 18),
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
),