6238b86dcb
- 登录/注册(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
92 lines
3.8 KiB
JavaScript
92 lines
3.8 KiB
JavaScript
// client/tool/check_ds_code.mjs
|
||
// 代码端「设计真相源」闸 —— 与原型 check-ds.mjs 对应,把单一真源纪律延伸到 Flutter 代码。
|
||
// 红线:颜色只走 context.tokens / AppChrome / AppDims(从 tokens.css codegen 单源),
|
||
// 禁硬编码 hex(Color(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 1(pre-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)' : '(仅报告)'}`);
|