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
+193
View File
@@ -0,0 +1,193 @@
#!/usr/bin/env node
/**
* migrate-brand-kole-ui.mjs — 品牌重命名迁移:Aurora Admin → Kole UI
*
* 用途:把仓库内所有品牌命名一次性迁到新名,含四类命名空间:
* 1) 文字品牌名 Aurora Admin / Aurora → Kole UI / Kole
* 2) 路径·包名 aurora-admin[-design|-showcase] → kole-ui[-showcase]
* 3) 类名前缀 aa- / au- → kole-
* 4) 令牌前缀 --au- → --kole-
* 另有精确标识符表(aaLogger / __aaCollectResult / AA_DATA 等),逐条列举而非正则,
* 避免误伤十六进制哈希(如 9f98c53aa3b2…)与 WCAG「AA」字样。
*
* 实现要点:
* - 以 latin1 逐字节读写:ASCII 模式在 UTF-8 / GBK 等 ASCII 超集编码下均按字节命中,
* 多字节汉字原样透传;CRLF 与无 BOM 状态自然保持。
* - 遇到 UTF-16 BOM 或含 NUL 字节的文件跳过并报告(不做猜测性转码)。
* - 幂等:再次运行第二遍报告 0 处改动。
*
* 用法:
* node tools/migrate-brand-kole-ui.mjs --dry-run # 只报告,不写盘
* node tools/migrate-brand-kole-ui.mjs # 执行替换
* node tools/migrate-brand-kole-ui.mjs --verify # 扫描残留旧命名(期望 0)
* node tools/migrate-brand-kole-ui.mjs --only=Aa # 只跑「来源串含 Aa」的规则
* ^ 用于增量补规则:全量重跑会把「刻意记录旧名」的
* 对照表(CHANGELOG 2.0.0 段、AGENTS §七)一起改写,
* 增量场景一律用 --only 限定规则。
*/
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const SELF = fileURLToPath(import.meta.url);
const ROOT = path.resolve(path.dirname(SELF), '..');
const argv = process.argv.slice(2);
const DRY = argv.includes('--dry-run');
const VERIFY = argv.includes('--verify');
const ONLY = (argv.find((a) => a.startsWith('--only=')) || '').slice('--only='.length) || null;
/** --only=<substr> 时只保留「来源串含该子串」的规则。 */
function pick(rules) {
if (!ONLY) return rules;
return rules.filter(([from]) => String(from).includes(ONLY));
}
const SKIP_DIRS = new Set([
'.git', 'node_modules', 'dist', 'dist-deploy', '.tmp', '.playwright-mcp', '.zcode',
]);
const SKIP_EXT = new Set([
'.png', '.jpg', '.jpeg', '.gif', '.ico', '.webp', '.bmp', '.exe', '.dll', '.zip',
'.gz', '.tgz', '.tar', '.woff', '.woff2', '.ttf', '.otf', '.eot', '.pdf', '.mp4', '.webm',
]);
/** 有序替换表:字面量必须排在能作为其子串的规则之后(长优先)。 */
const LITERAL_RULES = [
// 1. 包名 / 镜像名(必须先于 aurora-admin)
['aurora-admin-design', 'kole-ui'],
['aurora-admin-showcase', 'kole-ui-showcase'],
['aurora-admin', 'kole-ui'],
// 2. 文字品牌名
['Aurora Admin', 'Kole UI'],
['Aurora', 'Kole'],
['aurora', 'kole'],
['AURORA', 'KOLE'],
// 3. 令牌前缀(必须先于裸 au-)
['--au-', '--kole-'],
// 4. 类名前缀
['aa-', 'kole-'],
// 5. 精确标识符(逐条列举,避开哈希误伤)
['AA_VERIFY_DEPS_DIR', 'KOLE_VERIFY_DEPS_DIR'],
['AA_DATA', 'KOLE_DATA'],
['AA_PORT', 'KOLE_PORT'],
['aaLogger', 'koleLogger'],
['__aaTranslateThemePanel', '__koleTranslateThemePanel'],
['__aaRefreshModeUI', '__koleRefreshModeUI'],
['__aaChangelogReady', '__koleChangelogReady'],
['__aaCollectResult', '__koleCollectResult'],
['__aaDetailReady', '__koleDetailReady'],
['__aaBehaviors', '__koleBehaviors'],
['__aaBehCache', '__koleBehCache'],
['__aaVersion', '__koleVersion'],
['__aaResult', '__koleResult'],
['__aaRun', '__koleRun'],
['aaZoom', 'koleZoom'],
['aaSlide', 'koleSlide'],
['aaFade', 'koleFade'],
];
/** 正则规则:`au-` 前必须不是字母数字连字符(排除 beau- 这类词内误伤)。 */
const REGEX_RULES = [
[/(?<![A-Za-z0-9-])au-/g, 'kole-'],
// 包公开导出名 `AaButton` / 命名空间导入 `import * as Aa` → `KoleButton` / `Kole`。
// 负向先行断言排除 `Aardvark` 这类「Aa + 小写」的英文词。
[/\bAa(?![a-z])/g, 'Kole'],
];
const VERIFY_PATTERNS = [
/aurora/i,
/\baa-/,
/--au-/,
/(?<![A-Za-z0-9-])au-/,
/\bAa(?![a-z])/,
/__aa[A-Z]/,
/\baaLogger\b/,
/AA_(DATA|PORT|VERIFY_DEPS_DIR)/,
/aa(Demo|Badge|Tab|Menu|Card|Btn)/,
];
function applyRules(src) {
let out = src;
const hits = [];
for (const [from, to] of pick(LITERAL_RULES)) {
const n = out.split(from).length - 1;
if (n) {
hits.push([from, to, n]);
out = out.split(from).join(to);
}
}
for (const [re, to] of pick(REGEX_RULES)) {
const n = (out.match(re) || []).length;
if (n) {
hits.push([String(re), to, n]);
out = out.replace(re, to);
}
}
return [out, hits];
}
function walk(dir, acc = []) {
for (const ent of fs.readdirSync(dir, { withFileTypes: true })) {
const p = path.join(dir, ent.name);
if (ent.isDirectory()) {
if (SKIP_DIRS.has(ent.name)) continue;
walk(p, acc);
} else if (ent.isFile()) {
if (SKIP_EXT.has(path.extname(ent.name).toLowerCase())) continue;
acc.push(p);
}
}
return acc;
}
const files = walk(ROOT).filter((f) => path.resolve(f) !== path.resolve(SELF));
const skipped = [];
const changed = [];
const ruleTotals = new Map();
for (const f of files) {
const buf = fs.readFileSync(f);
if (buf.length >= 2 && ((buf[0] === 0xff && buf[1] === 0xfe) || (buf[0] === 0xfe && buf[1] === 0xff))) {
skipped.push([path.relative(ROOT, f), 'UTF-16 BOM']);
continue;
}
const src = buf.toString('latin1');
if (VERIFY) {
const found = [];
for (const re of VERIFY_PATTERNS) {
const m = src.match(new RegExp(re.source, re.flags.includes('g') ? re.flags : re.flags + 'g'));
if (m) found.push(`${re.source} ×${m.length}`);
}
if (found.length) changed.push([path.relative(ROOT, f), found.join(', ')]);
continue;
}
const [out, hits] = applyRules(src);
if (out === src) continue;
const rel = path.relative(ROOT, f).split(path.sep).join('/');
let total = 0;
for (const [from, , n] of hits) {
total += n;
ruleTotals.set(from, (ruleTotals.get(from) || 0) + n);
}
changed.push([rel, `${total} 处`]);
if (!DRY) fs.writeFileSync(f, Buffer.from(out, 'latin1'));
}
const mode = VERIFY ? 'verify(残留扫描)' : DRY ? 'dry-run(未写盘)' : 'apply(已写盘)';
console.log(`\n[migrate-brand] 模式 ${mode} · 扫描 ${files.length} 个文本文件 · 命中 ${changed.length} 个\n`);
for (const [rel, info] of changed) console.log(` ${rel} — ${info}`);
if (skipped.length) {
console.log(`\n跳过(非 ASCII 超集编码):`);
for (const [rel, why] of skipped) console.log(` ${rel} (${why})`);
}
if (!VERIFY) {
console.log(`\n规则命中统计:`);
for (const [rule, n] of [...ruleTotals.entries()].sort((a, b) => b[1] - a[1])) {
console.log(` ${String(n).padStart(6)} ${rule}`);
}
}
console.log('');
if (VERIFY && changed.length) process.exitCode = 1;