Files
aurora-admin/site/scenario/user-management.html
T
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

242 lines
14 KiB
HTML

<!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 79 个组件搭出的完整后台页面示范:筛选表单 + 数据表格 + 分页 + 弹窗 + 消息反馈。">
<link rel="stylesheet" href="../../.design_library/kole-ui/colors_and_type.css">
<link rel="stylesheet" href="../../.design_library/kole-ui/components.css">
<style>
/* 场景页布局:仅排版类样式,组件视觉全部来自 components.css 与令牌 */
* { box-sizing: border-box; }
body { margin: 0; background: var(--kole-color-page-bg); font-family: var(--kole-font-family); color: var(--kole-color-text-body); font-size: var(--kole-type-body-size); line-height: var(--kole-type-body-height); }
.scene-topbar { height: 56px; background: var(--kole-color-card-bg); border-bottom: 1px solid var(--kole-color-border); display: flex; align-items: center; padding: 0 24px; gap: 16px; position: sticky; top: 0; z-index: 10; }
.scene-logo { width: 28px; height: 28px; border-radius: 8px; background: var(--kole-color-brand); color: var(--kole-color-text-inverse); font-weight: 600; display: flex; align-items: center; justify-content: center; }
.scene-title { font-size: 16px; font-weight: 600; color: var(--kole-color-text-title); }
.scene-topnav { margin-left: auto; display: flex; gap: 20px; font-size: 13px; color: var(--kole-color-text-secondary); }
.scene-topnav .on { color: var(--kole-color-brand); font-weight: 500; }
.scene-main { max-width: 1200px; margin: 0 auto; padding: 24px; }
.scene-h { display: flex; align-items: center; justify-content: space-between; margin-bottom: 16px; }
.scene-h h1 { font-size: 20px; line-height: 28px; font-weight: 600; color: var(--kole-color-text-title); margin: 0; }
.scene-h .sub { font-size: 12px; color: var(--kole-color-text-secondary); margin-top: 4px; }
.panel { background: var(--kole-color-card-bg); border: 1px solid var(--kole-color-border); border-radius: 8px; padding: 16px; margin-bottom: 16px; }
.filter-grid { display: grid; grid-template-columns: repeat(4, 1fr) auto; gap: 12px; align-items: end; }
.field label { display: block; font-size: 12px; color: var(--kole-color-text-secondary); margin-bottom: 4px; }
.table-wrap table { width: 100%; border-collapse: collapse; }
.table-wrap th, .table-wrap td { text-align: left; padding: 10px 12px; border-bottom: 1px solid var(--kole-color-border); font-size: 13px; }
.table-wrap th { background: var(--kole-color-table-header-bg); color: var(--kole-color-text-secondary); font-weight: 500; }
.table-wrap tbody tr:hover { background: var(--kole-color-brand-bg); }
.table-foot { display: flex; align-items: center; justify-content: space-between; margin-top: 12px; font-size: 12px; color: var(--kole-color-text-secondary); }
.user-cell { display: flex; align-items: center; gap: 8px; }
.avatar { width: 28px; height: 28px; border-radius: 50%; background: var(--kole-color-brand-bg); color: var(--kole-color-brand); display: flex; align-items: center; justify-content: center; font-size: 12px; font-weight: 500; }
.money { font-family: var(--kole-font-family-num); font-variant-numeric: tabular-nums; }
.modal-mask { position: fixed; inset: 0; background: var(--kole-color-mask); display: none; align-items: center; justify-content: center; z-index: 100; }
.modal-mask.open { display: flex; }
.modal-box { width: 440px; background: var(--kole-color-card-bg); border-radius: 8px; box-shadow: var(--kole-shadow-high); }
.modal-head { padding: 16px 20px; font-size: 16px; font-weight: 600; color: var(--kole-color-text-title); border-bottom: 1px solid var(--kole-color-border); }
.modal-body { padding: 20px; display: grid; gap: 14px; }
.modal-body label { display: block; font-size: 12px; color: var(--kole-color-text-secondary); margin-bottom: 4px; }
.modal-body input, .modal-body select { width: 100%; }
.modal-foot { padding: 12px 20px; border-top: 1px solid var(--kole-color-border); display: flex; justify-content: flex-end; gap: 8px; }
.toast { position: fixed; top: 72px; left: 50%; transform: translateX(-50%) translateY(-8px); background: var(--kole-color-success); color: #fff; font-size: 13px; padding: 8px 16px; border-radius: 6px; opacity: 0; pointer-events: none; transition: all .2s; z-index: 200; }
.toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }
.back { font-size: 12px; color: var(--kole-color-text-secondary); text-decoration: none; }
.back:hover { color: var(--kole-color-brand); }
.sc-tag { display:inline-block; font-size:12px; line-height:20px; padding:0 8px; border-radius:4px; }
.sc-tag-green { background:var(--kole-color-tag-green-bg); color:var(--kole-color-success); }
.sc-tag-default { background:var(--kole-color-table-header-bg); color:var(--kole-color-text-secondary); }
.sc-pager { display:flex; gap:6px; }
.sc-page { min-width:28px; height:28px; border:1px solid var(--kole-color-border); background:var(--kole-color-card-bg); color:var(--kole-color-text-body); border-radius:4px; cursor:pointer; font-family:var(--kole-font-family-num); font-size:13px; }
.sc-page.active { background:var(--kole-color-brand); border-color:var(--kole-color-brand); color:var(--kole-color-text-inverse); }
</style>
</head>
<body>
<header class="scene-topbar">
<div class="scene-logo">A</div>
<div class="scene-title">Kole UI · 管理控制台</div>
<nav class="scene-topnav"><span class="on">用户管理</span><span>角色权限</span><span>系统设置</span></nav>
</header>
<main class="scene-main">
<div class="scene-h">
<div>
<h1>用户管理</h1>
<div class="sub">场景示范页:全部视觉来自 colors_and_type.css 令牌 + components.css 组件样式</div>
</div>
<a class="back" href="../index.html#/overview">← 返回组件文档</a>
</div>
<section class="panel">
<div class="filter-grid">
<div class="field"><label>关键词</label><input class="kole-input" id="f-key" placeholder="姓名 / 邮箱"></div>
<div class="field"><label>部门</label>
<select class="kole-input" id="f-dept">
<option value="">全部</option><option>研发部</option><option>设计部</option><option>市场部</option>
</select>
</div>
<div class="field"><label>状态</label>
<select class="kole-input" id="f-status">
<option value="">全部</option><option>启用</option><option>禁用</option>
</select>
</div>
<div class="field"><label>注册区间</label><input class="kole-input kole-input-sm" id="f-num" placeholder="2024 - 2026"></div>
<button class="btn btn-primary btn-md" id="btn-search">查询</button>
</div>
</section>
<section class="panel">
<div style="display:flex; justify-content:space-between; margin-bottom:12px;">
<div style="display:flex; gap:8px;">
<button class="btn btn-primary btn-md" id="btn-new">新建用户</button>
<button class="btn btn-default btn-md" id="btn-export">导出</button>
<button class="btn btn-text btn-md" style="color:var(--color-error)" id="btn-ban">批量禁用</button>
</div>
<span style="font-size:12px; color:var(--kole-color-text-secondary);" id="count-hint"></span>
</div>
<div class="table-wrap">
<table>
<thead><tr>
<th style="width:36px;"><input type="checkbox" id="check-all"></th>
<th>用户</th><th>部门</th><th>角色</th><th>邮箱</th><th style="text-align:right;">剩余额度</th><th>状态</th><th style="width:120px;">操作</th>
</tr></thead>
<tbody id="tbody"></tbody>
</table>
</div>
<div class="table-foot">
<span>共 <span id="total-n">0</span> 条</span>
<div class="sc-pager" id="pager"></div>
</div>
</section>
</main>
<div class="modal-mask" id="modal">
<div class="modal-box" role="dialog" aria-modal="true" aria-label="新建用户">
<div class="modal-head">新建用户</div>
<div class="modal-body">
<div><label>姓名</label><input class="kole-input" id="m-name" placeholder="请输入姓名"></div>
<div><label>邮箱</label><input class="kole-input" id="m-mail" placeholder="name@company.com"></div>
<div><label>部门</label>
<select class="kole-input" id="m-dept"><option>研发部</option><option>设计部</option><option>市场部</option></select>
</div>
<div><label>角色</label>
<select class="kole-input" id="m-role"><option>普通成员</option><option>管理员</option></select>
</div>
</div>
<div class="modal-foot">
<button class="btn btn-default btn-md" id="m-cancel">取消</button>
<button class="btn btn-primary btn-md" id="m-ok">提交</button>
</div>
</div>
</div>
<div class="toast" id="toast"></div>
<script>
(function () {
'use strict';
var DEPTS = ['研发部', '设计部', '市场部'];
var db = [];
var NAMES = ['沈知言', '顾清晏', '林晚舟', '陆则鸣', '苏未央', '江与城', '温叙白', '程既明', '阮青梧', '孟山月', '裴照野', '宋听澜'];
for (var i = 0; i < 23; i++) {
db.push({
name: NAMES[i % NAMES.length] + (i >= NAMES.length ? String(i) : ''),
dept: DEPTS[i % 3],
role: i % 5 === 0 ? '管理员' : '普通成员',
mail: 'user' + (1000 + i) + '@company.com',
quota: Math.round((Math.sin(i) * 0.5 + 0.5) * 8000 + 500),
banned: i % 7 === 3,
year: 2024 + (i % 3)
});
}
var tbody = document.getElementById('tbody');
var pagerEl = document.getElementById('pager');
var page = 1, SIZE = 8;
function toast(msg) {
var t = document.getElementById('toast');
t.textContent = msg;
t.classList.add('show');
setTimeout(function () { t.classList.remove('show'); }, 1600);
}
function esc(s) { return String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;'); }
function filtered() {
var kw = document.getElementById('f-key').value.trim();
var dept = document.getElementById('f-dept').value;
var st = document.getElementById('f-status').value;
return db.filter(function (u) {
if (kw && u.name.indexOf(kw) < 0 && u.mail.indexOf(kw) < 0) return false;
if (dept && u.dept !== dept) return false;
if (st === '启用' && u.banned) return false;
if (st === '禁用' && !u.banned) return false;
return true;
});
}
function render() {
var list = filtered();
var pages = Math.max(1, Math.ceil(list.length / SIZE));
if (page > pages) page = pages;
var slice = list.slice((page - 1) * SIZE, page * SIZE);
document.getElementById('total-n').textContent = list.length;
document.getElementById('count-hint').textContent = '已选 0 项';
tbody.innerHTML = '';
slice.forEach(function (u, i) {
var tr = document.createElement('tr');
tr.innerHTML =
'<td><input type="checkbox" class="row-check"></td>' +
'<td><span class="user-cell"><span class="avatar">' + esc(u.name.slice(0, 1)) + '</span>' + esc(u.name) + '</span></td>' +
'<td>' + esc(u.dept) + '</td><td>' + esc(u.role) + '</td><td>' + esc(u.mail) + '</td>' +
'<td class="money" style="text-align:right;">¥ ' + u.quota.toLocaleString() + '</td>' +
'<td><span class="' + (u.banned ? 'sc-tag sc-tag-default' : 'sc-tag sc-tag-green') + '">' + (u.banned ? '禁用' : '启用') + '</span></td>' +
'<td><button class="btn btn-link btn-sm row-edit">编辑</button><button class="btn btn-link btn-sm row-ban">' + (u.banned ? '启用' : '禁用') + '</button></td>';
tr.querySelector('.row-edit').addEventListener('click', function () { toast('编辑:' + u.name + '(示范交互)'); });
tr.querySelector('.row-ban').addEventListener('click', function () {
u.banned = !u.banned;
toast(u.banned ? '已禁用 ' + u.name : '已启用 ' + u.name);
render();
});
tbody.appendChild(tr);
});
pagerEl.innerHTML = '';
function mk(label, p, on) {
var b = document.createElement('button');
b.className = 'sc-page' + (on ? ' active' : '');
b.textContent = label;
b.addEventListener('click', function () { page = p; render(); });
pagerEl.appendChild(b);
}
for (var p = 1; p <= pages; p++) mk(String(p), p, p === page);
}
document.getElementById('btn-search').addEventListener('click', function () { page = 1; render(); });
document.getElementById('check-all').addEventListener('change', function (e) {
tbody.querySelectorAll('.row-check').forEach(function (c) { c.checked = e.target.checked; });
document.getElementById('count-hint').textContent = '已选 ' + (e.target.checked ? tbody.querySelectorAll('.row-check:checked').length : 0) + ' 项';
});
var modal = document.getElementById('modal');
document.getElementById('btn-new').addEventListener('click', function () { modal.classList.add('open'); document.getElementById('m-name').focus(); });
document.getElementById('m-cancel').addEventListener('click', function () { modal.classList.remove('open'); });
modal.addEventListener('click', function (e) { if (e.target === modal) modal.classList.remove('open'); });
document.getElementById('m-ok').addEventListener('click', function () {
var name = document.getElementById('m-name').value.trim();
if (!name) { toast('请输入姓名'); return; }
db.unshift({
name: name, dept: document.getElementById('m-dept').value,
role: document.getElementById('m-role').value,
mail: document.getElementById('m-mail').value.trim() || name.toLowerCase() + '@company.com',
quota: 3000, banned: false, year: 2026
});
modal.classList.remove('open');
document.getElementById('m-name').value = '';
document.getElementById('m-mail').value = '';
page = 1; render();
toast('用户「' + name + '」已创建');
});
document.addEventListener('keydown', function (e) { if (e.key === 'Escape') modal.classList.remove('open'); });
render();
})();
</script>
</body>
</html>