docs(design): UI 风格定稿(U5),设计规范与 tokens 升到 v0.2

- ui-demo/explore/:工作台风格的六轮方案(A–F、D2–D5),D5 为定稿样张
- tokens.css v0.2:墨色派生的中性浅灰作页面底色;圆角三档(6 / 8 / 12);控件高 36;侧栏渐变与套餐标签变量;旧变量保留兼容
- design-system.md v0.2:页面框架(96px 墨色渐变侧栏、透明顶栏、内容区圆角)、只用功能图标、项目行「下一步 / 更多」列、少写说明
- decisions.md 记录 U5;AGENTS.md 与 design/README 同步

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
YANG JIANKUAN
2026-11-03 14:35:08 +08:00
parent 604e889ca4
commit 067cad2120
16 changed files with 5910 additions and 39 deletions

View File

@@ -0,0 +1,544 @@
<!doctype html>
<!--
工作台 · 风格探索第二轮 方案 E「下一步」
概念:工作台是「今天该做什么」的行动清单,不是报表。最上方是按紧急程度排好的待办队列,每条都能直接动手;项目全貌放在下面。
内容、数据沿用 ../page-workbench.html;颜色只用 tokens.css 的色值和语义,浅底与分隔线全部由墨色派生。
本方案相对 design-system.md 改了这些规则:
1. 底色与分隔线(§3.1 / §3.2):页面底色 --c-paper 改为 --n-50(墨 3% 混白);分隔线 --c-rule 改为 --n-line(墨 11% 混白);
行悬停 --c-hover 改为 --n-50;进度条轨道、侧栏当前项用 --n-100(墨 6%)。不再使用 --c-paper / --c-sunken / --c-hover / --c-rule。
2. 圆角(§5.1 控件和卡片 2px、浮层 4px):卡片 12px;按钮、输入框、侧栏当前项、轻提示 8px;问题级别标签 4px。
3. 阴影(§5.1 只用于浮层):只有可点击的待办卡在悬停 / 键盘聚焦时出现一层极轻阴影并加深边框,作为「可以点」的提示;静止时所有卡片都不带阴影。
4. 按钮(§7.1):默认高度 32 → 36;次按钮白底 + 1px --border-control;表格行内按钮 28 不变。
5. 统计卡(原型四张统计卡):取消。数字并入区块标题(待办 4、项目 4)和待办标题右侧的一行概要。
6. 提醒条(§7.12):取消,并入待办队列。今天截止的那条用左侧 3px 赭色竖条 + 赭色时间文字。
7. 筛选(§7.1 分段控件):改为文字标签页,选中项墨色 2px 下划线。
8. 表格(§7.8):表头去掉凹陷底色和加粗下边线,只留小号辅助灰字;行间发丝线;「状态」「就绪」「致命」合并为一列;
状态改为纯文字,全页只保留问题级别一种标签(形状规则不变:致命实心、重要描边、提示无框)。新增「下一步」列,每行一个可直接点的操作。
9. 页面框架(§5.3):去掉顶栏,通知、快捷键和新建项目并入页头一行;侧栏白底,与内容之间一条 --n-line 发丝线;额度只在右栏出现一次。
10. 区块标题:16/24 黑体 600(宋体只用于 H1「工作台」)。
-->
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>工作台 · 标博士</title>
<link rel="icon" href="data:,">
<link rel="stylesheet" href="../../system/tokens.css">
<link rel="stylesheet" href="../../system/fonts-local.css">
<style>
/* ---------- 本方案派生的变量:浅底和分隔线只从墨色派生 ---------- */
:root {
--n-50: color-mix(in srgb, var(--c-ink) 3%, white); /* 页面底色、行悬停 */
--n-100: color-mix(in srgb, var(--c-ink) 6%, white); /* 进度轨道、侧栏当前项 */
--n-line: color-mix(in srgb, var(--c-ink) 11%, white); /* 卡片边框、发丝线 */
--n-line-strong: color-mix(in srgb, var(--c-ink) 24%, white); /* 待办卡悬停边框 */
--e-lift: 0 1px 2px color-mix(in srgb, var(--c-ink) 6%, transparent),
0 6px 16px -6px color-mix(in srgb, var(--c-ink) 14%, transparent);
--e-r-card: 12px;
--e-r-ctl: 8px;
--e-h: 36px;
--e-h-sm: 28px;
--e-gap: 16px; /* 卡片之间、卡片内分组之间的统一间距 */
}
* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
margin: 0; min-width: 1280px;
background: var(--n-50); color: var(--text-primary);
font: var(--fw-regular) var(--fs-body)/var(--lh-body) var(--font-sans);
-webkit-font-smoothing: antialiased;
}
a { color: inherit; }
button { font: inherit; color: inherit; }
h1, h2, h3, p { margin: 0; }
.fig { font-family: var(--font-figures); font-variant-numeric: tabular-nums; }
.vh { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
.ic { width: 16px; height: 16px; flex: none; fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }
.ic.lg { width: 20px; height: 20px; }
:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; border-radius: 4px; }
.sub { color: var(--text-secondary); }
.cap { font-size: var(--fs-caption); line-height: var(--lh-caption); }
.lnk { color: var(--text-primary); text-decoration: underline; text-decoration-color: var(--n-line-strong); text-underline-offset: 3px; cursor: pointer; }
.lnk:hover { text-decoration-color: currentColor; }
/* ---------- 按钮:一种主按钮、一种次按钮、一种图标按钮 ---------- */
.btn {
display: inline-flex; align-items: center; justify-content: center; gap: 6px;
height: var(--e-h); padding: 0 var(--sp-4); border: 1px solid transparent; border-radius: var(--e-r-ctl);
font-weight: var(--fw-bold); text-decoration: none; white-space: nowrap; cursor: pointer;
transition: background var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease);
}
.btn.primary { background: var(--c-ink); color: var(--text-on-dark); }
.btn.primary:hover { background: color-mix(in srgb, var(--c-ink) 86%, white); }
.btn.secondary { background: var(--c-white); border-color: var(--border-control); color: var(--text-primary); }
.btn.secondary:hover { background: var(--n-50); border-color: var(--c-ink); }
.btn.sm { height: var(--e-h-sm); padding: 0 var(--sp-3); font-weight: var(--fw-regular); }
.icon-btn {
position: relative; display: inline-grid; place-items: center; width: var(--e-h); height: var(--e-h);
border: 0; border-radius: var(--e-r-ctl); background: none; color: var(--text-secondary); cursor: pointer;
}
.icon-btn:hover { background: var(--n-100); color: var(--text-primary); }
.icon-btn.sm { width: var(--e-h-sm); height: var(--e-h-sm); }
.icon-btn .dot { position: absolute; top: 8px; right: 8px; width: 7px; height: 7px; border-radius: 50%; background: var(--warn); box-shadow: 0 0 0 2px var(--n-50); }
/* ---------- 唯一的标签:问题级别 ---------- */
.sev { display: inline-flex; align-items: center; gap: 4px; height: 20px; padding: 0 6px; border-radius: 4px; font-size: var(--fs-caption); line-height: 20px; font-weight: var(--fw-bold); white-space: nowrap; }
.sev.fatal { background: var(--sev-fatal); color: var(--text-on-dark); }
.sev .fig { font-size: 13px; }
/* ---------- 框架 ---------- */
.app { display: grid; grid-template-columns: var(--sidebar-w) minmax(0, 1fr); min-height: 100vh; }
.side {
position: sticky; top: 0; height: 100vh; display: flex; flex-direction: column;
padding: var(--sp-6) var(--sp-3); background: var(--c-white); border-right: 1px solid var(--n-line);
}
.brand { display: inline-block; padding: 0 var(--sp-3); margin-bottom: var(--sp-8); }
.brand img { display: block; height: 32px; width: auto; }
.nav { display: flex; flex-direction: column; gap: 2px; }
.nav a { display: flex; align-items: center; gap: 10px; height: 40px; padding: 0 var(--sp-3); border-radius: var(--e-r-ctl); color: var(--text-secondary); text-decoration: none; }
.nav a:hover { background: var(--n-50); color: var(--text-primary); }
.nav a[aria-current="page"] { background: var(--n-100); color: var(--text-primary); font-weight: var(--fw-bold); }
.nav .demo { margin-top: var(--sp-6); height: 32px; font-size: var(--fs-caption); }
.side-foot { margin-top: auto; padding: var(--sp-4) var(--sp-3) 0; border-top: 1px solid var(--n-line); font-size: var(--fs-caption); line-height: var(--lh-caption); color: var(--text-secondary); }
.side-foot b { display: block; font-size: var(--fs-body); line-height: var(--lh-body); font-weight: var(--fw-bold); color: var(--text-primary); }
.main { min-width: 0; padding: var(--sp-6) var(--sp-6) var(--sp-16); }
.wrap { max-width: 1320px; }
/* 页头 */
.head { display: flex; align-items: center; gap: var(--sp-4); min-height: 48px; }
.head h1 { font: var(--fw-bold) 28px/36px var(--font-serif); letter-spacing: .02em; }
.head .date { color: var(--text-secondary); padding-top: 6px; }
.head .tools { margin-left: auto; display: flex; align-items: center; gap: var(--sp-1); }
.head .cost { margin: 0 var(--sp-3) 0 var(--sp-4); color: var(--text-secondary); }
.head .cost .fig { color: var(--text-primary); font-weight: var(--fw-bold); font-size: 13px; }
.layout { display: grid; grid-template-columns: minmax(0, 1fr) clamp(272px, 20.5vw, 304px); gap: var(--sp-6); align-items: start; margin-top: var(--sp-6); }
/* 区块标题:标题 + 小号数字 + 右侧补充 */
.sec-h { display: flex; align-items: baseline; gap: var(--sp-2); margin-bottom: var(--sp-3); min-height: 28px; }
.sec-h h2 { font-size: var(--fs-title); line-height: var(--lh-title); font-weight: var(--fw-bold); }
.sec-h .count { font-family: var(--font-figures); font-variant-numeric: tabular-nums; font-size: 16px; color: var(--text-secondary); }
.sec-h .aside { margin-left: auto; display: flex; gap: var(--sp-4); color: var(--text-secondary); font-size: var(--fs-caption); line-height: var(--lh-caption); }
.sec-h .aside .fig { font-size: 14px; font-weight: var(--fw-bold); color: var(--text-primary); margin: 0 2px; }
.sec-h .aside .fig.ochre { color: var(--warn-text); }
.sec-h .aside .fig.fatal { color: var(--sev-fatal); }
/* ---------- 唯一的容器:白卡片 ---------- */
.card { background: var(--c-white); border: 1px solid var(--n-line); border-radius: var(--e-r-card); }
/* ---------- 待办队列(主角) ---------- */
.queue { list-style: none; margin: 0; padding: 0; display: grid; gap: 12px; }
.todo {
position: relative; overflow: hidden; cursor: pointer;
transition: border-color var(--dur-fast) var(--ease), box-shadow var(--dur-base) var(--ease), transform var(--dur-base) var(--ease),
opacity 220ms var(--ease), height 260ms var(--ease) 120ms, margin 260ms var(--ease) 120ms;
}
.todo:hover, .todo:focus-within { border-color: var(--n-line-strong); box-shadow: var(--e-lift); transform: translateY(-1px); }
.todo.today::before { content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px; background: var(--warn); }
.todo-row {
display: grid; align-items: center; column-gap: var(--sp-4);
grid-template-columns: 20px minmax(0, 1fr) 136px 160px 28px;
padding: var(--sp-4) var(--sp-3) var(--sp-4) var(--sp-5);
}
.todo .kind { align-self: start; margin-top: 2px; color: var(--text-secondary); }
.todo.today .kind { color: var(--warn); }
.todo.fatal .kind { color: var(--sev-fatal); }
.todo h3 { font-size: 15px; line-height: 24px; font-weight: var(--fw-bold); }
.todo h3 .proj { font-weight: var(--fw-bold); }
.todo .why { margin-top: 2px; color: var(--text-secondary); }
.todo .why b { font-weight: var(--fw-bold); color: var(--warn-text); }
.todo .why .sev { margin-right: 4px; position: relative; top: -1px; }
.todo .meta { display: flex; flex-direction: column; align-items: flex-start; gap: 2px; white-space: nowrap; }
.todo .meta .d1 { font-size: 14px; line-height: 22px; }
.todo .meta .d1 .fig { font-size: 15px; }
.todo .meta .d2 { font-size: var(--fs-caption); line-height: var(--lh-caption); color: var(--text-secondary); }
.todo.today .meta .d1, .todo.today .meta .d2 { color: var(--warn-text); font-weight: var(--fw-bold); }
.todo.soon .meta .d1, .todo.soon .meta .d2 { color: var(--warn-text); }
.todo .go { width: 100%; }
@media (max-width: 1400px) { .todo-row { column-gap: var(--sp-3); grid-template-columns: 20px minmax(0, 1fr) 120px 148px 28px; } .todo .go { padding: 0 var(--sp-3); } }
.todo .snooze { opacity: 0; transition: opacity var(--dur-fast) var(--ease); }
.todo:hover .snooze, .todo:focus-within .snooze, .todo .snooze:focus-visible { opacity: 1; }
/* 致命问题展开 */
.toggle { display: inline-flex; align-items: center; gap: 4px; margin-top: var(--sp-2); padding: 0 6px 0 0; height: 24px; border: 0; background: none; color: var(--text-primary); cursor: pointer; text-decoration: underline; text-decoration-color: var(--n-line-strong); text-underline-offset: 3px; }
.toggle:hover { text-decoration-color: currentColor; }
.toggle .ic { transition: transform var(--dur-base) var(--ease); }
.toggle[aria-expanded="true"] .ic { transform: rotate(180deg); }
.issues { list-style: none; margin: 0; padding: 0 var(--sp-5) var(--sp-2) calc(var(--sp-5) + 20px + var(--sp-4)); cursor: default; }
.issues[hidden] { display: none; }
.issues li { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; column-gap: var(--sp-3); align-items: baseline; padding: var(--sp-3) 0; border-top: 1px solid var(--n-line); }
.issues .sev { position: relative; top: -1px; }
.issues b { display: block; font-weight: var(--fw-bold); }
.issues p { color: var(--text-secondary); font-size: var(--fs-caption); line-height: var(--lh-caption); margin-top: 2px; }
.cite { display: inline-flex; align-items: center; gap: 4px; color: var(--ai); text-decoration: none; white-space: nowrap; font-size: var(--fs-caption); line-height: var(--lh-caption); }
.cite:hover { color: var(--ai-strong); text-decoration: underline; text-underline-offset: 3px; }
.cite .fig { font-size: 13px; }
.issues .foot { display: block; border-top: 1px solid var(--n-line); padding: var(--sp-3) 0 var(--sp-1); color: var(--text-secondary); font-size: var(--fs-caption); line-height: var(--lh-caption); }
.queue-done { padding: var(--sp-6) var(--sp-5); text-align: center; color: var(--text-secondary); }
.queue-done b { display: block; color: var(--text-primary); font-weight: var(--fw-bold); }
/* ---------- 项目列表 ---------- */
.projects-sec { margin-top: var(--sp-10); }
.list-card { overflow: hidden; }
.bar { display: flex; align-items: flex-end; justify-content: space-between; gap: var(--sp-4); padding: var(--sp-2) var(--sp-5) 0; border-bottom: 1px solid var(--n-line); }
.tabs { display: flex; gap: var(--sp-5); }
.tabs button {
height: 44px; padding: 0 2px; border: 0; background: none; cursor: pointer; color: var(--text-secondary);
box-shadow: inset 0 -2px 0 transparent; transition: color var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease);
}
.tabs button:hover { color: var(--text-primary); box-shadow: inset 0 -2px 0 var(--n-line-strong); }
.tabs button[aria-selected="true"] { color: var(--text-primary); font-weight: var(--fw-bold); box-shadow: inset 0 -2px 0 var(--c-ink); }
.tabs .fig { margin-left: 4px; }
.search { position: relative; display: block; width: 240px; margin-bottom: var(--sp-2); }
.search .ic { position: absolute; left: 10px; top: 8px; color: var(--text-secondary); pointer-events: none; }
.search input {
width: 100%; height: 32px; padding: 0 var(--sp-3) 0 34px; border: 1px solid var(--border-control); border-radius: var(--e-r-ctl);
background: var(--c-white); font: inherit; color: var(--text-primary);
}
.search input::placeholder { color: var(--text-secondary); }
.search input:hover { border-color: var(--c-ink); }
.search input:focus-visible { outline-offset: 1px; border-color: var(--c-ink); }
.projects { width: 100%; border-collapse: collapse; table-layout: fixed; }
.projects th { padding: var(--sp-3) 10px var(--sp-2); text-align: left; font-size: var(--fs-caption); line-height: var(--lh-caption); font-weight: var(--fw-regular); color: var(--text-secondary); white-space: nowrap; }
.projects th:first-child, .projects td:first-child { padding-left: var(--sp-5); }
.projects th:last-child, .projects td:last-child { padding-right: var(--sp-4); }
.projects td { padding: var(--sp-3) 10px; border-top: 1px solid var(--n-line); vertical-align: middle; }
.projects tbody tr.row { cursor: pointer; transition: background var(--dur-fast) var(--ease); }
.projects tbody tr.row:hover, .projects tbody tr.row:focus-within { background: var(--n-50); }
.c-st { width: 104px; } .c-due { width: 124px; } .c-next { width: 172px; }
.pname { font-weight: var(--fw-bold); text-decoration: none; }
.row:hover .pname { text-decoration: underline; text-decoration-color: var(--n-line-strong); text-underline-offset: 3px; }
.psub { display: flex; gap: var(--sp-3); color: var(--text-secondary); font-size: var(--fs-caption); line-height: var(--lh-caption); }
.st { display: flex; flex-direction: column; align-items: flex-start; gap: 2px; }
.st .r2 { font-size: var(--fs-caption); line-height: var(--lh-caption); color: var(--text-secondary); }
.ready { text-decoration: underline dotted var(--text-secondary); text-underline-offset: 3px; cursor: help; }
.due { white-space: nowrap; }
.due .d1 { display: block; }
.due .d2 { display: block; font-size: var(--fs-caption); line-height: var(--lh-caption); color: var(--text-secondary); }
.due.today .d1, .due.today .d2 { color: var(--warn-text); font-weight: var(--fw-bold); }
.due.soon .d1, .due.soon .d2 { color: var(--warn-text); }
.due.past .d1 { color: var(--text-secondary); }
.next { display: flex; align-items: center; justify-content: flex-end; gap: 4px; }
.next .more { opacity: 0; transition: opacity var(--dur-fast) var(--ease); }
.row:hover .more, .row:focus-within .more { opacity: 1; }
.row:hover .btn.secondary { border-color: var(--c-ink); }
.empty { padding: var(--sp-10) var(--sp-5); text-align: center; color: var(--text-secondary); }
.empty b { display: block; margin-bottom: var(--sp-1); color: var(--text-primary); font-weight: var(--fw-bold); }
.list-note { padding: var(--sp-3) var(--sp-5); border-top: 1px solid var(--n-line); color: var(--text-secondary); font-size: var(--fs-caption); line-height: var(--lh-caption); }
/* ---------- 右栏:同一种白卡片 ---------- */
.rail { display: grid; gap: var(--e-gap); padding-top: calc(28px + var(--sp-3)); }
.rail .card { padding: var(--sp-4) var(--sp-5) var(--sp-5); }
.rail h3 { display: flex; align-items: baseline; gap: var(--sp-2); margin-bottom: var(--sp-3); font-size: var(--fs-title); line-height: var(--lh-title); font-weight: var(--fw-bold); }
.rail h3 .r { margin-left: auto; font-size: var(--fs-caption); line-height: var(--lh-caption); font-weight: var(--fw-regular); color: var(--text-secondary); }
.quota { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-5); }
.quota small { display: block; font-size: var(--fs-caption); line-height: var(--lh-caption); color: var(--text-secondary); }
.quota .big { font: var(--fw-bold) var(--fs-figure)/var(--lh-figure) var(--font-figures); font-variant-numeric: tabular-nums; }
.quota .unit { margin-left: 4px; font-family: var(--font-figures); font-size: 15px; color: var(--text-secondary); }
.track { height: 4px; border-radius: 2px; background: var(--n-100); overflow: hidden; margin-top: var(--sp-2); }
.track i { display: block; height: 100%; background: var(--c-ink); border-radius: 2px; }
.links { display: flex; gap: var(--sp-5); margin-top: var(--sp-4); padding-top: var(--sp-3); border-top: 1px solid var(--n-line); font-size: var(--fs-caption); line-height: var(--lh-caption); }
.task-h { display: flex; justify-content: space-between; align-items: baseline; gap: var(--sp-2); }
.task-h span { font-size: var(--fs-caption); color: var(--text-secondary); }
.progress { display: flex; align-items: center; gap: var(--sp-3); }
.progress .track { flex: 1; margin: 0; }
.progress .fig { font-size: 15px; font-weight: var(--fw-bold); min-width: 32px; text-align: right; }
.task + .task { margin-top: var(--sp-3); padding-top: var(--sp-3); border-top: 1px solid var(--n-line); }
.task .wait { display: flex; gap: var(--sp-2); color: var(--text-secondary); }
.task .wait .ic { margin-top: 3px; }
.rail .hint { margin-top: var(--sp-3); font-size: var(--fs-caption); line-height: var(--lh-caption); color: var(--text-secondary); }
.log { list-style: none; margin: 0; padding: 0; }
.log li { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: var(--sp-3); padding: 6px 0; font-size: 13px; line-height: 20px; }
.log li + li { border-top: 1px solid var(--n-line); }
.log .t { font-family: var(--font-figures); font-variant-numeric: tabular-nums; color: var(--text-secondary); white-space: nowrap; }
.log .who { font-weight: var(--fw-bold); }
.log .ok { color: var(--confirmed); white-space: nowrap; }
/* 轻提示 */
.toasts { position: fixed; left: 50%; bottom: var(--sp-8); transform: translateX(-50%); display: grid; gap: var(--sp-2); justify-items: center; z-index: 20; }
.toast { display: flex; align-items: center; gap: var(--sp-3); min-height: 40px; padding: var(--sp-2) var(--sp-4); border-radius: var(--e-r-ctl); background: var(--c-ink); color: var(--text-on-dark); box-shadow: var(--shadow-overlay); white-space: nowrap; }
.toast button { padding: 0; border: 0; background: none; color: var(--text-on-dark); text-decoration: underline; text-underline-offset: 3px; cursor: pointer; }
.toast button:focus-visible { outline-color: var(--c-white); }
@media (prefers-reduced-motion: reduce) { * { transition: none !important; animation: none !important; } .todo:hover, .todo:focus-within { transform: none; } }
</style>
</head>
<body>
<div class="app">
<aside class="side">
<a class="brand" href="../page-workbench.html"><img src="../../logo/svg/logo-horizontal.svg" alt="标博士"></a>
<nav class="nav" aria-label="主导航">
<a href="#" aria-current="page"><svg class="ic lg" viewBox="0 0 24 24" aria-hidden="true"><rect x="3" y="3" width="7" height="9" rx="1"/><rect x="14" y="3" width="7" height="5" rx="1"/><rect x="14" y="12" width="7" height="9" rx="1"/><rect x="3" y="16" width="7" height="5" rx="1"/></svg>工作台</a>
<a href="#" data-demo><svg class="ic lg" viewBox="0 0 24 24" aria-hidden="true"><rect x="3" y="4" width="18" height="16" rx="2"/><path d="M7 9h10M7 13h6"/><circle cx="16" cy="15" r="2"/></svg>资质库</a>
<a href="#quota"><svg class="ic lg" viewBox="0 0 24 24" aria-hidden="true"><path d="m12 14 4-4"/><path d="M3.34 19a10 10 0 1 1 17.32 0"/></svg>额度</a>
<a class="demo" href="index.html"><svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 7v14"/><path d="M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z"/></svg>风格探索目录</a>
</nav>
<div class="side-foot"><b>王敏</b>南京某信息技术有限公司<br>专业版,至 2027-10-31</div>
</aside>
<main class="main">
<div class="wrap">
<header class="head">
<h1>工作台</h1>
<span class="date">11月3日 周二</span>
<div class="tools">
<button class="icon-btn" aria-label="通知(有 1 条未读)" title="通知" data-notice><svg class="ic lg" viewBox="0 0 24 24" aria-hidden="true"><path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9"/><path d="M10.3 21a1.94 1.94 0 0 0 3.4 0"/></svg><span class="dot"></span></button>
<button class="icon-btn" aria-label="快捷键说明" title="快捷键说明(?)" data-keys><svg class="ic lg" viewBox="0 0 24 24" aria-hidden="true"><rect x="2" y="4" width="20" height="16" rx="2"/><path d="M6 8h.01M10 8h.01M14 8h.01M18 8h.01M8 12h.01M12 12h.01M16 12h.01M7 16h10"/></svg></button>
<span class="cost cap">扣 1 次解析,剩 <span class="fig">37</span> 次</span>
<a class="btn primary" href="../forms.html#s-new"><svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M5 12h14"/><path d="M12 5v14"/></svg>新建项目</a>
</div>
</header>
<div class="layout">
<div>
<!-- 待办队列 -->
<section aria-labelledby="todo-h">
<div class="sec-h">
<h2 id="todo-h">待办</h2><span class="count" data-todo-count>4</span>
<div class="aside"><span>7 天内截止<span class="fig ochre">2</span>个项目</span><span>致命问题<span class="fig fatal">3</span>个</span><span>清单待确认<span class="fig">4</span>条,在 2 个项目里</span></div>
</div>
<ol class="queue" data-queue>
<li class="card todo today" data-go="../page-reading.html">
<div class="todo-row">
<svg class="ic lg kind" viewBox="0 0 24 24" aria-hidden="true"><path d="m3 17 2 2 4-4"/><path d="m3 7 2 2 4-4"/><path d="M13 6h8M13 12h8M13 18h8"/></svg>
<div><h3>确认清单:<span class="proj">某县数据中心机房改造项目</span></h3><p class="why"><b>今天 17:00 截止</b>,清单还是草稿,还没做过检查。确认后上传投标文件就能发起检查。</p></div>
<div class="meta"><span class="d1">今天 <span class="fig">17:00</span></span><span class="d2">剩 6 小时</span></div>
<a class="btn primary go" href="../page-reading.html">确认清单</a>
<button class="icon-btn sm snooze" aria-label="稍后提醒:确认清单" title="稍后提醒" data-snooze><svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M10.1 2.18a9.93 9.93 0 0 1 3.8 0"/><path d="M17.6 3.71a9.95 9.95 0 0 1 2.69 2.7"/><path d="M21.82 10.1a9.93 9.93 0 0 1 0 3.8"/><path d="M20.29 17.6a9.95 9.95 0 0 1-2.7 2.69"/><path d="M13.9 21.82a9.94 9.94 0 0 1-3.8 0"/><path d="M6.4 20.29a9.95 9.95 0 0 1-2.69-2.7"/><path d="M2.18 13.9a9.93 9.93 0 0 1 0-3.8"/><path d="M3.71 6.4a9.95 9.95 0 0 1 2.7-2.69"/><path d="M12 7v5l3 2"/></svg></button>
</div>
</li>
<li class="card todo fatal" data-go="../page-reading.html">
<div class="todo-row">
<svg class="ic lg kind" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 16h.01"/><path d="M12 8v4"/><path d="M15.312 2a2 2 0 0 1 1.414.586l4.688 4.688A2 2 0 0 1 22 8.688v6.624a2 2 0 0 1-.586 1.414l-4.688 4.688a2 2 0 0 1-1.414.586H8.688a2 2 0 0 1-1.414-.586l-4.688-4.688A2 2 0 0 1 2 15.312V8.688a2 2 0 0 1 .586-1.414l4.688-4.688A2 2 0 0 1 8.688 2z"/></svg>
<div><h3>致命问题:<span class="proj">某市政务服务中心智慧大厅建设项目</span></h3><p class="why"><span class="sev fatal">致命<span class="fig">3</span></span> 第 2 次检查发现 3 个致命问题。没处理完也能导出,项目会保持「未就绪」。</p>
<button class="toggle cap" aria-expanded="false" aria-controls="fatal-list" data-toggle>看看是哪 3 个<svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="m6 9 6 6 6-6"/></svg></button></div>
<div class="meta"><span class="d1"><span class="fig">11</span>月<span class="fig">20</span>日 <span class="fig">09:30</span></span><span class="d2">剩 17 天</span></div>
<a class="btn primary go" href="../page-reading.html">处理 3 个致命问题</a>
<button class="icon-btn sm snooze" aria-label="稍后提醒:致命问题" title="稍后提醒" data-snooze><svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M10.1 2.18a9.93 9.93 0 0 1 3.8 0"/><path d="M17.6 3.71a9.95 9.95 0 0 1 2.69 2.7"/><path d="M21.82 10.1a9.93 9.93 0 0 1 0 3.8"/><path d="M20.29 17.6a9.95 9.95 0 0 1-2.7 2.69"/><path d="M13.9 21.82a9.94 9.94 0 0 1-3.8 0"/><path d="M6.4 20.29a9.95 9.95 0 0 1-2.69-2.7"/><path d="M2.18 13.9a9.93 9.93 0 0 1 0-3.8"/><path d="M3.71 6.4a9.95 9.95 0 0 1 2.7-2.69"/><path d="M12 7v5l3 2"/></svg></button>
</div>
<ul class="issues" id="fatal-list" hidden>
<li><span class="sev fatal">致命</span><div><b>报价超过采购包 1 的最高限价</b><p>开标一览表 287.60 万元,限价 286.00 万元</p></div><a class="cite" href="#" data-cite>报价册 第 <span class="fig">1</span> 页</a></li>
<li><span class="sev fatal">致命</span><div><b>★ 条款疑似不满足:应用服务器内存</b><p>响应 256G,要求 ≥ 512G。上次标记为已修改,问题仍在</p></div><a class="cite" href="#" data-cite>技术册 第 <span class="fig">12</span> 页</a></li>
<li><span class="sev fatal">致命</span><div><b>授权委托书的单位签章位置未发现盖章</b><p>只看有无,不鉴定真伪</p></div><a class="cite" href="#" data-cite>商务册 第 <span class="fig">5</span> 页</a></li>
</ul>
</li>
<li class="card todo soon" data-go="">
<div class="todo-row">
<svg class="ic lg kind" viewBox="0 0 24 24" aria-hidden="true"><path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/><path d="m9 15 2 2 4-4"/></svg>
<div><h3>递交前复核:<span class="proj">某区智慧社区平台运维服务</span></h3><p class="why">已就绪。新上传的版本在排队检查,前面还有 1 个任务,出结果后会通知你。</p></div>
<div class="meta"><span class="d1"><span class="fig">11</span>月<span class="fig">5</span>日 <span class="fig">16:00</span></span><span class="d2">剩 2 天 5 小时</span></div>
<button class="btn secondary go" data-demo-go="(演示)会打开该项目上一次的检查结果">查看检查结果</button>
<button class="icon-btn sm snooze" aria-label="稍后提醒:递交前复核" title="稍后提醒" data-snooze><svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M10.1 2.18a9.93 9.93 0 0 1 3.8 0"/><path d="M17.6 3.71a9.95 9.95 0 0 1 2.69 2.7"/><path d="M21.82 10.1a9.93 9.93 0 0 1 0 3.8"/><path d="M20.29 17.6a9.95 9.95 0 0 1-2.7 2.69"/><path d="M13.9 21.82a9.94 9.94 0 0 1-3.8 0"/><path d="M6.4 20.29a9.95 9.95 0 0 1-2.69-2.7"/><path d="M2.18 13.9a9.93 9.93 0 0 1 0-3.8"/><path d="M3.71 6.4a9.95 9.95 0 0 1 2.7-2.69"/><path d="M12 7v5l3 2"/></svg></button>
</div>
</li>
<li class="card todo" data-go="">
<div class="todo-row">
<svg class="ic lg kind" viewBox="0 0 24 24" aria-hidden="true"><path d="M21 12a9 9 0 1 1-6.219-8.56"/></svg>
<div><h3>补遗解析中:<span class="proj">某市医保信息系统等保测评服务</span></h3><p class="why">补遗 2 正在提取清单,已完成 62%。完成后需要确认补遗带来的变更。</p></div>
<div class="meta"><span class="d1"><span class="fig">11</span>月<span class="fig">28</span>日 <span class="fig">10:00</span></span><span class="d2">剩 25 天</span></div>
<button class="btn secondary go" data-demo-go="(演示)会打开解析进度:补遗 2,提取清单 62%">查看解析进度</button>
<button class="icon-btn sm snooze" aria-label="稍后提醒:补遗解析中" title="稍后提醒" data-snooze><svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M10.1 2.18a9.93 9.93 0 0 1 3.8 0"/><path d="M17.6 3.71a9.95 9.95 0 0 1 2.69 2.7"/><path d="M21.82 10.1a9.93 9.93 0 0 1 0 3.8"/><path d="M20.29 17.6a9.95 9.95 0 0 1-2.7 2.69"/><path d="M13.9 21.82a9.94 9.94 0 0 1-3.8 0"/><path d="M6.4 20.29a9.95 9.95 0 0 1-2.69-2.7"/><path d="M2.18 13.9a9.93 9.93 0 0 1 0-3.8"/><path d="M3.71 6.4a9.95 9.95 0 0 1 2.7-2.69"/><path d="M12 7v5l3 2"/></svg></button>
</div>
</li>
</ol>
</section>
<!-- 项目列表 -->
<section class="projects-sec" aria-labelledby="proj-h">
<div class="sec-h"><h2 id="proj-h">项目</h2><span class="count" data-proj-count>4</span></div>
<div class="card list-card">
<div class="bar">
<div class="tabs" role="tablist" aria-label="按状态筛选" data-tabs>
<button role="tab" aria-selected="true" data-v="active">进行中<span class="fig">4</span></button>
<button role="tab" aria-selected="false" data-v="all" tabindex="-1">全部<span class="fig">6</span></button>
<button role="tab" aria-selected="false" data-v="已递交" tabindex="-1">已递交<span class="fig">1</span></button>
<button role="tab" aria-selected="false" data-v="已归档" tabindex="-1">已归档<span class="fig">1</span></button>
</div>
<label class="search"><svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg><input type="search" placeholder="搜索项目、采购人、编号" aria-label="搜索项目" data-q></label>
</div>
<table class="projects">
<thead><tr><th scope="col">项目</th><th scope="col" class="c-st">状态</th><th scope="col" class="c-due" aria-sort="ascending">投标截止</th><th scope="col" class="c-next"><span class="vh">下一步</span></th></tr></thead>
<tbody data-rows></tbody>
</table>
<p class="list-note" id="ready-note">按投标截止时间排序。「就绪」只表示致命问题都已处理,不代表投标文件没有问题。</p>
</div>
</section>
</div>
<!-- 右栏 -->
<aside class="rail" aria-label="额度、任务与最近处理">
<section class="card" id="quota">
<h3>本期额度<span class="r">至 <span class="fig">2027-10-31</span></span></h3>
<div class="quota">
<div><small>解析剩余</small><span class="big">37</span><span class="unit">/ 50</span><div class="track" aria-hidden="true"><i style="width:74%"></i></div></div>
<div><small>检查剩余</small><span class="big">12</span><span class="unit">/ 20</span><div class="track" aria-hidden="true"><i style="width:60%"></i></div></div>
</div>
<div class="links"><a class="lnk" href="#" data-demo>扣点规则</a><a class="lnk" href="#" data-demo>查看流水</a></div>
</section>
<section class="card">
<h3>正在进行的任务<span class="count fig sub">2</span></h3>
<div class="task">
<div class="task-h"><b>解析补遗 2</b><span>提取清单</span></div>
<div class="cap sub">某市医保信息系统等保测评服务</div>
<div class="progress"><div class="track" role="progressbar" aria-valuenow="62" aria-valuemin="0" aria-valuemax="100" aria-label="解析补遗 2 进度"><i style="width:62%"></i></div><span class="fig">62%</span></div>
</div>
<div class="task">
<div class="wait"><svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg><span>检查排队中:某区智慧社区平台运维服务,前面还有 1 个任务</span></div>
</div>
<p class="hint">可以离开本页,完成后会发站内通知。</p>
</section>
<section class="card">
<h3>最近处理<a class="r lnk" href="#" data-demo>全部记录</a></h3>
<ul class="log">
<li><span><span class="who">王敏</span> <span class="ok">✓ 已修改</span>「授权委托书签章」</span><span class="t">14:26</span></li>
<li><span><span class="who">王敏</span> 确认了清单 v2:智慧大厅</span><span class="t">11-02</span></li>
<li><span><span class="who">系统</span> 完成第 2 次检查,致命 3</span><span class="t">11-02</span></li>
<li><span><span class="who">系统</span> 并入补遗 1,更新 2 条</span><span class="t">11-02</span></li>
<li><span><span class="who">李工</span> 忽略「其他项目名称」:业绩介绍</span><span class="t">11-01</span></li>
</ul>
</section>
</aside>
</div>
</div>
</main>
</div>
<script>
(function () {
const $ = (s, el = document) => el.querySelector(s);
const $$ = (s, el = document) => [...el.querySelectorAll(s)];
const fig = (s) => String(s).replace(/(\d[\d:.]*)/g, '<span class="fig">$1</span>');
function toast(msg, undo) {
let box = $(".toasts");
if (!box) { box = document.createElement("div"); box.className = "toasts"; box.setAttribute("role", "status"); box.setAttribute("aria-live", "polite"); document.body.appendChild(box); }
const t = document.createElement("div"); t.className = "toast";
t.innerHTML = `<span></span>${undo ? "<button type=\"button\">撤销</button>" : ""}`;
t.firstChild.textContent = msg;
if (undo) t.querySelector("button").onclick = () => { t.remove(); undo(); };
box.appendChild(t);
setTimeout(() => t.remove(), 5000);
}
/* ---------- 待办队列 ---------- */
const queue = $("[data-queue]");
const countTodo = () => { $("[data-todo-count]").textContent = $$(".todo:not(.leaving)", queue).length; };
queue.addEventListener("click", (e) => {
const card = e.target.closest(".todo"); if (!card) return;
if (e.target.closest("[data-toggle]")) {
const b = e.target.closest("[data-toggle]"), open = b.getAttribute("aria-expanded") !== "true";
b.setAttribute("aria-expanded", open);
b.firstChild.textContent = open ? "收起" : "看看是哪 3 个";
$("#" + b.getAttribute("aria-controls")).hidden = !open;
return;
}
if (e.target.closest("[data-cite]")) { e.preventDefault(); toast("(演示)出处会在原文查看器里打开"); return; }
if (e.target.closest(".issues")) return;
if (e.target.closest("[data-snooze]")) { snooze(card); return; }
const demo = card.querySelector("[data-demo-go]");
if (!card.dataset.go) { e.preventDefault(); toast(demo ? demo.dataset.demoGo : "(演示)这一页还没做"); return; }
if (!e.target.closest("a")) location.href = card.dataset.go;
});
/* 唯一的演示动效:推迟一条待办后,该条淡出、队列上移 */
function snooze(card) {
const title = card.querySelector("h3").textContent;
const h = card.offsetHeight;
card.style.height = h + "px";
card.classList.add("leaving");
card.offsetHeight; // reflow
card.style.opacity = "0";
card.style.height = "0px";
card.style.marginBottom = "-12px";
card.style.borderWidth = "0";
countTodo();
const done = () => { card.hidden = true; card.removeEventListener("transitionend", onEnd); emptyCheck(); };
const onEnd = (ev) => { if (ev.propertyName === "height") done(); };
card.addEventListener("transitionend", onEnd);
if (matchMedia("(prefers-reduced-motion: reduce)").matches) done();
toast(`「${title}」已推迟,明天 9:00 再提醒`, () => {
card.hidden = false; card.classList.remove("leaving");
card.style.height = h + "px"; card.style.borderWidth = ""; card.style.marginBottom = "";
card.offsetHeight; card.style.opacity = "";
setTimeout(() => { card.style.height = ""; }, 400);
countTodo(); emptyCheck();
});
}
function emptyCheck() {
let e = $(".queue-done");
const left = $$(".todo:not(.leaving)", queue).length;
if (!left && !e) { queue.insertAdjacentHTML("afterend", '<div class="card queue-done"><b>今天的待办都安排好了</b>被推迟的事项明天 9:00 会回到这里。</div>'); }
if (left && e) e.remove();
}
/* ---------- 项目列表 ---------- */
const P = [
{ name: "某县数据中心机房改造项目", buyer: "某县大数据管理局", lot: "标段 1", d1: "今天 17:00", d2: "剩 6 小时", cls: "today", st: "编制中", ready: "清单草稿", fatal: 0, next: "确认清单", href: "../page-reading.html" },
{ name: "某区智慧社区平台运维服务", buyer: "某区民政局", lot: "全部", d1: "11月5日 16:00", d2: "剩 2 天 5 小时", cls: "soon", st: "已检查", ready: "就绪", fatal: 0, next: "查看检查结果" },
{ name: "某市政务服务中心智慧大厅建设项目", buyer: "某市政务服务管理办公室", lot: "采购包 1", d1: "11月20日 09:30", d2: "剩 17 天", cls: "", st: "已检查", ready: "未就绪", fatal: 3, next: "处理致命问题", href: "../page-reading.html" },
{ name: "某市医保信息系统等保测评服务", buyer: "某市医疗保障局", lot: "全部", d1: "11月28日 10:00", d2: "剩 25 天", cls: "", st: "跟进中", ready: "补遗解析中", fatal: 0, next: "查看解析进度" },
{ name: "某区智慧校园一期", buyer: "某区教育局", lot: "全部", d1: "已截止", d2: "", cls: "past", st: "已递交", ready: "", fatal: 0, next: "查看检查报告" },
{ name: "某市不动产登记信息平台升级", buyer: "某市自然资源和规划局", lot: "采购包 2", d1: "已截止", d2: "", cls: "past", st: "已归档", ready: "", fatal: 0, next: "查看项目" }
];
const DONE = ["已递交", "已归档"];
let seg = "active", q = "";
function status(p) {
let r2 = "";
if (p.fatal) r2 = `<span class="sev fatal">致命<span class="fig">${p.fatal}</span></span>`;
else if (p.ready === "就绪") r2 = '<span class="r2"><span class="ready" tabindex="0" aria-describedby="ready-note" title="只表示致命问题都已处理,不代表投标文件没有问题。">就绪</span></span>';
else if (p.ready) r2 = `<span class="r2">${p.ready}</span>`;
return `<div class="st"><span>${p.st}</span>${r2}</div>`;
}
function render() {
const rows = P.filter((p) => (seg === "all" || (seg === "active" ? !DONE.includes(p.st) : p.st === seg)) && (!q || (p.name + p.buyer).includes(q)));
$("[data-proj-count]").textContent = rows.length;
$("[data-rows]").innerHTML = rows.map((p) => `<tr class="row" data-i="${P.indexOf(p)}">
<td><a class="pname" href="${p.href || "#"}">${p.name}</a><div class="psub"><span>${p.buyer}</span><span>${fig(p.lot)}</span></div></td>
<td>${status(p)}</td>
<td class="due ${p.cls}"><span class="d1">${fig(p.d1)}</span>${p.d2 ? `<span class="d2">${p.d2}</span>` : ""}</td>
<td><div class="next"><button class="btn sm secondary" data-next>${p.next}</button><button class="icon-btn sm more" aria-label="更多操作:${p.name}" title="更多操作" data-more><svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="1"/><circle cx="19" cy="12" r="1"/><circle cx="5" cy="12" r="1"/></svg></button></div></td></tr>`).join("")
|| `<tr><td colspan="4"><div class="empty"><b>没有符合条件的项目</b>换个关键词,或切到「全部」看看。</div></td></tr>`;
}
function setSeg(btn) {
seg = btn.dataset.v;
$$("[data-tabs] button").forEach((b) => { const on = b === btn; b.setAttribute("aria-selected", on); b.tabIndex = on ? 0 : -1; });
render();
}
$("[data-tabs]").addEventListener("click", (e) => { const b = e.target.closest("button"); if (b) setSeg(b); });
$("[data-tabs]").addEventListener("keydown", (e) => {
if (e.key !== "ArrowRight" && e.key !== "ArrowLeft") return;
const bs = $$("[data-tabs] button"), i = bs.indexOf(document.activeElement);
const n = bs[(i + (e.key === "ArrowRight" ? 1 : -1) + bs.length) % bs.length];
n.focus(); setSeg(n);
});
$("[data-q]").addEventListener("input", (e) => { q = e.target.value.trim(); render(); });
$("[data-rows]").addEventListener("click", (e) => {
const r = e.target.closest("tr.row"); if (!r) return;
const p = P[r.dataset.i];
if (e.target.closest("[data-more]")) { toast("(演示)更多操作:改状态、归档、删除项目"); return; }
if (e.target.closest("[data-next]") && p.href) { location.href = p.href; return; }
if (!p.href) { e.preventDefault(); toast(`(演示)会打开「${p.name}」:${p.next}`); return; }
if (!e.target.closest("a")) location.href = p.href;
});
document.addEventListener("click", (e) => {
const a = e.target.closest("[data-demo]"); if (!a) return;
e.preventDefault(); toast("(演示)这一页还没做");
});
$("[data-notice]").onclick = () => toast("解析完成:补遗 1 已并入清单(新增 2 条,更新 1 条)。");
$("[data-keys]").onclick = () => toast("(演示)快捷键:J / K 逐条,1 / 2 / 3 处理,? 显示说明");
render();
})();
</script>
</body>
</html>