Files
aurora-admin/frameworks/table.html
T
aurora-admin 382ca79b66 v1.4.0: 组件层全面令牌化(设计系统根基修复)
问题
- 79 个组件的 844 个颜色属性里,只有 2 个(0.2%)引用令牌,
  788 个(93.4%)硬编码 hex
- 后果:改令牌组件不跟着变、主题定制器对组件无效、For Agents 页写的
  「所有色值必须取自 au-* 变量」形同虚设
- 这是本项目第五次「声称≠实现」,且动摇了设计系统的根本

两层令牌化(此前只改了从未被加载的文件,走了弯路)
- 组件样式表 frameworks/*.css:915 处 / 78 文件
- 演示页内嵌 <style>(实际生效的样式层):287 处 / 51 文件
- 内嵌样式层是决定性的:演示页只 link colors_and_type.css + 自己的 CSS,
  而多数页面把关键样式写在 <style> 块里 —— 只改 CSS 文件不生效

安全策略
- 只替换颜色属性值位置的 hex(color/background*/border*/outline*/fill/stroke)
- 跳过 rgba()/渐变/url() 复合值;不动 SVG 属性与 JS 数据(色板数组)
- 歧义色值按属性语义消解(#FFFFFF 作 color→text-inverse,作 background→card-bg)
- 幂等,可重复运行

补齐令牌语义(71 → 74)
- --au-color-text-tertiary #595959(规范未定义、实现补齐,7.00:1)
- --au-color-text-disabled #BFBFBF(规范定义于组件1/2.txt;WCAG 1.4.3 豁免)
- --au-color-border-strong #F0F0F0(规范未定义、实现补齐)
- 同步 legacy 别名;修复 colors_and_type.css 一处重复注释起始行

修复
- Countdown 首帧空白:setInterval(render, 1000) 首次执行要等 1 秒,
  期间容器为空。改为定义函数后立即执行一次再挂 interval

验证(本轮核心验收)
- 逐组件验证「改令牌 → 元素是否跟着变」:79/79 全部响应
- 过程中修正两处测量方法缺陷(初测 40/79 是误报):
  ① 选择器 .demo * 在无 .demo 容器的页面抓不到元素
  ② 探针令牌选错 —— Breadcrumb 用 text-tertiary 而只改了 text-body
- CSS 结构完整:158 个文件括号平衡、var(--*) 引用全部有定义
- 回归保持 100%:961 断言 / 0 失败 / 79 页全通过,三次连跑一致
2026-09-11 17:29:30 +08:00

144 lines
6.5 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Aurora Admin · Table (H5)</title>
<link rel="stylesheet" href="../.design_library/aurora-admin/colors_and_type.css">
<style>
/* 对齐 components.css 与 table.json 契约 */
.demo { padding: 24px; background: var(--au-color-page-bg); color: var(--au-color-text-body); font-family: var(--font-family); }
h1 { font-size: 20px; margin: 0 0 4px; }
.sub { color: var(--au-color-text-secondary); margin: 0 0 24px; font-size: 12px; }
.aa-table { width: 100%; border: 1px solid var(--au-color-border); border-radius: 8px; overflow: hidden; background: var(--au-color-card-bg); position: relative; }
.aa-table table { width: 100%; border-collapse: collapse; font-size: 14px; color: var(--au-color-text-body); }
.aa-table thead th {
background: var(--au-color-table-header-bg); color: var(--au-color-text-body); font-weight: 600; height: 48px; text-align: left;
padding: 0 16px; border-bottom: 1px solid var(--au-color-border); white-space: nowrap;
}
.aa-table tbody td { height: 48px; padding: 0 16px; border-bottom: 1px solid var(--au-color-border); text-align: left; }
.aa-table tbody tr:last-child td { border-bottom: none; }
.aa-table tbody tr:hover { background: var(--au-color-page-bg); }
.aa-table tbody tr.is-selected { background: var(--au-color-brand-bg); }
.aa-table th.align-right, .aa-table td.align-right { text-align: right; }
.aa-table .sortable { cursor: pointer; user-select: none; }
.aa-table .sort-ind { color: var(--au-color-text-placeholder); margin-left: 4px; }
.aa-table .sort-ind.active { color: var(--au-color-brand); }
.aa-table .col-action { text-align: right; }
.aa-table .link-btn { color: var(--au-color-brand); background: none; border: none; cursor: pointer; font-size: 14px; padding: 0; }
.aa-table .link-btn + .link-btn { margin-left: 8px; }
.aa-table .link-btn.danger { color: var(--au-color-error); }
.aa-table .checkbox { width: 16px; height: 16px; cursor: pointer; vertical-align: middle; }
.aa-table .empty { padding: 48px 16px; text-align: center; color: var(--au-color-text-placeholder); font-size: 14px; }
.aa-table .loading-mask {
position: absolute; inset: 0; background: rgba(255,255,255,0.7); display: flex;
align-items: center; justify-content: center; z-index: 2;
}
.aa-table .spinner {
width: 20px; height: 20px; border: 2px solid var(--au-color-brand); border-top-color: transparent;
border-radius: 50%; animation: aa-tbl-spin .6s linear infinite;
}
@keyframes aa-tbl-spin { to { transform: rotate(360deg); } }
</style>
</head>
<body>
<div class="demo">
<h1>Aurora Admin · Table 组件(纯 H5)</h1>
<p class="sub">对齐 table.json 契约:表头/行高48px、分割线 #E8ECF1、悬停 #F5F7FA、选中 #F0F5FF、单元格左右16px、数字右对齐、操作列右对齐、加载/空态</p>
<label style="font-size:13px;color:var(--color-text-secondary);">
<input type="checkbox" id="toggleLoading"> 模拟加载态
</label>
<div class="aa-table" role="table" style="margin-top:12px">
<table>
<thead>
<tr>
<th style="width:48px"><input type="checkbox" class="checkbox" id="selectAll"></th>
<th>客户名称</th>
<th>负责人</th>
<th class="align-right sortable" id="sortAmount">合同金额<span class="sort-ind" id="sortInd">↕</span></th>
<th>状态</th>
<th class="col-action">操作</th>
</tr>
</thead>
<tbody id="tbody"></tbody>
</table>
<div class="loading-mask" id="loading" hidden><span class="spinner"></span></div>
</div>
</div>
<script>
const data = [
{ id: 1, name: '北京云图科技', owner: '张伟', amount: 128000, status: '已签约' },
{ id: 2, name: '上海恒创', owner: '李娜', amount: 86000, status: '跟进中' },
{ id: 3, name: '广州锐捷', owner: '王芳', amount: 254000, status: '已签约' },
{ id: 4, name: '深圳智联', owner: '刘强', amount: 43000, status: '待回款' }
];
let selected = new Set();
let sortOrder = null; // 'asc' | 'desc' | null
function rows() {
if (!sortOrder) return data;
return [...data].sort((a, b) => sortOrder === 'asc' ? a.amount - b.amount : b.amount - a.amount);
}
function render() {
const tbody = document.getElementById('tbody');
if (!rows().length) {
tbody.innerHTML = `<tr><td class="empty" colspan="6">暂无数据</td></tr>`;
return;
}
tbody.innerHTML = rows().map(r => {
const sel = selected.has(r.id) ? 'checked' : '';
const cls = selected.has(r.id) ? ' class="is-selected"' : '';
return `<tr${cls} data-id="${r.id}">
<td><input type="checkbox" class="checkbox row-cb" data-id="${r.id}" ${sel}></td>
<td>${r.name}</td>
<td>${r.owner}</td>
<td class="align-right">¥${r.amount.toLocaleString()}</td>
<td>${r.status}</td>
<td class="col-action">
<button class="link-btn" data-act="edit" data-id="${r.id}">编辑</button>
<button class="link-btn danger" data-act="del" data-id="${r.id}">删除</button>
</td>
</tr>`;
}).join('');
}
document.getElementById('tbody').addEventListener('change', (e) => {
if (e.target.classList.contains('row-cb')) {
const id = +e.target.dataset.id;
e.target.checked ? selected.add(id) : selected.delete(id);
render(); syncSelectAll();
}
});
document.getElementById('tbody').addEventListener('click', (e) => {
const b = e.target.closest('[data-act]');
if (b) alert(`${b.dataset.act} 行 id=${b.dataset.id}`);
});
document.getElementById('selectAll').addEventListener('change', (e) => {
selected = e.target.checked ? new Set(data.map(d => d.id)) : new Set();
render(); syncSelectAll();
});
function syncSelectAll() {
const sa = document.getElementById('selectAll');
sa.checked = selected.size === data.length && data.length > 0;
sa.indeterminate = selected.size > 0 && selected.size < data.length;
}
document.getElementById('sortAmount').addEventListener('click', () => {
sortOrder = sortOrder === null ? 'asc' : sortOrder === 'asc' ? 'desc' : null;
const ind = document.getElementById('sortInd');
ind.textContent = sortOrder === 'asc' ? '↑' : sortOrder === 'desc' ? '↓' : '↕';
ind.classList.toggle('active', sortOrder !== null);
render();
});
document.getElementById('toggleLoading').addEventListener('change', (e) => {
document.getElementById('loading').hidden = !e.target.checked;
});
render(); syncSelectAll();
</script>
</body>
</html>