Regression / regression (push) Canceled after 0s
## 品牌标识(本次会话) 起因:品牌此前没有任何图形标识 —— 唯一 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 未做部分)
242 lines
14 KiB
HTML
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, '&').replace(/</g, '<'); }
|
|
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>
|