Files
bid-doctor/docs/design/ui-demo/explore/workbench-e.html
YANG JIANKUAN 067cad2120 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>
2026-11-03 14:35:08 +08:00

545 lines
43 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>
<!--
工作台 · 风格探索第二轮 方案 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>