Files
jiu/client/tool/check_ds_code.mjs
T
wangjia 6238b86dcb feat(client): 登录/注册页照原型重建,ds 真相源组件族统一全部屏
- 登录/注册(login.html/register.html 1:1):两栏卡片+品牌渐变面板+主题小衣服
  pill(onSurface 变体)+记住我(记录并预填最近账号)+原型式 toast 校验;
  登录 fidelity 1.4–2.1% 三主题全绿;注册暂不入闸(少 门店编号/兑换券 字段,
  已记 CONTRACT,screens.mjs 留存根)
- ds 原子补齐:DsToast(.toast 单例)/DsCheck(.check/.agree)/DsButton lg 档/
  DsSelect 替换全部旧 DropdownButton/DsField label 在上/DsInput 后缀与密码形态
- 全屏统一:对话框按钮全 DsButton、盒式输入主题钉死(visualDensity.standard、
  h38、InputDecorationTheme 渗漏修复)、图标全 lucide、JetBrains Mono 三端同源、
  BrandMark 真相源 logo、只读模式写操作全量守卫(WriteGuard+DsToast)
- 出入库列表:版式对齐原型(卡片对齐+搜索框居中)、KPI 近30天滚动、结清后
  失效财务应收应付表;商品编辑抽屉介绍库改搜索下拉、图片双击全屏预览
- 删除旧 UI 死代码:DataTableCard/FormDialog/PageScaffold/SearchChip/
  SelectProductDialog/tabStateProvider
- golden/fidelity:stock-in/out 补注册(9%)、login 入册(8%)、goldens 全量重打;
  修复 pubspec flutter_web_plugins 非法声明(CI pub get 阻断)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JJ1g8XV1YhhmHRzhwWEW7o
2026-07-03 09:58:14 +08:00

92 lines
3.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// client/tool/check_ds_code.mjs
// 代码端「设计真相源」闸 —— 与原型 check-ds.mjs 对应,把单一真源纪律延伸到 Flutter 代码。
// 红线:颜色只走 context.tokens / AppChrome / AppDims(从 tokens.css codegen 单源),
// 禁硬编码 hexColor(0x..))与具名色(Colors.red 等)。合理特例须显式 `// ds-ignore: 理由` 豁免。
//
// 用法(在 client/ 下):
// node tool/check_ds_code.mjs # 全量扫描,报存量(不 fail,便于逐步清零)
// node tool/check_ds_code.mjs --strict # 全量,有违规即 exit 1
// node tool/check_ds_code.mjs --changed # 仅扫 git 改动的 dart 文件,有违规即 exit 1pre-commit 用)
//
// 豁免:在该行加 `// ds-ignore: <理由>`(理由必填,便于审计)。
import fs from 'node:fs';
import path from 'node:path';
import { execFileSync } from 'node:child_process';
import { fileURLToPath } from 'node:url';
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); // client/
const LIB = path.join(ROOT, 'lib');
const args = new Set(process.argv.slice(2));
const CHANGED = args.has('--changed');
const STRICT = args.has('--strict') || CHANGED;
// 违规模式:硬编码 hex 颜色;具名色(Colors.x,除 transparent=无色合理)。
const HEX = /Color\(0x[0-9A-Fa-f]{6,8}\)/;
const NAMED = /\bColors\.(?!transparent\b)[a-zA-Z]\w*/;
const IGNORE = /\/\/\s*ds-ignore/;
// 文件级豁免:对外公开页(顾客扫码固定品牌页)为**有意独立设计**
// warm-paper/酒红 bespoke 调色板,不随 A/B/C 主题,见 design/CONTRACT.md
// 「公开商品/门店页」条目)——整页自成体系,不逐行豁免。
const EXEMPT_FILES = [
'lib/screens/public/public_product_screen.dart',
'lib/screens/public/public_shop_products_screen.dart',
];
function listDartFiles() {
if (CHANGED) {
let out = '';
try {
out = execFileSync(
'git', ['diff', '--name-only', '--diff-filter=ACM', 'HEAD'],
{ cwd: ROOT, encoding: 'utf8' });
} catch { out = ''; }
return out.split('\n')
.map((s) => s.trim())
.filter((f) => f.endsWith('.dart') && !f.endsWith('.g.dart'))
.map((f) => path.resolve(ROOT, '..', f)) // git 路径相对仓库根
.filter((f) => f.startsWith(LIB) && fs.existsSync(f));
}
const out = [];
(function walk(d) {
for (const e of fs.readdirSync(d, { withFileTypes: true })) {
const p = path.join(d, e.name);
if (e.isDirectory()) walk(p);
else if (e.name.endsWith('.dart') && !e.name.endsWith('.g.dart')) out.push(p);
}
})(LIB);
return out;
}
const isExempt = (f) =>
EXEMPT_FILES.some((e) => f.replaceAll(path.sep, '/').endsWith(e));
const violations = [];
for (const file of listDartFiles()) {
if (isExempt(file)) continue; // 文件级豁免(对外公开页 bespoke 设计)
const lines = fs.readFileSync(file, 'utf8').split('\n');
lines.forEach((line, i) => {
if (IGNORE.test(line)) return; // 显式豁免
const m = HEX.exec(line) || NAMED.exec(line);
if (m) {
violations.push({
file: path.relative(ROOT, file),
line: i + 1,
hit: m[0],
text: line.trim().slice(0, 90),
});
}
});
}
const banner = (s) => `\n${'─'.repeat(60)}\n${s}\n${'─'.repeat(60)}`;
if (violations.length) {
console.log(banner(`✗ 硬编码颜色 ${violations.length} 处(应走 token,或显式 // ds-ignore: 理由)`));
for (const v of violations) console.log(` ${v.file}:${v.line} [${v.hit}] ${v.text}`);
} else {
console.log(banner('✓ 通过:颜色全部走 token / 已显式豁免'));
}
if (STRICT && violations.length) process.exit(1);
console.log(`\n范围:${CHANGED ? 'git 改动文件' : '全量 lib/'} 违规:${violations.length}${STRICT ? 'strict' : '(仅报告)'}`);