Files
aurora-admin f1fbfc2ddb
Regression / regression (push) Canceled after 0s
feat(品牌标识): 几何 K 图标(favicon/顶栏标记/theme-color) + 并行会话成果入库
## 品牌标识(本次会话)

起因:品牌此前没有任何图形标识 —— 唯一 favicon 是内联 data-URI 里的字母「A」,
那是 v2.0.0「Aurora Admin → Kole UI」改名漏掉的一处(PC 顶栏也是「A」,
移动端站已是「K」;移动端文档站则完全没有 favicon)。

- 几何:24 网格三个互不接触的笔画(竖 + 两斜),圆头描边;
  描边 2.25 → 16px 标签页尺寸下正好 1.5px = 规范原文「描边1.5px」
- 取色分两套(刻意):favicon 硬编码品牌蓝/白(渲染在浏览器标签栏,不继承 kole-dark);
  顶栏标记走 currentColor(实测暗色下自动转 rgb(20,22,28))
- 新增 theme-color 双条(light #FFFFFF / dark #1C1F26,取 --kole-color-card-bg)
- 修 site/app.js hero 标语 KOLE ADMIN → KOLE UI(改名变形残留)
- 移动端 7 个模板补 favicon(此前计数 0)

验收:门禁 9 条全 OK(site-routing/site-routes/mobile-docs/mobile-site/isolation/
theme/nav/i18n/icons);PC 回归 1464/1464 · 移动端 807/807,各连跑 8 次一致;
两端 favicon 405 字节逐字节一致;PC 站控制台错误 1→0。

## 并行会话成果(本次一并入库)

- 图标系统:2576 图标(TDesign/Element Plus,MIT)+ 11 端注入 + 5 个构建门禁工具
  + IconPreview 预览页 + ICON-SPEC.md 冻结规格
- 移动端平台:47 组件 × 6 端 + 文档站 53 页 + 隔离门禁
- PC 组件:103 个大后台组件 / 组件11 批次
- uni-app:PC 端试点 + 移动端端实现 + 真实编译验证

## 工程

- .gitignore 补 .scratch/ 与 .zcode-preexisting-*.txt(会话中间产物,实测 9.1MB,不入库)
- CHANGELOG 补品牌标识条目
- ROADMAP 登 S8-P4(品牌标识任务包 + og:image/apple-touch-icon 未做部分)
2026-09-21 10:05:48 +08:00

395 lines
16 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>图标预览 · Kole UI</title>
<meta name="description" content="Kole UI 图标预览:2574 个图标的网格浏览、搜索、按名/按代码复制、尺寸调节。">
<meta name="color-scheme" content="light dark">
<link rel="stylesheet" href="../.design_library/kole-ui/colors_and_type.css">
<style>
/* 图标预览(IconPreview)— 对齐 .design_library/kole-ui/specs/组件5.txt「图标预览 IconPreview」:
展示系统所有图标 / 网格布局,支持搜索 / 点击图标复制名称或代码 / 可调整图标大小
自包含样式:不引 site/style.css(本页是工具页,与文档站 SPA 解耦,同 playground.html 的取向)。 */
*, *::before, *::after { box-sizing: border-box; }
body {
margin: 0;
font-family: var(--kole-font-family);
color: var(--kole-color-text-body);
background: var(--kole-color-page-bg);
}
.ip-shell { max-width: 1280px; margin: 0 auto; padding: 24px; }
.ip-head { display: flex; align-items: baseline; justify-content: space-between; gap: 16px; flex-wrap: wrap; margin-bottom: 4px; }
.ip-head h1 { margin: 0; font-size: 22px; color: var(--kole-color-text-title); }
.ip-head .back { font-size: 13px; color: var(--kole-color-brand); text-decoration: none; }
.ip-head .back:hover { text-decoration: underline; }
.ip-sub { margin: 0 0 18px; font-size: 13px; color: var(--kole-color-text-secondary); }
/* ---------- 工具条 ---------- */
.ip-toolbar {
display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
padding: 12px 16px; margin-bottom: 16px;
background: var(--kole-color-card-bg);
border: 1px solid var(--kole-color-border);
border-radius: var(--kole-radius-large);
}
.ip-field { display: flex; align-items: center; gap: 6px; font-size: 13px; color: var(--kole-color-text-secondary); }
.ip-input {
height: 32px; min-width: 220px; padding: 0 10px;
font: inherit; font-size: 13px;
color: var(--kole-color-text-body);
background: var(--kole-color-card-bg);
border: 1px solid var(--kole-color-border);
border-radius: var(--kole-radius-base);
}
.ip-input:focus-visible { outline: 2px solid var(--kole-color-brand); outline-offset: 1px; border-color: var(--kole-color-brand); }
/* size 档位:对齐图标 API 的四档关键字(ICON-SPEC §4.2) */
.ip-sizes { display: inline-flex; border: 1px solid var(--kole-color-border); border-radius: var(--kole-radius-base); overflow: hidden; }
.ip-size {
height: 32px; padding: 0 12px; border: 0; border-inline-end: 1px solid var(--kole-color-border);
background: var(--kole-color-card-bg); color: var(--kole-color-text-body);
font: inherit; font-size: 13px; cursor: pointer;
}
.ip-size:last-child { border-inline-end: 0; }
.ip-size:hover { background: var(--kole-color-table-header-bg); }
.ip-size[aria-pressed="true"] { background: var(--kole-color-brand); color: var(--kole-color-text-inverse); }
.ip-size:focus-visible { outline: 2px solid var(--kole-color-brand); outline-offset: -2px; }
.ip-toggle { display: inline-flex; align-items: center; gap: 6px; font-size: 13px; color: var(--kole-color-text-body); cursor: pointer; }
.ip-count { margin-inline-start: auto; font-size: 13px; color: var(--kole-color-text-secondary); font-family: var(--kole-font-family-num); }
/* ---------- 分组 ---------- */
.ip-group { margin-bottom: 20px; }
.ip-group > summary {
cursor: pointer; padding: 8px 4px; font-size: 14px; font-weight: 600;
color: var(--kole-color-text-title); list-style: none; display: flex; align-items: center; gap: 8px;
}
.ip-group > summary::-webkit-details-marker { display: none; }
.ip-group > summary::before { content: '▸'; color: var(--kole-color-text-secondary); font-size: 12px; }
.ip-group[open] > summary::before { content: '▾'; }
.ip-group .badge {
font-family: var(--kole-font-family-num); font-size: 12px; font-weight: 400;
padding: 1px 7px; border-radius: var(--kole-radius-small);
background: var(--kole-color-table-header-bg); color: var(--kole-color-text-secondary);
}
/* ---------- 网格 ---------- */
.ip-grid {
display: grid; gap: 8px;
grid-template-columns: repeat(auto-fill, minmax(132px, 1fr));
padding: 4px 0 8px;
}
.ip-cell {
display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 8px;
min-height: 96px; padding: 12px 8px;
background: var(--kole-color-card-bg);
border: 1px solid var(--kole-color-border);
border-radius: var(--kole-radius-medium);
cursor: pointer; text-align: center;
font: inherit; color: inherit;
}
.ip-cell:hover { border-color: var(--kole-color-brand); background: var(--kole-color-brand-bg); }
.ip-cell:focus-visible { outline: 2px solid var(--kole-color-brand); outline-offset: 2px; }
.ip-cell .glyph { display: flex; align-items: center; justify-content: center; color: var(--kole-color-text-title); }
.ip-cell .glyph svg { width: 1em; height: 1em; display: block; }
.ip-cell .label {
font-size: 11px; line-height: 1.35; color: var(--kole-color-text-secondary);
word-break: break-all; font-family: var(--kole-font-family-num);
}
.ip-cell.is-copied { border-color: var(--kole-color-success); background: color-mix(in srgb, var(--kole-color-success) 8%, var(--kole-color-card-bg)); }
.ip-cell.is-copied .label { color: var(--kole-color-success); }
.ip-empty { padding: 40px 16px; text-align: center; color: var(--kole-color-text-secondary); font-size: 14px; }
/* 提示条:复制反馈(同 playground 的做法,不引第三方 toast) */
.ip-toast {
position: fixed; inset-inline: 0; bottom: 24px; margin: 0 auto; width: fit-content; max-width: 90vw;
padding: 9px 16px; border-radius: var(--kole-radius-base);
background: var(--kole-color-text-title); color: var(--kole-color-text-inverse);
font-size: 13px; opacity: 0; transition: opacity .18s; pointer-events: none; z-index: 50;
}
.ip-toast.show { opacity: 1; }
.ip-note {
margin: 20px 0 0; padding: 12px 16px; font-size: 12px; line-height: 1.7;
color: var(--kole-color-text-secondary);
background: var(--kole-color-table-header-bg);
border-radius: var(--kole-radius-base);
}
.ip-note code { font-family: var(--kole-font-family-num); }
@media (prefers-reduced-motion: reduce) { .ip-toast { transition: none; } }
</style>
</head>
<body>
<div class="ip-shell">
<div class="ip-head">
<h1>图标预览</h1>
<a class="back" href="./">← 返回文档站</a>
</div>
<p class="ip-sub">2574 个图标 · 点击任意图标复制名称(Shift + 点击复制内联 SVG 代码)</p>
<div class="ip-toolbar">
<label class="ip-field">
搜索
<input id="ip-q" class="ip-input" type="search" placeholder="图标名,如 search / 箭头 / chart" autocomplete="off">
</label>
<div class="ip-sizes" role="group" aria-label="图标大小">
<button class="ip-size" type="button" data-size="16" aria-pressed="false">16</button>
<button class="ip-size" type="button" data-size="20" aria-pressed="true">20</button>
<button class="ip-size" type="button" data-size="24" aria-pressed="false">24</button>
<button class="ip-size" type="button" data-size="32" aria-pressed="false">32</button>
</div>
<label class="ip-toggle"><input id="ip-stroke-only" type="checkbox"> 仅线性</label>
<span id="ip-count" class="ip-count">—</span>
</div>
<div id="ip-groups"></div>
<p id="ip-empty" class="ip-empty" hidden>没有匹配的图标。试试更短的关键词,或清空搜索。</p>
<p class="ip-note">
图标名直接用在组件上:<code>&lt;KoleIcon name="search" /&gt;</code>(PC)与
<code>&lt;Icon name="search" /&gt;</code>(移动端)。<br>
数据真源 <code>.design_library/kole-ui/icons/</code>;来源与许可见
<code>ATTRIBUTION.md</code>(TDesign + Element Plus,MIT)。描边宽度统一 1.5px。
</p>
</div>
<div id="ip-toast" class="ip-toast" role="status" aria-live="polite"></div>
<script>
/* 图标预览页 —— 自包含实现(零依赖、无外部脚本),同 playground.html 的取向。
*
* 数据分两段加载:
* 1) icons.json(51KB)—— 名字 / 层 / 分组,首屏立即出网格骨架;
* 2) registry.json(670KB)—— 路径数据,按需装载,装载前网格显示占位。
* 为什么不合并:合并后首屏要等 670KB,而用户往往只搜几个图标。
*/
(function () {
var SIZE = 20;
var DATA = null; /* registry:{ icons: { name: 'S…|F…' } } */
var INDEX = null; /* icons.json:{ names:[], tier:[], group:[], groups:[] } */
var GROUPS = []; /* 分组名,来自 icons.json */
function $(id) { return document.getElementById(id); }
/* ---------- 渲染 ---------- */
function viewBoxOf(name, raw) {
var vb = (DATA && DATA.viewBox) || {};
/* 例外表按 canonical 名给;别名与 canonical 路径串相同,按路径反查覆盖别名 */
if (vb[name]) return vb[name];
if (raw && DATA && DATA.byPath && DATA.byPath[raw]) return DATA.byPath[raw];
return '0 0 24 24';
}
function svgFor(name, sizePx) {
var raw = DATA.icons[name];
if (!raw) return null;
var sw = (DATA && DATA.strokeWidth) || 1.5;
var segs = raw.split('|');
var body = '';
for (var i = 0; i < segs.length; i++) {
var seg = segs[i];
var mode = seg.charAt(0) === 'S' ? 'stroke' : 'fill';
var d = seg.slice(1);
body += mode === 'stroke'
? '<path d="' + d + '" fill="none" stroke="currentColor" stroke-width="' + sw + '" stroke-linecap="round" stroke-linejoin="round"/>'
: '<path d="' + d + '" fill="currentColor"/>';
}
return '<svg viewBox="' + viewBoxOf(name, raw) + '" width="' + sizePx + '" height="' + sizePx +
'" aria-hidden="true" focusable="false">' + body + '</svg>';
}
function buildByPath() {
/* 路径串 → viewBox 反查表(覆盖别名) */
var vb = (DATA && DATA.viewBox) || {};
var icons = DATA.icons;
var out = {};
for (var k in vb) {
if (icons[k] && !out[icons[k]]) out[icons[k]] = vb[k];
}
DATA.byPath = out;
}
function matches(name, q) {
if (!q) return true;
return name.toLowerCase().indexOf(q) >= 0;
}
/* "仅线性"判据:该图标没有任何填充段 */
function isStrokeOnly(name) {
var raw = DATA && DATA.icons[name];
if (!raw) return true;
return raw.split('|').every(function (s) { return s.charAt(0) === 'S'; });
}
function render() {
var q = ($('ip-q').value || '').trim().toLowerCase();
var strokeOnly = $('ip-stroke-only').checked;
var host = $('ip-groups');
host.textContent = '';
if (!DATA) {
/* 路径数据还没到:先用索引渲染名字(图标位留空),避免白屏 */
var pending = indexNames().filter(function (n) { return matches(n, q); });
$('ip-count').textContent = pending.length + ' / ' + indexNames().length + '(加载图标数据…)';
return;
}
var total = 0;
var shown = 0;
GROUPS.forEach(function (g, gi) {
var names = [];
for (var i = 0; i < INDEX.names.length; i++) {
if (INDEX.group[i] !== gi) continue;
total++;
var n = INDEX.names[i];
if (!matches(n, q)) continue;
if (strokeOnly && !isStrokeOnly(n)) continue;
names.push(n);
}
if (!names.length) return;
shown += names.length;
var d = document.createElement('details');
/* 搜索时展开全部(否则用户搜到了却看不见);无搜索词时只展开前两组 */
d.className = 'ip-group';
d.open = !!q || gi < 2;
var sum = document.createElement('summary');
sum.innerHTML = g + ' <span class="badge">' + names.length + '</span>';
d.appendChild(sum);
var grid = document.createElement('div');
grid.className = 'ip-grid';
names.forEach(function (name) {
var cell = document.createElement('button');
cell.type = 'button';
cell.className = 'ip-cell';
cell.setAttribute('data-name', name);
cell.setAttribute('aria-label', '图标 ' + name + ',点击复制名称');
var glyph = document.createElement('span');
glyph.className = 'glyph';
glyph.innerHTML = svgFor(name, SIZE) || '';
var lab = document.createElement('span');
lab.className = 'label';
lab.textContent = name;
cell.appendChild(glyph);
cell.appendChild(lab);
grid.appendChild(cell);
});
d.appendChild(grid);
host.appendChild(d);
});
$('ip-count').textContent = shown + ' / ' + total;
$('ip-empty').hidden = shown > 0;
}
function indexNames() { return (INDEX && INDEX.names) || []; }
/* ---------- 复制 ---------- */
var toastTimer = null;
function toast(msg) {
var t = $('ip-toast');
t.textContent = msg;
t.classList.add('show');
if (toastTimer) clearTimeout(toastTimer);
toastTimer = setTimeout(function () { t.classList.remove('show'); }, 1600);
}
function copyText(text) {
/* navigator.clipboard 需要安全上下文(https / localhost);http 局域网下回退 execCommand */
if (navigator.clipboard && window.isSecureContext) {
return navigator.clipboard.writeText(text);
}
return new Promise(function (resolve, reject) {
var ta = document.createElement('textarea');
ta.value = text;
ta.setAttribute('readonly', '');
ta.style.position = 'fixed';
ta.style.opacity = '0';
document.body.appendChild(ta);
ta.select();
try { document.execCommand('copy') ? resolve() : reject(new Error('execCommand failed')); }
catch (e) { reject(e); }
finally { document.body.removeChild(ta); }
});
}
function flash(cell) {
cell.classList.add('is-copied');
setTimeout(function () { cell.classList.remove('is-copied'); }, 700);
}
/* ---------- 事件 ---------- */
$('ip-groups').addEventListener('click', function (e) {
var cell = e.target.closest('.ip-cell');
if (!cell) return;
var name = cell.getAttribute('data-name');
/* Shift + 点击复制内联 SVG 代码(规范要求「复制名称或代码」) */
var payload = e.shiftKey ? (svgFor(name, SIZE) || name) : name;
copyText(payload).then(function () {
flash(cell);
toast(e.shiftKey ? '已复制 SVG:' + name : '已复制名称:' + name);
}).catch(function () {
toast('复制失败,请手动选中:' + name);
});
});
$('ip-q').addEventListener('input', render);
$('ip-stroke-only').addEventListener('change', render);
Array.prototype.forEach.call(document.querySelectorAll('.ip-size'), function (btn) {
btn.addEventListener('click', function () {
SIZE = Number(btn.getAttribute('data-size'));
Array.prototype.forEach.call(document.querySelectorAll('.ip-size'), function (b) {
b.setAttribute('aria-pressed', b === btn ? 'true' : 'false');
});
Array.prototype.forEach.call(document.querySelectorAll('.ip-cell'), function (c) {
var g = c.querySelector('.glyph');
if (!g) return;
var svg = g.querySelector('svg');
if (svg) { svg.setAttribute('width', SIZE); svg.setAttribute('height', SIZE); }
});
});
});
/* ---------- 装载 ---------- */
function loadIndex() {
return fetch('icons.json').then(function (r) { return r.ok ? r.json() : null; });
}
function loadRegistry() {
return fetch('../.design_library/kole-ui/icons/registry.json').then(function (r) {
return r.ok ? r.json() : null;
});
}
loadIndex().then(function (idx) {
if (!idx) { $('ip-count').textContent = '索引加载失败'; return; }
INDEX = idx;
GROUPS = idx.groups || [];
render();
return loadRegistry().then(function (reg) {
if (!reg) { $('ip-count').textContent = '图标数据加载失败'; return; }
/* registry.json 的键是 icons(与 build-icons.mjs 产物一致) */
DATA = { icons: reg.icons || {}, viewBox: reg.viewBox || {}, strokeWidth: reg.strokeWidth || 1.5 };
if (!Object.keys(DATA.viewBox).length) {
/* viewBox 例外表在 registry 里可能只含非默认项;缺失时按 24 网格渲染 */
}
buildByPath();
render();
});
}).catch(function () {
$('ip-count').textContent = '加载失败';
});
}());
</script>
</body>
</html>