Files
aurora-admin/site/index.html
T
aurora-admin 5ee5d829a4
Regression / regression (push) Canceled after 0s
refactor(详情页+S8): 详情页减重(导航互斥/入口去重/i18n去重) + 品牌标识入库
本提交含两条并行工作线,因互相咬合(package.json scripts、regression.yml、
npm run build 链)无法按文件干净拆分,故合并为一次自洽提交。

## 详情页减重(本轮主任务:修复「AI 干太重」)

- 导航收敛:宽屏只用右侧目录、≤1200px 只用页内 sticky 导航,纯 CSS 媒体查询
  实现(不引入 JS 宽度监听)。此前两套导航同时可见,active 状态互相打架。
- 入口去重:标题区由「查看示例/在线测试/契约 JSON」三个减为「在线测试」一个;
  契约 JSON 归入实现资源;删除示例底部重复的「在线测试」。
- 示例工具栏:删除与目录锚点重复的示例下拉选择器;「全部展开代码」只在
  示例数 >1 时出现(103 个组件里 49 个仅 1 个示例,此前恒显示)。
- 重复文案:示例区两句同义导语合并为一句。
- 首页 CTA 由 5 个减为 2 个(浏览组件/快速开始),测试总览入口挂到已有的
  通过率统计卡上,不再另占 Hero 按钮。
