feat(品牌标识): 几何 K 图标(favicon/顶栏标记/theme-color) + 并行会话成果入库
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 未做部分)
This commit is contained in:
aurora-admin
2026-09-21 10:05:48 +08:00
parent eb25feedaf
commit f1fbfc2ddb
2541 changed files with 270385 additions and 25873 deletions
+144
View File
@@ -0,0 +1,144 @@
#!/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');