Files
bid-doctor/docs/design/ui-demo/explore/workbench-d5.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

659 lines
52 KiB
HTML
Raw Permalink 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>
<!--
工作台 · 风格方案 D「明亮工作区」(探索稿,不是定稿)
数据、文案、交互沿用 ../page-workbench.html 与 ../demo.js。
本方案相对 design-system.md 改了哪些规则:
0e. (d5)侧栏改为单一状态的窄栏(96px,不再折叠):竖版 logo;菜单上图标、下文字;当前项用圆角矩形的半透明浅色底 + 白色加粗文字,不再连进内容区。图标换为 Lucide house / award / wallet。
0d. (d4 修订)按用户意见去掉 d3 加的装饰图标(统计、区块标题、下一步、最近处理、顶栏),只保留原有的功能图标;问候语保留。
0c. (d4)侧栏与内容区融合:侧栏双色竖向渐变(墨 → 墨混 18% 蓝铅笔的深墨蓝,只作底色、不承担 AI 语义)+ 底部淡点阵;内容区左上角 12px 圆角压在深色上;当前菜单项:浅色半透明底 + 侧栏左缘 3px 白色竖条(?sel=bar 只有竖条,?sel=solid 白色实底),不再连进内容区(折叠动画会穿帮)。项目行去掉状态图标列。?v=flat 看 D3 的平铺侧栏对照。
0b. (d3)顶栏:透明落在页面底色上、滚动后变为半透明白 + 毛玻璃 + 发丝线,和墨色侧栏区分;搜索框靠右。图标:统计、区块标题、项目行、下一步、最近处理都加 Lucide 图标;图标底块只用中性浅灰,彩色只给有语义的(致命红、赭、蓝铅笔)。页头加问候语。
0. (d2)侧边栏改为墨色底(--c-ink),logo 用反白版;可折叠到 64px 只留图标,折叠状态记在本地。?side=light 可切换为浅灰侧栏对照。
1. §3.1 页面底色:校样纸 #F5F6F3 → --n-50(墨 3% 混白的中性冷灰);侧边栏、顶栏、卡片纯白。
偏绿的 --c-paper / --c-sunken / --c-hover / --c-rule 一律不用,浅底和分隔线只用下面三个从墨派生的值。
2. §5.1 层次:只有两种容器——页面(--n-50)和白卡片(圆角 12、1px --n-line、无阴影)。卡片里不再套面板。
可点击的整张卡片(本页没有)和浮层才用阴影;行、格的悬停只出 --n-50 底色。
3. §5.1 圆角:2px 一档 → 卡片 12、按钮 / 输入框 / 菜单 8、标签 6。
4. §7.1 按钮:高 32 → 36(主 / 次);表格行内 32。主按钮墨底白字;次按钮白底 + --c-muted 控件边框(3.9:1);
文字按钮悬停出 --n-100 底。普通链接仍是墨色 + 下划线,蓝铅笔只给出处。
5. §7.7 表头:凹陷灰底 + 粗底线 → 无底色,12px 辅助灰列名,无线。行高 64,行间 --n-line 发丝线。
6. §7.9 项目行:
- 「状态」中性描边标签 → --n-100 实底、圆角 6、无描边;全页只有这一种中性标签。
- 「就绪」从描边标签改为墨色普通字 + 虚线下划线(悬停说明不变),和「未就绪」并排写在状态标签后面,省掉一列。
- 「所投包」并入项目名下方的辅助灰行。
- 新增「下一步」列:默认是带箭头的文字链,整行悬停 / 键盘聚焦时变成次按钮,同时浮现「更多」图标按钮。
会扣点的操作(发起检查)放在「更多」里,并写明扣多少、还剩多少;有致命问题时「导出检查报告」仍可用。
7. 工作台统计:四张卡片 → 一张卡片四格,竖向发丝线分隔,数字 30 → 28(Barlow 600)。
每格可点击筛选项目列表,悬停出 --n-50 底,选中格底部 2px 墨色边条。
8. §5.3 顶栏:面包屑位置改为企业主体名;中间加全局搜索(白底 + 控件边框、圆角 8、Ctrl K),本页用它筛选项目列表。
9. §7.12 截止提醒条:赭色浅底条 → 同规格白卡片,赭色图标 + 赭色时间字。
10. §7.4「致命」行左侧 3px 竖线:工作台的待处理列表不用竖线,靠左侧实心「致命」标签扫读。
未改:颜色语义、问题级别形状、字体分工、字阶(正文 14 / 标题 16 / 页标题 24)、4px 间距基准、Lucide 图标。
-->
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>工作台 · 方案 D5</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); /* 发丝线、卡片边 */
--ctl-border: var(--c-muted); /* 控件边框 3.9:1 */
--primary-hover: color-mix(in srgb, var(--c-ink) 88%, white);
--r-card: 12px; --r-ctl: 8px; --r-tag: 6px;
--btn-h: 36px; --btn-h-sm: 32px;
--top-h: 56px;
--pad: 32px; /* 页面左右内边距 */
--gap: 16px; /* 卡片之间、主列与右栏之间:全页一个节奏 */
--card-x: 20px; /* 卡片内左右边距:所有卡片统一 */
--rail-w: 296px;
--side-w: var(--sidebar-w); --side-w-min: 64px;
/* 侧栏:墨色底。文字和底色都从墨、白派生,不引入新色相 */
--side-bg: var(--c-ink);
--side-fg: color-mix(in srgb, white 74%, var(--c-ink)); /* 未选中项 约 9:1 */
--side-fg-strong: var(--c-white);
--side-hover: color-mix(in srgb, white 7%, transparent); /* 半透明:在渐变上各处一致 */
--side-active: color-mix(in srgb, white 14%, transparent);
--side-line: color-mix(in srgb, white 12%, var(--c-ink));
--side-muted: color-mix(in srgb, white 58%, var(--c-ink)); /* 分组小字、页脚 约 6:1 */
--cols: minmax(0,1fr) 120px 120px 64px 140px 32px; /* 项目行栅格 */
--col-gap: 16px;
}
@media (max-width: 1399px){
:root{ --pad: 24px; --rail-w: 256px; --cols: minmax(0,1fr) 108px 108px 56px 132px 32px; --col-gap: 12px; }
}
*,*::before,*::after{box-sizing:border-box}
html,body{margin:0}
body{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;min-width:1280px}
h1,h2,h3,p,ul{margin:0}
ul{padding:0;list-style:none}
a{color:inherit}
button{font:inherit;color:inherit}
.fig{font-family:var(--font-figures);font-variant-numeric:tabular-nums;font-weight:var(--fw-bold)}
.sec{color:var(--text-secondary)}
.cap{font-size:var(--fs-caption);line-height:var(--lh-caption)}
.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}
.vh{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px;border-radius:var(--r-ctl)}
/* ---------- 链接与按钮 ---------- */
.lnk{color:var(--text-primary);text-decoration:underline;text-decoration-color:color-mix(in srgb, var(--c-ink) 30%, white);text-underline-offset:3px;transition:text-decoration-color var(--dur-fast) var(--ease)}
.lnk:hover{text-decoration-color:var(--c-ink)}
.cite{display:inline-flex;align-items:center;gap:4px;color:var(--ai);text-decoration:none;white-space:nowrap;border-radius:var(--r-ctl);padding:2px 6px;margin:-2px -6px;transition:background-color var(--dur-fast) var(--ease)}
.cite:hover{background:var(--ai-soft);color:var(--ai-strong)}
.btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;height:var(--btn-h);padding:0 14px;border-radius:var(--r-ctl);border:1px solid var(--ctl-border);background:var(--c-white);color:var(--text-primary);font-size:var(--fs-body);line-height:1;white-space:nowrap;text-decoration:none;cursor:pointer;transition:background-color var(--dur-fast) var(--ease),border-color var(--dur-fast) var(--ease)}
.btn:hover{background:var(--n-50);border-color:var(--c-ink)}
.btn:active{background:var(--n-100)}
.btn.primary{background:var(--c-ink);border-color:var(--c-ink);color:var(--c-white);font-weight:var(--fw-bold)}
.btn.primary:hover{background:var(--primary-hover)}
.btn.sm{height:var(--btn-h-sm);padding:0 12px}
.tbtn{display:inline-flex;align-items:center;gap:6px;height:var(--btn-h-sm);padding:0 10px;border:0;border-radius:var(--r-ctl);background:transparent;cursor:pointer;white-space:nowrap;text-decoration:none;transition:background-color var(--dur-fast) var(--ease)}
.tbtn:hover{background:var(--n-100)}
.ibtn{position:relative;display:inline-grid;place-items:center;width:var(--btn-h);height:var(--btn-h);border:0;border-radius:var(--r-ctl);background:transparent;color:var(--text-primary);cursor:pointer;transition:background-color var(--dur-fast) var(--ease)}
.ibtn:hover{background:var(--n-100)}
.ibtn.sm{width:var(--btn-h-sm);height:var(--btn-h-sm)}
/* ---------- 标签:中性一种 + 问题级别 ---------- */
.tag{display:inline-flex;align-items:center;height:22px;padding:0 8px;border-radius:var(--r-tag);background:var(--n-100);color:var(--text-primary);font-size:var(--fs-caption);white-space:nowrap}
.sev{display:inline-flex;align-items:center;gap:4px;height:22px;padding:0 8px;border-radius:var(--r-tag);font-size:var(--fs-caption);font-weight:var(--fw-bold);white-space:nowrap}
.sev.fatal{background:var(--sev-fatal);color:var(--c-white)}
.sev .fig{font-weight:var(--fw-bold)}
.dash{color:var(--text-secondary)}
/* ---------- 应用框架 ---------- */
.app{display:grid;grid-template-columns:var(--side-w) minmax(0,1fr);min-height:100vh;transition:grid-template-columns var(--dur-base) var(--ease)}
.side{background:var(--side-bg);color:var(--side-fg);min-width:0}
/* ---- d4:侧栏与内容区融合 ---- */
.app:not([data-side="light"]):not([data-v="flat"]){
--side-deep: color-mix(in srgb, var(--c-ink) 82%, var(--c-pencil)); /* 渐变下端:深墨蓝 */
background:linear-gradient(180deg, var(--c-ink) 0%, var(--side-deep) 100%) fixed;
}
.app:not([data-side="light"]):not([data-v="flat"]) .side{background:transparent;position:relative}
.app:not([data-side="light"]):not([data-v="flat"]) .side-in::after{content:"";position:absolute;left:0;right:0;bottom:0;height:260px;pointer-events:none;
background-image:radial-gradient(color-mix(in srgb, white 14%, transparent) 1px, transparent 1.2px);background-size:14px 14px;
-webkit-mask-image:linear-gradient(to top, black, transparent);mask-image:linear-gradient(to top, black, transparent)}
.app:not([data-side="light"]):not([data-v="flat"]) .side-in > *{position:relative;z-index:1}
.app:not([data-side="light"]):not([data-v="flat"]) .main{background:var(--n-50);border-top-left-radius:12px;min-height:100vh}
.app:not([data-side="light"]):not([data-v="flat"]) .top{border-top-left-radius:12px}
/* 浅灰侧栏对照(?side=light) */
.app[data-side="light"]{--side-bg:color-mix(in srgb, var(--c-ink) 9%, white);--side-fg:var(--text-secondary);--side-fg-strong:var(--text-primary);--side-hover:color-mix(in srgb, var(--c-ink) 14%, white);--side-active:var(--c-white);--side-line:var(--n-line);--side-muted:var(--text-secondary)}
.app[data-side="light"] .side{border-right:1px solid var(--n-line)}
.app[data-side="light"] .nav a[aria-current]{box-shadow:0 0 0 1px var(--n-line)}
.brand .mini{display:none;height:32px;width:32px}
.app[data-side="light"] .brand .full.rev,.app:not([data-side="light"]) .brand .full.std{display:none}
.side-in{position:sticky;top:0;height:100vh;display:flex;flex-direction:column;padding:0 12px 12px;overflow:hidden}
.brand{display:flex;align-items:center;justify-content:center;height:72px;padding:0;margin:0 0 8px;flex:none}
.brand:focus-visible{outline:2px solid var(--side-fg-strong);outline-offset:-2px;border-radius:var(--r-ctl)}
.brand img{height:34px;display:block}
.nav{display:flex;flex-direction:column;gap:2px}
.nav a,.side-btn{position:relative;display:flex;align-items:center;gap:10px;height:var(--btn-h);padding:0 10px;border-radius:var(--r-ctl);text-decoration:none;color:var(--side-fg);white-space:nowrap;transition:background-color var(--dur-fast) var(--ease),color var(--dur-fast) var(--ease)}
.nav a .ic,.side-btn .ic{flex:none}
.nav a:hover,.side-btn:hover{background:var(--side-hover);color:var(--side-fg-strong)}
.nav a[aria-current]{background:var(--side-active);color:var(--side-fg-strong);font-weight:var(--fw-bold)}
/* 选中样式(d4 修订):选中效果全部留在侧栏内,折叠动画中不会露出。?sel=bar / solid 切换对照 */
.nav a[aria-current]::before{content:"";position:absolute;left:-12px;top:8px;bottom:8px;width:3px;border-radius:0 3px 3px 0;background:var(--side-fg-strong)}
.app[data-sel="bar"] .nav a[aria-current]{background:transparent}
.app[data-sel="bar"] .nav a[aria-current]:hover{background:var(--side-hover)}
.app[data-sel="solid"] .nav a[aria-current]{background:var(--c-white);color:var(--text-primary)}
.app[data-sel="solid"] .nav a[aria-current]::before{display:none}
.app[data-side="light"] .nav a[aria-current]::before{background:var(--c-ink)}
.nav a:focus-visible,.side-btn:focus-visible{outline:2px solid var(--side-fg-strong);outline-offset:-2px}
.nav .grp{margin:20px 10px 6px;height:20px;font-size:var(--fs-caption);line-height:20px;color:var(--side-muted);white-space:nowrap}
.side-btn{width:100%;border:0;background:transparent;font:inherit;cursor:pointer;margin-top:auto}
.side-foot{display:flex;align-items:center;gap:10px;margin-top:8px;padding:12px 6px 0;border-top:1px solid var(--side-line);font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--side-muted);white-space:nowrap}
.side-foot b{display:block;color:var(--side-fg-strong);font-size:var(--fs-body);line-height:var(--lh-body);font-weight:var(--fw-bold)}
.avatar{flex:none;display:inline-grid;place-items:center;width:32px;height:32px;border-radius:50%;background:var(--side-active);color:var(--side-fg-strong);font-weight:var(--fw-bold);font-size:var(--fs-body)}
.app[data-side="light"] .avatar{background:var(--c-ink);color:var(--c-white)}
/* 折叠:只留图标;文字保留给读屏,悬停出提示 */
.side-in:has([data-stip]:hover){overflow:visible}
.top{position:sticky;top:0;z-index:5;display:flex;align-items:center;gap:12px;height:var(--top-h);padding:0 var(--pad);background:transparent;border-bottom:1px solid transparent;transition:background-color var(--dur-base) var(--ease),border-color var(--dur-base) var(--ease)}
.top.scrolled{background:color-mix(in srgb, var(--c-white) 82%, transparent);-webkit-backdrop-filter:blur(14px) saturate(1.2);backdrop-filter:blur(14px) saturate(1.2);border-bottom-color:var(--n-line)}
.top .search{margin-left:auto;width:280px;transition:width var(--dur-base) var(--ease),border-color var(--dur-fast) var(--ease)}
.top .search:focus-within{width:360px}
.top .vsep{width:1px;height:20px;background:var(--n-line);margin:0 4px}
.org .ic{color:var(--text-secondary)}
.org{justify-self:start;display:inline-flex;align-items:center;gap:6px;color:var(--text-secondary)}
.org b{color:var(--text-primary);font-weight:var(--fw-bold)}
.search{position:relative;display:flex;align-items:center;height:var(--btn-h);border:1px solid var(--ctl-border);border-radius:var(--r-ctl);background:var(--c-white);transition:border-color var(--dur-fast) var(--ease)}
.search:hover{border-color:var(--c-ink)}
.search:focus-within{border-color:var(--c-ink);box-shadow:0 0 0 1px var(--c-ink)}
.search > .ic{position:absolute;left:11px;color:var(--text-secondary);pointer-events:none}
.search input{flex:1;min-width:0;height:100%;border:0;background:transparent;padding:0 8px 0 34px;font:inherit;color:var(--text-primary);outline:0}
.search input::placeholder{color:var(--text-secondary)}
.search input::-webkit-search-cancel-button{display:none}
.kbd{flex:none;margin-right:6px;padding:0 6px;height:22px;display:inline-flex;align-items:center;border-radius:var(--r-tag);background:var(--n-100);color:var(--text-secondary);font-size:var(--fs-caption);font-family:var(--font-figures);font-weight:var(--fw-bold)}
.top-r{justify-self:end;display:flex;align-items:center;gap:4px}
.qchip{display:inline-flex;align-items:center;gap:6px;height:var(--btn-h-sm);padding:0 10px;color:var(--text-secondary)}
.qchip .fig{color:var(--text-primary);font-size:15px}
.qchip .sep{width:1px;height:12px;background:var(--n-line);margin:0 4px}
.dot{position:absolute;top:8px;right:8px;width:7px;height:7px;border-radius:50%;background:var(--c-ink);box-shadow:0 0 0 2px var(--c-white)}
.content{padding:20px var(--pad) 48px}
.ch h2,.ch h3{display:inline-flex;align-items:center;gap:8px}
.ch h2 > .ic,.ch h3 > .ic{color:var(--text-secondary)}
/* ---------- 页头 ---------- */
.ph{display:flex;align-items:flex-end;justify-content:space-between;gap:24px;margin-bottom:20px}
.ph h1{font-family:var(--font-serif);font-weight:var(--fw-bold);font-size:var(--fs-h1);line-height:var(--lh-h1)}
.ph p{margin-top:2px;color:var(--text-secondary)}
.warn-t{color:var(--warn-text);font-weight:var(--fw-bold)}
.ph .act{display:flex;align-items:center;gap:12px}
.cost{font-size:var(--fs-caption);color:var(--text-secondary);white-space:nowrap}
.cost .fig{color:var(--text-primary)}
/* ---------- 唯一的容器:白卡片 ---------- */
.card{background:var(--c-white);border:1px solid var(--n-line);border-radius:var(--r-card)}
.ch{display:flex;align-items:center;gap:12px;min-height:56px;padding:12px var(--card-x)}
.ch h2,.ch h3{font-size:var(--fs-title);line-height:var(--lh-title);font-weight:var(--fw-bold)}
.ch .more{margin-left:auto}
.cf{padding:12px var(--card-x);border-top:1px solid var(--n-line)}
.stack > * + *{margin-top:var(--gap)}
/* 截止提醒 */
.alert{display:flex;align-items:center;gap:14px;padding:14px var(--card-x);margin-bottom:var(--gap)}
.alert > .ic{color:var(--c-ochre)}
.alert p{flex:1}
.alert .when{color:var(--warn-text);font-weight:var(--fw-bold)}
/* 统计:一张卡片四格 */
.stats{display:grid;grid-template-columns:repeat(4,1fr);margin-bottom:var(--gap);overflow:hidden}
.stat{position:relative;display:grid;grid-template-rows:auto auto auto;justify-items:start;gap:2px;padding:16px var(--card-x) 16px;border:0;border-left:1px solid var(--n-line);background:transparent;text-align:left;cursor:pointer;transition:background-color var(--dur-fast) var(--ease)}
.stat:first-child{border-left:0}
.stat:hover{background:var(--n-50)}
.stat::after{content:"";position:absolute;left:0;right:0;bottom:0;height:2px;background:var(--c-ink);transform:scaleY(0);transform-origin:bottom;transition:transform var(--dur-fast) var(--ease)}
.stat[aria-pressed="true"]::after{transform:scaleY(1)}
.stat[aria-pressed="true"] small{color:var(--text-primary);font-weight:var(--fw-bold)}
.stat:focus-visible{outline-offset:-2px;border-radius:0}
.stat small{font-size:var(--fs-body);color:var(--text-secondary)}
.stat .big{font-family:var(--font-figures);font-variant-numeric:tabular-nums;font-weight:var(--fw-bold);font-size:28px;line-height:36px}
.stat .big.fatal{color:var(--sev-fatal)}
.stat .big.due{color:var(--warn-text)}
.stat > .ic{position:absolute;top:18px;right:var(--card-x);color:var(--text-secondary);opacity:0;transition:opacity var(--dur-fast) var(--ease)}
.stat:hover > .ic,.stat:focus-visible > .ic,.stat[aria-pressed="true"] > .ic{opacity:1}
.stat[aria-pressed="true"] > .ic{color:var(--text-primary)}
.stat p{font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--text-secondary)}
.stat p.due{color:var(--warn-text);font-weight:var(--fw-bold)}
/* 两栏 */
.wb{display:grid;grid-template-columns:minmax(0,1fr) var(--rail-w);gap:var(--gap);align-items:start}
/* 项目卡片头:筛选 */
.filters{display:flex;align-items:center;gap:2px;margin-left:8px}
.filters .tbtn{color:var(--text-secondary)}
.filters .tbtn .fig{font-size:15px}
.filters .tbtn[aria-pressed="true"]{background:var(--n-100);color:var(--text-primary);font-weight:var(--fw-bold)}
.clear{margin-left:auto;color:var(--text-primary)}
/* 项目行 */
.grid-cols{display:grid;grid-template-columns:var(--cols);column-gap:var(--col-gap);align-items:center;padding:0 var(--card-x)}
.cols{padding-bottom:8px;font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--text-secondary)}
.cols .r,.row .r{justify-self:end;text-align:right}
.row .more-cell{justify-self:end}
.cols .c,.row .c{justify-self:center;text-align:center}
.row{position:relative;min-height:64px;padding-top:10px;padding-bottom:10px;border-top:1px solid var(--n-line);cursor:pointer;transition:background-color var(--dur-fast) var(--ease)}
.row:hover,.row:focus-within{background:var(--n-50)}
.row:has(.pname:focus-visible){outline:2px solid var(--focus-ring);outline-offset:-2px}
.pcell{display:flex;align-items:center;gap:10px;min-width:0}
.pcell .pt{min-width:0}
.pname{display:block;font-weight:var(--fw-bold);text-decoration:none;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;outline:0}
.pname::after{content:"";position:absolute;inset:0}
.psub{display:block;font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--text-secondary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.due b{display:block;font-weight:var(--fw-regular);white-space:nowrap}
.due small{display:block;font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--text-secondary);white-space:nowrap}
.due.today b{color:var(--warn-text);font-weight:var(--fw-bold)}
.due.soon b{color:var(--warn-text)}
.due.past b{color:var(--text-secondary)}
.st{display:flex;align-items:center;gap:8px;white-space:nowrap}
.ready{position:relative;z-index:1;text-decoration:underline dashed;text-decoration-color:color-mix(in srgb, var(--c-ink) 40%, white);text-underline-offset:4px;cursor:help}
.row.arch .pname,.row.arch .due b{color:var(--text-secondary);font-weight:var(--fw-regular)}
/* 下一步:默认文字链,行悬停 / 聚焦变次按钮,同时浮现「更多」 */
.nx-cell{display:flex;align-items:center;justify-content:flex-start;min-width:0}
.nx{position:relative;z-index:1;display:inline-flex;align-items:center;gap:4px;height:var(--btn-h-sm);padding:0;border:0;border-radius:var(--r-ctl);background:transparent;color:var(--text-primary);white-space:nowrap;cursor:pointer;transition:background-color var(--dur-fast) var(--ease),border-color var(--dur-fast) var(--ease)}
.nx span{text-decoration:underline;text-decoration-color:color-mix(in srgb, var(--c-ink) 30%, white);text-underline-offset:3px}
.nx .ic{color:var(--text-secondary);transition:transform var(--dur-fast) var(--ease)}
.row:hover .nx .ic,.row:focus-within .nx .ic{transform:translateX(3px);color:var(--text-primary)}
.row .nx:hover span{text-decoration-color:var(--c-ink)}
.row .ibtn{position:relative;z-index:1;color:var(--text-secondary);transition:color var(--dur-fast) var(--ease),background-color var(--dur-fast) var(--ease)}
.row:hover .ibtn,.row:focus-within .ibtn,.row .ibtn[aria-expanded="true"]{color:var(--text-primary)}
.empty{padding:32px var(--card-x);border-top:1px solid var(--n-line);text-align:center}
.empty b{display:block;font-weight:var(--fw-bold)}
.empty p{color:var(--text-secondary);margin-top:4px}
/* 待处理的致命问题 */
.todo{display:grid;grid-template-columns:auto minmax(0,1fr) auto auto;column-gap:16px;align-items:center;padding:12px var(--card-x);border-top:1px solid var(--n-line)}
.todo b{display:block;font-weight:var(--fw-bold)}
.todo .psub{white-space:normal}
.todo .cite{margin-right:2px}
/* 右栏 */
.rail .ch{min-height:52px;padding-bottom:4px}
.rbody{padding:4px var(--card-x) 16px}
.task-h{display:flex;align-items:baseline;gap:8px}
.task-h b{font-weight:var(--fw-bold)}
.task-h .cap{margin-left:auto}
.pbar{display:flex;align-items:center;gap:10px;margin-top:10px}
.bar{flex:1;height:6px;border-radius:3px;background:var(--n-100);overflow:hidden}
.bar i{display:block;height:100%;background:var(--c-ink);border-radius:3px}
.queue{display:flex;gap:8px;align-items:flex-start;margin-top:14px;padding-top:12px;border-top:1px solid var(--n-line)}
.queue .ic{margin-top:2px;color:var(--text-secondary)}
.q2{display:grid;grid-template-columns:1fr 1fr;gap:20px}
.q2 small .ic{vertical-align:-3px;margin-right:4px;color:var(--text-secondary)}
.q2 small{display:block;font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--text-secondary)}
.q2 .big{font-family:var(--font-figures);font-variant-numeric:tabular-nums;font-weight:var(--fw-bold);font-size:28px;line-height:36px}
.q2 .unit{margin-left:4px;color:var(--text-secondary);font-family:var(--font-figures);font-variant-numeric:tabular-nums}
.q2 .bar{margin-top:6px}
.links{display:flex;gap:16px}
.log li{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:8px;padding:8px 0;border-top:1px solid var(--n-line);font-size:var(--fs-caption);line-height:var(--lh-caption)}
.log li:first-child{border-top:0;padding-top:0}
.log li > span:last-child{color:var(--text-secondary);font-family:var(--font-figures);font-variant-numeric:tabular-nums;font-size:13px}
.log .who{font-weight:var(--fw-bold)}
.av{display:inline-grid;place-items:center;width:22px;height:22px;border-radius:50%;background:var(--n-100);color:var(--text-primary);font-size:var(--fs-caption);font-weight:var(--fw-bold);line-height:1}
.av .ic{width:14px;height:14px;color:var(--text-secondary)}
.log .ok{color:var(--confirmed);white-space:nowrap}
/* ---------- 浮层:菜单、提示、轻提示 ---------- */
.menu{position:fixed;z-index:20;min-width:248px;padding:6px;background:var(--c-white);border:1px solid var(--n-line);border-radius:var(--r-ctl);box-shadow:var(--shadow-overlay)}
.menu button{display:block;width:100%;padding:7px 10px;border:0;border-radius:6px;background:transparent;text-align:left;cursor:pointer}
.menu button:hover,.menu button:focus-visible{background:var(--n-100);outline:0}
.menu button small{display:block;font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--text-secondary)}
.menu button small .fig{color:var(--text-primary)}
.menu hr{border:0;border-top:1px solid var(--n-line);margin:6px 0}
[data-tip]{position:relative}
[data-tip]:hover::before,[data-tip]:focus-visible::before{content:attr(data-tip);position:absolute;z-index:30;top:calc(100% + 8px);left:50%;transform:translateX(-50%);width:max-content;max-width:260px;padding:6px 10px;border-radius:var(--r-ctl);background:var(--c-ink);color:var(--c-white);font-size:var(--fs-caption);line-height:var(--lh-caption);font-weight:var(--fw-regular);white-space:normal;text-align:left;pointer-events:none}
[data-tip].tip-r:hover::before,[data-tip].tip-r:focus-visible::before{left:auto;right:0;transform:none}
.toasts{position:fixed;left:50%;bottom:28px;transform:translateX(-50%);z-index:40;display:flex;flex-direction:column;gap:8px;align-items:center}
.toast{padding:10px 16px;border-radius:var(--r-ctl);background:var(--c-ink);color:var(--c-white);box-shadow:var(--shadow-overlay)}
/* ---------- d5:单一状态窄侧栏 ---------- */
.app{--side-w:96px}
.side .side-in{padding:0 0 16px 10px;overflow:visible}
.side .brand{height:auto;justify-content:center;padding:20px 10px 20px 0;margin:0}
.side .brand img{width:60px;height:auto}
.side .nav{gap:4px}
.side .nav a{flex-direction:column;justify-content:center;gap:4px;height:62px;padding:0;margin-right:10px;border-radius:10px;font-size:var(--fs-caption);line-height:16px;color:var(--side-fg);font-weight:var(--fw-regular);text-align:center}
.side .nav a .ic{width:22px;height:22px}
.side .nav a:hover{background:var(--side-hover);color:var(--side-fg-strong)}
.side .nav a[aria-current]{background:var(--side-active);color:var(--side-fg-strong);font-weight:var(--fw-bold)}
.side .nav a[aria-current]::before{display:none}
.side .nav .grp{display:block;height:1px;margin:10px 22px 10px 12px;background:var(--side-line)}
.side .side-foot{flex-direction:column;justify-content:center;gap:0;margin-top:auto;padding:16px 10px 4px 0;border-top:0;color:var(--side-muted);text-align:center;white-space:normal}
.side .side-foot .avatar{width:44px;height:44px;font-size:var(--fs-title);margin-bottom:8px;background:color-mix(in srgb, white 16%, transparent);box-shadow:0 0 0 1px color-mix(in srgb, white 22%, transparent)}
.side .side-foot .uname{color:var(--side-fg-strong);font-size:var(--fs-body);line-height:var(--lh-body);font-weight:var(--fw-bold)}
.side .side-foot .plan{margin-top:4px;padding:0 8px;border-radius:4px;font-size:var(--fs-caption);line-height:20px;font-weight:var(--fw-bold)}
/* 套餐标签:只在账户区使用的「金色」阶梯,从赭色派生,不引入新色相;
免费版中性、专业版浅金底、企业版金色实底,靠深浅区分,不靠多种颜色。?plan=free / ent 预览 */
.plan[data-plan="free"]{background:color-mix(in srgb, white 12%, transparent);color:var(--side-fg)}
.plan[data-plan="pro"]{background:color-mix(in srgb, var(--c-ochre) 42%, transparent);color:color-mix(in srgb, var(--c-ochre) 12%, white);box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--c-ochre) 70%, white 10%)}
.plan[data-plan="ent"]{background:color-mix(in srgb, var(--c-ochre) 55%, white);color:var(--c-ink)}
</style>
</head>
<body>
<div class="app">
<aside class="side"><div class="side-in">
<a class="brand" href="workbench-d5.html" aria-label="标博士"><img src="../../logo/svg/logo-vertical-reverse.svg" alt=""></a>
<nav class="nav" aria-label="主导航">
<a href="workbench-d5.html" aria-current="page"><i data-ic="house"></i><span>工作台</span></a>
<a href="#" data-demo><i data-ic="award"></i><span>资质库</span></a>
<a href="#" data-demo><i data-ic="wallet"></i><span>额度</span></a>
<span class="grp" role="separator"></span>
<a href="index.html"><i data-ic="book"></i><span>方案目录</span></a>
</nav>
<div class="side-foot"><span class="avatar" aria-hidden="true">王</span><b class="uname">王敏</b><span class="plan" data-plan="pro">专业版</span></div>
</div></aside>
<div class="main">
<header class="top">
<span class="org"><b>南京某信息技术有限公司</b><span>/</span>工作台</span>
<form class="search" role="search" data-gsearch>
<i data-ic="search"></i>
<input type="search" placeholder="搜索项目、采购人" aria-label="搜索项目、采购人" aria-keyshortcuts="Control+K" data-q>
<span class="kbd" aria-hidden="true">Ctrl K</span>
</form>
<span class="vsep" aria-hidden="true"></span>
<div class="top-r">
<a class="qchip tbtn" href="#" data-demo data-tip="本期剩余额度:解析 37 / 50 次,检查 12 / 20 次" aria-label="本期剩余额度:解析 37 次,检查 12 次">解析<span class="fig">37</span><span class="sep"></span>检查<span class="fig">12</span></a>
<button class="ibtn tip-r" aria-label="通知(1 条未读)" data-tip="通知" data-notice><i data-ic="bell" class="lg"></i><span class="dot"></span></button>
</div>
</header>
<main class="content">
<div class="ph">
<div><h1>工作台</h1><p>下午好,王敏。今天是 11月3日,周二。</p></div>
<div class="act">
<span class="cost">扣 1 次解析 · 剩 <span class="fig">37</span> 次</span>
<a class="btn primary" href="../forms.html#s-new"><i data-ic="plus"></i>新建项目</a>
</div>
</div>
<div class="card alert" role="note">
<i data-ic="clock" class="lg"></i>
<p><b>某县数据中心机房改造项目</b> <span class="when">今天 17:00 截止</span>,清单还没确认。</p>
<a class="btn sm" href="../page-reading.html"><i data-ic="list"></i>确认清单</a>
</div>
<section class="card stats" aria-label="项目统计,点击可筛选项目列表" data-stats>
<button class="stat" data-f="active" aria-pressed="true"><small>进行中的项目</small><span class="big">4</span><i data-ic="filter"></i></button>
<button class="stat" data-f="due7" aria-pressed="false"><small>7 天内截止</small><span class="big due">2</span><i data-ic="filter"></i></button>
<button class="stat" data-f="fatal" aria-pressed="false"><small>待处理的致命问题</small><span class="big fatal">3</span><i data-ic="filter"></i></button>
<button class="stat" data-f="pend" aria-pressed="false"><small>待确认的清单条目</small><span class="big">4</span><i data-ic="filter"></i></button>
</section>
<div class="wb">
<div class="stack">
<section class="card" aria-labelledby="h-proj">
<div class="ch">
<h2 id="h-proj">项目</h2>
<div class="filters" role="group" aria-label="按状态筛选" data-seg>
<button class="tbtn" aria-pressed="true" data-v="active">进行中 <span class="fig">4</span></button>
<button class="tbtn" aria-pressed="false" data-v="all">全部 <span class="fig">6</span></button>
<button class="tbtn" aria-pressed="false" data-v="已递交">已递交</button>
<button class="tbtn" aria-pressed="false" data-v="已归档">已归档</button>
</div>
<span class="more" data-chip></span>
</div>
<div class="cols grid-cols" aria-hidden="true"><span>项目</span><span>投标截止</span><span>状态</span><span class="c">致命</span><span>下一步</span><span></span></div>
<ul data-rows aria-label="项目列表"></ul>
</section>
<section class="card" aria-labelledby="h-todo">
<div class="ch">
<h2 id="h-todo">待处理的致命问题</h2>
<span class="sev fatal">致命<span class="fig">3</span></span>
<a class="more lnk cap" href="#" data-demo>查看全部</a>
</div>
<ul>
<li class="todo"><span class="sev fatal">致命</span><div><b>报价超过采购包 1 的最高限价</b><span class="psub">智慧大厅建设项目 · 开标一览表 287.60 万元,限价 286.00 万元</span></div><a class="cite" href="#" data-demo><i data-ic="file"></i>报价册 · 第 <span class="fig">1</span> 页</a><a class="btn sm" href="../page-reading.html">去处理</a></li>
<li class="todo"><span class="sev fatal">致命</span><div><b>★ 条款疑似不满足:应用服务器内存</b><span class="psub">智慧大厅建设项目 · 响应 256G,要求 ≥ 512G · 上次已修改,问题仍在</span></div><a class="cite" href="#" data-demo><i data-ic="file"></i>技术册 · 第 <span class="fig">12</span> 页</a><a class="btn sm" href="../page-reading.html">去处理</a></li>
<li class="todo"><span class="sev fatal">致命</span><div><b>授权委托书的单位签章位置未发现盖章</b><span class="psub">智慧大厅建设项目 · 只看有无,不鉴定真伪</span></div><a class="cite" href="#" data-demo><i data-ic="file"></i>商务册 · 第 <span class="fig">5</span> 页</a><a class="btn sm" href="../page-reading.html">去处理</a></li>
</ul>
</section>
</div>
<div class="stack rail">
<section class="card" aria-labelledby="h-task">
<div class="ch"><h3 id="h-task">正在进行的任务</h3></div>
<div class="rbody">
<div class="task-h"><b>解析补遗 2</b><span class="cap sec">提取清单</span></div>
<div class="cap sec">某市医保信息系统等保测评服务</div>
<div class="pbar"><div class="bar" 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 class="queue"><i data-ic="clock"></i><span class="cap sec">检查排队中:智慧社区平台运维,前面 1 个</span></div>
</div>
</section>
<section class="card" aria-labelledby="h-quota">
<div class="ch"><h3 id="h-quota">本期额度</h3><span class="more cap sec">至 2027-10-31</span></div>
<div class="rbody">
<div class="q2">
<div><small>解析剩余</small><span class="big">37</span><span class="unit">/ 50</span><div class="bar" aria-hidden="true"><i style="width:26%"></i></div></div>
<div><small>检查剩余</small><span class="big">12</span><span class="unit">/ 20</span><div class="bar" aria-hidden="true"><i style="width:40%"></i></div></div>
</div>
</div>
<div class="cf links cap"><a class="lnk" href="#" data-demo>扣点规则</a><a class="lnk" href="#" data-demo>查看流水</a></div>
</section>
<section class="card" aria-labelledby="h-log">
<div class="ch"><h3 id="h-log">最近处理</h3><a class="more lnk cap" href="#" data-demo>全部记录</a></div>
<div class="rbody">
<ul class="log">
<li><span><span class="who">王敏</span> <span class="ok">✓ 已修改</span>「授权委托书签章」</span><span>14:26</span></li>
<li><span><span class="who">王敏</span> 确认了清单 v2 · 智慧大厅</span><span>11-02</span></li>
<li><span><span class="who">系统</span> 完成第 2 次检查 · 致命 3</span><span>11-02</span></li>
<li><span><span class="who">系统</span> 并入补遗 1 · 更新 2 条</span><span>11-02</span></li>
<li><span><span class="who">李工</span> 忽略「其他项目名称」:业绩介绍</span><span>11-01</span></li>
</ul>
</div>
</section>
</div>
</div>
</main>
</div>
</div>
<script>
(function () {
const $ = (s, el = document) => el.querySelector(s);
const $$ = (s, el = document) => [...el.querySelectorAll(s)];
/* Lucide 图标(ISC 许可)路径 */
const ICONS = {
grid: '<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"/>',
cert: '<rect x="3" y="4" width="18" height="16" rx="2"/><path d="M7 9h10M7 13h6"/><circle cx="16" cy="15" r="2"/>',
gauge: '<path d="m12 14 4-4"/><path d="M3.34 19a10 10 0 1 1 17.32 0"/>',
bell: '<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"/>',
book: '<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"/>',
search: '<circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/>',
plus: '<path d="M5 12h14"/><path d="M12 5v14"/>',
clock: '<circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/>',
file: '<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="M10 9H8M16 13H8M16 17H8"/>',
list: '<path d="m3 17 2 2 4-4"/><path d="m3 7 2 2 4-4"/><path d="M13 6h8M13 12h8M13 18h8"/>',
"chev-right": '<path d="m9 18 6-6-6-6"/>',
more: '<circle cx="12" cy="12" r="1"/><circle cx="19" cy="12" r="1"/><circle cx="5" cy="12" r="1"/>',
filter: '<path d="M22 3H2l8 9.46V19l4 2v-8.54L22 3z"/>',
x: '<path d="M18 6 6 18"/><path d="m6 6 12 12"/>',
building: '<path d="M6 22V4a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v18Z"/><path d="M6 12H4a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2h2"/><path d="M18 9h2a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-2"/><path d="M10 6h4M10 10h4M10 14h4M10 18h4"/>',
folders: '<path d="M20 17a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2h-3.9a2 2 0 0 1-1.69-.9l-.81-1.2a2 2 0 0 0-1.67-.9H8a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2Z"/><path d="M2 8v11a2 2 0 0 0 2 2h14"/>',
"calendar-clock": '<path d="M21 7.5V6a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h3.5"/><path d="M16 2v4M8 2v4M3 10h5"/><path d="M17.5 17.5 16 16.3V14"/><circle cx="16" cy="16" r="6"/>',
"octagon-alert": '<path d="M12 16h.01M12 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"/>',
activity: '<path d="M22 12h-2.48a2 2 0 0 0-1.93 1.46l-2.35 8.36a.25.25 0 0 1-.48 0L9.24 2.18a.25.25 0 0 0-.48 0l-2.35 8.36A2 2 0 0 1 4.49 12H2"/>',
history: '<path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/><path d="M12 7v5l4 2"/>',
loader: '<path d="M21 12a9 9 0 1 1-6.219-8.56"/>',
"file-search": '<path d="M14 2v4a2 2 0 0 0 2 2h4"/><path d="M4.268 21a2 2 0 0 0 1.727 1H18a2 2 0 0 0 2-2V7l-5-5H6a2 2 0 0 0-2 2v3"/><path d="m9 18-1.5-1.5"/><circle cx="5" cy="14" r="3"/>',
scan: '<path d="M3 7V5a2 2 0 0 1 2-2h2M17 3h2a2 2 0 0 1 2 2v2M21 17v2a2 2 0 0 1-2 2h-2M7 21H5a2 2 0 0 1-2-2v-2"/><circle cx="12" cy="12" r="3"/><path d="m16 16-1.9-1.9"/>',
bookmark: '<path d="m19 21-7-4-7 4V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v16z"/>',
pen: '<path d="M12 20h9"/><path d="M16.376 3.622a1 1 0 0 1 3.002 3.002L7.368 18.635a2 2 0 0 1-.855.506l-2.872.838a.5.5 0 0 1-.62-.62l.838-2.872a2 2 0 0 1 .506-.854z"/>',
send: '<path d="M14.536 21.686a.5.5 0 0 0 .937-.024l6.5-19a.496.496 0 0 0-.635-.635l-19 6.5a.5.5 0 0 0-.024.937l7.93 3.18a2 2 0 0 1 1.112 1.11z"/><path d="m21.854 2.147-10.94 10.939"/>',
archive: '<rect width="20" height="5" x="2" y="3" rx="1"/><path d="M4 8v11a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8"/><path d="M10 12h4"/>',
download: '<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><path d="m7 10 5 5 5-5"/><path d="M12 15V3"/>',
upload: '<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><path d="m17 8-5-5-5 5"/><path d="M12 3v12"/>',
wrench: '<path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/>',
cpu: '<rect width="16" height="16" x="4" y="4" rx="2"/><rect width="6" height="6" x="9" y="9" rx="1"/><path d="M15 2v2M15 20v2M2 15h2M2 9h2M20 15h2M20 9h2M9 2v2M9 20v2"/>',
house: '<path d="M15 21v-8a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v8"/><path d="M3 10a2 2 0 0 1 .709-1.528l7-5.999a2 2 0 0 1 2.582 0l7 5.999A2 2 0 0 1 21 10v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/>',
award: '<path d="m15.477 12.89 1.515 8.526a.5.5 0 0 1-.81.47l-3.58-2.687a1 1 0 0 0-1.197 0l-3.586 2.686a.5.5 0 0 1-.81-.469l1.514-8.526"/><circle cx="12" cy="8" r="6"/>',
wallet: '<path d="M19 7V4a1 1 0 0 0-1-1H5a2 2 0 0 0 0 4h15a1 1 0 0 1 1 1v4h-3a2 2 0 0 0 0 4h3a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1"/><path d="M3 5v14a2 2 0 0 0 2 2h15a1 1 0 0 0 1-1v-4"/>',
"panel-close": '<rect width="18" height="18" x="3" y="3" rx="2"/><path d="M9 3v18"/><path d="m16 15-3-3 3-3"/>',
"panel-open": '<rect width="18" height="18" x="3" y="3" rx="2"/><path d="M9 3v18"/><path d="m14 9 3 3-3 3"/>'
};
const icon = (n, cls = "") => `<svg class="ic ${cls}" viewBox="0 0 24 24" aria-hidden="true">${ICONS[n] || ""}</svg>`;
$$("i[data-ic]").forEach((el) => { el.outerHTML = icon(el.dataset.ic, el.className); });
/* 顶栏:滚动后变为半透明白 + 毛玻璃 */
const topEl = $(".top"); const onScroll = () => topEl.classList.toggle("scrolled", scrollY > 4); addEventListener("scroll", onScroll, { passive: true }); onScroll();
/* 侧栏:主题(?side=light 为浅灰对照)与折叠(记在本地,读写失败不影响页面) */
const app = $(".app");
if (new URLSearchParams(location.search).get("side") === "light") app.dataset.side = "light";
if (new URLSearchParams(location.search).get("v") === "flat") app.dataset.v = "flat";
const plan = new URLSearchParams(location.search).get("plan"); const PLAN = { free: "免费版", pro: "专业版", ent: "企业版" };
if (PLAN[plan]) { const el = $(".plan"); el.dataset.plan = plan; el.textContent = PLAN[plan]; }
function toast(msg) {
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.textContent = msg; box.appendChild(t);
setTimeout(() => t.remove(), 4000);
}
const DEMO = "(演示)这里只做了工作台,可以打开「某县数据中心」或「智慧大厅」看读标页";
/* 数据沿用 page-workbench.html;pend 为待确认清单条目数(对应统计「在 2 个项目里」)。
next 为「下一步」,menu 为「更多」里的操作;cost 表示会扣点。 */
const CHECK_COST = { label: "扣 1 次检查 · 剩 ", left: 12, unit: " 次" };
const P = [
{ name: "某县数据中心机房改造项目", buyer: "某县大数据管理局", lot: "标段 1", h: 6, due: ["今天 17:00 截止", "剩 6 小时"], cls: "today", st: "编制中", ready: "", fatal: 0, pend: 3, href: "../page-reading.html",
next: "确认清单", menu: [["上传投标文件", ""], ["发起检查", "cost"], ["导出清单", ""]] },
{ name: "某区智慧社区平台运维服务", buyer: "某区民政局", lot: "全部", h: 53, due: ["剩 2 天 5 小时", "11月5日 16:00"], cls: "soon", st: "已检查", ready: "就绪", fatal: 0, pend: 0,
next: "导出检查报告", menu: [["查看检查结果", ""], ["标记为已递交", ""]] },
{ name: "某市政务服务中心智慧大厅建设项目", buyer: "某市政务服务管理办公室", lot: "采购包 1", h: 408, due: ["剩 17 天", "11月20日 09:30"], cls: "", st: "已检查", ready: "未就绪", fatal: 3, pend: 0, href: "../page-reading.html",
next: "处理致命问题", menu: [["重新检查", "cost"], ["导出检查报告", "致命问题未处理也可以导出"], ["标记为已递交", ""]] },
{ name: "某市医保信息系统等保测评服务", buyer: "某市医疗保障局", lot: "全部", h: 600, due: ["剩 25 天", "11月28日 10:00"], cls: "", st: "跟进中", ready: "", fatal: 0, pend: 1,
next: "确认清单", menu: [["查看解析进度", "补遗 2 解析中 62%"], ["上传补遗", ""]] },
{ name: "某区智慧校园一期", buyer: "某区教育局", lot: "全部", h: -1, due: ["已截止", "已递交"], cls: "past", st: "已递交", ready: "", fatal: 0, pend: 0,
next: "归档项目", menu: [["导出检查报告", ""]] },
{ name: "某市不动产登记信息平台升级", buyer: "某市自然资源和规划局", lot: "采购包 2", h: -2, due: ["已截止", "已归档"], cls: "past", st: "已归档", ready: "", fatal: 0, pend: 0,
next: "查看项目", menu: [["恢复为进行中", ""]] }
];
const STAT = {
active: { label: "进行中", fn: (p) => !["已递交", "已归档"].includes(p.st) },
due7: { label: "7 天内截止", fn: (p) => p.h >= 0 && p.h <= 168 },
fatal: { label: "有致命问题", fn: (p) => p.fatal > 0 },
pend: { label: "有待确认条目", fn: (p) => p.pend > 0 }
};
let seg = "active", stat = "", q = "";
function status(p) {
let r = "";
if (p.ready === "就绪") r = '<span class="ready" tabindex="0" data-tip="只表示致命问题都已处理,不代表投标文件没有问题。">就绪</span>';
else if (p.ready) r = `<span>${p.ready}</span>`;
return `<span class="st"><span class="tag">${p.st}</span>${r}</span>`;
}
function render() {
const rows = P.map((p, i) => [p, i]).filter(([p]) => {
if (stat) { if (!STAT[stat].fn(p)) return false; }
else if (seg !== "all" && !(seg === "active" ? STAT.active.fn(p) : p.st === seg)) return false;
return !q || (p.name + p.buyer).includes(q);
});
$("[data-rows]").innerHTML = rows.map(([p, i]) => `<li class="row grid-cols ${p.st === "已归档" ? "arch" : ""}" data-i="${i}">
<span class="pcell"><span class="pt"><a class="pname" href="${p.href || "#"}" title="${p.name}" ${p.href ? "" : "data-demo"}>${p.name}</a><span class="psub">${p.buyer} · ${p.lot}</span></span></span>
<span class="due ${p.cls}"><b>${p.due[0]}</b><small>${p.due[1]}</small></span>
${status(p)}
<span class="c">${p.fatal ? `<span class="sev fatal">致命<span class="fig">${p.fatal}</span></span>` : '<span class="dash" aria-label="无">—</span>'}</span>
<span class="nx-cell"><button class="nx" data-next><span>${p.next}</span>${icon("chev-right")}</button></span><span class="more-cell"><button class="ibtn sm" aria-label="更多操作:${p.name}" aria-haspopup="menu" aria-expanded="false" data-menu>${icon("more")}</button></span>
</li>`).join("")
|| `<li class="empty"><b>没有符合条件的项目</b><p>换个关键词,或切到「全部」看看。</p></li>`;
$$("[data-seg] button").forEach((b) => b.setAttribute("aria-pressed", !stat && b.dataset.v === seg));
$$("[data-stats] .stat").forEach((b) => b.setAttribute("aria-pressed", b.dataset.f === stat || (!stat && b.dataset.f === "active" && seg === "active")));
$("[data-chip]").innerHTML = stat ? `<button class="tbtn clear" data-clear>${STAT[stat].label}${icon("x")}<span class="vh">,清除筛选</span></button>` : "";
}
/* 「更多」菜单 */
let menuEl = null, menuBtn = null;
function closeMenu(focusBack) {
if (!menuEl) return;
menuEl.remove(); menuEl = null;
if (menuBtn) { menuBtn.setAttribute("aria-expanded", "false"); if (focusBack) menuBtn.focus(); }
menuBtn = null;
}
function openMenu(btn, p) {
closeMenu();
menuBtn = btn; btn.setAttribute("aria-expanded", "true");
menuEl = document.createElement("div");
menuEl.className = "menu"; menuEl.setAttribute("role", "menu");
const items = [["打开项目", ""], ...p.menu].map(([t, s]) => {
const sub = s === "cost" ? `${CHECK_COST.label}<span class="fig">${CHECK_COST.left}</span>${CHECK_COST.unit}` : s;
return `<button role="menuitem" data-act="${t}">${t}${sub ? `<small>${sub}</small>` : ""}</button>`;
});
items.splice(1, 0, "<hr>");
menuEl.innerHTML = items.join("");
document.body.appendChild(menuEl);
const r = btn.getBoundingClientRect();
menuEl.style.top = `${r.bottom + 4}px`;
menuEl.style.left = `${r.right - menuEl.offsetWidth}px`;
menuEl.addEventListener("click", (e) => {
const it = e.target.closest("[data-act]"); if (!it) return;
const a = it.dataset.act; closeMenu(true);
if (a === "打开项目" && p.href) location.href = p.href;
else toast(`(演示)${a}:${p.name}`);
});
menuEl.addEventListener("keydown", (e) => {
const its = $$("[role=menuitem]", menuEl), k = its.indexOf(document.activeElement);
if (e.key === "ArrowDown") { e.preventDefault(); its[(k + 1) % its.length].focus(); }
if (e.key === "ArrowUp") { e.preventDefault(); its[(k - 1 + its.length) % its.length].focus(); }
});
$("[role=menuitem]", menuEl).focus();
}
$("[data-rows]").addEventListener("click", (e) => {
const row = e.target.closest(".row"); if (!row) return;
const p = P[+row.dataset.i];
const m = e.target.closest("[data-menu]");
if (m) { e.preventDefault(); e.stopPropagation(); if (menuBtn === m) closeMenu(); else openMenu(m, p); return; }
if (e.target.closest(".ready")) return;
if (e.target.closest("[data-next]")) {
if (p.href) location.href = p.href;
else toast(`(演示)${p.next}:${p.name}`);
return;
}
if (!e.target.closest(".pname")) { if (p.href) location.href = p.href; else toast(DEMO); }
});
document.addEventListener("click", (e) => { if (menuEl && !menuEl.contains(e.target)) closeMenu(); });
window.addEventListener("scroll", () => closeMenu(), { passive: true });
window.addEventListener("resize", () => closeMenu());
$("[data-seg]").addEventListener("click", (e) => { const b = e.target.closest("button"); if (!b) return; seg = b.dataset.v; stat = ""; render(); });
$("[data-stats]").addEventListener("click", (e) => {
const b = e.target.closest(".stat"); if (!b) return;
const f = b.dataset.f;
if (f === "active") { seg = "active"; stat = ""; }
else if (stat === f) { stat = ""; seg = "active"; }
else stat = f;
render();
});
$("[data-chip]").addEventListener("click", (e) => { if (e.target.closest("[data-clear]")) { stat = ""; seg = "active"; render(); $("[data-seg] button[aria-pressed=true]").focus(); } });
$("[data-q]").addEventListener("input", (e) => { q = e.target.value.trim(); render(); });
$("[data-gsearch]").addEventListener("submit", (e) => e.preventDefault());
document.addEventListener("click", (e) => { const d = e.target.closest("[data-demo]"); if (d) { e.preventDefault(); toast(DEMO); } });
$("[data-notice]").onclick = () => toast("解析完成:补遗 1 已并入清单(新增 2 条,更新 1 条)。");
document.addEventListener("keydown", (e) => {
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "k") { e.preventDefault(); $("[data-q]").focus(); }
if (e.key === "Escape") {
if (menuEl) closeMenu(true);
else if (document.activeElement === $("[data-q]")) $("[data-q]").blur();
}
});
render();
})();
</script>
</body>
</html>