- 统一详情取数:抽出 fetchDetail/loadDetail 作为 details/*.json 的唯一路径,
  FAQ 不再自行 fetch 一遍,与组件页共用缓存与失败兜底。

## i18n

- 删除 12 组重复键(含整段 FAQ 说明),字典 604 → 585 唯一键。
- 删除本次改动产生的 6 个死键。
- verify-i18n.mjs 新增 `unique dictionary keys` 断言:重复键在对象字面量里
  是静默的后值覆盖,此前无从发现;现由门禁拦住。

## 文档事实修正(实测为准)

- TESTING/CONTRIBUTING:79 → 103 组件;旧断言数改为回指 tests/report.json。
- PLATFORMS:移动端 108 文件/18 端 → 282 文件/47 端;契约 5 → 47;
  令牌 17 → 15;uni-app SFC 21 → 50。
- AGENTS:断言 1405/18 页 → 1464/103 页(PC)、807/47 页(移动端)。
- package.json:YOUR-ACCOUNT 占位 → gitea 实址与 kole-ui.mymoyu.top。

## 品牌标识(并行会话成果,一并入库)

- brand-mark.json 收归真源,build:brand 生成 favicon 与单色 SVG;
  PC 与移动端共用资产,verify:brand 24 条断言。
- regression.yml 增加 verify:brand 步骤。

## 门禁与验收

新增工具:verify-component-page.mjs(86 条真实浏览器断言,随详情页改造同步
更新为「只允许一套导航可见」)、verify-brand-mark.mjs、lib/i18n-dead-keys.mjs
(只读诊断)。

回归:PC 100%(1464/1464,103 页,N/A 50)· 移动端 100%(807/807,47 页)。
门禁:component-page 86 · i18n 17 · brand 24 · routes all · smoke all ·
theme OK · isolation 31 · nav all · examples 9 · api-docs 10 · mobile-docs 12。

已知未做:i18n 另有约 44 条历史死键(非本次产生),已记为 ROADMAP S8-P6;
顶栏与悬浮区的两个主题入口为刻意设计(verify-theme 断言其互斥),未删。
2026-09-22 23:56:05 +08:00

253 lines
20 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="color-scheme" content="light dark">
<title>Kole UI · 组件库文档</title>
<!-- 主题在解析期定型,避免「先白后黑」闪烁:模式存在 localStorage['kole-mode'],
默认 auto(没有显式选择即跟随系统 prefers-color-scheme)。app.js 里的
normalizeMode/readMode 必须与此处同规则,两处任一改动都要同步。 -->
<script>
(function () {
var mode = 'auto';
try { mode = localStorage.getItem('kole-mode') || 'auto'; } catch (e) { /* ignore */ }
if (mode !== 'light' && mode !== 'dark' && mode !== 'auto') mode = 'auto';
var dark = mode === 'dark'
|| (mode === 'auto' && !!(window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches));
if (dark) document.documentElement.className += ' kole-dark';
})();
</script>
<!-- 路由是 History API 的无 # 形态:深层 URL 形如 /site/component/button/h5,
磁盘上没有对应文件(服务器回落到本页)。这种 URL 下相对路径会以深层路径为
基准解析而全部失效,所以先算出站点根:既写进 <base>(页面内链接交给它解析),
也把资源地址写成绝对路径再输出。
规则与 site/app.js 的 SITE_BASE 必须一致:取 pathname 里第一段 "/site/",
前缀因此可变(本地/nginx 是 /site/,GitHub Pages 是 /<repo>/site/)。
站点目录必须继续叫 site,改目录名要同时改这里和 app.js。
资源不能写成静态的相对地址:预扫描器在脚本执行前就按深层 URL 预取,
实测会多出 2 个 404 + 2 条控制台报错(脚本那侧更贵 —— data.js 有 900+ KB,
会被整份白取一次)。写成绝对地址后,扫描器不需要 base 也能取对。 -->
<script>
(function () {
var p = location.pathname, i = p.indexOf('/site/');
var base = i >= 0 ? p.slice(0, i + 6) : p.replace(/[^/]*$/, '');
window.KOLE_SITE_BASE = base;
document.write('<base href="' + base + '">'
+ '<link rel="stylesheet" href="' + base + '../.design_library/kole-ui/colors_and_type.css">'
+ '<link rel="stylesheet" href="' + base + 'style.css">');
})();
</script>
<!-- 品牌 favicon 由 .design_library/kole-ui/brand/brand-mark.json 生成。
独立 SVG 资产不依赖主题令牌,深层 History API 路由通过本页注入的 <base>
解析到同一个 /site/assets/kole-mark.svg。顶栏标记则使用同一真源的单色版本,
通过 CSS mask 继承 currentColor。 -->
<link rel="icon" href="assets/kole-mark.svg">
<!-- 浏览器 UI 着色(移动端地址栏 / 桌面端标题栏):取 --kole-color-card-bg —— 顶栏
background 就是这个令牌(site/style.css 的 .topbar),所以色值与视觉连续。 -->
<meta name="theme-color" content="#FFFFFF" media="(prefers-color-scheme: light)">
<meta name="theme-color" content="#1C1F26" media="(prefers-color-scheme: dark)">
</head>
<body>
<!-- 顶部导航 -->
<header class="topbar">
<div class="topbar-inner">
<a class="logo" href="./">
<!-- 品牌标记:由 brand-mark.json 生成的单色 K-Node,CSS mask 继承 currentColor。 -->
<span class="logo-mark" data-brand-mark="mono" aria-hidden="true"></span>
<span class="logo-text">Kole UI <em class="logo-sub">组件库</em></span>
</a>
<!-- 版本入口:清单由 site/versions.json 启动时填充(app.js 的 bindVersionSelect)。
下拉菜单必须与触发角标同处一个定位容器 —— 菜单是绝对定位,容器没有
position 时它会锚到页面上而不是角标下方(实测跑到了左上角)。 -->
<div class="ver-picker" id="ver-picker">
<button id="version-trigger" class="ver-trigger" type="button" aria-haspopup="listbox" aria-expanded="false" aria-label="选择版本">
<span id="ver">v1.0.0</span>
<svg class="ver-caret" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m6 9 6 6 6-6"/></svg>
</button>
<div id="version-menu" class="ver-menu hidden" role="listbox" aria-label="文档版本">
<div class="ver-menu-head" id="ver-head">文档版本</div>
</div>
</div>
<button id="search-trigger" class="search-trigger" type="button">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/></svg>
<span class="st-text">搜索组件…</span>
<kbd>Ctrl K</kbd>
</button>
<!-- 技术栈选择器(2026-09-20 美化):原先是一个裸 <select>(系统原生下拉、无悬停/展开态、
与旁边的语言/主题选择器视觉不同源)。改为「胶囊触发器 + 卡片菜单」,与 .lang-select /
.theme-select 同款式:图标 + 当前值 + 箭头,菜单含标题分隔线、每项的端文件名角标与对勾。
原生 <select> 保留在包装内(.fw-native,1px 视觉隐藏)作为状态真源 —— app.js 的
change 管道、localStorage 与既有验证脚本(run-site-smoke 读 inputValue)全部不变。 -->
<div class="fw-select-wrap" id="fw-select-wrap" title="选择你的技术栈,文档将只展示对应框架的内容">
<button id="fw-trigger" class="fw-trigger" type="button" aria-haspopup="listbox" aria-expanded="false" aria-label="框架">
<svg class="fw-glyph" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m12 3 8.5 4.7L12 12.4 3.5 7.7 12 3z"/><path d="m3.5 12.3 8.5 4.7 8.5-4.7"/></svg>
<span class="fw-cur" id="fw-cur">H5</span>
<svg class="fw-caret" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m6 9 6 6 6-6"/></svg>
</button>
<select id="fw-select" class="fw-native" tabindex="-1" aria-hidden="true">
<option value="h5">H5</option>
<option value="react">React</option>
<option value="vue2">Vue 2</option>
<option value="vue3">Vue 3</option>
</select>
<div id="fw-menu" class="lang-menu fw-menu hidden" role="listbox" aria-label="技术栈">
<div class="lang-head"><span id="fw-head">技术栈</span><span class="fw-head-file" id="fw-head-file">html</span></div>
<button class="lang-opt fw-opt" type="button" role="option" data-fw="h5" aria-selected="true">
<span class="lang-opt-name">H5</span>
<span class="lang-opt-code">html</span>
<svg class="lang-check" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5"/></svg>
</button>
<button class="lang-opt fw-opt" type="button" role="option" data-fw="react" aria-selected="false">
<span class="lang-opt-name">React</span>
<span class="lang-opt-code">jsx</span>
<svg class="lang-check" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5"/></svg>
</button>
<button class="lang-opt fw-opt" type="button" role="option" data-fw="vue2" aria-selected="false">
<span class="lang-opt-name">Vue 2</span>
<span class="lang-opt-code">vue2</span>
<svg class="lang-check" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5"/></svg>
</button>
<button class="lang-opt fw-opt" type="button" role="option" data-fw="vue3" aria-selected="false">
<span class="lang-opt-name">Vue 3</span>
<span class="lang-opt-code">vue3</span>
<svg class="lang-check" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5"/></svg>
</button>
</div>
</div>
<nav class="topnav" id="topnav">
<a href="./" data-nav="home">首页</a>
<a href="overview" data-nav="overview">组件</a>
<a href="guide" data-nav="guide">快速开始</a>
<a href="design" data-nav="design">设计规范</a>
<a href="faq" data-nav="faq">常见问题</a>
<a href="changelog" data-nav="changelog">更新日志</a>
<!-- 抽屉内的版本入口:≤560px 顶栏放不下版本控件(实测溢出),抽屉是这一档的自然位置。
选项由 site/versions.json 填充;清单拿不到版本时整行不显示。 -->
<label class="drawer-ver" id="drawer-ver" hidden>
<span class="drawer-ver-label">版本</span>
<select id="drawer-ver-select" aria-label="选择版本"></select>
</label>
</nav>
<!-- 主题模式(日间 / 夜间 / 自动):顶栏常驻入口,与右下角悬浮按钮共用同一份状态。
菜单选项的 data-mode 是三态的取值来源,改这里要同步 app.js 的 MODE_LABELS。 -->
<div class="theme-select" id="theme-select">
<button id="topbar-mode" class="theme-trigger" type="button" title="主题模式" aria-label="主题模式" aria-expanded="false" aria-haspopup="true">
<svg class="theme-icon" id="tb-icon-moon" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>
<svg class="theme-icon" id="tb-icon-sun" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" style="display:none"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41"/></svg>
<svg class="theme-icon" id="tb-icon-auto" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" style="display:none"><rect x="3" y="4" width="18" height="13" rx="2"/><path d="M8 21h8M12 17v4"/></svg>
<span class="theme-cur" id="topbar-mode-label">自动模式</span>
<svg class="theme-caret" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m6 9 6 6 6-6"/></svg>
</button>
<div id="topbar-mode-menu" class="mode-menu hidden" role="radiogroup" aria-label="主题模式">
<!-- 菜单头由 app.js 的 updateModeUI 填充(两行:当前选中 / 实际生效),
这里留空 —— 静态占位会在脚本执行前闪现过时文案 -->
<div class="mode-current" id="tb-mode-current" aria-live="polite"></div>
<button type="button" role="radio" data-mode="light" aria-checked="false"><span class="mode-dot mode-dot-light"></span><span data-mode-label="light">日间模式</span></button>
<button type="button" role="radio" data-mode="dark" aria-checked="false"><span class="mode-dot mode-dot-dark"></span><span data-mode-label="dark">夜间模式</span></button>
<button type="button" role="radio" data-mode="auto" aria-checked="false"><span class="mode-dot mode-dot-auto"></span><span data-mode-label="auto">自动模式</span></button>
</div>
</div>
<div class="lang-select" id="lang-select">
<button id="lang-trigger" class="lang-trigger" type="button" title="界面语言" aria-label="选择语言" aria-haspopup="listbox" aria-expanded="false">
<svg class="lang-globe" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M3 12h18"/><path d="M12 3a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg>
<span class="lang-cur" id="lang-cur">简体中文</span>
<svg class="lang-caret" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m6 9 6 6 6-6"/></svg>
</button>
<div id="lang-menu" class="lang-menu hidden" role="listbox" aria-label="界面语言">
<div class="lang-head">界面语言</div>
<button class="lang-opt" type="button" role="option" data-lang="zh" aria-selected="true">
<span class="lang-opt-name">简体中文</span>
<span class="lang-opt-code">ZH</span>
<svg class="lang-check" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5"/></svg>
</button>
<button class="lang-opt" type="button" role="option" data-lang="en" aria-selected="false">
<span class="lang-opt-name">English</span>
<span class="lang-opt-code">EN</span>
<svg class="lang-check" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5"/></svg>
</button>
</div>
</div>
<!-- 窄屏版本入口:≤900px 时顶栏角标被隐藏(见 style.css),这里改用原生 select 顶上。
选项由 site/versions.json 填充;只有一版时整块不显示。 -->
<label class="ver-select-wrap" id="ver-select-wrap" title="文档版本" hidden>
<select id="ver-select" aria-label="选择版本"></select>
</label>
<button id="nav-toggle" class="nav-toggle" type="button" aria-label="打开导航菜单" aria-controls="topnav" aria-expanded="false">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M3 6h18"/><path d="M3 12h18"/><path d="M3 18h18"/></svg>
</button>
</div>
<!-- 窄屏导航抽屉的遮罩:仅 ≤1366px 且抽屉打开时可见。
必须放在 .topbar 内 —— .topbar 有 z-index:100 会形成层叠上下文,
放到外面会让遮罩(110)压住抽屉(120),抽屉可见却点不动。 -->
<div id="nav-backdrop" class="nav-backdrop"></div>
</header>
<div class="body-wrap">
<aside class="sidebar" id="sidebar"></aside>
<main class="content" id="content"></main>
<aside class="toc" id="toc"></aside>
</div>
<!-- 全局搜索弹窗 -->
<div class="modal-mask hidden" id="search-modal">
<div class="modal">
<div class="modal-input-wrap">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/></svg>
<input id="search-input" placeholder="搜索组件(中文 / 英文 / slug)" autocomplete="off" spellcheck="false">
</div>
<div id="search-cats" class="search-cats"></div>
<div id="search-results" class="search-results"></div>
<div class="modal-foot"><span>↑↓ 选择</span><span>Enter 跳转</span><span>Esc 关闭</span></div>
</div>
</div>
<div id="fatal" class="fatal hidden"></div>
<!-- 悬浮工具:深色模式 + 主题定制 + 回到顶部 -->
<div class="fab-stack">
<div class="theme-panel hidden" id="theme-panel">
<div class="tp-head">主题定制</div>
<div id="theme-presets" class="tp-presets"></div>
<div id="theme-colors"></div>
<div class="tp-actions">
<button id="theme-reset" class="tp-btn" type="button">重置</button>
<button id="theme-copy" class="tp-btn primary" type="button">复制覆盖代码</button>
</div>
<div class="tp-note">覆盖代码可直接粘贴到令牌文件之后,即完成整站换色。</div>
</div>
<div class="mode-control">
<button id="fab-mode" class="fab" type="button" title="主题模式" aria-label="主题模式" aria-expanded="false" aria-haspopup="true">
<svg id="icon-moon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>
<svg id="icon-sun" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display:none"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41"/></svg>
<svg id="icon-auto" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display:none"><rect x="3" y="4" width="18" height="13" rx="2"/><path d="M8 21h8M12 17v4"/></svg>
</button>
<div id="mode-menu" class="mode-menu hidden" role="radiogroup" aria-label="主题模式">
<div id="mode-current" class="mode-current" aria-live="polite"></div>
<button type="button" role="radio" data-mode="light" aria-checked="false"><span class="mode-dot mode-dot-light"></span><span data-mode-label="light">日间模式</span></button>
<button type="button" role="radio" data-mode="dark" aria-checked="false"><span class="mode-dot mode-dot-dark"></span><span data-mode-label="dark">夜间模式</span></button>
<button type="button" role="radio" data-mode="auto" aria-checked="false"><span class="mode-dot mode-dot-auto"></span><span data-mode-label="auto">自动模式</span></button>
</div>
</div>
<button id="fab-theme" class="fab" type="button" title="主题定制">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><path d="M12 3a9 9 0 0 0 0 18c.83 0 1.5-.67 1.5-1.5 0-.39-.15-.74-.39-1.01a1.5 1.5 0 0 1 1.12-2.49H16a5 5 0 0 0 5-5c0-4.42-4.03-8-9-8z"/></svg>
</button>
<button id="fab-top" class="fab" type="button" title="回到顶部" style="display:none">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 19V5M5 12l7-7 7 7"/></svg>
</button>
</div>
<!-- 脚本同样必须写成绝对地址(理由见 <head> 顶部注释)。document.write 在解析期
注入的是 parser-inserted script:按顺序同步执行,且仍是 DOMContentLoaded 的
阻塞项,语义与静态 <script src> 一致。 -->
<script>
(function () {
var b = window.KOLE_SITE_BASE;
['data.js', 'logger.js', 'i18n.js', 'app.js'].forEach(function (f) {
document.write('<script src="' + b + f + '"><\/script>');
});
})();
</script>
</body>
</html>