Regression / regression (push) Canceled after 0s
## 品牌标识(本次会话) 起因:品牌此前没有任何图形标识 —— 唯一 favicon 是内联 data-URI 里的字母「A」, 那是 v2.0.0「Aurora Admin → Kole UI」改名漏掉的一处(PC 顶栏也是「A」, 移动端站已是「K」;移动端文档站则完全没有 favicon)。 - 几何:24 网格三个互不接触的笔画(竖 + 两斜),圆头描边; 描边 2.25 → 16px 标签页尺寸下正好 1.5px = 规范原文「描边1.5px」 - 取色分两套(刻意):favicon 硬编码品牌蓝/白(渲染在浏览器标签栏,不继承 kole-dark); 顶栏标记走 currentColor(实测暗色下自动转 rgb(20,22,28)) - 新增 theme-color 双条(light #FFFFFF / dark #1C1F26,取 --kole-color-card-bg) - 修 site/app.js hero 标语 KOLE ADMIN → KOLE UI(改名变形残留) - 移动端 7 个模板补 favicon(此前计数 0) 验收:门禁 9 条全 OK(site-routing/site-routes/mobile-docs/mobile-site/isolation/ theme/nav/i18n/icons);PC 回归 1464/1464 · 移动端 807/807,各连跑 8 次一致; 两端 favicon 405 字节逐字节一致;PC 站控制台错误 1→0。 ## 并行会话成果(本次一并入库) - 图标系统:2576 图标(TDesign/Element Plus,MIT)+ 11 端注入 + 5 个构建门禁工具 + IconPreview 预览页 + ICON-SPEC.md 冻结规格 - 移动端平台:47 组件 × 6 端 + 文档站 53 页 + 隔离门禁 - PC 组件:103 个大后台组件 / 组件11 批次 - uni-app:PC 端试点 + 移动端端实现 + 真实编译验证 ## 工程 - .gitignore 补 .scratch/ 与 .zcode-preexisting-*.txt(会话中间产物,实测 9.1MB,不入库) - CHANGELOG 补品牌标识条目 - ROADMAP 登 S8-P4(品牌标识任务包 + og:image/apple-touch-icon 未做部分)
145 lines
6.4 KiB
JavaScript
145 lines
6.4 KiB
JavaScript
#!/usr/bin/env node
|
||
/**
|
||
* 图标表注入:把生成好的 core 图标表写进 11 个端实现的标记块里。
|
||
*
|
||
* 为什么要有这一步(而不是让 11 个端各自抄一份表):
|
||
* 11 份手抄的表必然漂移 —— 今天给 core.js 加一个图标,明天有个端忘了同步,
|
||
* 表现是"同一个 name 在 React 能渲染、在 Vue3 是空白",而且不会报错。
|
||
* 所以数据只有一份真源(core.js),各端只留**标记块**,内容由本脚本填。
|
||
* `tools/verify-icons.mjs` 会逐端比对标记块与 core.js,漂移当场拦下。
|
||
*
|
||
* 标记格式(各端按自己的注释语法):
|
||
* JS/CSS: /* === KOLE_ICON_TABLE:BEGIN === */ ... /* === KOLE_ICON_TABLE:END === */
|
||
* HTML: <!-- === KOLE_ICON_TABLE:BEGIN === --> ... <!-- === KOLE_ICON_TABLE:END === -->
|
||
*
|
||
* 注入后第 4 行是各自的分隔符写法差异 —— 见 FILES 表。
|
||
*
|
||
* 用法:
|
||
* node tools/inject-icon-table.mjs # 注入
|
||
* node tools/inject-icon-table.mjs --check # 只校验不写(CI 用)
|
||
*/
|
||
import { readFileSync, writeFileSync, existsSync, mkdirSync, renameSync } from 'node:fs';
|
||
import { fileURLToPath } from 'node:url';
|
||
import { dirname, join } from 'node:path';
|
||
|
||
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..');
|
||
const ICON_DIR = join(ROOT, '.design_library', 'kole-ui', 'icons');
|
||
const CORE_JS = join(ICON_DIR, 'core.js');
|
||
const REGISTRY = join(ICON_DIR, 'registry.json');
|
||
const ALIASES = join(ICON_DIR, 'aliases.json');
|
||
|
||
const CHECK = process.argv.includes('--check');
|
||
|
||
/** 需要注入的端文件(相对仓库根)。html 用 HTML 注释,其余用块注释。 */
|
||
const FILES = [
|
||
['frameworks/Icon.html', 'html'],
|
||
['frameworks/Icon.jsx', 'js'],
|
||
['frameworks/Icon.vue2.vue', 'js'],
|
||
['frameworks/Icon.vue3.vue', 'js'],
|
||
['frameworks-mobile/Icon.html', 'html'],
|
||
['frameworks-mobile/Icon.jsx', 'js'],
|
||
['frameworks-mobile/Icon.vue2.vue', 'js'],
|
||
['frameworks-mobile/Icon.vue3.vue', 'js'],
|
||
['frameworks-mobile/Icon.uniapp.vue', 'js'],
|
||
];
|
||
|
||
const BEGIN = '=== KOLE_ICON_TABLE:BEGIN ===';
|
||
const END = '=== KOLE_ICON_TABLE:END ===';
|
||
|
||
function markers(kind) {
|
||
return kind === 'html'
|
||
? [`<!-- ${BEGIN} -->`, `<!-- ${END} -->`]
|
||
: [`/* ${BEGIN} */`, `/* ${END} */`];
|
||
}
|
||
|
||
function die(msg) { console.error('FAIL: ' + msg); process.exit(1); }
|
||
function atomicWrite(file, content) {
|
||
mkdirSync(dirname(file), { recursive: true });
|
||
const tmp = `${file}.tmp-${process.pid}`;
|
||
writeFileSync(tmp, content.replace(/\r\n/g, '\n'), 'utf8');
|
||
renameSync(tmp, file);
|
||
}
|
||
|
||
if (!existsSync(CORE_JS)) die(`缺 ${CORE_JS} —— 先跑 node tools/build-icons.mjs`);
|
||
|
||
/* 从 core.js 里取 CORE 字面量:它是 UMD 文件,require 一次最稳(不猜正则) */
|
||
const { createRequire } = await import('node:module');
|
||
const require2 = createRequire(import.meta.url);
|
||
const runtime = require2(CORE_JS);
|
||
const CORE = runtime.core;
|
||
const coreNames = Object.keys(CORE).sort();
|
||
if (!coreNames.length) die('core.js 里没有图标');
|
||
|
||
/* 别名表:各端运行时要用它把历史名映射到 canonical 名 */
|
||
const aliasMap = JSON.parse(readFileSync(ALIASES, 'utf8')).aliases;
|
||
|
||
/* 生成注入块。
|
||
*
|
||
* 关键设计:**别名在这里就折叠掉**,注入的是「任何名字 → 路径」的终态表。
|
||
* 这样 11 个端只需要「查表 + 拼 SVG」两行逻辑,不需要各自实现别名解析 ——
|
||
* 别名规则只有一处(build-icon-aliases.mjs),端上不可能漂移。
|
||
*
|
||
* 体积取舍:compact 单行 JSON。实测 core 151 个约 33KB/文件,9 个文件共约 300KB。
|
||
* 换来的是每个端文件**自包含**(拷走单个文件就能用,dist 无需额外依赖)。
|
||
* 仓库既有 4 个移动端端实现已经是「各自内联一份 9 名字形表」的写法,
|
||
* 这里是同一模式的规模化,不是新引入的坏味道。门禁逐端比对,漂移无法静默。
|
||
*/
|
||
function buildBlock(kind) {
|
||
const merged = {};
|
||
/* 先放 canonical,再放别名(别名不覆盖 canonical —— 别名表已保证不遮蔽真图标) */
|
||
for (const [name, paths] of Object.entries(CORE)) merged[name] = paths;
|
||
for (const [alias, target] of Object.entries(aliasMap)) {
|
||
if (!(alias in merged) && merged[target]) merged[alias] = merged[target];
|
||
}
|
||
const payload = {
|
||
sw: runtime.strokeWidth,
|
||
vb: runtime.viewBox,
|
||
i: merged,
|
||
};
|
||
/* 让 `</script` 不能提前闭合 HTML 里的数据块 */
|
||
const json = JSON.stringify(payload).replace(/</g, '\\u003c');
|
||
const count = Object.keys(merged).length;
|
||
const head = kind === 'html'
|
||
? `<!-- 生成物:tools/inject-icon-table.mjs 注入(勿手改)。数据真源 .design_library/kole-ui/icons/。\n ${count} 个名字(${Object.keys(CORE).length} canonical + 别名折叠)· 描边 ${runtime.strokeWidth} · 别名规则见 build-icon-aliases.mjs -->`
|
||
: `/* 生成物:tools/inject-icon-table.mjs 注入(勿手改)。数据真源 .design_library/kole-ui/icons/。\n ${count} 个名字(${Object.keys(CORE).length} canonical + 别名折叠)· 描边 ${runtime.strokeWidth} · 别名规则见 build-icon-aliases.mjs */`;
|
||
if (kind === 'html') {
|
||
return `${head}\n<script type="application/json" id="kole-icon-data">${json}</script>`;
|
||
}
|
||
return `${head}\nconst KOLE_ICON_DATA = ${json};`;
|
||
}
|
||
|
||
/* 注入 */
|
||
let changed = 0;
|
||
const missing = [];
|
||
for (const [rel, kind] of FILES) {
|
||
const abs = join(ROOT, rel);
|
||
if (!existsSync(abs)) { missing.push(rel); continue; }
|
||
const text = readFileSync(abs, 'utf8');
|
||
const [b, e] = markers(kind);
|
||
const bi = text.indexOf(b);
|
||
const ei = text.indexOf(e);
|
||
if (bi < 0 || ei < 0 || ei < bi) {
|
||
missing.push(`${rel}(缺标记对)`);
|
||
continue;
|
||
}
|
||
const block = buildBlock(kind);
|
||
const next = text.slice(0, bi + b.length) + '\n' + block + '\n' + text.slice(ei);
|
||
if (next !== text) {
|
||
changed++;
|
||
if (!CHECK) atomicWrite(abs, next);
|
||
}
|
||
}
|
||
|
||
if (missing.length) {
|
||
console.log(` 跳过 ${missing.length} 个文件(未就绪或缺标记):`);
|
||
missing.forEach((m) => console.log(' - ' + m));
|
||
}
|
||
|
||
console.log('图标表注入');
|
||
console.log(` core 图标 ${coreNames.length} 个 | 别名 ${Object.keys(aliasMap).length} 条`);
|
||
console.log(` ${CHECK ? '需更新' : '已更新'} ${changed} 个端文件 / 共 ${FILES.length} 个`);
|
||
if (CHECK && changed) {
|
||
die(`有 ${changed} 个端文件的图标表与 core.js 不一致 —— 跑 npm run build:icons 重新注入`);
|
||
}
|
||
console.log('OK');
|