diff --git a/.gitea/workflows/checks.yml b/.gitea/workflows/checks.yml new file mode 100644 index 0000000..663fda2 --- /dev/null +++ b/.gitea/workflows/checks.yml @@ -0,0 +1,39 @@ +name: Design Source Checks + +# 设计真相源检查(治理规则见 CLAUDE.md「设计真相源分层」): +# PR 与合并 main 时自动跑四道闸,全部纯静态检查(node + dart format), +# 不跑 golden/fidelity(重型像素闸维持「定期体检」定位,本地手动跑)。 +on: + pull_request: + push: + branches: [main] + +concurrency: + group: checks-${{ gitea.ref }} + cancel-in-progress: true + +jobs: + design-source: + runs-on: mac + + steps: + - uses: actions/checkout@v4 + + - name: Provision (idempotent — auto-runs if host not initialized) + run: sh scripts/ci/provision-mac.sh + + - name: 原型设计系统闸(check-ds 12 道) + run: node .superpowers/prototype/tools/check-ds.mjs + + - name: L1 真相源同源闸(tokens 快照 / icons / 官网 token 值 / web hex) + run: node tools/check-l1-sync.mjs + + - name: Flutter 颜色单源闸(check_ds_code) + run: node client/tool/check_ds_code.mjs + + - name: token codegen 新鲜度(regen → format → 与入库产物零 diff) + run: | + cd client + node tool/gen_tokens.mjs + dart format lib/core/theme/app_tokens.g.dart lib/core/theme/app_dims.g.dart lib/core/theme/app_chrome.g.dart + git diff --exit-code -- lib/core/theme/app_tokens.g.dart lib/core/theme/app_dims.g.dart lib/core/theme/app_chrome.g.dart diff --git a/CLAUDE.md b/CLAUDE.md index c1157f5..36da61c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -265,6 +265,7 @@ cd client && flutter test - `代码先行`:无原型屏,golden 为唯一像素基准。 - **L3 新屏/整屏改版**:design-first 不变(原型 → CONTRACT → 实现 → fidelity 验收),建成后入 `同步` 态。 - **漂移体检**:不逐提交强制同步;大版本/整屏改版前跑 `node tools/fidelity.mjs`,按报告逐屏决定「重新对齐原型」或「降级快照」,结果回填 CONTRACT。 +- **CI 闸**(`.gitea/workflows/checks.yml`,PR + main push 自动跑):① `check-ds.mjs` 原型 12 道;② `tools/check-l1-sync.mjs` L1 同源四道(tokens 快照逐字节 / icons 两端同集同内容 / 官网 token 值对齐原型主题 A / web 硬编码 hex,白名单 #fff、#1677ff,`ds-allow` 可豁免);③ `client/tool/check_ds_code.mjs` Flutter 颜色单源;④ codegen 新鲜度(regen→`dart format`→与入库产物零 diff)。本地可直接跑同名命令。 ### 表格与列头筛选(ds 真相源) - 列表屏统一 `DsTable`(`widgets/ds/ds_table.dart`:toolbar+表格+pager 连成一卡),列定义 `DsColumn`,可筛选列走其列头漏斗(`filtered` 态高亮),可隐藏列走列设置菜单 diff --git a/tools/check-l1-sync.mjs b/tools/check-l1-sync.mjs new file mode 100644 index 0000000..3121682 --- /dev/null +++ b/tools/check-l1-sync.mjs @@ -0,0 +1,117 @@ +#!/usr/bin/env node +// tools/check-l1-sync.mjs — L1 设计真相源同源闸(四道,纯 Node 零依赖) +// +// 治理依据:CLAUDE.md「设计真相源分层」L1 层——tokens/atoms/icons 以 +// .superpowers/prototype/ 为单一真源,client 与 web 只能是它的同步副本。 +// CI(.gitea/workflows/checks.yml,PR + main push)与本地均可直接跑: +// node tools/check-l1-sync.mjs +// +// 四道检查(任一违规 exit 1): +// ① tokens 快照同源 原型 tokens.css ≡ client token_source/tokens.css(逐字节) +// ② icons 同源 原型 icons.js 与 web icons.njk 的 symbol 同集且同内容 +// ③ 官网 token 值同源 web/assets/tokens.css 与原型(:root+主题A) 同名 token 值一致 +// (官网独有 --mk-*/兼容别名层、App 外壳专属 token 不算违规) +// ④ web 硬编码色扫描 web/**/*.{njk,css,js} 禁裸 hex;白名单 #fff/#ffffff/#1677ff +// (对齐原型 check-ds 豁免口径);行内 `ds-allow` 注释可豁免; +// 排除 web/assets/tokens.css(token 定义位)与 web/dist/。 + +import { readFileSync, readdirSync, statSync } from 'node:fs'; +import { join, relative } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const ROOT = fileURLToPath(new URL('..', import.meta.url)); +const read = (p) => readFileSync(join(ROOT, p), 'utf8'); +const problems = []; + +// ── ① tokens 快照同源 ────────────────────────────────────── +{ + const proto = read('.superpowers/prototype/tokens.css'); + const snap = read('client/lib/core/theme/token_source/tokens.css'); + if (proto !== snap) { + problems.push( + '[tokens快照] .superpowers/prototype/tokens.css 与 ' + + 'client/lib/core/theme/token_source/tokens.css 不一致 —— ' + + '改原型后需拷贝快照并重跑 client/lib/core/theme/tool/gen_tokens.mjs', + ); + } +} + +// ── ② icons 同源 ─────────────────────────────────────────── +{ + const symbols = (s) => { + const map = {}; + for (const [, id, body] of s.matchAll(/]*>(.*?)<\/symbol>/gs)) { + map[id] = body.trim(); + } + return map; + }; + const a = symbols(read('.superpowers/prototype/screens/icons.js')); + const b = symbols(read('web/_includes/icons.njk')); + for (const id of Object.keys(a)) { + if (!(id in b)) problems.push(`[icons] symbol "${id}" 仅原型有,web/_includes/icons.njk 缺 —— 改原型后同步官网`); + } + for (const id of Object.keys(b)) { + if (!(id in a)) problems.push(`[icons] symbol "${id}" 仅官网有 —— 图标必须先登记原型 icons.js 再同步`); + } + for (const id of Object.keys(a)) { + if (id in b && a[id] !== b[id]) problems.push(`[icons] symbol "${id}" 两端内容不一致`); + } +} + +// ── ③ 官网 token 值同源 ──────────────────────────────────── +{ + const vars = (css) => + Object.fromEntries( + [...css.matchAll(/(--[\w-]+)\s*:\s*([^;}]+)[;}]/g)].map(([, k, v]) => [k, v.replace(/\s+/g, '')]), + ); + const proto = read('.superpowers/prototype/tokens.css'); + const root = proto.match(/:root\s*{([^}]*)}/s)?.[1] ?? ''; + const themeA = proto.match(/\[data-theme="a"\]\s*{([^}]*)}/s)?.[1] ?? ''; + const pv = { ...vars(root), ...vars(themeA) }; + const wv = vars(read('web/assets/tokens.css')); + for (const [k, v] of Object.entries(pv)) { + if (k in wv && wv[k] !== v) { + problems.push(`[web-token] ${k} 值漂移:原型=${v} 官网=${wv[k]} —— web/assets/tokens.css 需同步原型主题 A`); + } + } +} + +// ── ④ web 硬编码色扫描 ───────────────────────────────────── +{ + const ALLOW_HEX = new Set(['fff', 'ffffff', '1677ff']); // 白 + 支付宝品牌蓝(同原型 check-ds 口径) + const SKIP = new Set(['web/assets/tokens.css']); // token 定义位 + const files = []; + const walk = (dir) => { + for (const name of readdirSync(join(ROOT, dir))) { + const rel = `${dir}/${name}`; + if (rel === 'web/dist' || name === 'node_modules') continue; + const st = statSync(join(ROOT, rel)); + if (st.isDirectory()) walk(rel); + else if (/\.(njk|css|js)$/.test(name) && !SKIP.has(rel)) files.push(rel); + } + }; + walk('web'); + for (const f of files) { + const lines = read(f).split('\n'); + lines.forEach((line, i) => { + if (line.includes('ds-allow')) return; + for (const [hex] of line.matchAll(/#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})\b/g)) { + const v = hex.slice(1).toLowerCase(); + if (!ALLOW_HEX.has(v)) problems.push(`[web-hex] ${f}:${i + 1} 硬编码颜色 ${hex} —— 改用 var(--token)(web/assets/tokens.css),确需保留加 ds-allow 注释`); + } + }); + } +} + +// ── 汇总 ─────────────────────────────────────────────────── +const line = '='.repeat(60); +if (problems.length) { + console.error(line); + console.error(`✗ L1 真相源同源闸未过(${problems.length} 处):`); + for (const p of problems) console.error(' · ' + p); + console.error(line); + process.exit(1); +} +console.log(line); +console.log('✓ 通过:L1 真相源同源(tokens 快照 / icons / 官网 token 值 / web 无硬编码色)'); +console.log(line); diff --git a/web/checkout.njk b/web/checkout.njk index c4bdda4..457707a 100644 --- a/web/checkout.njk +++ b/web/checkout.njk @@ -49,7 +49,7 @@ permalink: /checkout/
-
+
对公转账需要合同与对公收款请联系我们