被问到「这么多组件能保证不出错吗」——诚实回答是不能。改为四轮机器审计 + 实测,把「我检查过了」替换成可复现的数字,并修掉查出的一切真问题。 审计 1 · 契约保真度(358 条 usageHints 回规格原文核对) - 逐字命中 336 条(93.9%);剩余 22 条经人工比对为同义改写(数值确在规格 别处),非发明 - 发现真实不一致:rate.json 选中星色仍写 #FAAD14,规格已在 v1.3.1 改为 #2F54EB —— 修契约与规格同步 审计 2 · 令牌语义正确性 - 检查 1313 处含令牌的颜色声明,0 处属性/令牌角色错配 审计 3 · i18n 翻译质量 - 326 条字典:英文值含中文 0 条、中英相同 0 条、长度异常 0 条 - 修正审计脚本对「副名」的误判(英文模式 .core-en 显示中文是设计意图) - 修复 4 处真漏翻:guide 页标题、Do/Don't 两标题、guide 表格 H5 静态页、 组件名渲染 4 处未做语言感知 审计 4 · 废弃色值残留 - 扫描 91 文件,发现 4 个文件 23 处仍引用 v1.3.1 前旧色值 - 最严重:css.json(For Agents 页宣称的结构化令牌源,机器读取方会拿到 过时颜色)已同步 10 处;README.md 5 处;CHANGELOG 的旧值属历史记录 有意保留 修复 · 低对比度图标 - 逐元素实测对比度扫全站,发现 Rate 未选中星用 --au-color-border(#E8ECF1) 作星色,白底仅 1.19:1 —— 边框色是装饰性色值,作图标不合格 - 新增 --au-color-icon-inactive #8F8F8F(白底 3.23:1,满足 WCAG 1.4.11) 令牌 74 → 75 - 同批扫出另 5 处经核实为禁用态(WCAG 1.4.3 豁免)或深色遮罩白字 (检测器把半透明底当白底),非缺陷 修复 · 回归偶发(第三次根治) - 现象:多次连跑偶发 99.9%,但单页打开总通过 - 根因:收集器 8 秒超时兜底直接记 fail:1;并发 4 个 iframe 抢主线程时 慢页面会撞超时 - 修法:阈值 8s→12s、超时后重试一次、超时与断言失败分开统计(timedOut 字段不计入通过率分母) - 验证:八次连跑全部 100% / 0 失败 / 79 页全通过 / 0 超时
2284 lines
100 KiB
JavaScript
2284 lines
100 KiB
JavaScript
/* Aurora Admin 组件库文档 — 交互逻辑(无依赖 vanilla JS)
|
||
布局与交互对齐 Element 文档:首页落地页 / 快速开始 / 分类侧栏 /
|
||
多演示块样式(居中箭头展开)/ 右侧锚点目录 / Ctrl+K 搜索 */
|
||
(function () {
|
||
'use strict';
|
||
|
||
var LIB_ROOT = '../.design_library/aurora-admin/';
|
||
var SPEC_ROOT = '../';
|
||
var TAB_ORDER = ['html', 'css', 'jsx', 'vue2', 'vue3'];
|
||
var TAB_LABELS = { html: 'H5 HTML', css: 'CSS', jsx: 'React JSX', vue2: 'Vue 2', vue3: 'Vue 3' };
|
||
/* 注意:这些标签必须在「渲染时」求值,不能在这里预先 T() ——
|
||
模块级常量只算一次,语言切换后不会更新。故改为函数。 */
|
||
var CAT_KEYS = ['general', 'navigation', 'input', 'display', 'feedback', 'system'];
|
||
var CAT_EN = { general: 'General', navigation: 'Navigation', input: 'Data Entry', display: 'Data Display', feedback: 'Feedback', system: 'System' };
|
||
var CAT_ORDER = CAT_KEYS;
|
||
/* KIND_KEYS 存中文源串,翻译在 kindLabel() 里运行时求值(语言可切换) */
|
||
var KIND_KEYS = { html: 'H5 静态页', css: '样式表', jsx: 'React 组件', vue2: 'Vue 2 组件', vue3: 'Vue 3 组件' };
|
||
/* 框架感知:每种技术栈需要的文件形态 */
|
||
var FRAMEWORKS = { h5: 'H5', react: 'React', vue2: 'Vue 2', vue3: 'Vue 3' };
|
||
|
||
/* ---------- 国际化 ----------
|
||
以中文源串为键(见 i18n.js):T() 在 en 模式下查表,未收录则回退原文,
|
||
因此新增文案即使漏翻也不会出现空白。
|
||
注意:T() 必须在任何使用点之前定义(模块级常量若预先求值会在切换语言后失效)。 */
|
||
var I18N = window.AA_I18N || { T: function (s) { return s; }, getLang: function () { return 'zh'; }, setLang: function () {} };
|
||
function T(s) { return I18N.T(s); }
|
||
function isEn() { return I18N.getLang() === 'en'; }
|
||
function kindLabel(k) { return T(KIND_KEYS[k] || k); }
|
||
/* 分类名:中文取字典、英文取 CAT_EN(分类是固定六项,不必进 i18n 字典) */
|
||
function catLabel(key) {
|
||
if (!key) return T('未分类');
|
||
if (isEn()) return CAT_EN[key] || key;
|
||
var zh = { general: T('通用'), navigation: T('导航'), input: T('数据录入'), display: T('数据展示'), feedback: T('反馈'), system: T('工具与系统') };
|
||
return zh[key] || key;
|
||
}
|
||
function catDisplay(key) { return isEn() ? catEn(key) : catLabel(key); }
|
||
/* 组件名:中英并存,按语言决定主次(splitName 拆出的 zh/en) */
|
||
function namePrimary(n) { return isEn() ? (n.en || n.zh) : n.zh; }
|
||
var FW_FILES = {
|
||
h5: ['html', 'css'],
|
||
react: ['jsx', 'css'],
|
||
vue2: ['vue2'],
|
||
vue3: ['vue3']
|
||
};
|
||
|
||
var data = window.AA_DATA;
|
||
var $ = function (id) { return document.getElementById(id); };
|
||
|
||
function fatal(msg) {
|
||
var el = $('fatal');
|
||
el.textContent = msg;
|
||
el.classList.remove('hidden');
|
||
}
|
||
if (!data || !data.components || !data.components.length) {
|
||
fatal(T('数据加载失败:site/data.js 缺失或为空。请先在仓库根目录运行 build-site.ps1 重新生成。'));
|
||
return;
|
||
}
|
||
|
||
var comps = data.components;
|
||
var bySlugMap = {};
|
||
comps.forEach(function (c) { bySlugMap[c.slug] = c; });
|
||
|
||
var state = { route: { page: 'home', slug: null }, query: '', ovCat: '', ovQ: '', framework: 'h5', searchCat: '' };
|
||
var thumbObserver = null;
|
||
|
||
function loadFramework() {
|
||
try {
|
||
var saved = localStorage.getItem('aa-framework');
|
||
if (saved && FRAMEWORKS[saved]) return saved;
|
||
} catch (e) { /* ignore */ }
|
||
return 'h5';
|
||
}
|
||
|
||
/* ---------- 工具 ---------- */
|
||
function el(tag, cls, html) {
|
||
var e = document.createElement(tag);
|
||
if (cls) e.className = cls;
|
||
if (html != null) e.innerHTML = html;
|
||
return e;
|
||
}
|
||
function esc(s) {
|
||
return String(s == null ? '' : s)
|
||
.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||
}
|
||
function splitName(name) {
|
||
var m = name.match(/^(.+?)\s+([A-Za-z0-9].*)$/);
|
||
return m ? { zh: m[1], en: m[2] } : { zh: name, en: '' };
|
||
}
|
||
function batchLabel(b) {
|
||
if (b === 1) return T('核心规范');
|
||
return isEn() ? ('Batch ' + b) : (T('第') + b + T('批'));
|
||
}
|
||
function catEn(key) { return CAT_EN[key] || ''; }
|
||
function fmtSize(bytes) {
|
||
if (bytes == null) return '—';
|
||
if (bytes < 1024) return bytes + ' B';
|
||
return (bytes / 1024).toFixed(1) + ' KB';
|
||
}
|
||
function specLink(file) { return SPEC_ROOT + file; }
|
||
function cleanSummary(s) { return (s || '').replace(/^[-•·]\s*/, '').trim(); }
|
||
|
||
/* ---------- 路由 ---------- */
|
||
function parseHash() {
|
||
var h = location.hash.replace(/^#/, '');
|
||
if (!h || h === '/') return { page: 'home', slug: null };
|
||
if (h === '/overview') return { page: 'overview', slug: null };
|
||
if (h === '/guide') return { page: 'guide', slug: null };
|
||
if (h === '/design') return { page: 'design', slug: null };
|
||
if (h === '/agents') return { page: 'agents', slug: null };
|
||
if (h === '/changelog') return { page: 'changelog', slug: null };
|
||
var m = h.match(/^\/component\/([a-z0-9-]+)/i);
|
||
if (m && bySlugMap[m[1]]) return { page: 'component', slug: m[1] };
|
||
return { page: 'home', slug: null };
|
||
}
|
||
|
||
function navigate(opts) {
|
||
var fwOnly = opts && opts.fwOnly;
|
||
state.route = parseHash();
|
||
closeSearch();
|
||
renderSidebar();
|
||
renderTopnav();
|
||
renderToc([]);
|
||
var content = $('content');
|
||
if (state.route.page === 'home') renderHome(content);
|
||
else if (state.route.page === 'overview') renderOverview(content);
|
||
else if (state.route.page === 'guide') renderGuide(content);
|
||
else if (state.route.page === 'design') renderDesign(content);
|
||
else if (state.route.page === 'agents') renderAgents(content);
|
||
else if (state.route.page === 'changelog') renderChangelog(content);
|
||
else renderComponent(content, state.route.slug);
|
||
/* 仅切换框架时不滚到顶部,避免用户感觉"页面没切换" */
|
||
if (!fwOnly) window.scrollTo(0, 0);
|
||
if (window.aaLogger) {
|
||
window.aaLogger.render(state.route.page || 'unknown', { slug: state.route.slug || null, framework: state.framework });
|
||
}
|
||
}
|
||
|
||
function renderTopnav() {
|
||
var r = state.route.page;
|
||
document.querySelectorAll('.topnav a').forEach(function (a) {
|
||
var nav = a.getAttribute('data-nav');
|
||
var active = (nav === r) ||
|
||
(nav === 'overview' && r === 'component');
|
||
a.classList.toggle('active', active);
|
||
});
|
||
}
|
||
|
||
/* ---------- 侧边栏 ---------- */
|
||
function renderSidebar() {
|
||
var sb = $('sidebar');
|
||
sb.innerHTML = '';
|
||
|
||
var r = state.route.page;
|
||
var links = [
|
||
{ label: T('组件总览'), hash: '#/overview', active: r === 'overview' || r === 'component' },
|
||
{ label: T('快速开始'), hash: '#/guide', active: r === 'guide' },
|
||
{ label: T('设计规范'), hash: '#/design', active: r === 'design' }
|
||
];
|
||
links.forEach(function (L) {
|
||
var a = el('a', 'side-link' + (L.active ? ' active' : ''), esc(L.label));
|
||
a.href = L.hash;
|
||
sb.appendChild(a);
|
||
});
|
||
|
||
CAT_ORDER.forEach(function (cat) {
|
||
var items = comps.filter(function (c) { return c.category === cat; });
|
||
if (!items.length) return;
|
||
var g = el('div', 'side-group');
|
||
g.innerHTML = '<span><i class="cat-zh"></i><i class="cat-en"></i></span><span class="cnt">' + items.length + '</span>';
|
||
g.querySelector('.cat-zh').textContent = catLabel(cat);
|
||
g.querySelector('.cat-en').textContent = catEn(cat);
|
||
sb.appendChild(g);
|
||
items.forEach(function (c) {
|
||
var n = splitName(c.name);
|
||
var b = el('button', 'side-item');
|
||
b.type = 'button';
|
||
b.setAttribute('data-slug', c.slug);
|
||
b.innerHTML = '<span class="zh"></span><span class="en"></span>';
|
||
/* 主次随语言调换:中文模式主显中文名,英文模式主显英文名 */
|
||
b.querySelector('.zh').textContent = namePrimary(n);
|
||
b.querySelector('.en').textContent = isEn() ? n.zh : n.en;
|
||
if (r === 'component' && state.route.slug === c.slug) b.classList.add('active');
|
||
b.addEventListener('click', function () { location.hash = '#/component/' + c.slug; });
|
||
sb.appendChild(b);
|
||
});
|
||
});
|
||
}
|
||
|
||
/* ---------- 右侧目录 ---------- */
|
||
function renderToc(items) {
|
||
var toc = $('toc');
|
||
toc.innerHTML = '';
|
||
if (!items.length) { toc.style.display = 'none'; return; }
|
||
toc.style.display = '';
|
||
toc.appendChild(el('div', 'toc-title', T('本页目录')));
|
||
items.forEach(function (it) {
|
||
var a = el('a', null, esc(it.label));
|
||
a.setAttribute('data-anchor', it.id);
|
||
a.addEventListener('click', function (e) {
|
||
e.preventDefault();
|
||
var target = document.getElementById(it.id);
|
||
if (target) target.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||
});
|
||
toc.appendChild(a);
|
||
});
|
||
setupScrollSpy(items);
|
||
}
|
||
|
||
var spyHandler = null;
|
||
function setupScrollSpy(items) {
|
||
if (spyHandler) window.removeEventListener('scroll', spyHandler);
|
||
spyHandler = function () {
|
||
var pos = window.scrollY + 120;
|
||
var current = items[0] && items[0].id;
|
||
items.forEach(function (it) {
|
||
var node = document.getElementById(it.id);
|
||
if (node && node.offsetTop <= pos) current = it.id;
|
||
});
|
||
document.querySelectorAll('.toc a').forEach(function (a) {
|
||
a.classList.toggle('active', a.getAttribute('data-anchor') === current);
|
||
});
|
||
};
|
||
window.addEventListener('scroll', spyHandler, { passive: true });
|
||
spyHandler();
|
||
}
|
||
|
||
/* ---------- 首页(落地页) ---------- */
|
||
function renderHome(content) {
|
||
content.innerHTML = '';
|
||
var inner = el('div', 'page-inner home');
|
||
|
||
/* Hero stats 第三项加入"测试" */
|
||
var hero = el('div', 'hero');
|
||
hero.innerHTML =
|
||
'<div class="hero-kicker">AURORA ADMIN DESIGN SYSTEM</div>' +
|
||
'<h1 class="hero-title">' + T('Aurora Admin 组件库') + '</h1>' +
|
||
'<p class="hero-sub">' + T('理性、高效、克制的 B 端中后台设计系统——信息密度适中,层级清晰,交互反馈即时。') + '</p>' +
|
||
'<div class="hero-actions">' +
|
||
' <a class="btn-primary-lg" href="#/overview">' + T('浏览组件 →') + '</a>' +
|
||
' <a class="btn-ghost-lg" href="scenario/user-management.html" target="_blank">' + T('场景示范') + '</a>' +
|
||
' <a class="btn-ghost-lg" href="#/guide">' + T('快速开始') + '</a>' +
|
||
' <a class="btn-ghost-lg" href="../tests/index.html" target="_blank">' + T('测试总览') + '</a>' +
|
||
' <a class="btn-ghost-lg" href="#/design">' + T('设计规范') + '</a>' +
|
||
'</div>' +
|
||
'<div class="hero-stats">' +
|
||
' <div class="stat"><strong>' + data.meta.total + '</strong><span>' + T('组件') + '</span></div>' +
|
||
' <div class="stat"><strong>' + CAT_ORDER.length + '</strong><span>' + T('大类') + '</span></div>' +
|
||
' <div class="stat"><strong>' + data.tokens.length + '</strong><span>' + T('设计令牌') + '</span></div>' +
|
||
' <div class="stat"><strong>5</strong><span>' + T('端实现') + '</span></div>' +
|
||
' <div class="stat"><strong>10</strong><span>' + T('份规范') + '</span></div>' +
|
||
' <div class="stat"><strong>79</strong><span>' + T('测试页') + '</span><em class="stat-sub" id="stat-passrate"></em></div>' +
|
||
'</div>';
|
||
inner.appendChild(hero);
|
||
/* 测试通过率:来自 tests/report.json(headless 回归产出,缺失时静默不显示) */
|
||
fetch('../tests/report.json', { cache: 'no-cache' }).then(function (r) { return r.ok ? r.json() : null; }).then(function (rep) {
|
||
var node = document.getElementById('stat-passrate');
|
||
if (rep && rep.passRate != null && node) {
|
||
node.textContent = T('通过率 ') + rep.passRate + '%';
|
||
node.title = rep.generated ? T('headless 回归 ') + rep.generated + ':' + rep.assertions.pass + '/' + rep.assertions.total + ' 断言通过' : '';
|
||
}
|
||
}).catch(function () { /* 报告不存在时不显示 */ });
|
||
|
||
/* 设计原则四卡 */
|
||
inner.appendChild(el('h2', 'home-h2', T('设计原则')));
|
||
var feats = [
|
||
[T('一致优先'), T('同类组件使用相同的尺寸、颜色与间距,跨页面保持一致体验。'), 'M4 12l5 5L20 7'],
|
||
[T('清晰高效'), T('信息层级明确,操作路径最短,避免过度装饰。'), 'M13 2L3 14h9l-1 8 10-12h-9l1-8z'],
|
||
[T('反馈及时'), T('所有操作都有加载、成功、失败三类状态反馈。'), 'M22 11.08V12a10 10 0 1 1-5.93-9.14'],
|
||
[T('无障碍'), T('满足键盘操作与焦点管理,正文对比度 ≥ 4.5:1。'), 'M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z']
|
||
];
|
||
var fgrid = el('div', 'feat-grid');
|
||
feats.forEach(function (f) {
|
||
var card = el('div', 'feat-card');
|
||
card.innerHTML =
|
||
'<div class="feat-icon"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="' + f[2] + '"/></svg></div>' +
|
||
'<div class="feat-body"><h3></h3><p></p></div>';
|
||
card.querySelector('h3').textContent = f[0];
|
||
card.querySelector('p').textContent = f[1];
|
||
fgrid.appendChild(card);
|
||
});
|
||
inner.appendChild(fgrid);
|
||
|
||
/* 核心组件快捷入口 */
|
||
inner.appendChild(el('h2', 'home-h2', T('核心组件')));
|
||
var core = el('div', 'core-grid');
|
||
comps.filter(function (c) { return c.contract; }).forEach(function (c) {
|
||
var n = splitName(c.name);
|
||
var a = el('a', 'core-card');
|
||
a.href = '#/component/' + c.slug;
|
||
a.innerHTML = '<div class="core-zh"></div><div class="core-en"></div>';
|
||
a.querySelector('.core-zh').textContent = namePrimary(n);
|
||
a.querySelector('.core-en').textContent = isEn() ? n.zh : n.en;
|
||
core.appendChild(a);
|
||
});
|
||
inner.appendChild(core);
|
||
|
||
/* 批次入口 */
|
||
inner.appendChild(el('h2', 'home-h2', T('规格批次')));
|
||
var batchWrap = el('div', 'batch-wrap');
|
||
[1, 2, 4, 5, 6, 7, 9].forEach(function (b) {
|
||
var items = comps.filter(function (c) { return c.specBatch === b; });
|
||
if (!items.length) return;
|
||
var a = el('a', 'batch-chip');
|
||
a.href = specLink('组件' + b + '.txt');
|
||
a.target = '_blank';
|
||
a.textContent = batchLabel(b) + ' · ' + items.length + ' 组件';
|
||
batchWrap.appendChild(a);
|
||
});
|
||
inner.appendChild(batchWrap);
|
||
|
||
content.appendChild(inner);
|
||
}
|
||
|
||
/* ---------- 快速开始 ---------- */
|
||
function renderGuide(content) {
|
||
content.innerHTML = '';
|
||
var inner = el('div', 'page-inner');
|
||
inner.appendChild(el('h1', 'page-title', T('快速开始')));
|
||
inner.appendChild(el('p', 'page-desc', T('了解如何在本项目中使用 Aurora Admin 组件:引入设计令牌、按端取用组件源码、以及目录与命名约定。')));
|
||
|
||
var tocItems = [];
|
||
|
||
/* 1. 引入设计令牌 */
|
||
var s1 = el('div', 'sec'); s1.id = 'install';
|
||
s1.appendChild(el('h2', null, T('引入设计令牌')));
|
||
s1.appendChild(el('p', 'guide-p', T('所有组件都基于同一份 CSS 变量令牌。在使用任何组件前,先在页面中引入令牌文件:')));
|
||
var code1 = el('pre', 'code-inline');
|
||
code1.appendChild(el('code', null, esc('<link rel="stylesheet" href=".design_library/aurora-admin/colors_and_type.css">')));
|
||
s1.appendChild(code1);
|
||
s1.appendChild(el('p', 'guide-p', T('如需结构化的令牌数据(供工具消费),可读取同目录的 <code>css.json</code>;聚合组件样式见 <code>components.css</code>。')));
|
||
inner.appendChild(s1);
|
||
tocItems.push({ id: 'install', label: '引入设计令牌' });
|
||
|
||
/* 2. 按端使用组件 */
|
||
var s2 = el('div', 'sec'); s2.id = 'usage';
|
||
s2.appendChild(el('h2', null, T('按端使用组件')));
|
||
s2.appendChild(el('p', 'guide-p', T('每个组件在 <code>frameworks/</code> 下有五种形态(H5 / CSS / React / Vue 2 / Vue 3),按你的技术栈取用:')));
|
||
var tbl = el('table', 'res-table');
|
||
var thead = el('thead');
|
||
var trh = el('tr');
|
||
[T('端'), T('文件'), T('使用方式')].forEach(function (t) { trh.appendChild(el('th', null, t)); });
|
||
thead.appendChild(trh); tbl.appendChild(thead);
|
||
var tbody = el('tbody');
|
||
[
|
||
[kindLabel('html'), 'Button.html', T('直接在浏览器打开即为完整演示页;将结构与内联样式拷贝进你的页面')],
|
||
['React', 'Button.jsx + Button.css', T('将 JSX 与 CSS 拷入工程,按需改造数据绑定')],
|
||
['Vue 2', 'Button.vue2.vue', T('Options API 单文件组件,直接注册使用')],
|
||
['Vue 3', 'Button.vue3.vue', T('<script setup> 组合式 API,直接注册使用')],
|
||
[T('样式令牌'), 'colors_and_type.css', T('所有变量以 --au- 前缀暴露,可整体引入或按需拷贝')]
|
||
].forEach(function (row) {
|
||
var tr = el('tr');
|
||
tr.appendChild(el('td', null, row[0]));
|
||
tr.appendChild(el('td', 'num', esc(row[1])));
|
||
tr.appendChild(el('td', null, esc(row[2])));
|
||
tbody.appendChild(tr);
|
||
});
|
||
tbl.appendChild(tbody);
|
||
s2.appendChild(tbl);
|
||
inner.appendChild(s2);
|
||
tocItems.push({ id: 'usage', label: '按端使用组件' });
|
||
|
||
/* 3. 目录结构 */
|
||
var s3 = el('div', 'sec'); s3.id = 'structure';
|
||
s3.appendChild(el('h2', null, T('目录结构')));
|
||
var code3 = el('pre', 'code-inline');
|
||
code3.appendChild(el('code', null, esc(
|
||
'组件规范第一套/\n' +
|
||
'├─ 组件1~10.txt # 设计规范文档(核心 + 各批次组件规范)\n' +
|
||
'├─ frameworks/ # 79 个组件 × 5 端实现\n' +
|
||
'└─ .design_library/aurora-admin/\n' +
|
||
' ├─ colors_and_type.css # 设计令牌(--au-* CSS 变量)\n' +
|
||
' ├─ css.json # 结构化令牌数据\n' +
|
||
' ├─ components.css # 聚合组件样式\n' +
|
||
' ├─ components/ # 核心组件契约 JSON + 全量索引\n' +
|
||
' └─ preview/ # 核心组件预览页'
|
||
)));
|
||
s3.appendChild(code3);
|
||
inner.appendChild(s3);
|
||
tocItems.push({ id: 'structure', label: '目录结构' });
|
||
|
||
/* 4. 命名约定 */
|
||
var s4 = el('div', 'sec'); s4.id = 'naming';
|
||
s4.appendChild(el('h2', null, T('命名约定')));
|
||
var ul = el('ul', 'guide-ul');
|
||
[
|
||
T('组件 slug 为小写(如 button、sidemenu),文件名前缀为 PascalCase(如 SideMenu.vue3.vue)'),
|
||
T('H5 演示页自带演示数据与交互,可直接作为视觉验收基准'),
|
||
T('组件 ↔ 规格批次 ↔ 实现的权威映射见 .design_library/aurora-admin/components/index.json'),
|
||
T('全部组件默认桌面端优先(最小宽度 1280px),不推荐移动端场景')
|
||
].forEach(function (t) { ul.appendChild(el('li', null, esc(t))); });
|
||
s4.appendChild(ul);
|
||
inner.appendChild(s4);
|
||
tocItems.push({ id: 'naming', label: '命名约定' });
|
||
|
||
content.appendChild(inner);
|
||
renderToc(tocItems);
|
||
}
|
||
|
||
/* ---------- For Agents(AI 消费入口) ---------- */
|
||
function renderAgents(content) {
|
||
content.innerHTML = '';
|
||
var inner = el('div', 'page-inner');
|
||
inner.appendChild(el('h1', 'page-title', T('For Agents · AI 消费入口')));
|
||
inner.appendChild(el('p', 'page-desc', T('本站全部数据机器可读:无需解析 HTML,一次抓取 data.json 即可获得 79 个组件、设计令牌与分类元数据。本页汇总 Agent 可直接消费的资源、数据形状与使用规则。')));
|
||
|
||
var tocItems = [];
|
||
|
||
/* 1. 机器可读资源 */
|
||
var s1 = el('div', 'sec'); s1.id = 'agents-resources';
|
||
s1.appendChild(el('h2', null, T('机器可读资源')));
|
||
var t1 = el('table', 'res-table');
|
||
t1.innerHTML = T('<thead><tr><th style="width:26%">资源</th><th style="width:38%">路径</th><th>说明</th></tr></thead><tbody></tbody>');
|
||
var tb1 = t1.querySelector('tbody');
|
||
[
|
||
[T('全量组件数据(首选)'), 'site/data.json', T('meta + 分类 + 设计令牌 + 79 个组件(规格摘要、5 端文件路径、API 提炼),单次请求即可')],
|
||
[T('组件注册表'), '.design_library/aurora-admin/components/index.json', T('组件 ↔ 规格批次 ↔ 实现文件的权威映射(schemaVersion 3)')],
|
||
[T('核心组件契约 ×6'), '.design_library/aurora-admin/components/{button,input,select,table,card,modal}.json', T('变体维度、代表变体、使用要点、结构解剖、不发明与未明示清单')],
|
||
[T('设计令牌(结构化)'), '.design_library/aurora-admin/css.json', T('全部 CSS 变量的 JSON 形态:颜色 / 字体 / 间距 / 圆角 / 阴影 / 控件高度')],
|
||
[T('设计令牌(CSS)'), '.design_library/aurora-admin/colors_and_type.css', T('可直接 <link> 引入的令牌源文件,所有色值必须取自 au-* 变量')],
|
||
[T('组件样式聚合'), '.design_library/aurora-admin/components.css', T('全部组件样式的单文件聚合')],
|
||
[T('组件实现源码 ×5 端'), 'frameworks/<Prefix>.{html,css,jsx,vue2.vue,vue3.vue}', T('H5 / React / Vue 2 / Vue 3 形态,文件名前缀为 PascalCase')],
|
||
[T('组件测试页 ×79'), 'tests/<slug>.html(总览 tests/index.html)', T('断言结果标注 data-assert / data-status="pass|fail",可机读回归')],
|
||
[T('LLM 入口清单'), 'site/llms.txt', T('本页内容的 llms.txt 形态,供爬虫与 Agent 定位')]
|
||
].forEach(function (r) {
|
||
var tr = document.createElement('tr');
|
||
tr.innerHTML = '<td></td><td><code></code></td><td></td>';
|
||
tr.cells[0].textContent = r[0];
|
||
tr.cells[1].textContent = r[1];
|
||
tr.cells[2].textContent = r[2];
|
||
tb1.appendChild(tr);
|
||
});
|
||
s1.appendChild(t1);
|
||
inner.appendChild(s1);
|
||
tocItems.push({ id: 'agents-resources', label: '机器可读资源' });
|
||
|
||
/* 2. data.json 数据形状 */
|
||
var s2 = el('div', 'sec'); s2.id = 'agents-schema';
|
||
s2.appendChild(el('h2', null, T('data.json 数据形状')));
|
||
var t2 = el('table', 'res-table');
|
||
t2.innerHTML = T('<thead><tr><th style="width:30%">字段</th><th>内容</th></tr></thead><tbody></tbody>');
|
||
var tb2 = t2.querySelector('tbody');
|
||
[
|
||
['meta', T('version(与 CHANGELOG 最新版本同步)、total=79、core/extension 数量、brand=#2F54EB、generated 构建时间')],
|
||
['categories', T('六大分类及组件计数:general / navigation / input / display / feedback / system')],
|
||
['tokens', T('设计令牌数组:name(au-*)、value、comment(中文用途说明)')],
|
||
['components[].slug / name / tier', T('小写 slug、中文名 + 英文名、core 或 extension')],
|
||
['components[].specBatch / specFile / specLines', T('规格批次 1-10、来源规格文件、规格章节原文(含"何时使用"等)')],
|
||
['components[].contract', T('核心组件契约:dims(变体维度)、variants、usage(使用要点)、structure、anatomy、doNot(不发明)、unknowns(未明示)')],
|
||
['components[].files / sizes', T('5 端实现文件相对路径与字节数:html / css / jsx / vue2 / vue3')]
|
||
].forEach(function (r) {
|
||
var tr = document.createElement('tr');
|
||
tr.innerHTML = '<td><code></code></td><td></td>';
|
||
tr.cells[0].textContent = r[0];
|
||
tr.cells[1].textContent = r[1];
|
||
tb2.appendChild(tr);
|
||
});
|
||
s2.appendChild(t2);
|
||
inner.appendChild(s2);
|
||
tocItems.push({ id: 'agents-schema', label: 'data.json 数据形状' });
|
||
|
||
/* 3. 消费示例 */
|
||
var s3 = el('div', 'sec'); s3.id = 'agents-example';
|
||
s3.appendChild(el('h2', null, T('消费示例')));
|
||
s3.appendChild(el('p', 'guide-p', T('抓取 data.json 并按分类筛选组件、读取令牌与契约的最小示例:')));
|
||
var code3 = el('pre', 'code-inline');
|
||
code3.appendChild(el('code', null, esc(
|
||
"const data = await fetch('site/data.json').then(r => r.json());\n" +
|
||
"const inputs = data.components.filter(c => c.category === 'input');\n" +
|
||
"const brand = data.tokens.find(t => t.name === 'au-color-brand');\n" +
|
||
"const btnUsage = data.components.find(c => c.slug === 'button').contract.usage;\n" +
|
||
"// 生成 UI 时:色值只取 tokens 中的 au-* 值;变体不超出 contract.dims 声明的维度"
|
||
)));
|
||
s3.appendChild(code3);
|
||
inner.appendChild(s3);
|
||
tocItems.push({ id: 'agents-example', label: '消费示例' });
|
||
|
||
/* 4. 使用规则 */
|
||
var s4 = el('div', 'sec'); s4.id = 'agents-rules';
|
||
s4.appendChild(el('h2', null, T('使用规则')));
|
||
var ul4 = el('ul', 'guide-ul');
|
||
[
|
||
T('不发明:组件变体不得超出契约 dims 声明的维度与取值;扩展组件以规格原文 specLines 为准'),
|
||
T('不硬编码颜色:所有色值取自 tokens 中的 au-* 变量,品牌主色 #2F54EB,语义色独立于品牌色'),
|
||
T('尊重未明示:契约 unknowns 列出的项规格未定义,生成前应向用户澄清而非假设'),
|
||
T('文案遵循内容规范:按钮用动词短语(新建 / 编辑 / 删除),反馈文案明确可操作,不使用 emoji'),
|
||
T('桌面端优先:整套体系最小宽度 1280px,不适用于移动端场景')
|
||
].forEach(function (t) { ul4.appendChild(el('li', null, esc(t))); });
|
||
s4.appendChild(ul4);
|
||
inner.appendChild(s4);
|
||
tocItems.push({ id: 'agents-rules', label: '使用规则' });
|
||
|
||
content.appendChild(inner);
|
||
renderToc(tocItems);
|
||
}
|
||
|
||
/* ---------- 更新日志 ---------- */
|
||
function renderChangelog(content) {
|
||
content.innerHTML = '';
|
||
var inner = el('div', 'page-inner');
|
||
inner.appendChild(el('h1', 'page-title', T('更新日志')));
|
||
inner.appendChild(el('p', 'page-desc', T('按 SemVer 记录的重要变更,与仓库根目录 CHANGELOG.md 同源(构建时自动解析注入)。')));
|
||
|
||
var tocItems = [];
|
||
var entries = data.changelog || [];
|
||
if (!entries.length) {
|
||
var empty = el('div', 'sec'); empty.id = 'cl-empty';
|
||
empty.appendChild(el('p', 'guide-p', T('暂无结构化日志数据:请运行 build-site.ps1 重新生成,构建脚本会解析 CHANGELOG.md 并注入 data.js。')));
|
||
inner.appendChild(empty);
|
||
tocItems.push({ id: 'cl-empty', label: T('暂无数据') });
|
||
}
|
||
entries.forEach(function (e) {
|
||
var sec = el('div', 'sec'); sec.id = 'v-' + String(e.version).replace(/\./g, '-');
|
||
var head = el('div', 'cl-head');
|
||
head.appendChild(el('span', 'chip blue', 'v' + e.version));
|
||
head.appendChild(el('span', 'cl-date', e.date || ''));
|
||
sec.appendChild(head);
|
||
(e.sections || []).forEach(function (s) {
|
||
sec.appendChild(el('h3', 'cl-sec-title', esc(s.title)));
|
||
var ul = el('ul', 'guide-ul');
|
||
(s.items || []).forEach(function (it) { ul.appendChild(el('li', null, esc(it))); });
|
||
sec.appendChild(ul);
|
||
});
|
||
inner.appendChild(sec);
|
||
tocItems.push({ id: sec.id, label: 'v' + e.version });
|
||
});
|
||
|
||
content.appendChild(inner);
|
||
renderToc(tocItems);
|
||
}
|
||
|
||
/* ---------- 组件总览 ---------- */
|
||
function renderOverview(content) {
|
||
if (thumbObserver) { thumbObserver.disconnect(); thumbObserver = null; }
|
||
content.innerHTML = '';
|
||
var inner = el('div', 'page-inner');
|
||
inner.appendChild(el('h1', 'page-title', T('组件总览')));
|
||
inner.appendChild(el('p', 'page-desc',
|
||
T('共 <strong>') + data.meta.total + T('</strong> 个组件(核心 <strong>') + data.meta.core + T('</strong> · 扩展 <strong>') + data.meta.extension +
|
||
T('</strong>),覆盖 ') + CAT_ORDER.length + T(' 大类。基于 Aurora Admin 设计系统规范构建,提供 H5 / React / Vue 2 / Vue 3 四端实现。')));
|
||
|
||
var grid = el('div', 'ov-grid');
|
||
grid.id = 'ov-grid';
|
||
inner.appendChild(grid);
|
||
content.appendChild(inner);
|
||
|
||
/* 过滤工具条(分类 chips + 关键词) */
|
||
renderOverviewFilter(inner, grid);
|
||
|
||
rebuildOverviewGrid(grid);
|
||
|
||
thumbObserver = new IntersectionObserver(function (entries) {
|
||
entries.forEach(function (en) {
|
||
if (!en.isIntersecting) return;
|
||
var card = en.target;
|
||
thumbObserver.unobserve(card);
|
||
var slug = card.getAttribute('data-slug');
|
||
var c = bySlugMap[slug];
|
||
var thumb = card.querySelector('.ov-thumb');
|
||
thumb.innerHTML = '';
|
||
var ifr = document.createElement('iframe');
|
||
ifr.setAttribute('loading', 'lazy');
|
||
ifr.setAttribute('tabindex', '-1');
|
||
ifr.setAttribute('title', slug + ' 预览');
|
||
ifr.src = c.files.html;
|
||
hookIframeTheme(ifr);
|
||
thumb.appendChild(ifr);
|
||
});
|
||
}, { rootMargin: '400px' });
|
||
grid.querySelectorAll('.ov-card').forEach(function (card) { thumbObserver.observe(card); });
|
||
}
|
||
|
||
function rebuildOverviewGrid(grid) {
|
||
var q = state.ovQ.trim().toLowerCase();
|
||
grid.innerHTML = '';
|
||
comps.filter(function (c) {
|
||
if (state.ovCat && c.category !== state.ovCat) return false;
|
||
if (!q) return true;
|
||
var n = splitName(c.name);
|
||
return c.slug.toLowerCase().indexOf(q) >= 0 ||
|
||
n.zh.toLowerCase().indexOf(q) >= 0 ||
|
||
n.en.toLowerCase().indexOf(q) >= 0;
|
||
}).forEach(function (c) {
|
||
var n = splitName(c.name);
|
||
var card = el('div', 'ov-card');
|
||
card.setAttribute('data-slug', c.slug);
|
||
var thumb = el('div', 'ov-thumb');
|
||
thumb.appendChild(el('div', 'ph', T('加载预览…')));
|
||
card.appendChild(thumb);
|
||
var info = el('div', 'ov-info');
|
||
info.innerHTML = '<span class="zh"></span><span class="en"></span>';
|
||
info.querySelector('.zh').textContent = namePrimary(n);
|
||
info.querySelector('.en').textContent = isEn() ? n.zh : n.en;
|
||
card.appendChild(info);
|
||
card.addEventListener('click', function () { location.hash = '#/component/' + c.slug; });
|
||
grid.appendChild(card);
|
||
if (thumbObserver) thumbObserver.observe(card);
|
||
});
|
||
if (!grid.children.length) grid.appendChild(el('div', 'sr-empty', T('无匹配组件')));
|
||
}
|
||
|
||
function renderOverviewFilter(inner, grid) {
|
||
var bar = el('div', 'ov-filter');
|
||
var chips = el('div', 'filter-chips');
|
||
function chip(label, val, active) {
|
||
var b = el('button', 'filter-chip' + (active ? ' active' : ''), esc(label));
|
||
b.type = 'button';
|
||
b.addEventListener('click', function () {
|
||
state.ovCat = val;
|
||
chips.querySelectorAll('.filter-chip').forEach(function (x) { x.classList.remove('active'); });
|
||
b.classList.add('active');
|
||
rebuildOverviewGrid(grid);
|
||
});
|
||
return b;
|
||
}
|
||
chips.appendChild(chip(T('全部 (') + comps.length + ')', '', state.ovCat === ''));
|
||
CAT_ORDER.forEach(function (cat) {
|
||
var n = comps.filter(function (c) { return c.category === cat; }).length;
|
||
chips.appendChild(chip(catLabel(cat) + ' (' + n + ')', cat, state.ovCat === cat));
|
||
});
|
||
bar.appendChild(chips);
|
||
var inp = el('input', 'filter-input');
|
||
inp.type = 'search';
|
||
inp.placeholder = T('过滤组件…');
|
||
inp.value = state.ovQ;
|
||
inp.addEventListener('input', function (e) {
|
||
state.ovQ = e.target.value;
|
||
rebuildOverviewGrid(grid);
|
||
});
|
||
bar.appendChild(inp);
|
||
/* 过滤条插在描述之后、网格之前 */
|
||
var desc = inner.querySelector('.page-desc');
|
||
if (desc && desc.nextSibling) inner.insertBefore(bar, desc.nextSibling);
|
||
else inner.appendChild(bar);
|
||
}
|
||
|
||
/* ---------- 设计规范页 ---------- */
|
||
function renderChangelog(content) {
|
||
content.innerHTML = '';
|
||
var inner = el('div', 'page-inner');
|
||
inner.appendChild(el('h1', 'page-title', T('更新日志')));
|
||
inner.appendChild(el('p', 'page-desc', T('按 SemVer 记录的重要变更,与仓库根目录 CHANGELOG.md 同源(构建时自动解析注入)。')));
|
||
|
||
var tocItems = [];
|
||
var entries = data.changelog || [];
|
||
if (!entries.length) {
|
||
var empty = el('div', 'sec'); empty.id = 'cl-empty';
|
||
empty.appendChild(el('p', 'guide-p', T('暂无结构化日志数据:请运行 build-site.ps1 重新生成,构建脚本会解析 CHANGELOG.md 并注入 data.js。')));
|
||
inner.appendChild(empty);
|
||
tocItems.push({ id: 'cl-empty', label: T('暂无数据') });
|
||
}
|
||
entries.forEach(function (e) {
|
||
var sec = el('div', 'sec'); sec.id = 'v-' + String(e.version).replace(/\./g, '-');
|
||
var head = el('div', 'cl-head');
|
||
head.appendChild(el('span', 'chip blue', 'v' + e.version));
|
||
head.appendChild(el('span', 'cl-date', e.date || ''));
|
||
sec.appendChild(head);
|
||
(e.sections || []).forEach(function (s) {
|
||
sec.appendChild(el('h3', 'cl-sec-title', esc(s.title)));
|
||
var ul = el('ul', 'guide-ul');
|
||
(s.items || []).forEach(function (it) { ul.appendChild(el('li', null, esc(it))); });
|
||
sec.appendChild(ul);
|
||
});
|
||
inner.appendChild(sec);
|
||
tocItems.push({ id: sec.id, label: 'v' + e.version });
|
||
});
|
||
|
||
content.appendChild(inner);
|
||
renderToc(tocItems);
|
||
}
|
||
|
||
/* ---------- 组件总览 ---------- */
|
||
function renderOverview(content) {
|
||
if (thumbObserver) { thumbObserver.disconnect(); thumbObserver = null; }
|
||
content.innerHTML = '';
|
||
var inner = el('div', 'page-inner');
|
||
inner.appendChild(el('h1', 'page-title', T('组件总览')));
|
||
inner.appendChild(el('p', 'page-desc',
|
||
T('共 <strong>') + data.meta.total + T('</strong> 个组件(核心 <strong>') + data.meta.core + T('</strong> · 扩展 <strong>') + data.meta.extension +
|
||
T('</strong>),覆盖 ') + CAT_ORDER.length + T(' 大类。基于 Aurora Admin 设计系统规范构建,提供 H5 / React / Vue 2 / Vue 3 四端实现。')));
|
||
|
||
var grid = el('div', 'ov-grid');
|
||
grid.id = 'ov-grid';
|
||
inner.appendChild(grid);
|
||
content.appendChild(inner);
|
||
|
||
/* 过滤工具条(分类 chips + 关键词) */
|
||
renderOverviewFilter(inner, grid);
|
||
|
||
rebuildOverviewGrid(grid);
|
||
|
||
thumbObserver = new IntersectionObserver(function (entries) {
|
||
entries.forEach(function (en) {
|
||
if (!en.isIntersecting) return;
|
||
var card = en.target;
|
||
thumbObserver.unobserve(card);
|
||
var slug = card.getAttribute('data-slug');
|
||
var c = bySlugMap[slug];
|
||
var thumb = card.querySelector('.ov-thumb');
|
||
thumb.innerHTML = '';
|
||
var ifr = document.createElement('iframe');
|
||
ifr.setAttribute('loading', 'lazy');
|
||
ifr.setAttribute('tabindex', '-1');
|
||
ifr.setAttribute('title', slug + ' 预览');
|
||
ifr.src = c.files.html;
|
||
hookIframeTheme(ifr);
|
||
thumb.appendChild(ifr);
|
||
});
|
||
}, { rootMargin: '400px' });
|
||
grid.querySelectorAll('.ov-card').forEach(function (card) { thumbObserver.observe(card); });
|
||
}
|
||
|
||
function rebuildOverviewGrid(grid) {
|
||
var q = state.ovQ.trim().toLowerCase();
|
||
grid.innerHTML = '';
|
||
comps.filter(function (c) {
|
||
if (state.ovCat && c.category !== state.ovCat) return false;
|
||
if (!q) return true;
|
||
var n = splitName(c.name);
|
||
return c.slug.toLowerCase().indexOf(q) >= 0 ||
|
||
n.zh.toLowerCase().indexOf(q) >= 0 ||
|
||
n.en.toLowerCase().indexOf(q) >= 0;
|
||
}).forEach(function (c) {
|
||
var n = splitName(c.name);
|
||
var card = el('div', 'ov-card');
|
||
card.setAttribute('data-slug', c.slug);
|
||
var thumb = el('div', 'ov-thumb');
|
||
thumb.appendChild(el('div', 'ph', T('加载预览…')));
|
||
card.appendChild(thumb);
|
||
var info = el('div', 'ov-info');
|
||
info.innerHTML = '<span class="zh"></span><span class="en"></span>';
|
||
info.querySelector('.zh').textContent = namePrimary(n);
|
||
info.querySelector('.en').textContent = isEn() ? n.zh : n.en;
|
||
card.appendChild(info);
|
||
card.addEventListener('click', function () { location.hash = '#/component/' + c.slug; });
|
||
grid.appendChild(card);
|
||
if (thumbObserver) thumbObserver.observe(card);
|
||
});
|
||
if (!grid.children.length) grid.appendChild(el('div', 'sr-empty', T('无匹配组件')));
|
||
}
|
||
|
||
function renderOverviewFilter(inner, grid) {
|
||
var bar = el('div', 'ov-filter');
|
||
var chips = el('div', 'filter-chips');
|
||
function chip(label, val, active) {
|
||
var b = el('button', 'filter-chip' + (active ? ' active' : ''), esc(label));
|
||
b.type = 'button';
|
||
b.addEventListener('click', function () {
|
||
state.ovCat = val;
|
||
chips.querySelectorAll('.filter-chip').forEach(function (x) { x.classList.remove('active'); });
|
||
b.classList.add('active');
|
||
rebuildOverviewGrid(grid);
|
||
});
|
||
return b;
|
||
}
|
||
chips.appendChild(chip(T('全部 (') + comps.length + ')', '', state.ovCat === ''));
|
||
CAT_ORDER.forEach(function (cat) {
|
||
var n = comps.filter(function (c) { return c.category === cat; }).length;
|
||
chips.appendChild(chip(catLabel(cat) + ' (' + n + ')', cat, state.ovCat === cat));
|
||
});
|
||
bar.appendChild(chips);
|
||
var inp = el('input', 'filter-input');
|
||
inp.type = 'search';
|
||
inp.placeholder = T('过滤组件…');
|
||
inp.value = state.ovQ;
|
||
inp.addEventListener('input', function (e) {
|
||
state.ovQ = e.target.value;
|
||
rebuildOverviewGrid(grid);
|
||
});
|
||
bar.appendChild(inp);
|
||
/* 过滤条插在描述之后、网格之前 */
|
||
var desc = inner.querySelector('.page-desc');
|
||
if (desc && desc.nextSibling) inner.insertBefore(bar, desc.nextSibling);
|
||
else inner.appendChild(bar);
|
||
}
|
||
|
||
/* ---------- 设计规范页 ---------- */
|
||
function renderDesign(content) {
|
||
content.innerHTML = '';
|
||
var inner = el('div', 'page-inner');
|
||
inner.appendChild(el('h1', 'page-title', T('设计规范')));
|
||
inner.appendChild(el('p', 'page-desc', T('Aurora Admin 设计系统的全部设计令牌,共 <strong>') + data.tokens.length + T('</strong> 项。所有组件均基于这些令牌构建,来源 <code>colors_and_type.css</code>。')));
|
||
|
||
var TOKENS = data.tokens; /* 不叫 T:会遮蔽全局翻译函数 */
|
||
function tok(prefix) {
|
||
return TOKENS.filter(function (t) { return t.name.indexOf(prefix) === 0; });
|
||
}
|
||
function swatchGrid(list) {
|
||
var g = el('div', 'tok-grid');
|
||
list.forEach(function (t) {
|
||
var card = el('div', 'tok-card');
|
||
var sw = el('div', 'tok-swatch');
|
||
sw.style.background = t.value;
|
||
card.appendChild(sw);
|
||
var meta = el('div', 'tok-meta');
|
||
meta.innerHTML = '<div class="t-name">--' + esc(t.name) + '</div><div class="t-val">' + esc(t.value) + '</div>' +
|
||
(t.comment ? '<div class="t-cmt">' + esc(t.comment) + '</div>' : '');
|
||
card.appendChild(meta);
|
||
g.appendChild(card);
|
||
});
|
||
return g;
|
||
}
|
||
function group(title, node) {
|
||
var g = el('div', 'tok-group');
|
||
g.appendChild(el('h2', null, title));
|
||
g.appendChild(node);
|
||
inner.appendChild(g);
|
||
}
|
||
|
||
var colorDefs = [
|
||
[T('品牌色'), function (t) { return /^au-color-(brand|secondary)/.test(t.name); }],
|
||
[T('背景与表面'), function (t) { return /^au-color-(page-bg|card-bg|table-header-bg|disabled-bg|tag-green-bg)/.test(t.name); }],
|
||
[T('文字色'), function (t) { return /^au-color-text/.test(t.name); }],
|
||
[T('边框'), function (t) { return /^au-color-border/.test(t.name); }],
|
||
[T('语义色'), function (t) { return /^au-color-(success|warning|error|info)/.test(t.name); }],
|
||
[T('遮罩与焦点'), function (t) { return /^au-color-(mask|focus)/.test(t.name); }]
|
||
];
|
||
colorDefs.forEach(function (def) {
|
||
var list = TOKENS.filter(def[1]);
|
||
if (list.length) group(T('色彩 · ') + def[0], swatchGrid(list));
|
||
});
|
||
|
||
var typeTokens = TOKENS.filter(function (t) { return /^au-(type|font)-/.test(t.name); });
|
||
if (typeTokens.length) {
|
||
var wrap = el('div');
|
||
var families = typeTokens.filter(function (t) { return t.name.indexOf('au-font') === 0; });
|
||
var sizes = typeTokens.filter(function (t) { return t.name.indexOf('au-type') === 0; });
|
||
['display', 'h1', 'h2', 'body', 'caption'].forEach(function (k) {
|
||
var group2 = sizes.filter(function (t) { return t.name.indexOf('au-type-' + k) === 0; });
|
||
if (!group2.length) return;
|
||
var size = (group2.find(function (t) { return /-size/.test(t.name); }) || {}).value || '';
|
||
var height = (group2.find(function (t) { return /-height/.test(t.name); }) || {}).value || '';
|
||
var weight = (group2.find(function (t) { return /-weight/.test(t.name); }) || {}).value || '400';
|
||
var row = el('div', 'type-row');
|
||
var sample = el('div', 'type-sample', T('Aa 字体示例'));
|
||
sample.style.fontSize = size;
|
||
sample.style.fontWeight = weight;
|
||
sample.style.lineHeight = height;
|
||
row.appendChild(sample);
|
||
row.appendChild(el('div', 'type-meta', k.toUpperCase() + ' · ' + size + ' / ' + height + ' · ' + weight));
|
||
wrap.appendChild(row);
|
||
});
|
||
families.forEach(function (t) {
|
||
var row = el('div', 'type-row');
|
||
row.appendChild(el('div', 'type-sample', T('字体族 Aa 123')));
|
||
row.appendChild(el('div', 'type-meta', '--' + t.name + ' · ' + esc(t.value)));
|
||
wrap.appendChild(row);
|
||
});
|
||
group(T('字体'), wrap);
|
||
}
|
||
|
||
var spaces = tok('au-space-');
|
||
if (spaces.length) {
|
||
var wrap2 = el('div');
|
||
spaces.forEach(function (t) {
|
||
var px = parseInt(t.value, 10) || 0;
|
||
var row = el('div', 'space-row');
|
||
var bar = el('div', 'space-bar');
|
||
bar.style.width = Math.max(px, 8) + 'px';
|
||
row.appendChild(bar);
|
||
row.appendChild(el('div', 'space-meta', '--' + t.name + ' = ' + t.value + (t.comment ? ' · ' + t.comment : '')));
|
||
wrap2.appendChild(row);
|
||
});
|
||
group(T('间距(4px 基准)'), wrap2);
|
||
}
|
||
|
||
var radii = tok('au-radius-');
|
||
if (radii.length) {
|
||
var wrap3 = el('div', 'radius-row');
|
||
radii.forEach(function (t) {
|
||
var d = el('div', 'radius-demo', t.name.replace('au-radius-', '') + ' ' + t.value);
|
||
d.style.borderRadius = t.value;
|
||
wrap3.appendChild(d);
|
||
});
|
||
group(T('圆角'), wrap3);
|
||
}
|
||
|
||
var shadows = tok('au-shadow-');
|
||
if (shadows.length) {
|
||
var wrap4 = el('div', 'shadow-row');
|
||
shadows.forEach(function (t) {
|
||
var d = el('div', 'shadow-demo', t.name.replace('au-shadow-', '') + ':' + (t.comment || ''));
|
||
d.style.boxShadow = t.value;
|
||
wrap4.appendChild(d);
|
||
});
|
||
group(T('阴影(3 层克制投影)'), wrap4);
|
||
}
|
||
|
||
var ctrls = tok('au-control-height');
|
||
if (ctrls.length) {
|
||
var wrap5 = el('div');
|
||
ctrls.forEach(function (t) {
|
||
var row = el('div', 'ctrl-row');
|
||
var pill = el('div', 'ctrl-pill', t.value);
|
||
pill.style.height = t.value;
|
||
pill.style.padding = '0 16px';
|
||
row.appendChild(pill);
|
||
row.appendChild(el('div', 'space-meta', '--' + t.name + ' = ' + t.value));
|
||
wrap5.appendChild(row);
|
||
});
|
||
group(T('控件高度'), wrap5);
|
||
}
|
||
|
||
/* ---------- 令牌导出:三种标准格式,构建期由 build-site.ps1 生成 ---------- */
|
||
var EXPORTS = [
|
||
{ file: 'tokens/tokens.css', name: T('CSS 自定义属性'), hint: T('直接 @import 或复制 :root 变量块') },
|
||
{ file: 'tokens/dtcg.json', name: 'W3C DTCG', hint: T('Design Tokens 社区组标准($value / $type)') },
|
||
{ file: 'tokens/figma.json', name: 'Figma Tokens', hint: T('Tokens Studio 插件可直接导入') }
|
||
];
|
||
var exportWrap = el('div');
|
||
exportWrap.appendChild(el('p', 'page-desc',
|
||
T('令牌可导出为三种标准格式,供设计工具与其它工程直接消费。文件由 <code>build-site.ps1</code> 在构建时生成,与上方展示的取值始终同源。')));
|
||
var exportGrid = el('div', 'tok-grid');
|
||
EXPORTS.forEach(function (x) {
|
||
var card = el('a', 'tok-card tok-export');
|
||
card.href = '../site/' + x.file;
|
||
card.setAttribute('download', '');
|
||
card.innerHTML = '<div class="t-name"><code>' + esc(x.file) + '</code></div>' +
|
||
'<div class="t-val">' + esc(x.name) + '</div>' +
|
||
'<div class="t-cmt">' + esc(x.hint) + '</div>';
|
||
exportGrid.appendChild(card);
|
||
});
|
||
exportWrap.appendChild(exportGrid);
|
||
group(T('令牌导出'), exportWrap);
|
||
|
||
content.appendChild(inner);
|
||
}
|
||
|
||
/* ---------- 轻量零依赖语法高亮 (Syntax Highlighting) ---------- */
|
||
function highlightCode(src) {
|
||
if (!src) return '';
|
||
var tokens = [];
|
||
/* 注意:本函数在步骤 3 之后文本已是转义态,saveToken 不再重复转义 */
|
||
function saveToken(content, cls) {
|
||
tokens.push('<span class="' + cls + '">' + content + '</span>');
|
||
return '___HL_' + (tokens.length - 1) + '___';
|
||
}
|
||
// 1. 注释(原始源码,需转义)
|
||
var s = src.replace(/<!--[\s\S]*?-->|\/\*[\s\S]*?\*\/|\/\/[^\n]*/g, function (m) {
|
||
return saveToken(esc(m), 'hl-com');
|
||
});
|
||
// 2. 字符串(原始源码,需转义)
|
||
s = s.replace(/"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|`[^`]*`/g, function (m) {
|
||
return saveToken(esc(m), 'hl-str');
|
||
});
|
||
// 3. 基础转义(此后文本已是转义态)
|
||
s = esc(s);
|
||
// 4. 属性(优先于标签匹配)
|
||
s = s.replace(/(\s+)([@:v-]?[a-zA-Z0-9_-]+)(=)/g, function (m, p1, p2, p3) {
|
||
return p1 + saveToken(p2, 'hl-attr') + p3;
|
||
});
|
||
// 5. 标签
|
||
s = s.replace(/(<\/?[a-zA-Z0-9_-]+)/g, function (m) {
|
||
return saveToken(m, 'hl-tag');
|
||
});
|
||
// 6. 关键字
|
||
var kw = /\b(const|let|var|function|return|import|export|from|default|if|else|defineProps|defineEmits|ref|computed|reactive|watch)\b/g;
|
||
s = s.replace(kw, function (m) {
|
||
return saveToken(m, 'hl-kw');
|
||
});
|
||
// 7. 还原所有 token
|
||
s = s.replace(/___HL_(\d+)___/g, function (_, i) {
|
||
return tokens[Number(i)];
|
||
});
|
||
return s;
|
||
}
|
||
|
||
/* ---------- 组件 API 提炼 (Props / Events / Slots) ---------- */
|
||
function extractComponentAPI(c) {
|
||
var v3 = (c.sources && c.sources.vue3) || '';
|
||
var v2 = (c.sources && c.sources.vue2) || '';
|
||
var jsx = (c.sources && c.sources.jsx) || '';
|
||
var code = v3 || v2;
|
||
|
||
var props = [];
|
||
var emits = [];
|
||
var slots = [];
|
||
|
||
/* 1. 提炼 Props
|
||
策略:括号深度扫描定位对象体(兼容单行/多行/嵌套对象/箭头函数/字符串内花括号),
|
||
再按「顶层逗号」分段解析每个 prop。
|
||
覆盖写法:defineProps({...}) / props: {...} (Vue2) / 兜底 JSX 解构参数 */
|
||
function scanObjectAt(src, openIdx) {
|
||
var depth = 0, inStr = null, escCh = false, end = -1;
|
||
for (var i = openIdx; i < src.length; i++) {
|
||
var ch = src.charAt(i);
|
||
if (inStr) {
|
||
if (escCh) { escCh = false; continue; }
|
||
if (ch === '\\') { escCh = true; continue; }
|
||
if (ch === inStr) inStr = null;
|
||
continue;
|
||
}
|
||
if (ch === '"' || ch === "'" || ch === '`') { inStr = ch; continue; }
|
||
if (ch === '{') depth++;
|
||
else if (ch === '}') { depth--; if (depth === 0) { end = i; break; } }
|
||
}
|
||
return end < 0 ? null : src.slice(openIdx, end + 1);
|
||
}
|
||
function stripOuterBraces(s) { return s.replace(/^\s*\{/, '').replace(/\}\s*$/, ''); }
|
||
function splitTopLevel(body) {
|
||
var segs = [], cur = '', depth = 0, inStr = null, escCh = false;
|
||
for (var i = 0; i < body.length; i++) {
|
||
var ch = body.charAt(i);
|
||
if (inStr) {
|
||
cur += ch;
|
||
if (escCh) { escCh = false; continue; }
|
||
if (ch === '\\') { escCh = true; continue; }
|
||
if (ch === inStr) inStr = null;
|
||
continue;
|
||
}
|
||
if (ch === '"' || ch === "'" || ch === '`') { inStr = ch; cur += ch; continue; }
|
||
if (ch === '{' || ch === '[' || ch === '(') { depth++; cur += ch; continue; }
|
||
if (ch === '}' || ch === ']' || ch === ')') { depth--; cur += ch; continue; }
|
||
if (ch === ',' && depth === 0) { segs.push(cur); cur = ''; continue; }
|
||
cur += ch;
|
||
}
|
||
if (cur.trim()) segs.push(cur);
|
||
return segs;
|
||
}
|
||
|
||
var propsBody = null;
|
||
var dpM = /defineProps\s*\(/.exec(code);
|
||
if (dpM) {
|
||
var openIdx = code.indexOf('{', dpM.index);
|
||
if (openIdx >= 0) propsBody = stripOuterBraces(scanObjectAt(code, openIdx) || '');
|
||
}
|
||
if (!propsBody) {
|
||
var prM = /props\s*:\s*\{/.exec(code);
|
||
if (prM) {
|
||
var openIdx2 = prM.index + prM[0].length - 1;
|
||
propsBody = stripOuterBraces(scanObjectAt(code, openIdx2) || '');
|
||
}
|
||
}
|
||
if (propsBody && propsBody.trim()) {
|
||
splitTopLevel(propsBody).forEach(function (seg) {
|
||
var m = seg.match(/^\s*([a-zA-Z0-9_$]+)\s*:\s*([\s\S]*)$/);
|
||
if (!m) return;
|
||
var name = m[1], rest = m[2];
|
||
var cM = rest.match(/\/\/\s*([^\n]+)\s*$/);
|
||
props.push(finalizeProp({ name: name, comment: cM ? cM[1].trim() : '', raw: rest }));
|
||
});
|
||
} else if (jsx) {
|
||
var fnM = jsx.match(/export default function\s+[A-Za-z0-9_$]+\s*\(\s*\{([^}]+)\}/);
|
||
if (fnM) {
|
||
var params = fnM[1].split(',');
|
||
params.forEach(function (param) {
|
||
var p = param.trim();
|
||
if (!p || p.indexOf('...') === 0) return;
|
||
var parts = p.split('=');
|
||
var name = parts[0].trim();
|
||
var def = parts[1] ? parts[1].trim() : '—';
|
||
if (name.indexOf('on') === 0 && /[A-Z]/.test(name.charAt(2))) {
|
||
emits.push({ name: name, desc: T('回调触发事件 (Event callback)'), params: '(event: any) => void' });
|
||
} else if (name === 'children') {
|
||
slots.push({ name: 'default', desc: T('子节点内容 (children)') });
|
||
} else {
|
||
props.push({ name: name, type: 'any', def: def, req: false, desc: '—' });
|
||
}
|
||
});
|
||
}
|
||
}
|
||
|
||
/* 2. 提炼 Emits */
|
||
var mEmits = code.match(/defineEmits\s*\(\s*(\[[^\]]+\])\s*\)/);
|
||
if (mEmits) {
|
||
try {
|
||
var emitArr = (new Function('return ' + mEmits[1]))();
|
||
emitArr.forEach(function (e) {
|
||
var desc = e.indexOf('update:') === 0 ? T('双向绑定更新事件 (v-model)') : T('组件交互触发事件');
|
||
if (!emits.some(function (x) { return x.name === e; })) {
|
||
emits.push({ name: e, desc: desc, params: '(val: any) => void' });
|
||
}
|
||
});
|
||
} catch (err) {}
|
||
}
|
||
var emitRegex = /\$emit\(\s*['"]([a-zA-Z0-9_:-]+)['"]/g;
|
||
var eMatch;
|
||
while ((eMatch = emitRegex.exec(code)) !== null) {
|
||
var eName = eMatch[1];
|
||
if (!emits.some(function (x) { return x.name === eName; })) {
|
||
emits.push({ name: eName, desc: T('交互回调触发事件'), params: '—' });
|
||
}
|
||
}
|
||
|
||
/* 3. 提炼 Slots */
|
||
var slotRegex = /<slot(?:\s+name=["']([^"']+)["'])?/g;
|
||
var sMatch;
|
||
while ((sMatch = slotRegex.exec(code)) !== null) {
|
||
var sName = sMatch[1] || 'default';
|
||
if (!slots.some(function (x) { return x.name === sName; })) {
|
||
slots.push({ name: sName, desc: sName === 'default' ? T('默认插槽(组件文本或主体内容)') : sName + T(' 自定义扩展插槽') });
|
||
}
|
||
}
|
||
|
||
return { props: props, emits: emits, slots: slots };
|
||
}
|
||
|
||
/* 从 str 起点扫描到「顶层逗号/字符串感知」为止,返回该段值文本 */
|
||
function scanTopLevelValue(str) {
|
||
var depth = 0, inStr = null, escCh = false;
|
||
for (var i = 0; i < str.length; i++) {
|
||
var ch = str.charAt(i);
|
||
if (inStr) {
|
||
if (escCh) { escCh = false; continue; }
|
||
if (ch === '\\') { escCh = true; continue; }
|
||
if (ch === inStr) inStr = null;
|
||
continue;
|
||
}
|
||
if (ch === '"' || ch === "'" || ch === '`') { inStr = ch; continue; }
|
||
if (ch === '{' || ch === '[' || ch === '(') depth++;
|
||
else if (ch === '}' || ch === ']' || ch === ')') { if (depth === 0) return str.slice(0, i); depth--; }
|
||
else if (ch === ',' && depth === 0) return str.slice(0, i);
|
||
}
|
||
return str;
|
||
}
|
||
|
||
function finalizeProp(p) {
|
||
var type = 'any';
|
||
var def = '—';
|
||
var req = false;
|
||
var typeM = p.raw.match(/type\s*:\s*(\[[^\]]+\]|[a-zA-Z0-9_$]+)/);
|
||
if (typeM) type = typeM[1].replace(/\[|\]/g, '').replace(/,\s*/g, ' | ');
|
||
var defM = /default\s*:/.exec(p.raw);
|
||
if (defM) {
|
||
var val = scanTopLevelValue(p.raw.slice(defM.index + defM[0].length)).trim();
|
||
def = val.replace(/\(\)\s*=>\s*/, '') || '—';
|
||
if (def === '()') def = '—';
|
||
/* 美化多行默认值(如数组工厂)为单行紧凑形式 */
|
||
def = def.replace(/\s*\n\s*/g, ' ').replace(/\s{2,}/g, ' ');
|
||
if (def.length > 48) def = def.slice(0, 45) + '…';
|
||
}
|
||
if (/required\s*:\s*true/.test(p.raw)) req = true;
|
||
return { name: p.name, type: type, def: def, req: req, desc: p.comment || '—' };
|
||
}
|
||
|
||
/* 渲染 API 规范表格 */
|
||
function buildApiSection(api) {
|
||
var wrap = el('div', 'api-section');
|
||
|
||
if (api.props && api.props.length) {
|
||
wrap.appendChild(el('h3', 'api-subhead', T('属性 (Props)')));
|
||
var tblP = el('table', 'api-table');
|
||
tblP.innerHTML = T('<thead><tr><th>参数名</th><th>说明</th><th>类型</th><th>默认值</th><th>必填</th></tr></thead>');
|
||
var tbP = el('tbody');
|
||
api.props.forEach(function (p) {
|
||
var tr = el('tr');
|
||
tr.innerHTML =
|
||
'<td class="prop-name"><code>' + esc(p.name) + '</code></td>' +
|
||
'<td class="prop-desc">' + esc(p.desc) + '</td>' +
|
||
'<td class="prop-type"><code>' + esc(p.type) + '</code></td>' +
|
||
'<td class="prop-def"><code>' + esc(p.def) + '</code></td>' +
|
||
'<td class="prop-req">' + (p.req ? T('<span class="badge-req">必填</span>') : T('<span class="badge-opt">可选</span>')) + '</td>';
|
||
tbP.appendChild(tr);
|
||
});
|
||
tblP.appendChild(tbP);
|
||
wrap.appendChild(tblP);
|
||
}
|
||
|
||
if (api.emits && api.emits.length) {
|
||
wrap.appendChild(el('h3', 'api-subhead', T('事件 (Events / Emits)')));
|
||
var tblE = el('table', 'api-table');
|
||
tblE.innerHTML = T('<thead><tr><th>事件名</th><th>说明</th><th>回调参数</th></tr></thead>');
|
||
var tbE = el('tbody');
|
||
api.emits.forEach(function (e) {
|
||
var tr = el('tr');
|
||
tr.innerHTML =
|
||
'<td class="prop-name"><code>@' + esc(e.name) + '</code></td>' +
|
||
'<td class="prop-desc">' + esc(e.desc) + '</td>' +
|
||
'<td class="prop-type"><code>' + esc(e.params || '—') + '</code></td>';
|
||
tbE.appendChild(tr);
|
||
});
|
||
tblE.appendChild(tbE);
|
||
wrap.appendChild(tblE);
|
||
}
|
||
|
||
if (api.slots && api.slots.length) {
|
||
wrap.appendChild(el('h3', 'api-subhead', T('插槽 (Slots)')));
|
||
var tblS = el('table', 'api-table');
|
||
tblS.innerHTML = T('<thead><tr><th>插槽名</th><th>说明</th></tr></thead>');
|
||
var tbS = el('tbody');
|
||
api.slots.forEach(function (s) {
|
||
var tr = el('tr');
|
||
tr.innerHTML =
|
||
'<td class="prop-name"><code>#' + esc(s.name) + '</code></td>' +
|
||
'<td class="prop-desc">' + esc(s.desc) + '</td>';
|
||
tbS.appendChild(tr);
|
||
});
|
||
tblS.appendChild(tbS);
|
||
wrap.appendChild(tblS);
|
||
}
|
||
|
||
return wrap;
|
||
}
|
||
|
||
/* 渲染无障碍键盘指引 */
|
||
function buildA11ySection(c) {
|
||
var wrap = el('div', 'a11y-section');
|
||
var intro = el('p', 'a11y-intro', T('遵循 W3C WAI-ARIA 规范,组件内置标准键盘交互与屏幕阅读器无障碍支持。'));
|
||
wrap.appendChild(intro);
|
||
|
||
var tbl = el('table', 'api-table');
|
||
tbl.innerHTML = T('<thead><tr><th style="width:160px">按键</th><th>交互说明</th></tr></thead>');
|
||
var tb = el('tbody');
|
||
|
||
var rules = [
|
||
{ key: 'Tab', desc: T('将焦点移入当前组件或在可聚焦子项间依次流转') },
|
||
{ key: 'Shift + Tab', desc: T('反向移动焦点到上一个交互元素') }
|
||
];
|
||
|
||
if (/button|link|action/.test(c.slug) || c.category === 'general') {
|
||
rules.push({ key: 'Enter / Space', desc: T('触发按钮点击动作,执行绑定的业务逻辑') });
|
||
} else if (/input|mention|autocomplete/.test(c.slug)) {
|
||
rules.push({ key: 'Enter', desc: T('确认输入内容或提交当前表单') });
|
||
rules.push({ key: 'Esc', desc: T('清除当前临时输入或收起下拉联想菜单') });
|
||
} else if (/select|cascader|dropdown|picker/.test(c.slug)) {
|
||
rules.push({ key: 'Enter / Space', desc: T('展开或收起下拉候选列表') });
|
||
rules.push({ key: '↑ / ↓', desc: T('在下拉选项菜单中高亮切换候选项目') });
|
||
rules.push({ key: 'Esc', desc: T('收起下拉菜单并保持输入框焦点') });
|
||
} else if (/modal|dialog|drawer|sheet/.test(c.slug) || c.category === 'feedback') {
|
||
rules.push({ key: 'Esc', desc: T('快捷关闭当前弹窗并还原底层焦点') });
|
||
rules.push({ key: T('Tab (循环锁定)'), desc: T('弹窗激活时,焦点锁定在弹窗内部,不会跳出到蒙层外部背景') });
|
||
} else if (/tabs|steplist|steps|segmented/.test(c.slug) || c.category === 'navigation') {
|
||
rules.push({ key: '← / →', desc: T('在导航标签项或步骤之间切换激活状态') });
|
||
rules.push({ key: 'Home / End', desc: T('快速定位到首个或末尾标签项') });
|
||
} else if (/table|tree/.test(c.slug)) {
|
||
rules.push({ key: '↑ / ↓', desc: T('在数据行或树形节点之间上下导航') });
|
||
rules.push({ key: 'Space', desc: T('快速切换当前行或节点的勾选状态') });
|
||
}
|
||
|
||
rules.forEach(function (r) {
|
||
var tr = el('tr');
|
||
tr.innerHTML = '<td class="a11y-key"><kbd>' + esc(r.key) + '</kbd></td><td class="prop-desc">' + esc(r.desc) + '</td>';
|
||
tb.appendChild(tr);
|
||
});
|
||
tbl.appendChild(tb);
|
||
wrap.appendChild(tbl);
|
||
return wrap;
|
||
}
|
||
|
||
/* 渲染设计避坑原则 (Do\'s & Don\'ts) */
|
||
function buildDosAndDonts(c) {
|
||
var grid = el('div', 'dodont-grid');
|
||
var cardDo = el('div', 'dodont-card do');
|
||
var cardDont = el('div', 'dodont-card dont');
|
||
|
||
cardDo.appendChild(el('div', 'dodont-title', T('✅ 推荐做法 (Do)')));
|
||
cardDont.appendChild(el('div', 'dodont-title', T('❌ 避免反模式 (Don\'t)')));
|
||
|
||
var ulDo = el('ul');
|
||
var ulDont = el('ul');
|
||
|
||
var dos = [];
|
||
var donts = [];
|
||
|
||
if (c.contract && c.contract.usage && c.contract.usage.length) {
|
||
dos = c.contract.usage.slice(0, 3);
|
||
}
|
||
if (c.contract && c.contract.doNot && c.contract.doNot.length) {
|
||
donts = c.contract.doNot.slice(0, 3);
|
||
}
|
||
|
||
if (!dos.length) {
|
||
if (/button/.test(c.slug)) {
|
||
dos = [T('主行动区原则上只保留一个 Primary 主按钮'), T('危险破坏性操作配合二次确认弹窗使用'), T('加载中状态下设置 disabled 避免二次误触')];
|
||
donts = [T('避免在同一行并列 3 个以上相同颜色的主按钮'), T('避免在表单提交操作中使用弱化文字按钮'), T('不要在非页面跳转场景滥用链接按钮')];
|
||
} else if (/input/.test(c.slug)) {
|
||
dos = [T('输入框建议配合明确的 Label 或 Placeholder'), T('密码及敏感凭据字段提供显隐切换图标'), T('必填项在标签旁显式标注红星 (*)')];
|
||
donts = [T('避免仅依赖 Placeholder 传达关键业务提示'), T('避免移动端使用过长不可换行的输入区域'), T('不要在禁用态下保留光标闪烁效果')];
|
||
} else if (/table/.test(c.slug)) {
|
||
dos = [T('列数较多(≥8列)时开启固定列与横向滚动'), T('长列表数据采用分页或虚拟滚动承载'), T('空数据状态提供具体的引导图文')];
|
||
donts = [T('避免单元格无限制换行撑大表格高度'), T('避免全表无边框导致复杂数据对比混淆'), T('不要在无分页情况下一次性渲染数百行 DOM')];
|
||
} else if (/modal/.test(c.slug)) {
|
||
dos = [T('弹窗宽度应适配内容复杂度,标准为 520px'), T('重要输入表单弹窗阻止点击蒙层空白处误关闭'), T('弹窗关闭后应将焦点自动交还触发按钮')];
|
||
donts = [T('避免多层弹窗连续嵌套破坏视觉层级'), T('避免在弹窗内塞入两屏以上的长篇表单'), T('不要无确定按钮直接让用户点击右上角叉号关闭')];
|
||
} else {
|
||
dos = [T('优先遵循 Aurora Admin 统一设计令牌体系'), T('保持组件语义化标签,便于辅助工具识别'), T('复杂业务合理组合基础原子组件')];
|
||
donts = [T('避免自行通过 style 强行覆盖组件内边距'), T('避免自创非常规控件高度破坏栅格对齐'), T('不要移除原生的键盘焦点可视化轮廓')];
|
||
}
|
||
}
|
||
|
||
dos.forEach(function (d) { ulDo.appendChild(el('li', null, esc(d))); });
|
||
donts.forEach(function (d) { ulDont.appendChild(el('li', null, esc(d))); });
|
||
cardDo.appendChild(ulDo);
|
||
cardDont.appendChild(ulDont);
|
||
grid.appendChild(cardDo);
|
||
grid.appendChild(cardDont);
|
||
return grid;
|
||
}
|
||
|
||
/* ---------- 组件详情 ---------- */
|
||
function renderComponent(content, slug) {
|
||
var c = bySlugMap[slug];
|
||
content.innerHTML = '';
|
||
var inner = el('div', 'page-inner');
|
||
content.appendChild(inner);
|
||
|
||
/* 头部:Element 风格 —— 大标题 + 一句话描述 */
|
||
var head = el('div', 'detail-head');
|
||
var n = splitName(c.name);
|
||
var h1 = el('h1');
|
||
h1.appendChild(document.createTextNode(namePrimary(n)));
|
||
/* 副名取『另一种语言』:中文模式副显英文,英文模式副显中文 */
|
||
h1.appendChild(el('span', 'en', esc(isEn() ? n.zh : n.en)));
|
||
head.appendChild(h1);
|
||
|
||
var desc = cleanSummary(c.specSummary);
|
||
if (desc) head.appendChild(el('p', 'page-desc', esc(desc)));
|
||
|
||
var chips = el('div', 'chips');
|
||
chips.appendChild(el('span', 'chip blue', c.tier === 'core' ? T('核心规范组件') : T('扩展组件')));
|
||
chips.appendChild(el('span', 'chip', catLabel(c.category)));
|
||
var batchA = el('a', 'chip', batchLabel(c.specBatch) + ' · ' + c.specFile);
|
||
batchA.href = specLink(c.specFile);
|
||
batchA.target = '_blank';
|
||
chips.appendChild(batchA);
|
||
if (c.contractRef) {
|
||
var ca = el('a', 'chip blue', T('契约 JSON ✓'));
|
||
ca.href = contractLink(c);
|
||
ca.target = '_blank';
|
||
chips.appendChild(ca);
|
||
} else {
|
||
var srcChip = el('span', 'chip', T('API 源码提炼'));
|
||
srcChip.title = '该组件暂无契约 JSON,API 表由 5 端源码自动提炼';
|
||
chips.appendChild(srcChip);
|
||
}
|
||
chips.appendChild(el('span', 'chip', 'slug: ' + c.slug));
|
||
head.appendChild(chips);
|
||
|
||
/* 测试页直达入口 */
|
||
var testLink = el('a', 'chip', T('测试页 →'));
|
||
testLink.href = '../tests/' + c.slug + '.html';
|
||
testLink.target = '_blank';
|
||
head.appendChild(testLink);
|
||
inner.appendChild(head);
|
||
|
||
var tocItems = [];
|
||
|
||
/* 1. 何时使用(规格说明) */
|
||
var secSpec = el('div', 'sec');
|
||
secSpec.id = 'spec';
|
||
secSpec.appendChild(el('h2', null, T('何时使用')));
|
||
var box = el('div', 'spec-box');
|
||
if (c.specLines && c.specLines.length) {
|
||
/* specLines 兼容数组与单字符串两种形态 */
|
||
renderSpecLines(box, Array.isArray(c.specLines) ? c.specLines : String(c.specLines).split('\n'));
|
||
} else {
|
||
box.appendChild(el('p', null, T('本组件暂无独立规格说明,参见 ') + esc(c.specFile) + '。'));
|
||
}
|
||
secSpec.appendChild(box);
|
||
inner.appendChild(secSpec);
|
||
tocItems.push({ id: 'spec', label: '何时使用' });
|
||
|
||
/* 2. 代码演示 */
|
||
var secDemo = el('div', 'sec');
|
||
secDemo.id = 'demo';
|
||
secDemo.appendChild(el('h2', null, T('代码演示')));
|
||
var demoIntro = el('div', 'demo-intro');
|
||
demoIntro.innerHTML = '<h3>' + T('基础用法') + '</h3><p></p><p class="demo-note"></p>';
|
||
demoIntro.querySelector('p').textContent = desc || T('完整的组件演示,包含常用状态与场景。');
|
||
demoIntro.querySelector('.demo-note').textContent =
|
||
T('视觉演示为 H5 形态(各端实现视觉一致);下方代码与资源已按「') + FRAMEWORKS[state.framework] + T('」过滤。');
|
||
secDemo.appendChild(demoIntro);
|
||
|
||
/* 2.1 场景化微用例卡片:拆分演示页里的每个场景(h2 分组) */
|
||
var scenarioCard = buildScenarioCard(c);
|
||
if (scenarioCard) secDemo.appendChild(scenarioCard);
|
||
|
||
secDemo.appendChild(buildDemoCard(c));
|
||
inner.appendChild(secDemo);
|
||
tocItems.push({ id: 'demo', label: T('代码演示') });
|
||
|
||
/* 2.5 组件 API 参数规范表(Props / Events / Slots) */
|
||
var apiData = extractComponentAPI(c);
|
||
if (apiData.props.length || apiData.emits.length || apiData.slots.length) {
|
||
var secApi = el('div', 'sec');
|
||
secApi.id = 'api';
|
||
secApi.appendChild(el('h2', null, T('组件 API 规范')));
|
||
secApi.appendChild(buildApiSection(apiData));
|
||
inner.appendChild(secApi);
|
||
tocItems.push({ id: 'api', label: T('组件 API') });
|
||
}
|
||
|
||
/* 2.6 无障碍指引(A11y & Keyboard) */
|
||
var secA11y = el('div', 'sec');
|
||
secA11y.id = 'a11y';
|
||
secA11y.appendChild(el('h2', null, T('无障碍与键盘交互')));
|
||
secA11y.appendChild(buildA11ySection(c));
|
||
inner.appendChild(secA11y);
|
||
tocItems.push({ id: 'a11y', label: T('无障碍指引') });
|
||
|
||
/* 2.7 设计避坑指南(Do\'s & Don\'ts) */
|
||
var secGuide = el('div', 'sec');
|
||
secGuide.id = 'guidelines';
|
||
secGuide.appendChild(el('h2', null, T('设计避坑指南 (Do\'s & Don\'ts)')));
|
||
secGuide.appendChild(buildDosAndDonts(c));
|
||
inner.appendChild(secGuide);
|
||
tocItems.push({ id: 'guidelines', label: T('设计避坑') });
|
||
|
||
/* 3. 设计契约(核心组件) */
|
||
if (c.contract) {
|
||
var secCon = el('div', 'sec');
|
||
secCon.id = 'contract';
|
||
secCon.appendChild(el('h2', null, T('设计契约')));
|
||
secCon.appendChild(buildContract(c.contract));
|
||
inner.appendChild(secCon);
|
||
tocItems.push({ id: 'contract', label: '设计契约' });
|
||
}
|
||
|
||
/* 4. 实现资源 */
|
||
var secFiles = el('div', 'sec');
|
||
secFiles.id = 'files';
|
||
secFiles.appendChild(el('h2', null, T('实现资源')));
|
||
secFiles.appendChild(buildFilesTable(c));
|
||
inner.appendChild(secFiles);
|
||
tocItems.push({ id: 'files', label: '实现资源' });
|
||
|
||
/* 5. 相关组件(交叉引用):同分类近邻 + 名称关键词映射(TDesign「相似组件」模式) */
|
||
var related = buildRelated(c);
|
||
if (related.length) {
|
||
var secRel = el('div', 'sec');
|
||
secRel.id = 'related';
|
||
secRel.appendChild(el('h2', null, T('相关组件')));
|
||
secRel.appendChild(el('p', 'page-desc', T('按「相似功能 / 常搭配使用」维度整理,点击跳转。')));
|
||
var relRow = el('div', 'rel-row');
|
||
related.forEach(function (r) {
|
||
var n = splitName(r.name);
|
||
var a = el('a', 'rel-card');
|
||
a.href = '#/component/' + r.slug;
|
||
a.innerHTML = '<strong></strong><span></span><i class="rel-why"></i>';
|
||
a.querySelector('strong').textContent = n.zh;
|
||
a.querySelector('span').textContent = n.en;
|
||
a.querySelector('.rel-why').textContent = r.why;
|
||
relRow.appendChild(a);
|
||
});
|
||
secRel.appendChild(relRow);
|
||
inner.appendChild(secRel);
|
||
tocItems.push({ id: 'related', label: '相关组件' });
|
||
}
|
||
|
||
/* 上一篇/下一篇 */
|
||
var idx2 = comps.indexOf(c);
|
||
var prev = comps[idx2 - 1], next = comps[idx2 + 1];
|
||
if (prev || next) {
|
||
var pager = el('div', 'pager');
|
||
if (prev) {
|
||
var a1 = el('a', null);
|
||
a1.href = '#/component/' + prev.slug;
|
||
a1.innerHTML = '<span class="dir">← ' + T('上一篇') + '</span><span class="name"></span>';
|
||
a1.querySelector('.name').textContent = namePrimary(splitName(prev.name));
|
||
pager.appendChild(a1);
|
||
} else {
|
||
pager.appendChild(el('div', null, ''));
|
||
}
|
||
if (next) {
|
||
var a2 = el('a', 'next');
|
||
a2.href = '#/component/' + next.slug;
|
||
a2.innerHTML = '<span class="dir">' + T('下一篇') + ' →</span><span class="name"></span>';
|
||
a2.querySelector('.name').textContent = namePrimary(splitName(next.name));
|
||
pager.appendChild(a2);
|
||
}
|
||
inner.appendChild(pager);
|
||
}
|
||
|
||
renderToc(tocItems);
|
||
}
|
||
|
||
/* 交叉引用:相似/搭配组件映射表(关键词 → 组件 slug 组),加同分类近邻兜底 */
|
||
var RELATED_MAP = [
|
||
{ match: /modal|confirmmodal|alertmodal|formmodal|fullscreenmodal|popconfirm|bottomsheet/, slugs: ['modal', 'confirmmodal', 'popconfirm', 'formmodal'], why: T('同属弹层家族,按承载内容与打断强度选择') },
|
||
{ match: /table|treetable|expandabletable|mergedcelltable|summaryrowtable|fixedcolumntable|cardlist/, slugs: ['table', 'treetable', 'cardlist'], why: T('数据列表的不同形态,按层级与信息量选择') },
|
||
{ match: /button|buttongroup|segmented/, slugs: ['buttongroup', 'segmented', 'radiocard'], why: T('操作触发与选项切换的相近形态') },
|
||
{ match: /input|autocomplete|mention|codeinput|passwordinput|phoneinput|bankcardinput|idcardinput|plateinput/, slugs: ['autocomplete', 'input', 'mention'], why: T('输入类组件,按提示方式与约束选择') },
|
||
{ match: /select|cascader|listpicker|transfer/, slugs: ['select', 'cascader', 'transfer'], why: T('选择类组件,按层级深度与数量选择') },
|
||
{ match: /rate|slider|numberrangeinput/, slugs: ['slider', 'rate', 'numberrangeinput'], why: T('数值输入的相近形态') },
|
||
{ match: /radiocard|checkboxcard|switchgroup/, slugs: ['radiocard', 'checkboxcard', 'switchgroup'], why: T('卡片式选项与开关组合') },
|
||
{ match: /tabs|enhancedtabnav|pagetabs|steplist|steps/, slugs: ['tabs', 'steps', 'steplist'], why: T('流程与视图组织的相近形态') }
|
||
];
|
||
function buildRelated(c) {
|
||
var seen = {};
|
||
var out = [];
|
||
RELATED_MAP.forEach(function (rule) {
|
||
var key = c.slug + '|' + c.name;
|
||
if (!rule.match.test(c.slug) && !rule.match.test(c.name)) return;
|
||
rule.slugs.forEach(function (s) {
|
||
if (s === c.slug || seen[s]) return;
|
||
var t = bySlugMap[s];
|
||
if (!t) return;
|
||
seen[s] = true;
|
||
out.push({ slug: s, name: t.name, why: rule.why });
|
||
});
|
||
});
|
||
/* 同分类近邻兜底,凑满 3 个 */
|
||
if (out.length < 3) {
|
||
comps.filter(function (x) { return x.category === c.category && x.slug !== c.slug && !seen[x.slug]; })
|
||
.slice(0, 3 - out.length)
|
||
.forEach(function (t) { seen[t.slug] = true; out.push({ slug: t.slug, name: t.name, why: T('同属') + catLabel(c.category) + T('分类') }); });
|
||
}
|
||
return out.slice(0, 4);
|
||
}
|
||
|
||
/* 规格说明渲染:- 列表 / 表格 / 普通段落 */
|
||
function renderSpecLines(box, lines) {
|
||
var list = null;
|
||
lines.forEach(function (raw) {
|
||
var line = raw.trim();
|
||
if (!line) { list = null; return; }
|
||
if (line.charAt(0) === '|' && line.charAt(line.length - 1) === '|') {
|
||
list = null;
|
||
var cells = line.split('|').slice(1, -1).map(function (s) { return s.trim(); });
|
||
if (cells.every(function (s) { return /^[-: ]*$/.test(s); })) return;
|
||
var tbl = box.querySelector('table:last-of-type');
|
||
if (!tbl || tbl.dataset.done) {
|
||
tbl = el('table');
|
||
tbl.dataset.done = '1';
|
||
var thead = el('thead');
|
||
var trh = el('tr');
|
||
cells.forEach(function (cell) { trh.appendChild(el('th', null, esc(cell))); });
|
||
thead.appendChild(trh);
|
||
tbl.appendChild(thead);
|
||
var tbody = el('tbody');
|
||
tbl.appendChild(tbody);
|
||
box.appendChild(tbl);
|
||
} else {
|
||
var tr = el('tr');
|
||
cells.forEach(function (cell) { tr.appendChild(el('td', null, esc(cell))); });
|
||
tbl.querySelector('tbody').appendChild(tr);
|
||
}
|
||
return;
|
||
}
|
||
if (/^[-•·]\s+/.test(line) || /^\d+[.、]\s*/.test(line)) {
|
||
if (!list) { list = el('ul'); box.appendChild(list); }
|
||
list.appendChild(el('li', null, esc(line.replace(/^[-•·]\s+/, ''))));
|
||
return;
|
||
}
|
||
list = null;
|
||
box.appendChild(el('p', null, esc(line)));
|
||
});
|
||
}
|
||
|
||
/* 代码演示卡片(Element 风格:底部居中箭头展开条) */
|
||
/* 场景化微用例卡片
|
||
从 H5 演示源码中提取 <h2> 场景分组标题,生成「场景速览」条。
|
||
点击场景 = 同源 iframe 内定位到对应分组(file:// 下自动降级隐藏跳转)。 */
|
||
function extractScenarios(c) {
|
||
var html = (c.sources && c.sources.html) || '';
|
||
var bodyM = html.match(/<body[^>]*>([\s\S]*)<\/body>/i);
|
||
var body = bodyM ? bodyM[1] : html;
|
||
var out = [];
|
||
var re = /<h2[^>]*>([\s\S]*?)<\/h2>/gi;
|
||
var m;
|
||
while ((m = re.exec(body)) !== null) {
|
||
var text = m[1].replace(/<[^>]+>/g, '').replace(/\s+/g, ' ').trim();
|
||
/* 去掉演示页里的「1. 」序号前缀,让场景名更干净 */
|
||
text = text.replace(/^\d+[.、]\s*/, '');
|
||
if (text) out.push(text);
|
||
}
|
||
return out;
|
||
}
|
||
|
||
function buildScenarioCard(c) {
|
||
var scenarios = extractScenarios(c);
|
||
if (scenarios.length < 2) return null;
|
||
var wrap = el('div', 'scenario-wrap');
|
||
wrap.appendChild(el('div', 'scenario-title', T('场景速览(') + scenarios.length + T(' 个典型用法)')));
|
||
|
||
var row = el('div', 'scenario-row');
|
||
scenarios.forEach(function (name, i) {
|
||
var chip = el('button', 'scenario-chip');
|
||
chip.type = 'button';
|
||
chip.textContent = (i + 1) + '. ' + name;
|
||
chip.addEventListener('click', function () {
|
||
/* 同源 iframe 内定位:给 H5 演示页 h2 动态打 id 后 scrollIntoView */
|
||
var iframe = wrap.parentElement ? wrap.parentElement.querySelector('.demo-stage iframe') : null;
|
||
if (!iframe) return;
|
||
try {
|
||
var doc = iframe.contentDocument;
|
||
if (!doc) throw new Error('cross-origin');
|
||
var hs = doc.querySelectorAll('h2');
|
||
for (var k = 0; k < hs.length; k++) {
|
||
var t = (hs[k].textContent || '').replace(/\s+/g, ' ').trim();
|
||
if (t === name) {
|
||
doc.documentElement.style.scrollBehavior = 'smooth';
|
||
hs[k].scrollIntoView({ block: 'start' });
|
||
hs[k].style.transition = 'color .2s';
|
||
hs[k].style.color = '#2F54EB';
|
||
(function (h) { setTimeout(function () { h.style.color = ''; }, 900); })(hs[k]);
|
||
return;
|
||
}
|
||
}
|
||
} catch (e) {
|
||
/* file:// 下 contentDocument 不可访问 → 退化为新窗口打开演示页 */
|
||
window.open(c.files.html, '_blank');
|
||
}
|
||
});
|
||
row.appendChild(chip);
|
||
});
|
||
wrap.appendChild(row);
|
||
return wrap;
|
||
}
|
||
|
||
function buildDemoCard(c) { var card = el('div', 'demo-card');
|
||
var stage = el('div', 'demo-stage');
|
||
var iframe = document.createElement('iframe');
|
||
iframe.src = c.files.html;
|
||
iframe.title = c.slug + ' 代码演示';
|
||
hookIframeTheme(iframe);
|
||
stage.appendChild(iframe);
|
||
card.appendChild(stage);
|
||
|
||
var open = false;
|
||
var codeWrap = null;
|
||
|
||
/* 在线试玩(Playground):编辑 H5 源码,srcdoc 实时重渲染,复位随时还原 */
|
||
var playbar = el('div', 'demo-playbar');
|
||
var playBtn = el('button', 'play-btn', T('▶ 在线试玩'));
|
||
var resetBtn = el('button', 'play-btn ghost hidden', T('复位'));
|
||
var exitBtn = el('button', 'play-btn ghost hidden', T('退出试玩'));
|
||
playBtn.type = resetBtn.type = exitBtn.type = 'button';
|
||
playBtn.title = T('在线编辑 H5 演示源码,实时预览效果(不影响源文件)');
|
||
playbar.appendChild(playBtn);
|
||
playbar.appendChild(resetBtn);
|
||
playbar.appendChild(exitBtn);
|
||
card.appendChild(playbar);
|
||
|
||
var playing = false;
|
||
var ta = null;
|
||
var typeTimer = null;
|
||
function enterPlay() {
|
||
playing = true;
|
||
if (!open) strip.click(); /* 自动展开代码区 */
|
||
if (!codeWrap) { codeWrap = buildCodeArea(c); card.insertBefore(codeWrap, strip); }
|
||
if (!ta) {
|
||
ta = document.createElement('textarea');
|
||
ta.className = 'pg-textarea';
|
||
ta.spellcheck = false;
|
||
ta.value = c.sources.html || '';
|
||
ta.addEventListener('input', function () {
|
||
clearTimeout(typeTimer);
|
||
typeTimer = setTimeout(function () { iframe.srcdoc = ta.value; }, 300);
|
||
});
|
||
}
|
||
var pre = codeWrap.querySelector('pre');
|
||
if (pre) pre.classList.add('hidden');
|
||
codeWrap.appendChild(ta);
|
||
ta.classList.remove('hidden');
|
||
iframe.removeAttribute('src');
|
||
iframe.srcdoc = ta.value;
|
||
playBtn.textContent = T('● 试玩中');
|
||
playBtn.classList.add('hidden');
|
||
resetBtn.classList.remove('hidden');
|
||
exitBtn.classList.remove('hidden');
|
||
}
|
||
function exitPlay() {
|
||
playing = false;
|
||
clearTimeout(typeTimer);
|
||
if (ta) ta.classList.add('hidden');
|
||
var pre = codeWrap && codeWrap.querySelector('pre');
|
||
if (pre) pre.classList.remove('hidden');
|
||
iframe.removeAttribute('srcdoc');
|
||
iframe.src = c.files.html; /* 重新加载原演示 */
|
||
playBtn.textContent = '▶ 在线试玩';
|
||
playBtn.classList.remove('hidden');
|
||
resetBtn.classList.add('hidden');
|
||
exitBtn.classList.add('hidden');
|
||
}
|
||
playBtn.addEventListener('click', enterPlay);
|
||
resetBtn.addEventListener('click', function () {
|
||
ta.value = c.sources.html || '';
|
||
iframe.srcdoc = ta.value;
|
||
});
|
||
exitBtn.addEventListener('click', exitPlay);
|
||
|
||
/* Element 风格展开条:整条居中 */
|
||
var strip = el('div', 'demo-strip');
|
||
strip.setAttribute('title', T('展开代码'));
|
||
strip.innerHTML = '<span class="arr">▼</span>';
|
||
strip.addEventListener('click', function () {
|
||
open = !open;
|
||
strip.classList.toggle('open', open);
|
||
strip.querySelector('.arr').textContent = open ? '▲' : '▼';
|
||
if (open && !codeWrap) { codeWrap = buildCodeArea(c); card.insertBefore(codeWrap, strip); }
|
||
if (codeWrap) codeWrap.classList.toggle('hidden', !open);
|
||
});
|
||
card.appendChild(strip);
|
||
return card;
|
||
}
|
||
|
||
function buildCodeArea(c) {
|
||
var area = el('div', 'demo-code');
|
||
var bar = el('div', 'code-bar');
|
||
var tabs = el('div', 'code-tabs');
|
||
var pre = el('pre');
|
||
var code = el('code');
|
||
pre.appendChild(code);
|
||
area.appendChild(bar);
|
||
area.appendChild(pre);
|
||
bar.appendChild(tabs);
|
||
var copyBtn = el('button', 'code-copy', T('复制源码'));
|
||
copyBtn.type = 'button';
|
||
bar.appendChild(copyBtn);
|
||
|
||
/* 只展示当前框架的源码形态 */
|
||
var kinds = FW_FILES[state.framework] || TAB_ORDER;
|
||
var available = kinds.filter(function (k) { return c.sources[k] != null; });
|
||
if (!available.length) available = TAB_ORDER.filter(function (k) { return c.sources[k] != null; });
|
||
var current = available[0];
|
||
available.forEach(function (k) {
|
||
var b = el('button', null, TAB_LABELS[k]);
|
||
b.type = 'button';
|
||
if (k === current) b.classList.add('active');
|
||
b.addEventListener('click', function () {
|
||
current = k;
|
||
tabs.querySelectorAll('button').forEach(function (x) { x.classList.remove('active'); });
|
||
b.classList.add('active');
|
||
code.innerHTML = highlightCode(c.sources[k] || '');
|
||
copyBtn.textContent = T('复制') + (TAB_LABELS[k] || '');
|
||
});
|
||
tabs.appendChild(b);
|
||
});
|
||
copyBtn.addEventListener('click', function () {
|
||
copyText(c.sources[current] || '', copyBtn, T('已复制 ✓'));
|
||
});
|
||
code.innerHTML = highlightCode(c.sources[current] || '');
|
||
return area;
|
||
}
|
||
|
||
function copyText(text, btn, okMsg) {
|
||
function done() {
|
||
var old = btn.textContent;
|
||
btn.textContent = okMsg;
|
||
setTimeout(function () { btn.textContent = old; }, 1200);
|
||
}
|
||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||
navigator.clipboard.writeText(text).then(done, function () { fallbackCopy(text); done(); });
|
||
} else {
|
||
fallbackCopy(text); done();
|
||
}
|
||
}
|
||
function fallbackCopy(text) {
|
||
var ta = document.createElement('textarea');
|
||
ta.value = text;
|
||
ta.style.position = 'fixed';
|
||
ta.style.opacity = '0';
|
||
document.body.appendChild(ta);
|
||
ta.select();
|
||
try { document.execCommand('copy'); } catch (e) { /* ignore */ }
|
||
document.body.removeChild(ta);
|
||
}
|
||
|
||
/* 设计契约 */
|
||
function buildContract(ct) {
|
||
var wrap = el('div');
|
||
wrap.style.background = 'var(--au-color-card-bg)';
|
||
wrap.style.border = '1px solid var(--au-color-border)';
|
||
wrap.style.borderRadius = 'var(--au-radius-large)';
|
||
wrap.style.padding = 'var(--au-space-20)';
|
||
|
||
if (ct.dims && ct.dims.length) {
|
||
var b1 = el('div', 'contract-block');
|
||
b1.appendChild(el('h3', null, T('变体维度')));
|
||
ct.dims.forEach(function (d) {
|
||
var row = el('div', 'dim-row');
|
||
row.appendChild(el('span', 'dim-name', d.name));
|
||
d.values.forEach(function (v) { row.appendChild(el('span', 'dim-chip', esc(v))); });
|
||
b1.appendChild(row);
|
||
});
|
||
wrap.appendChild(b1);
|
||
}
|
||
|
||
if (ct.variants && ct.variants.length) {
|
||
var b2 = el('div', 'contract-block');
|
||
b2.appendChild(el('h3', null, T('代表变体')));
|
||
var row = el('div', 'dim-row');
|
||
ct.variants.forEach(function (v) { row.appendChild(el('span', 'dim-chip', esc(v))); });
|
||
b2.appendChild(row);
|
||
wrap.appendChild(b2);
|
||
}
|
||
|
||
if (ct.usage && ct.usage.length) {
|
||
var b3 = el('div', 'contract-block');
|
||
b3.appendChild(el('h3', null, T('使用要点')));
|
||
var ul = el('ul');
|
||
ul.style.margin = '0';
|
||
ul.style.paddingLeft = '18px';
|
||
ct.usage.forEach(function (u) { ul.appendChild(el('li', null, esc(u))); });
|
||
b3.appendChild(ul);
|
||
wrap.appendChild(b3);
|
||
}
|
||
|
||
function kvBlock(title, obj) {
|
||
if (!obj) return;
|
||
var keys = Object.keys(obj);
|
||
if (!keys.length) return;
|
||
var b = el('div', 'contract-block');
|
||
b.appendChild(el('h3', null, title));
|
||
var tbl = el('table', 'kv-table');
|
||
keys.forEach(function (k) {
|
||
var tr = el('tr');
|
||
tr.appendChild(el('th', null, esc(k)));
|
||
tr.appendChild(el('td', null, esc(obj[k])));
|
||
tbl.appendChild(tr);
|
||
});
|
||
b.appendChild(tbl);
|
||
wrap.appendChild(b);
|
||
}
|
||
kvBlock(T('结构规格'), ct.structure);
|
||
kvBlock(T('结构解剖'), ct.anatomy);
|
||
|
||
if (ct.doNot && ct.doNot.length) {
|
||
var co1 = el('div', 'callout warn', '<strong>' + T('不要自行发明:') + '</strong>');
|
||
var ul1 = el('ul');
|
||
ct.doNot.forEach(function (x) { ul1.appendChild(el('li', null, esc(x))); });
|
||
co1.appendChild(ul1);
|
||
wrap.appendChild(co1);
|
||
}
|
||
if (ct.unknowns && ct.unknowns.length) {
|
||
var co2 = el('div', 'callout note', '<strong>' + T('规范未明示(待补充):') + '</strong>');
|
||
var ul2 = el('ul');
|
||
ct.unknowns.forEach(function (x) { ul2.appendChild(el('li', null, esc(x))); });
|
||
co2.appendChild(ul2);
|
||
wrap.appendChild(co2);
|
||
}
|
||
return wrap;
|
||
}
|
||
|
||
function contractLink(c) { return c.contractRef ? LIB_ROOT + c.contractRef : null; }
|
||
|
||
/* 实现资源(按当前框架过滤) */
|
||
function buildFilesTable(c) {
|
||
var fw = state.framework;
|
||
var wrap = el('div');
|
||
var note = el('p', 'guide-p');
|
||
note.innerHTML = T('当前框架:<strong>') + esc(FRAMEWORKS[fw]) + T('</strong> —— 仅展示该端需要的文件。切换框架请使用顶部选择器。');
|
||
wrap.appendChild(note);
|
||
|
||
var tbl = el('table', 'res-table');
|
||
var thead = el('thead');
|
||
var trh = el('tr');
|
||
['文件', T('大小'), T('操作')].forEach(function (t) { trh.appendChild(el('th', null, t)); });
|
||
thead.appendChild(trh);
|
||
tbl.appendChild(thead);
|
||
var tbody = el('tbody');
|
||
(FW_FILES[fw] || []).forEach(function (k) {
|
||
if (!c.files[k]) return;
|
||
var tr = el('tr');
|
||
tr.appendChild(el('td', null, kindLabel(k) || k));
|
||
var tdF = el('td');
|
||
tdF.appendChild(el('span', null, esc(c.files[k].replace('../frameworks/', ''))));
|
||
tr.appendChild(tdF);
|
||
var tdS = el('td', 'num', fmtSize(c.sizes ? c.sizes[k] : null));
|
||
tr.appendChild(tdS);
|
||
var tdA = el('td');
|
||
var view = el('a', null, T('新窗口打开'));
|
||
view.href = c.files[k];
|
||
view.target = '_blank';
|
||
tdA.appendChild(view);
|
||
tr.appendChild(tdA);
|
||
tbody.appendChild(tr);
|
||
});
|
||
tbl.appendChild(tbody);
|
||
wrap.appendChild(tbl);
|
||
|
||
/* 其余端入口(折叠提示) */
|
||
var others = TAB_ORDER.filter(function (k) { return (FW_FILES[fw] || []).indexOf(k) < 0 && c.files[k]; });
|
||
if (others.length) {
|
||
var det = el('details', 'other-files');
|
||
det.appendChild(el('summary', null, T('查看其他 ') + others.length + T(' 个端的文件')));
|
||
var ul = el('ul', 'guide-ul');
|
||
others.forEach(function (k) {
|
||
var li = el('li');
|
||
var a = el('a', null, esc(kindLabel(k) + ' · ' + c.files[k].replace('../frameworks/', '')));
|
||
a.href = c.files[k];
|
||
a.target = '_blank';
|
||
li.appendChild(a);
|
||
ul.appendChild(li);
|
||
});
|
||
det.appendChild(ul);
|
||
wrap.appendChild(det);
|
||
}
|
||
return wrap;
|
||
}
|
||
|
||
/* ---------- 主题定制器 ---------- */
|
||
var THEME_DEFAULTS = {
|
||
'--au-color-brand': '#2F54EB',
|
||
'--au-color-brand-hover': '#1D39C4',
|
||
'--au-color-brand-active': '#10239E',
|
||
'--au-color-brand-bg': '#F0F5FF'
|
||
};
|
||
var THEME_META = [
|
||
['--au-color-brand', T('品牌主色')],
|
||
['--au-color-brand-hover', T('主色悬停')],
|
||
['--au-color-brand-active', T('主色点击')],
|
||
['--au-color-brand-bg', T('主色浅背景')]
|
||
];
|
||
/* 主题预设:一键切换品牌色族(点击后仍可用色板微调) */
|
||
var THEME_PRESETS = [
|
||
{ key: 'aurora', label: T('科技蓝'), colors: ['#2F54EB', '#1D39C4', '#10239E', '#F0F5FF'] },
|
||
{ key: 'cyan', label: T('青碧'), colors: ['#13C2C2', '#0E8C8C', '#0A6E6E', '#E6FFFB'] },
|
||
{ key: 'violet', label: T('极夜紫'), colors: ['#722ED1', '#5B1FA8', '#45187F', '#F9F0FF'] },
|
||
{ key: 'sunset', label: T('暖橙'), colors: ['#FA8C16', '#D46B08', '#AD4E00', '#FFF7E6'] },
|
||
{ key: 'magenta', label: T('绯红'), colors: ['#EB2F96', '#C31D7C', '#941661', '#FFF0F6'] },
|
||
{ key: 'forest', label: T('松石绿'), colors: ['#52C41A', '#389E0D', '#237804', '#F6FFED'] }
|
||
];
|
||
function hexMix(hex, other, ratio) {
|
||
if (!/^#[0-9a-fA-F]{6}$/.test(hex) || !/^#[0-9a-fA-F]{6}$/.test(other)) return hex;
|
||
function ch(a, b) { return Math.round(parseInt(a, 16) * (1 - ratio) + parseInt(b, 16) * ratio); }
|
||
return '#' + [ch(hex.slice(1, 3), other.slice(1, 3)), ch(hex.slice(3, 5), other.slice(3, 5)), ch(hex.slice(5, 7), other.slice(5, 7))]
|
||
.map(function (v) { return ('0' + v.toString(16)).slice(-2); }).join('');
|
||
}
|
||
function hexToRgba(hex, alpha) {
|
||
if (!/^#[0-9a-fA-F]{6}$/.test(hex)) return hex;
|
||
return 'rgba(' + parseInt(hex.slice(1, 3), 16) + ',' + parseInt(hex.slice(3, 5), 16) + ',' + parseInt(hex.slice(5, 7), 16) + ',' + alpha + ')';
|
||
}
|
||
var themeState = (function () {
|
||
try {
|
||
var saved = localStorage.getItem('aa-theme');
|
||
if (saved) return Object.assign({}, THEME_DEFAULTS, JSON.parse(saved));
|
||
} catch (e) { /* ignore */ }
|
||
return Object.assign({}, THEME_DEFAULTS);
|
||
})();
|
||
|
||
function focusRingValue() {
|
||
/* 品牌色 20% 透明度作为聚焦光晕 */
|
||
var hex = themeState['--au-color-brand'] || '#2F54EB';
|
||
if (/^#[0-9a-fA-F]{6}$/.test(hex)) return hex + '33';
|
||
return 'rgba(47,84,235,0.2)';
|
||
}
|
||
function injectIframeTheme(ifr) {
|
||
try {
|
||
var d = ifr.contentDocument;
|
||
if (!d || !d.documentElement) return;
|
||
Object.keys(themeState).forEach(function (k) {
|
||
d.documentElement.style.setProperty(k, themeState[k]);
|
||
});
|
||
d.documentElement.style.setProperty('--au-color-focus-ring', focusRingValue());
|
||
} catch (e) { /* file:// 跨源时静默跳过 */ }
|
||
}
|
||
function hookIframeTheme(ifr) {
|
||
ifr.addEventListener('load', function () { injectIframeTheme(ifr); });
|
||
}
|
||
function applyTheme(persist) {
|
||
var dark = document.documentElement.classList.contains('aa-dark');
|
||
/* 暗色模式下品牌色族向白色混合一档,保证链接/选中态在深底上的对比度;
|
||
演示 iframe 仍注入原始值(保持浅色基调) */
|
||
var display = {
|
||
'--au-color-brand': dark ? hexMix(themeState['--au-color-brand'], '#FFFFFF', 0.25) : themeState['--au-color-brand'],
|
||
'--au-color-brand-hover': dark ? hexMix(themeState['--au-color-brand-hover'], '#FFFFFF', 0.25) : themeState['--au-color-brand-hover'],
|
||
'--au-color-brand-active': dark ? hexMix(themeState['--au-color-brand-active'], '#FFFFFF', 0.12) : themeState['--au-color-brand-active']
|
||
};
|
||
display['--au-color-brand-bg'] = dark ? hexToRgba(display['--au-color-brand'], 0.18) : themeState['--au-color-brand-bg'];
|
||
Object.keys(display).forEach(function (k) {
|
||
document.documentElement.style.setProperty(k, display[k]);
|
||
});
|
||
document.documentElement.style.setProperty('--au-color-focus-ring', focusRingValue());
|
||
document.querySelectorAll('iframe').forEach(injectIframeTheme);
|
||
if (persist) {
|
||
try { localStorage.setItem('aa-theme', JSON.stringify(themeState)); } catch (e) { /* ignore */ }
|
||
}
|
||
}
|
||
function bindThemeTools() {
|
||
var panel = $('theme-panel');
|
||
var wrap = $('theme-colors');
|
||
/* 预设行:一键切换品牌色族 */
|
||
var presetWrap = $('theme-presets');
|
||
if (presetWrap) {
|
||
THEME_PRESETS.forEach(function (p) {
|
||
var btn = el('button', 'tp-preset');
|
||
btn.type = 'button';
|
||
btn.title = p.label;
|
||
var sw = el('span', 'tp-swatch');
|
||
sw.style.background = 'linear-gradient(135deg, ' + p.colors[0] + ' 0%, ' + p.colors[2] + ' 100%)';
|
||
btn.appendChild(sw);
|
||
btn.appendChild(el('span', 'tp-preset-label', esc(p.label)));
|
||
btn.addEventListener('click', function () {
|
||
THEME_META.forEach(function (m, i) { themeState[m[0]] = p.colors[i]; });
|
||
try { localStorage.setItem('aa-theme', JSON.stringify(themeState)); } catch (e) { /* ignore */ }
|
||
wrap.querySelectorAll('input[type=color]').forEach(function (inp, i) {
|
||
inp.value = p.colors[i];
|
||
wrap.querySelectorAll('.tp-val')[i].textContent = p.colors[i].toUpperCase();
|
||
});
|
||
applyTheme(true);
|
||
});
|
||
presetWrap.appendChild(btn);
|
||
});
|
||
}
|
||
THEME_META.forEach(function (m) {
|
||
var row = el('div', 'tp-row');
|
||
var input = document.createElement('input');
|
||
input.type = 'color';
|
||
input.value = themeState[m[0]];
|
||
var label = el('span', 'tp-label', esc(m[1]));
|
||
var val = el('span', 'tp-val', esc(themeState[m[0]].toUpperCase()));
|
||
input.addEventListener('input', function () {
|
||
themeState[m[0]] = input.value;
|
||
val.textContent = input.value.toUpperCase();
|
||
applyTheme(true);
|
||
});
|
||
row.appendChild(input);
|
||
row.appendChild(label);
|
||
row.appendChild(val);
|
||
wrap.appendChild(row);
|
||
});
|
||
$('fab-theme').addEventListener('click', function () {
|
||
panel.classList.toggle('hidden');
|
||
});
|
||
$('theme-reset').addEventListener('click', function () {
|
||
themeState = Object.assign({}, THEME_DEFAULTS);
|
||
try { localStorage.removeItem('aa-theme'); } catch (e) { /* ignore */ }
|
||
wrap.querySelectorAll('input[type=color]').forEach(function (inp, i) {
|
||
inp.value = THEME_DEFAULTS[THEME_META[i][0]];
|
||
wrap.querySelectorAll('.tp-val')[i].textContent = THEME_DEFAULTS[THEME_META[i][0]].toUpperCase();
|
||
});
|
||
applyTheme(true);
|
||
});
|
||
$('theme-copy').addEventListener('click', function () {
|
||
var lines = [':root {'];
|
||
THEME_META.forEach(function (m) {
|
||
lines.push(' --' + m[0] + ': ' + themeState[m[0]] + ';');
|
||
});
|
||
lines.push(' --au-color-focus-ring: ' + focusRingValue() + ';');
|
||
lines.push('}');
|
||
copyText(lines.join('\n'), $('theme-copy'), '已复制 ✓');
|
||
});
|
||
/* 深色 / 浅色模式切换 */
|
||
var DARK_TOKENS = {
|
||
'--au-color-page-bg': '#14161C',
|
||
'--au-color-card-bg': '#1D2027',
|
||
'--au-color-table-header-bg': '#23262F',
|
||
'--au-color-disabled-bg': '#262A33',
|
||
'--au-color-tag-green-bg': '#16261A',
|
||
'--au-color-text-title': '#E8EAED',
|
||
'--au-color-text-body': '#C9CDD4',
|
||
'--au-color-text-secondary': '#8B95A7',
|
||
'--au-color-text-placeholder': '#5C6470',
|
||
'--au-color-border': '#2E323C'
|
||
};
|
||
function isDark() { return document.documentElement.classList.contains('aa-dark'); }
|
||
function applyMode() {
|
||
var dark = false;
|
||
try { dark = localStorage.getItem('aa-mode') === 'dark'; } catch (e) { /* ignore */ }
|
||
document.documentElement.classList.toggle('aa-dark', dark);
|
||
$('icon-moon').style.display = dark ? 'none' : '';
|
||
$('icon-sun').style.display = dark ? '' : 'none';
|
||
applyTheme(false); /* 模式切换后重算品牌色的暗色显示值 */
|
||
}
|
||
$('fab-mode').addEventListener('click', function () {
|
||
var dark = !isDark();
|
||
try { localStorage.setItem('aa-mode', dark ? 'dark' : 'light'); } catch (e) { /* ignore */ }
|
||
applyMode();
|
||
applyTheme(false); /* 重新应用品牌色 + 同步 iframe */
|
||
});
|
||
applyMode();
|
||
|
||
/* 悬浮按钮显隐 */
|
||
var topBtn = $('fab-top');
|
||
window.addEventListener('scroll', function () {
|
||
topBtn.style.display = window.scrollY > 400 ? 'flex' : 'none';
|
||
}, { passive: true });
|
||
topBtn.addEventListener('click', function () {
|
||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||
});
|
||
applyTheme(false);
|
||
}
|
||
|
||
/* ---------- 全局搜索 ---------- */
|
||
var searchIndex = comps.map(function (c) {
|
||
var n = splitName(c.name);
|
||
return { c: c, zh: n.zh.toLowerCase(), en: n.en.toLowerCase(), slug: c.slug.toLowerCase(), cat: catLabel(c.category) };
|
||
});
|
||
var srItems = [];
|
||
var srActive = -1;
|
||
|
||
function openSearch() {
|
||
$('search-modal').classList.remove('hidden');
|
||
var inp = $('search-input');
|
||
inp.value = '';
|
||
renderSearchCats();
|
||
doSearch('');
|
||
setTimeout(function () { inp.focus(); }, 0);
|
||
}
|
||
function closeSearch() {
|
||
$('search-modal').classList.add('hidden');
|
||
$('search-results').innerHTML = '';
|
||
srItems = []; srActive = -1;
|
||
}
|
||
/* 命中片段高亮:安全转义后用 <mark> 包裹首个匹配(大小写不敏感) */
|
||
function hiMark(text, q) {
|
||
var t = String(text == null ? '' : text);
|
||
if (!q) return esc(t);
|
||
var lower = t.toLowerCase();
|
||
var idx = lower.indexOf(q);
|
||
if (idx < 0) return esc(t);
|
||
return esc(t.slice(0, idx)) + '<mark>' + esc(t.slice(idx, idx + q.length)) + '</mark>' + esc(t.slice(idx + q.length));
|
||
}
|
||
/* 模糊容错:q 的每个字符按顺序出现在 s 中(子序列匹配),用于无精确命中时回退 */
|
||
function fuzzySubseq(q, s) {
|
||
var i = 0;
|
||
for (var j = 0; j < s.length && i < q.length; j++) {
|
||
if (s[j] === q[i]) i++;
|
||
}
|
||
return i === q.length;
|
||
}
|
||
/* 搜索弹窗顶部的分类过滤 chips(计数固定,初始化时渲染一次) */
|
||
function renderSearchCats() {
|
||
var wrap = $('search-cats');
|
||
if (!wrap) return;
|
||
var counts = {};
|
||
searchIndex.forEach(function (r) { counts[r.c.category] = (counts[r.c.category] || 0) + 1; });
|
||
var mk = function (key, label, cnt) {
|
||
var b = el('button', 'sc-chip' + (state.searchCat === key ? ' active' : ''));
|
||
b.type = 'button';
|
||
b.textContent = label + ' ' + cnt;
|
||
b.addEventListener('click', function () {
|
||
state.searchCat = key;
|
||
renderSearchCats();
|
||
doSearch($('search-input').value);
|
||
$('search-input').focus();
|
||
});
|
||
wrap.appendChild(b);
|
||
};
|
||
wrap.innerHTML = '';
|
||
mk('', T('全部'), searchIndex.length);
|
||
CAT_ORDER.forEach(function (cat) {
|
||
if (counts[cat]) mk(cat, catLabel(cat), counts[cat]);
|
||
});
|
||
}
|
||
function doSearch(q) {
|
||
q = (q || '').trim().toLowerCase();
|
||
var cat = state.searchCat || '';
|
||
var pool = searchIndex.filter(function (r) { return !cat || r.c.category === cat; });
|
||
var hits = pool.filter(function (r) {
|
||
return !q || r.zh.indexOf(q) >= 0 || r.en.indexOf(q) >= 0 || r.slug.indexOf(q) >= 0;
|
||
});
|
||
var fuzzy = false;
|
||
if (q && q.length >= 2 && !hits.length) {
|
||
fuzzy = true;
|
||
hits = pool.filter(function (r) {
|
||
return fuzzySubseq(q, r.zh) || fuzzySubseq(q, r.en) || fuzzySubseq(q, r.slug);
|
||
});
|
||
}
|
||
hits = hits.slice(0, 20);
|
||
var box = $('search-results');
|
||
box.innerHTML = '';
|
||
srItems = []; srActive = -1;
|
||
if (!hits.length) {
|
||
box.appendChild(el('div', 'sr-empty', fuzzy ? T('无匹配组件(已尝试模糊匹配)') : '无匹配组件'));
|
||
return;
|
||
}
|
||
hits.forEach(function (r, i) {
|
||
var n = splitName(r.c.name);
|
||
var item = el('div', 'sr-item' + (i === 0 ? ' hover' : ''));
|
||
item.setAttribute('data-slug', r.c.slug);
|
||
item.innerHTML = '<span class="sr-name"><span class="zh"></span><span class="en"></span></span><span class="sr-cat"></span>';
|
||
/* 主名随语言调换;两种语言都做高亮,命中任何一种输入都能标出 */
|
||
item.querySelector('.zh').innerHTML = hiMark(isEn() ? n.en : n.zh, q);
|
||
item.querySelector('.en').innerHTML = hiMark(isEn() ? n.zh : n.en, q);
|
||
item.querySelector('.sr-cat').textContent = r.cat;
|
||
item.addEventListener('click', function () { location.hash = '#/component/' + r.c.slug; navigate(); });
|
||
box.appendChild(item);
|
||
srItems.push(item);
|
||
});
|
||
srActive = 0;
|
||
}
|
||
function moveActive(delta) {
|
||
if (!srItems.length) return;
|
||
srItems[srActive] && srItems[srActive].classList.remove('hover');
|
||
srActive = (srActive + delta + srItems.length) % srItems.length;
|
||
srItems[srActive].classList.add('hover');
|
||
srItems[srActive].scrollIntoView({ block: 'nearest' });
|
||
}
|
||
function bindSearch() {
|
||
$('search-trigger').addEventListener('click', openSearch);
|
||
$('search-modal').addEventListener('click', function (e) {
|
||
if (e.target === this) closeSearch();
|
||
});
|
||
$('search-input').addEventListener('input', function (e) {
|
||
var q = e.target.value;
|
||
var t0 = performance.now();
|
||
doSearch(q);
|
||
var t1 = performance.now();
|
||
if (window.aaLogger) {
|
||
window.aaLogger.search(q, searchIndex.length, Math.round(t1 - t0));
|
||
}
|
||
});
|
||
document.addEventListener('keydown', function (e) {
|
||
var modalHidden = $('search-modal').classList.contains('hidden');
|
||
if ((e.ctrlKey || e.metaKey) && (e.key === 'k' || e.key === 'K')) {
|
||
e.preventDefault();
|
||
modalHidden ? openSearch() : closeSearch();
|
||
return;
|
||
}
|
||
if (modalHidden) return;
|
||
if (e.key === 'Escape') closeSearch();
|
||
else if (e.key === 'ArrowDown') { e.preventDefault(); moveActive(1); }
|
||
else if (e.key === 'ArrowUp') { e.preventDefault(); moveActive(-1); }
|
||
else if (e.key === 'Enter') {
|
||
var active = srItems[srActive];
|
||
if (active) {
|
||
if (window.aaLogger) {
|
||
var inp = $('search-input');
|
||
window.aaLogger.searchHit(inp ? inp.value : '', (bySlugMap[active.getAttribute('data-slug') || ''] || {}).slug);
|
||
}
|
||
active.click();
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
/* ---------- 启动 ---------- */
|
||
/* ---------- 语言切换 ----------
|
||
切换后整站文案需要重算:顶栏静态 HTML、侧栏、当前页面全部重渲染。
|
||
translateChrome() 负责顶栏/搜索框等 index.html 里的静态文案。 */
|
||
function translateChrome() {
|
||
var NAV_KEY = { home: T('首页'), overview: '组件', guide: '快速开始', design: '设计规范', changelog: '更新日志', agents: T('AI 消费') };
|
||
document.querySelectorAll('.topnav a[data-nav]').forEach(function (a) {
|
||
var k = NAV_KEY[a.getAttribute('data-nav')];
|
||
if (k) a.textContent = T(k);
|
||
});
|
||
var st = document.querySelector('#search-trigger .st-text');
|
||
if (st) st.textContent = T('搜索组件…');
|
||
var fwl = document.querySelector('.fw-label');
|
||
if (fwl) fwl.textContent = T('框架');
|
||
var si = $('search-input');
|
||
if (si) si.placeholder = T('搜索组件(中文 / 英文 / slug)');
|
||
var foot = document.querySelectorAll('.modal-foot span');
|
||
if (foot.length >= 3) {
|
||
foot[0].textContent = T('↑↓ 选择');
|
||
foot[1].textContent = T('Enter 跳转');
|
||
foot[2].textContent = T('Esc 关闭');
|
||
}
|
||
var fwSel = document.querySelector('.fw-select-wrap');
|
||
if (fwSel) fwSel.title = T('选择你的技术栈,文档将只展示对应框架的内容');
|
||
var lt = $('lang-toggle');
|
||
if (lt) {
|
||
lt.querySelector('.lang-zh').classList.toggle('active', !isEn());
|
||
lt.querySelector('.lang-en').classList.toggle('active', isEn());
|
||
}
|
||
var sub = document.querySelector('.logo-sub');
|
||
if (sub) sub.textContent = isEn() ? 'Components' : T('组件库');
|
||
document.title = isEn() ? 'Aurora Admin · Component Library' : 'Aurora Admin · 组件库文档';
|
||
/* FAB 的 title 从固定的中文源串翻译(不能读当前 title 再翻,会累积成 T(T(x))) */
|
||
var FAB_TITLE = { 'fab-mode': T('深色 / 浅色模式切换'), 'fab-top': T('回到顶部') };
|
||
Object.keys(FAB_TITLE).forEach(function (id) {
|
||
var b = $(id);
|
||
if (b) b.title = T(FAB_TITLE[id]);
|
||
});
|
||
}
|
||
|
||
function bindLangToggle() {
|
||
var lt = $('lang-toggle');
|
||
if (!lt) return;
|
||
lt.addEventListener('click', function () {
|
||
I18N.setLang(isEn() ? 'zh' : 'en');
|
||
// 语言变了,缓存的预览/搜索状态不再适用,但路由与框架选择保留
|
||
state.searchCat = '';
|
||
translateChrome();
|
||
renderSidebar();
|
||
navigate({ fwOnly: true }); // 不滚顶,保留阅读位置
|
||
});
|
||
}
|
||
|
||
function init() {
|
||
$('ver').textContent = 'v' + (data.meta.version || '1.0.0');
|
||
state.framework = loadFramework();
|
||
$('fw-select').value = state.framework;
|
||
$('fw-select').addEventListener('change', function (e) {
|
||
state.framework = e.target.value;
|
||
try { localStorage.setItem('aa-framework', state.framework); } catch (err) { /* ignore */ }
|
||
navigate({ fwOnly: true }); /* 切换框架时不滚到顶部 */
|
||
});
|
||
translateChrome();
|
||
bindLangToggle();
|
||
bindSearch();
|
||
bindThemeTools();
|
||
window.addEventListener('hashchange', function () {
|
||
if (window.aaLogger) window.aaLogger.render(state.route.page || 'unknown', { slug: state.route.slug || null });
|
||
navigate();
|
||
});
|
||
if (window.aaLogger) window.aaLogger.render('init', null);
|
||
navigate();
|
||
}
|
||
init();
|
||
})();
|