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

470 lines
34 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>
<!--
工作台 · 风格方案 B「工作面板」(探索稿,不是定稿)
数据、文案、交互沿用 ../page-workbench.html。
本方案相对 design-system.md 改了哪些规则:
1. §5.1 圆角:2px 一档 → 分层圆角。面板 10、列表行 8、按钮 / 输入框 / 分段控件 6、标签 4。仍不用胶囊形。
2. §5.1 层次:「靠分隔线和底色」→ 只靠底色。画布白、侧边栏和面板用校样纸色,面板里的行是白底,行与行留 3px 缝,不画分隔线。
3. §3.1 页面底色:校样纸 → 白;校样纸改作侧边栏和面板底色。
4. §7.1 次按钮:白底 + 控件边框 → 浅墨实底、无边框。只有输入框保留控件边框(WCAG 1.4.11)。
5. §7.9 状态标签:中性描边 → 浅灰实底、无描边;「就绪」同样改为实底。
6. §7.9 项目行:「状态」列改为「进度」列,用四段状态轨(跟进中 → 编制中 → 已检查 → 已递交)加当前状态标签;已归档整条变浅。
7. §7.9 倒计时:一行写完 → 两行,上行写剩余时间(≤ 72 小时赭色),下行写截止时刻。
8. §7.7 表头:凹陷灰底 + 2px 墨色底线 → 去掉,只留辅助灰列名。
9. 工作台统计:四张一样的卡片 → 一个面板里四列,数字 32px,每列可点击筛选;致命数字用致命红,截止数字用赭。
10. §4.2 字阶:H1 24/32 → 28/36;统计数字 30/36 → 32/40。其余不变。
11. §7.2 级别标签:圆角 2 → 4,形状规则(致命实心 / 重要描边 / 提示无框)不变。
12. 顶栏:面包屑 → 居中的全局搜索(Ctrl K);额度从文字改为小进度条 + 数字。
新增的派生色全部用 color-mix() 从 tokens.css 的现有色得到,集中声明在 <style> 顶部,没有新色相。
-->
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>工作台 · 方案 B 工作面板</title>
<link rel="icon" href="data:,">
<link rel="stylesheet" href="../../system/tokens.css">
<link rel="stylesheet" href="../../system/fonts-local.css">
<style>
/* ---------- 本方案派生变量(全部从 tokens.css 现有色派生,不引入新色相) ---------- */
:root{
--wb-canvas: var(--c-white); /* 应用画布 */
--wb-side: color-mix(in srgb, var(--c-paper) 55%, var(--c-sunken)); /* 侧边栏:比校样纸略深 */
--wb-panel: var(--c-paper); /* 内容分区面板 */
--wb-fill: color-mix(in srgb, var(--c-ink) 6%, var(--c-white)); /* 次按钮、图标按钮悬停 */
--wb-fill-hover: color-mix(in srgb, var(--c-ink) 11%, var(--c-white)); /* 次按钮悬停 */
--wb-track: color-mix(in srgb, var(--c-ink) 7%, var(--c-paper)); /* 分段控件底、面板内的凹槽 */
--wb-row-hover: color-mix(in srgb, var(--c-ink) 5%, var(--c-white)); /* 列表行悬停 */
--wb-stat-hover: color-mix(in srgb, var(--c-white) 55%, var(--c-paper));/* 统计列悬停 */
--wb-tag: var(--c-sunken); /* 状态标签实底 */
--wb-rail-off: color-mix(in srgb, var(--c-ink) 12%, var(--c-white)); /* 状态轨:未走到的段 */
--wb-rail-on: var(--c-ink); /* 状态轨:已走过 / 当前段 */
--wb-rail-arch: color-mix(in srgb, var(--c-ink) 32%, var(--c-white)); /* 状态轨:已归档整条变浅 */
--wb-primary-hover: color-mix(in srgb, var(--c-ink) 88%, var(--c-white));/* 主按钮悬停:变浅 */
--wb-r-panel: 10px; --wb-r-row: 8px; --wb-r-ctl: 6px; --wb-r-tag: 4px;
--wb-fs-h1: 28px; --wb-lh-h1: 36px;
--wb-fs-stat: 32px; --wb-lh-stat: 40px;
--wb-top-h: 60px;
--wb-rail-w: 296px;
--wb-track-w: 156px;
}
@media (max-width: 1399px){ :root{ --wb-rail-w: 260px; --wb-track-w: 132px; } }
*,*::before,*::after{box-sizing:border-box}
html,body{margin:0}
body{background:var(--wb-canvas);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,input{font:inherit;color:inherit}
:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}
.fig{font-family:var(--font-figures);font-variant-numeric:tabular-nums;font-weight:var(--fw-bold)}
.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}
.sec{color:var(--text-secondary)}
.cap{font-size:var(--fs-caption);line-height:var(--lh-caption)}
.vh{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
/* ---------- 框架 ---------- */
.app{display:grid;grid-template-columns:var(--sidebar-w) minmax(0,1fr);min-height:100vh}
.side{background:var(--wb-side)}
.side-in{display:flex;flex-direction:column;padding:20px 12px 16px;position:sticky;top:0;height:100vh}
.side .brand{display:block;padding:0 8px 24px}
.side .brand img{height:30px;display:block}
.nav{display:flex;flex-direction:column;gap:2px}
.nav a{display:flex;align-items:center;gap:10px;height:36px;padding:0 10px;border-radius:var(--wb-r-ctl);text-decoration:none;color:var(--text-secondary)}
.nav a:hover{background:var(--wb-track);color:var(--text-primary)}
.nav a[aria-current="page"]{background:var(--c-white);color:var(--text-primary);font-weight:var(--fw-bold)}
.nav .grp{margin:20px 10px 6px;font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--text-secondary)}
.side-foot{margin-top:auto;padding:12px 10px 0;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);color:var(--text-primary);font-weight:var(--fw-bold)}
.main{min-width:0;display:flex;flex-direction:column}
.top{height:var(--wb-top-h);display:grid;grid-template-columns:minmax(max-content,1fr) minmax(280px,540px) minmax(max-content,1fr);align-items:center;gap:24px;padding:0 24px 0 32px;position:sticky;top:0;z-index:20;background:var(--wb-canvas)}
.top .date{color:var(--text-secondary);white-space:nowrap}
.gsearch{position:relative;display:flex;align-items:center}
.gsearch .ic{position:absolute;left:12px;color:var(--text-secondary);pointer-events:none}
.gsearch input{width:100%;height:36px;padding:0 64px 0 38px;border:1px solid var(--border-control);border-radius:var(--wb-r-ctl);background:var(--c-white)}
.gsearch input::placeholder{color:var(--text-secondary)}
.gsearch input:focus-visible{outline:2px solid var(--focus-ring);outline-offset:1px;border-color:var(--c-ink)}
.kbd{display:inline-flex;align-items:center;height:20px;padding:0 6px;border-radius:var(--wb-r-tag);background:var(--wb-tag);color:var(--text-secondary);font:var(--fw-regular) var(--fs-caption)/1 var(--font-sans)}
.gsearch .kbd{position:absolute;right:8px;pointer-events:none}
.top-r{display:flex;align-items:center;justify-content:flex-end;gap:4px}
.qchip{display:flex;align-items:center;gap:16px;height:36px;padding:0 10px;border-radius:var(--wb-r-ctl);text-decoration:none}
.qchip:hover{background:var(--wb-fill)}
.qchip .q{display:flex;align-items:center;gap:8px;color:var(--text-secondary);white-space:nowrap}
.qchip .q .fig{color:var(--text-primary);font-size:16px;min-width:18px}
.mbar{width:40px;height:4px;border-radius:2px;background:var(--wb-rail-off);overflow:hidden}
.mbar i{display:block;height:100%;background:var(--c-ink)}
.ibtn{position:relative;display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;border:0;border-radius:var(--wb-r-ctl);background:none;cursor:pointer;color:var(--text-primary)}
.ibtn:hover{background:var(--wb-fill)}
.ibtn .dot{position:absolute;top:8px;right:9px;width:6px;height:6px;border-radius:50%;background:var(--c-ink);box-shadow:0 0 0 2px var(--wb-canvas)}
.content{width:100%;max-width:calc(var(--content-max) + 64px);padding:12px 32px 48px}
/* ---------- 页头 ---------- */
.ph{display:flex;align-items:flex-end;gap:24px;margin-bottom:20px}
.ph h1{font:var(--fw-bold) var(--wb-fs-h1)/var(--wb-lh-h1) var(--font-serif);letter-spacing:.02em}
.ph p{margin-top:4px;color:var(--text-secondary)}
.ph .act{margin-left:auto;display:flex;align-items:center;gap:12px}
/* ---------- 按钮 ---------- */
.btn{display:inline-flex;align-items:center;gap:6px;height:34px;padding:0 14px;border:0;border-radius:var(--wb-r-ctl);background:var(--wb-fill);color:var(--text-primary);text-decoration:none;white-space:nowrap;cursor:pointer;transition:background var(--dur-fast) var(--ease)}
.btn:hover{background:var(--wb-fill-hover)}
.btn.primary{background:var(--c-ink);color:var(--text-on-dark);font-weight:var(--fw-bold)}
.btn.primary:hover{background:var(--wb-primary-hover)}
.btn.on-tint{background:var(--c-white)}
.btn.on-tint:hover{background:var(--wb-fill)}
.cost{color:var(--text-secondary);font-size:var(--fs-caption)}
.cost .fig{color:var(--text-primary);font-size:14px}
.lnk{color:var(--text-primary);text-underline-offset:3px}
.lnk:hover{text-decoration-thickness:2px}
/* ---------- 提醒 ---------- */
.alert{display:flex;align-items:center;gap:12px;padding:12px 12px 12px 16px;border-radius:var(--wb-r-panel);background:var(--warn-soft);margin-bottom:16px}
.alert > .ic{color:var(--warn)}
.alert p{flex:1;min-width:0}
.alert b{font-weight:var(--fw-bold)}
/* ---------- 面板 ---------- */
.panel{background:var(--wb-panel);border-radius:var(--wb-r-panel);padding:6px}
.panel-h{display:flex;align-items:center;gap:12px;min-height:44px;padding:4px 10px 6px 12px}
.panel-h h2,.panel-h h3{font:var(--fw-bold) var(--fs-title)/var(--lh-title) var(--font-sans)}
.panel-h .more{margin-left:auto}
.panel-f{padding:8px 12px 6px}
/* ---------- 统计 ---------- */
.stats{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin-bottom:16px}
.stat{display:block;text-align:left;border:0;background:none;border-radius:var(--wb-r-row);padding:12px 16px 14px;cursor:pointer;transition:background var(--dur-fast) var(--ease)}
.stat:hover{background:var(--wb-stat-hover)}
.stat[aria-pressed="true"]{background:var(--c-white)}
.stat small{display:flex;align-items:center;gap:6px;font-size:var(--fs-body);line-height:var(--lh-body);color:var(--text-secondary)}
.stat .big{display:block;margin:6px 0 2px;font:var(--fw-bold) var(--wb-fs-stat)/var(--wb-lh-stat) var(--font-figures);font-variant-numeric:tabular-nums;color:var(--text-primary)}
.stat .big.fatal{color:var(--sev-fatal)}
.stat .big.due{color:var(--warn)}
.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(--wb-rail-w);gap:16px;align-items:start}
.col{display:flex;flex-direction:column;gap:16px;min-width:0}
/* 分段控件:整体浅底,选中项白底,无描边 */
.seg{display:inline-flex;gap:2px;padding:3px;border-radius:calc(var(--wb-r-ctl) + 3px);background:var(--wb-track)}
.seg button{display:flex;align-items:center;gap:6px;height:28px;padding:0 12px;border:0;border-radius:var(--wb-r-ctl);background:none;color:var(--text-secondary);cursor:pointer;white-space:nowrap;transition:background var(--dur-fast) var(--ease),color var(--dur-fast) var(--ease)}
.seg button:hover{color:var(--text-primary)}
.seg button[aria-pressed="true"]{background:var(--c-white);color:var(--text-primary);font-weight:var(--fw-bold)}
.seg .fig{font-weight:var(--fw-bold)}
.chip{display:inline-flex;align-items:center;gap:4px;height:28px;padding:0 6px 0 10px;border:0;border-radius:var(--wb-r-ctl);background:var(--c-ink);color:var(--text-on-dark);cursor:pointer;white-space:nowrap}
.chip:hover{background:var(--wb-primary-hover)}
.lsearch{position:relative;margin-left:auto;display:flex;align-items:center}
.lsearch .ic{position:absolute;left:10px;color:var(--text-secondary);pointer-events:none}
.lsearch input{width:220px;height:34px;padding:0 10px 0 32px;border:1px solid var(--border-control);border-radius:var(--wb-r-ctl);background:var(--c-white)}
.lsearch input::placeholder{color:var(--text-secondary)}
input[type=search]::-webkit-search-cancel-button{-webkit-appearance:none;appearance:none}
/* 嵌入式列表 */
.grid-cols{display:grid;grid-template-columns:minmax(0,1fr) 120px var(--wb-track-w) 56px 64px 16px;gap:16px;align-items:center}
@media (max-width: 1399px){ .grid-cols{grid-template-columns:minmax(0,1fr) 112px var(--wb-track-w) 52px 60px 16px;gap:12px} }
.cols{padding:6px 14px 6px 16px;font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--text-secondary)}
.cols .r,.row .r{text-align:right;justify-self:end}
.rows{display:flex;flex-direction:column;gap:3px}
.row{padding:12px 14px 12px 16px;min-height:64px;background:var(--c-white);border-radius:var(--wb-r-row);text-decoration:none;color:inherit;cursor:pointer;transition:background var(--dur-fast) var(--ease)}
.row:hover{background:var(--wb-row-hover)}
.row:focus-visible{outline-offset:-2px}
.row .go{color:var(--text-secondary);opacity:0;transition:opacity var(--dur-fast) var(--ease)}
.row:hover .go,.row:focus-visible .go{opacity:1}
.pname{display:block;font-weight:var(--fw-bold);font-size:15px;line-height:22px}
.psub{display:block;margin-top:2px;font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--text-secondary)}
.due b{display:block;font-weight:var(--fw-regular)}
.due b .fig{font-size:15px}
.due small{display:block;font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--text-secondary)}
.due.today b,.due.soon b{color:var(--warn-text)}
.due.today b{font-weight:var(--fw-bold)}
.due.past b{color:var(--text-secondary)}
/* 状态轨:四段 = 跟进中 → 编制中 → 已检查 → 已递交 */
.track{position:relative;width:var(--wb-track-w);padding-bottom:22px}
.track .segs{display:grid;grid-template-columns:repeat(4,1fr);gap:3px}
.track .segs i{height:6px;border-radius:2px;background:var(--wb-rail-off)}
.track .segs i.on{background:var(--wb-rail-on)}
.track .lab{position:absolute;top:12px;white-space:nowrap}
.track.arch .segs i{background:var(--wb-rail-arch)}
.tag{display:inline-flex;align-items:center;height:20px;padding:0 6px;border-radius:var(--wb-r-tag);background:var(--wb-tag);color:var(--text-primary);font-size:var(--fs-caption);line-height:1;white-space:nowrap}
.track.arch .tag{color:var(--text-secondary)}
/* 问题级别标签:致命实心 / 重要描边 / 提示无框 */
.sev{display:inline-flex;align-items:center;gap:4px;height:20px;padding:0 6px;border-radius:var(--wb-r-tag);font-size:var(--fs-caption);line-height:1;font-weight:var(--fw-bold);white-space:nowrap}
.sev.fatal{background:var(--sev-fatal);color:var(--text-on-dark)}
.sev .fig{font-size:13px}
.dash{color:var(--text-secondary)}
.empty{padding:28px 16px;background:var(--c-white);border-radius:var(--wb-r-row);text-align:center}
.empty b{display:block;font-weight:var(--fw-bold)}
.empty p{margin-top:4px;color:var(--text-secondary)}
/* 待处理的致命问题 */
.todo{position:relative;display:grid;grid-template-columns:auto minmax(0,1fr) auto;gap:12px;align-items:baseline;padding:12px 16px 12px 20px;background:var(--c-white);border-radius:var(--wb-r-row)}
.todo::before{content:"";position:absolute;left:7px;top:12px;bottom:12px;width:3px;border-radius:2px;background:var(--sev-fatal)}
.todo b{display:block;font-weight:var(--fw-bold)}
.cite{display:inline-flex;align-items:center;gap:6px;color:var(--ai);text-decoration:none;white-space:nowrap}
.cite:hover{color:var(--ai-strong);text-decoration:underline;text-underline-offset:3px}
/* 右栏 */
.rail .panel{padding:6px 6px 8px}
.rail .body{padding:4px 12px 4px}
.task-h{display:flex;align-items:baseline;gap:8px}
.task-h b{font-weight:var(--fw-bold)}
.task-h span{margin-left:auto}
.pbar{display:flex;align-items:center;gap:10px;margin-top:8px}
.pbar .bar{flex:1;height:6px;border-radius:3px;background:var(--wb-rail-off);overflow:hidden}
.pbar .bar i{display:block;height:100%;background:var(--c-ink);border-radius:3px}
.pbar .fig{font-size:15px;min-width:34px;text-align:right}
.queue{display:flex;gap:8px;align-items:flex-start;margin-top:12px;padding:10px 12px;border-radius:var(--wb-r-row);background:var(--c-white)}
.queue .ic{margin-top:2px;color:var(--text-secondary)}
.rnote{margin-top:10px}
.q2{display:grid;grid-template-columns:1fr 1fr;gap:6px}
.q2 > div{padding:10px 12px 12px;background:var(--c-white);border-radius:var(--wb-r-row)}
.q2 small{display:block;color:var(--text-secondary)}
.q2 .big{font:var(--fw-bold) 26px/34px var(--font-figures);font-variant-numeric:tabular-nums}
.q2 .unit{margin-left:4px;color:var(--text-secondary);font-family:var(--font-figures);font-size:14px}
.q2 .mbar{width:100%;margin-top:6px}
.links{display:flex;justify-content:space-between;padding:10px 12px 2px}
.log{display:flex;flex-direction:column;gap:2px}
.log li{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:8px;padding:6px 12px;border-radius:var(--wb-r-row);font-size:var(--fs-caption);line-height:var(--lh-caption)}
.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)}
.log .ok{color:var(--confirmed);white-space:nowrap}
/* 悬停说明(浮层,可用阴影) */
[data-tip]{position:relative}
[data-tip]:hover::after,[data-tip]:focus-visible::after{content:attr(data-tip);position:absolute;z-index:30;top:calc(100% + 6px);left:0;width:max-content;max-width:260px;padding:6px 10px;border-radius:var(--wb-r-ctl);background:var(--c-ink);color:var(--text-on-dark);font:var(--fw-regular) var(--fs-caption)/var(--lh-caption) var(--font-sans);box-shadow:var(--shadow-overlay);pointer-events:none;white-space:normal}
[data-tip].tip-r:hover::after,[data-tip].tip-r:focus-visible::after{left:auto;right:0}
.toasts{position:fixed;left:50%;bottom:28px;transform:translateX(-50%);z-index:60;display:flex;flex-direction:column;gap:8px}
.toast{padding:10px 16px;border-radius:var(--wb-r-ctl);background:var(--c-ink);color:var(--text-on-dark);box-shadow:var(--shadow-overlay)}
</style>
</head>
<body>
<div class="app">
<aside class="side"><div class="side-in">
<a class="brand" href="workbench-b.html"><img src="../../logo/svg/logo-horizontal.svg" alt="标博士"></a>
<nav class="nav" aria-label="主导航">
<a href="workbench-b.html" aria-current="page"><i data-ic="grid" class="lg"></i>工作台</a>
<a href="#" data-demo><i data-ic="cert" class="lg"></i>资质库</a>
<a href="#" data-demo><i data-ic="gauge" class="lg"></i>额度</a>
<div class="grp">UI demo</div>
<a href="../index.html"><i data-ic="book" class="lg"></i>demo 目录</a>
</nav>
<div class="side-foot"><b>王敏</b>南京某信息技术有限公司<br>专业版 · 至 2027-10-31</div>
</div></aside>
<div class="main">
<header class="top">
<span class="date">11月3日 周二</span>
<form class="gsearch" role="search" data-gsearch>
<i data-ic="search"></i>
<input type="search" placeholder="搜索项目、文件、条款" aria-label="全局搜索:项目、文件、条款" aria-keyshortcuts="Control+K" data-gq>
<span class="kbd" aria-hidden="true">Ctrl K</span>
</form>
<div class="top-r">
<a class="qchip" href="#" data-demo data-tip="本期剩余额度:解析 37 / 50 次,检查 12 / 20 次" aria-label="本期剩余额度:解析 37 次,检查 12 次">
<span class="q">解析<span class="mbar" aria-hidden="true"><i style="width:26%"></i></span><span class="fig">37</span></span>
<span class="q">检查<span class="mbar" aria-hidden="true"><i style="width:40%"></i></span><span class="fig">12</span></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>按投标截止时间排序,截止临近的排在前面。</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="alert" role="note">
<i data-ic="clock" class="lg"></i>
<p><b>某县数据中心机房改造项目今天 17:00 截止。</b>清单还是草稿,还没有做过检查。</p>
<a class="btn on-tint" href="../page-reading.html">去确认清单</a>
</div>
<section class="panel stats" aria-label="项目统计,点击可筛选下方项目列表" data-stats>
<button class="stat" data-f="active" aria-pressed="false"><small>进行中的项目</small><span class="big">4</span><p>已递交 1 · 已归档 1</p></button>
<button class="stat" data-f="due7" aria-pressed="false"><small><i data-ic="clock"></i>7 天内截止</small><span class="big due">2</span><p class="due">最近:今天 17:00</p></button>
<button class="stat" data-f="fatal" aria-pressed="false"><small>待处理的致命问题</small><span class="big fatal">3</span><p>在 1 个项目里</p></button>
<button class="stat" data-f="pend" aria-pressed="false"><small>待确认的清单条目</small><span class="big">4</span><p>在 2 个项目里</p></button>
</section>
<div class="wb">
<div class="col">
<section class="panel" aria-labelledby="h-proj">
<div class="panel-h">
<h2 id="h-proj">项目</h2>
<div class="seg" role="group" aria-label="按状态筛选" data-seg>
<button aria-pressed="true" data-v="active">进行中 <span class="fig">4</span></button>
<button aria-pressed="false" data-v="all">全部 <span class="fig">6</span></button>
<button aria-pressed="false" data-v="已递交">已递交</button>
<button aria-pressed="false" data-v="已归档">已归档</button>
</div>
<span data-chip></span>
<label class="lsearch"><i data-ic="search"></i><input type="search" placeholder="搜索项目、采购人" aria-label="在项目列表中搜索" data-q></label>
</div>
<div class="cols grid-cols" aria-hidden="true"><span>项目</span><span>投标截止</span><span>进度</span><span>就绪</span><span class="r">致命</span><span></span></div>
<ul class="rows" data-rows aria-label="项目列表"></ul>
<p class="panel-f cap sec">「就绪」只表示致命问题都已处理,不代表投标文件没有问题。</p>
</section>
<section class="panel" aria-labelledby="h-todo">
<div class="panel-h">
<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 class="rows">
<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></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></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></li>
</ul>
</section>
</div>
<div class="col rail">
<section class="panel" aria-labelledby="h-task">
<div class="panel-h"><h3 id="h-task">正在进行的任务</h3></div>
<div class="body">
<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>
<p class="rnote cap sec">可以离开本页,完成后会发站内通知。</p>
</div>
</section>
<section class="panel" aria-labelledby="h-quota">
<div class="panel-h"><h3 id="h-quota">本期额度</h3><span class="more cap sec">至 2027-10-31</span></div>
<div class="q2">
<div><small>解析剩余</small><span class="big">37</span><span class="unit">/ 50</span><div class="mbar" 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="mbar" aria-hidden="true"><i style="width:40%"></i></div></div>
</div>
<div class="links"><a class="lnk cap" href="#" data-demo>扣点规则</a><a class="lnk cap" href="#" data-demo>查看流水</a></div>
</section>
<section class="panel" aria-labelledby="h-log">
<div class="panel-h"><h3 id="h-log">最近处理</h3><a class="more lnk cap" href="#" data-demo>全部记录</a></div>
<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>
</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"/>',
"chev-right": '<path d="m9 18 6-6-6-6"/>',
x: '<path d="M18 6 6 18"/><path d="m6 6 12 12"/>'
};
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); });
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);
}
/* 数据沿用 page-workbench.html;due 拆成两行,pend 为待确认清单条目数(对应统计「在 2 个项目里」) */
const STEPS = ["跟进中", "编制中", "已检查", "已递交"];
const P = [
{ name: "某县数据中心机房改造项目", buyer: "某县大数据管理局", lot: "标段 1", h: 6, due: ["今天 <span class=fig>17:00</span> 截止", "剩 <span class=fig>6</span> 小时"], cls: "today", st: "编制中", ready: "", fatal: 0, pend: 3, href: "../page-reading.html" },
{ name: "某区智慧社区平台运维服务", buyer: "某区民政局", lot: "全部", h: 53, due: ["剩 <span class=fig>2</span> 天 <span class=fig>5</span> 小时", "11月5日 16:00"], cls: "soon", st: "已检查", ready: "就绪", fatal: 0, pend: 0 },
{ name: "某市政务服务中心智慧大厅建设项目", buyer: "某市政务服务管理办公室", lot: "采购包 1", h: 408, due: ["剩 <span class=fig>17</span> 天", "11月20日 09:30"], cls: "", st: "已检查", ready: "未就绪", fatal: 3, pend: 0, href: "../page-reading.html" },
{ name: "某市医保信息系统等保测评服务", buyer: "某市医疗保障局", lot: "全部", h: 600, due: ["剩 <span class=fig>25</span> 天", "11月28日 10:00"], cls: "", st: "跟进中", ready: "", fatal: 0, pend: 1 },
{ name: "某区智慧校园一期", buyer: "某区教育局", lot: "全部", h: -1, due: ["已截止", ""], cls: "past", st: "已递交", ready: "", fatal: 0, pend: 0 },
{ name: "某市不动产登记信息平台升级", buyer: "某市自然资源和规划局", lot: "采购包 2", h: -2, due: ["已截止", ""], cls: "past", st: "已归档", ready: "", fatal: 0, pend: 0 }
];
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 track(p) {
const arch = p.st === "已归档";
const idx = arch ? 3 : STEPS.indexOf(p.st);
const segs = STEPS.map((s, i) => `<i class="${i <= idx ? "on" : ""}"></i>`).join("");
/* 当前状态标签对齐到它那一段的起点;最后一段右对齐 */
const pos = arch || idx === 3 ? "right:0" : `left:calc(${idx} * (100% + 3px) / 4)`;
const sr = arch ? "进度:已归档(四步都已走完)" : `进度:${p.st},第 ${idx + 1} 步,共 4 步(${STEPS.join("、")})`;
return `<span class="track ${arch ? "arch" : ""}"><span class="vh">${sr}</span><span class="segs" aria-hidden="true">${segs}</span><span class="lab" style="${pos}" aria-hidden="true"><span class="tag">${p.st}</span></span></span>`;
}
function render() {
const rows = P.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) => `<li><a class="row grid-cols" href="${p.href || "#"}" ${p.href ? "" : "data-demo"}>
<span><span class="pname">${p.name}</span><span class="psub">${p.buyer} · ${p.lot}</span></span>
<span class="due ${p.cls}"><b>${p.due[0]}</b>${p.due[1] ? `<small>${p.due[1]}</small>` : ""}</span>
${track(p)}
<span>${p.ready === "就绪" ? '<span class="tag" data-tip="只表示致命问题都已处理,不代表投标文件没有问题。">就绪</span>' : p.ready ? p.ready : '<span class="dash" aria-label="不适用">—</span>'}</span>
<span class="r">${p.fatal ? `<span class="sev fatal">致命<span class="fig">${p.fatal}</span></span>` : '<span class="dash" aria-label="无">—</span>'}</span>
<span class="go">${icon("chev-right")}</span></a></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 && stat !== "active" ? `<button class="chip" data-clear aria-label="清除筛选:${STAT[stat].label}">${STAT[stat].label}${icon("x")}</button>` : "";
}
$("[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(); });
document.addEventListener("click", (e) => { const d = e.target.closest("[data-demo]"); if (d) { e.preventDefault(); toast("(演示)这里只做了工作台,可以打开「某县数据中心」或「智慧大厅」看读标页"); } });
$("[data-notice]").onclick = () => toast("解析完成:补遗 1 已并入清单(新增 2 条,更新 1 条)。");
$("[data-gsearch]").addEventListener("submit", (e) => { e.preventDefault(); toast("(演示)全局搜索还没有接入,项目可以在下方列表里搜索"); });
document.addEventListener("keydown", (e) => {
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "k") { e.preventDefault(); $("[data-gq]").focus(); }
if (e.key === "Escape" && document.activeElement === $("[data-gq]")) $("[data-gq]").blur();
});
render();
})();
</script>
</body>
</html>