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

437 lines
33 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>
<!--
工作台 · 风格探索方案 A「留白文档」
内容、数据、交互沿用 ../page-workbench.html;颜色只用 tokens.css 的色值和语义。
本方案相对 design-system.md 改了这些规则:
1. 圆角(§5.1 控件和卡片 2px、浮层 4px):控件 6px,主列白纸面 12px,侧栏当前项、提醒条、表格行悬停 8px,问题级别标签 4px。
2. 阴影(§5.1 只用于浮层):主列白纸面加一层极轻阴影,只为把白纸从校样纸上托起来;右栏、提醒条、标签都不用阴影。
3. 分隔方式(§5.1 层次靠分隔线和底色):表头去掉凹陷灰底和加粗下边线,只剩小号辅助灰字;表格不画竖线,行间只留一条极浅分隔线;
侧栏不画右边线、顶栏不画下边线;右栏三组内容不加卡片框,只用间距和小标题分组。
4. 字阶(§4.2):H1 24/32 → 32/40;区块标题改用宋体 20/28;项目名保持 16/24;新增「概要句」22/52,句中数字 Barlow 44;
正文仍为 14/22,最小 12px,字重只用 400 / 600。
5. 统计卡(§7.9 / 原型的四张统计卡):换成一句概要句,句中数字可点,点了就筛选下方项目列表。
6. 控件(§7.1):按钮高 32 → 36;分段筛选改为文字标签页,选中用 2px 墨色下划线;搜索框去掉描边,只用校样纸浅底
(放宽了 §3.2「需要看清边界的控件用 --border-control」:靠浅底、放大镜图标和占位符说明这是输入框);提醒条里的次按钮改为无描边白底。
7. 状态 / 就绪(§7.9 中性描边标签):改为纯文字;「就绪」用虚下划线提示可悬停查看说明。问题级别标签的形状规则不变(致命实心、重要描边、提示无框)。
8. 提醒条(§7.12):去掉外框,赭浅底 + 左侧 3px 赭色竖条。
9. 截止时间:日期一行(数字用 Barlow)+ 剩余时间一行,整列右对齐放在最后一列;颜色规则不变(≤72 小时赭小字,≤24 小时加粗,已截止辅助灰)。
-->
<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>
/* ---------- 本方案派生的变量(全部由 tokens.css 现有颜色 color-mix 得到,不引入新色相) ---------- */
:root {
--a-sheet-shadow: 0 1px 2px color-mix(in srgb, var(--c-ink) 5%, transparent),
0 12px 32px -16px color-mix(in srgb, var(--c-ink) 12%, transparent); /* 白纸面的极轻阴影:墨 5% / 12% */
--a-row-rule: color-mix(in srgb, var(--c-rule) 60%, var(--c-white)); /* 表格行间分隔:分隔线 60% 混白,比 --c-rule 更浅 */
--a-nav-hover: color-mix(in srgb, var(--c-white) 55%, var(--c-paper)); /* 侧栏悬停:白与校样纸之间 */
--a-underline: color-mix(in srgb, var(--c-ink) 22%, var(--c-white)); /* 概要句数字的常态下划线:墨 22% */
--a-radius-ctl: 6px; /* 按钮、输入框、图标按钮 */
--a-radius-block: 8px; /* 侧栏当前项、提醒条、行悬停 */
--a-radius-sheet: 12px; /* 主列白纸面 */
--a-ctl-h: 36px;
}
* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
margin: 0; min-width: 1280px;
background: var(--bg-page); 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; }
.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; }
.lnk { color: var(--text-primary); text-decoration: underline; text-decoration-color: var(--a-underline); text-underline-offset: 3px; }
.lnk:hover { text-decoration-color: currentColor; }
/* ---------- 框架 ---------- */
.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-4) var(--sp-6) var(--sp-5); }
.brand { display: inline-block; padding: 0 var(--sp-2); margin-bottom: var(--sp-10); }
.brand img { display: block; height: 34px; 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(--a-radius-block); color: var(--text-secondary); text-decoration: none;
}
.nav a:hover { background: var(--a-nav-hover); color: var(--text-primary); }
.nav a[aria-current="page"] { background: var(--bg-surface); color: var(--text-primary); font-weight: var(--fw-bold); }
.nav .demo { margin-top: var(--sp-6); font-size: var(--fs-caption); line-height: var(--lh-caption); height: 32px; }
.side-foot { margin-top: auto; padding: 0 var(--sp-3); 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 { padding: 0 var(--sp-10) var(--sp-16) var(--sp-6); min-width: 0; }
.top { display: flex; justify-content: flex-end; align-items: center; gap: var(--sp-1); height: 64px; }
.quota-chip { display: inline-flex; align-items: center; gap: 6px; height: var(--a-ctl-h); padding: 0 var(--sp-3); border-radius: var(--a-radius-ctl); color: var(--text-secondary); text-decoration: none; }
.quota-chip .fig { font-size: 16px; font-weight: var(--fw-bold); color: var(--text-primary); margin-right: 6px; }
.quota-chip:hover, .icon-btn:hover { background: var(--a-nav-hover); }
.icon-btn { position: relative; display: inline-grid; place-items: center; width: var(--a-ctl-h); height: var(--a-ctl-h); border: 0; border-radius: var(--a-radius-ctl); background: none; color: var(--text-secondary); cursor: pointer; }
.icon-btn:hover { color: var(--text-primary); }
.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(--bg-page); }
.layout { display: grid; grid-template-columns: minmax(0, 1fr) clamp(256px, 20.5vw, 296px); gap: clamp(32px, 2.8vw, 48px); align-items: start; max-width: 1240px; }
/* ---------- 白纸面 ---------- */
.sheet { background: var(--bg-surface); border-radius: var(--a-radius-sheet); box-shadow: var(--a-sheet-shadow); padding: var(--sp-10) clamp(36px, 3.3vw, 52px) var(--sp-12); }
.head { display: flex; align-items: flex-end; justify-content: space-between; gap: var(--sp-6); }
.head h1 { margin: 0; font: var(--fw-bold) 32px/40px var(--font-serif); letter-spacing: .02em; }
.head .date { margin-left: var(--sp-4); color: var(--text-secondary); font-family: var(--font-sans); font-size: var(--fs-body); font-weight: var(--fw-regular); letter-spacing: 0; }
.head .actions { display: flex; align-items: center; gap: var(--sp-4); flex: none; }
.cost { font-size: var(--fs-caption); line-height: var(--lh-caption); color: var(--text-secondary); }
.btn {
display: inline-flex; align-items: center; justify-content: center; gap: 6px; height: var(--a-ctl-h); padding: 0 var(--sp-4);
border: 0; border-radius: var(--a-radius-ctl); font-weight: var(--fw-bold); text-decoration: none; cursor: pointer; white-space: nowrap;
}
.btn.primary { background: var(--c-ink); color: var(--text-on-dark); }
.btn.primary:hover { background: color-mix(in srgb, var(--c-ink) 92%, var(--c-white)); }
.btn.quiet { height: 32px; padding: 0 var(--sp-3); background: var(--bg-surface); color: var(--text-primary); }
.btn.quiet:hover { background: color-mix(in srgb, var(--c-white) 60%, var(--warn-soft)); }
/* 概要句:本页唯一的「大声」元素 */
.lede { margin: var(--sp-8) 0 0; max-width: 34em; font-size: 22px; line-height: 52px; color: var(--text-primary); }
.lede .half { display: block; white-space: nowrap; } /* 两半句在分号处固定断行 */
.n {
padding: 0 3px; margin: 0 1px; border: 0; background: none; cursor: pointer;
font: var(--fw-bold) 44px/1 var(--font-figures); font-variant-numeric: tabular-nums; color: var(--text-primary);
text-decoration: underline; text-decoration-thickness: 2px; text-underline-offset: 7px; text-decoration-color: var(--a-underline);
vertical-align: -3px;
}
.n:hover { text-decoration-color: currentColor; }
.n[aria-pressed="true"] { text-decoration-color: currentColor; text-decoration-thickness: 3px; }
.n.due { color: var(--c-ochre); } /* ≥18px,可用 --c-ochre */
.n.fatal { color: var(--sev-fatal); }
.lede-sub { margin: var(--sp-2) 0 0; color: var(--text-secondary); }
/* 提醒条 */
.alert { position: relative; display: flex; align-items: center; gap: var(--sp-3); margin-top: var(--sp-8); padding: var(--sp-3) var(--sp-4) var(--sp-3) var(--sp-5); border-radius: var(--a-radius-block); background: var(--warn-soft); overflow: hidden; }
.alert::before { content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px; background: var(--warn); }
.alert > .ic { color: var(--warn); }
.alert p { margin: 0; flex: 1; min-width: 0; }
.alert b { color: var(--warn-text); font-weight: var(--fw-bold); }
/* 筛选 + 搜索 */
.bar { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-6); margin-top: var(--sp-10); }
.tabs { display: flex; gap: var(--sp-6); }
.tabs button {
height: var(--a-ctl-h); padding: 0 2px; border: 0; background: none; cursor: pointer;
font-size: 15px; color: var(--text-secondary); box-shadow: inset 0 -2px 0 transparent;
}
.tabs button:hover { color: var(--text-primary); }
.tabs button[aria-pressed="true"] { color: var(--text-primary); font-weight: var(--fw-bold); box-shadow: inset 0 -2px 0 var(--c-ink); }
.tabs .fig { margin-left: 4px; font-size: 15px; }
.search { position: relative; display: block; width: 248px; }
.search .ic { position: absolute; left: 12px; top: 10px; color: var(--text-secondary); pointer-events: none; }
.search input {
width: 100%; height: var(--a-ctl-h); padding: 0 var(--sp-3) 0 36px; border: 0; border-radius: var(--a-radius-ctl);
background: var(--bg-page); font: inherit; color: var(--text-primary);
}
.search input::placeholder { color: var(--text-secondary); }
.search input:focus-visible { outline-offset: 0; border-radius: var(--a-radius-ctl); background: var(--bg-surface); }
.filter-note { display: flex; align-items: center; gap: var(--sp-3); margin-top: var(--sp-3); color: var(--text-secondary); }
.filter-note[hidden] { display: none; }
.filter-note b { color: var(--text-primary); font-weight: var(--fw-bold); }
.text-btn { display: inline-flex; align-items: center; gap: 4px; height: 28px; padding: 0 var(--sp-2); border: 0; border-radius: var(--a-radius-ctl); background: none; cursor: pointer; color: var(--text-primary); }
.text-btn:hover { background: var(--bg-hover); }
/* 项目列表 */
.projects { width: calc(100% + 24px); margin: var(--sp-5) -12px 0; border-collapse: separate; border-spacing: 0; table-layout: fixed; }
.projects th { padding: 0 12px 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 td { padding: var(--sp-3) 12px; vertical-align: middle; border-top: 1px solid var(--a-row-rule); }
.projects tbody tr:first-child td { border-top-color: transparent; }
.projects tbody tr.row { cursor: pointer; }
.projects tbody tr.row:hover td { background: var(--bg-hover); }
.projects tbody tr.row:hover td:first-child { border-radius: var(--a-radius-block) 0 0 var(--a-radius-block); }
.projects tbody tr.row:hover td:last-child { border-radius: 0 var(--a-radius-block) var(--a-radius-block) 0; }
.projects tbody tr.row:hover td, .projects tbody tr.row:hover + tr td { border-top-color: transparent; }
.projects .r { text-align: right; }
.c-st { width: 72px; } .c-rd { width: 72px; } .c-ft { width: 72px; } .c-due { width: 128px; }
.projects td:nth-child(2), .projects td:nth-child(3) { white-space: nowrap; }
.projects th .ic { vertical-align: -3px; margin-left: 2px; }
.pname { font-size: var(--fs-title); line-height: var(--lh-title); font-weight: var(--fw-bold); text-decoration: none; }
.row:hover .pname { text-decoration: underline; text-underline-offset: 3px; }
.psub { color: var(--text-secondary); }
.psub span + span { margin-left: var(--sp-3); }
.due { white-space: nowrap; }
.due .d1 { display: block; font-size: 15px; }
.due .d1 .fig { font-size: 16px; }
.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); }
.dash { color: var(--text-secondary); }
.ready { text-decoration: underline dotted var(--text-secondary); text-underline-offset: 4px; cursor: help; position: relative; }
.ready:hover::after, .ready:focus-visible::after {
content: attr(data-tip); position: absolute; left: -8px; top: calc(100% + 8px); z-index: 5; width: 232px; padding: var(--sp-2) var(--sp-3);
border-radius: var(--a-radius-ctl); background: var(--c-ink); color: var(--text-on-dark); font-size: var(--fs-caption); line-height: var(--lh-caption); font-weight: var(--fw-regular);
box-shadow: var(--shadow-overlay); white-space: normal; text-align: left;
}
.empty { padding: var(--sp-10) 12px; text-align: center; color: var(--text-secondary); }
.empty b { display: block; margin-bottom: var(--sp-1); font-size: var(--fs-title); line-height: var(--lh-title); color: var(--text-primary); font-weight: var(--fw-bold); }
.note { margin: var(--sp-4) 0 0; font-size: var(--fs-caption); line-height: var(--lh-caption); color: var(--text-secondary); }
/* 问题级别标签(形状规则不变) */
.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; }
/* 待处理的致命问题 */
.sec-h { display: flex; align-items: center; gap: var(--sp-3); margin-top: var(--sp-12); }
.sec-h h2 { margin: 0; font: var(--fw-bold) var(--fs-h2)/var(--lh-h2) var(--font-serif); }
.sec-h .more { margin-left: auto; }
.issues { list-style: none; margin: var(--sp-4) 0 0; padding: 0; }
.issues li { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; column-gap: var(--sp-4); align-items: baseline; padding: var(--sp-3) 0; }
.issues li + li { margin-top: var(--sp-2); }
.issues .sev { position: relative; top: -2px; }
.issues b { display: block; font-size: 15px; line-height: 24px; font-weight: var(--fw-bold); }
.issues p { margin: 2px 0 0; color: var(--text-secondary); }
.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; }
.cite .fig { font-size: 15px; }
/* ---------- 右栏:直接放在校样纸上 ---------- */
.rail { padding-top: var(--sp-10); }
.rail section + section { margin-top: var(--sp-12); }
.rail h3 { display: flex; align-items: baseline; justify-content: space-between; margin: 0 0 var(--sp-4); font-size: var(--fs-body); line-height: var(--lh-body); font-weight: var(--fw-bold); }
.rail h3 small { font-size: var(--fs-caption); font-weight: var(--fw-regular); color: var(--text-secondary); }
.rail h3 small .fig { font-size: 13px; }
.task-h { display: flex; justify-content: space-between; align-items: baseline; gap: var(--sp-2); }
.task-h span, .task-p { font-size: var(--fs-caption); line-height: var(--lh-caption); color: var(--text-secondary); }
.progress { display: flex; align-items: center; gap: var(--sp-3); margin-top: var(--sp-2); }
.track { flex: 1; height: 4px; border-radius: 2px; background: var(--c-sunken); overflow: hidden; }
.track i { display: block; height: 100%; background: var(--c-ink); border-radius: 2px; }
.progress .fig { font-size: 15px; font-weight: var(--fw-bold); min-width: 34px; text-align: right; }
.queue { display: flex; gap: var(--sp-2); margin-top: var(--sp-4); color: var(--text-secondary); }
.queue .ic { margin-top: 3px; }
.rail .hint { margin: var(--sp-3) 0 0; font-size: var(--fs-caption); line-height: var(--lh-caption); color: var(--text-secondary); }
.quota { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-6); }
.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); }
.quota .track { margin-top: var(--sp-2); }
.rail .links { display: flex; gap: var(--sp-5); margin-top: var(--sp-4); font-size: var(--fs-caption); line-height: var(--lh-caption); }
.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; }
.log .t { font-family: var(--font-figures); font-variant-numeric: tabular-nums; font-size: 13px; 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); z-index: 20; }
.toast { padding: 10px var(--sp-4); border-radius: var(--a-radius-block); background: var(--c-ink); color: var(--text-on-dark); box-shadow: var(--shadow-overlay); }
@media (prefers-reduced-motion: reduce) { * { transition: none !important; animation: none !important; } }
</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="#" data-demo><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>UI demo 目录</a>
</nav>
<div class="side-foot"><b>王敏</b>南京某信息技术有限公司<br>专业版,至 2027-10-31</div>
</aside>
<main class="main">
<header class="top">
<a class="quota-chip" href="#quota" title="本期剩余额度">解析<span class="fig">37</span>检查<span class="fig">12</span></a>
<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>
</header>
<div class="layout">
<article class="sheet">
<div class="head">
<h1>工作台<span class="date">11月3日 周二</span></h1>
<div class="actions">
<span class="cost">扣 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>
</div>
<p class="lede" data-lede>
<span class="half"><button class="n" data-f="active" aria-pressed="false"><span data-to="4">4</span><span class="vh">(只看进行中的项目)</span></button> 个项目进行中,<button class="n due" data-f="due7" aria-pressed="false"><span data-to="2">2</span><span class="vh">(只看 7 天内截止的项目)</span></button> 个 7 天内截止;</span>
<span class="half"><button class="n fatal" data-f="fatal" aria-pressed="false"><span data-to="3">3</span><span class="vh">(只看有致命问题的项目)</span></button> 个致命问题待处理,<button class="n" data-f="pend" aria-pressed="false"><span data-to="4">4</span><span class="vh">(只看有待确认清单条目的项目)</span></button> 条清单待确认。</span>
</p>
<p class="lede-sub">另有 1 个已递交、1 个已归档。致命问题都在 1 个项目里,待确认的清单条目在 2 个项目里。</p>
<div class="alert" role="note">
<svg class="ic lg" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg>
<p><b>某县数据中心机房改造项目今天 17:00 截止。</b>清单还是草稿,还没有做过检查。</p>
<a class="btn quiet" href="../page-reading.html">去确认清单</a>
</div>
<div class="bar">
<div class="tabs" role="group" aria-label="按状态筛选" data-tabs>
<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>
<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>
<div class="filter-note" data-fnote hidden><span>正在看:<b data-fname></b></span><button class="text-btn" data-clear><svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>清除筛选</button></div>
<table class="projects">
<thead><tr><th scope="col">项目</th><th scope="col" class="c-st">状态</th><th scope="col" class="c-rd">就绪</th><th scope="col" class="c-ft">致命</th><th scope="col" class="c-due r" aria-sort="ascending">投标截止<svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 5v14"/><path d="m19 12-7 7-7-7"/></svg></th></tr></thead>
<tbody data-rows></tbody>
</table>
<p class="note">按投标截止时间排序,截止临近的排在前面。<span id="ready-note">「就绪」只表示致命问题都已处理,不代表投标文件没有问题。</span></p>
<section id="fatal" aria-labelledby="fatal-h">
<div class="sec-h"><h2 id="fatal-h">待处理的致命问题</h2><span class="sev fatal">致命<span class="fig">3</span></span><a class="more lnk" href="#" data-demo>去查标页处理</a></div>
<ul class="issues">
<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>
</section>
</article>
<aside class="rail" aria-label="任务、额度与最近处理">
<section>
<h3>正在进行的任务</h3>
<div class="task-h"><b>解析补遗 2</b><span>提取清单</span></div>
<div class="task-p">某市医保信息系统等保测评服务</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 class="queue"><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>
<p class="hint">可以离开本页,完成后会发站内通知。</p>
</section>
<section id="quota">
<h3>本期额度<small>至 <span class="fig">2027-10-31</span></small></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:26%"></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:40%"></i></div></div>
</div>
<div class="links"><a class="lnk" href="#" data-demo>扣点规则</a><a class="lnk" href="#" data-demo>查看流水</a></div>
</section>
<section>
<h3>最近处理<a class="lnk" href="#" data-demo style="font-size:var(--fs-caption);font-weight:400">全部记录</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>
</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) {
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;pend = 有待确认的清单条目(4 条在 2 个项目里) */
const P = [
{ name: "某县数据中心机房改造项目", buyer: "某县大数据管理局", lot: "标段 1", h: 6, d1: "今天 17:00", d2: "剩 6 小时", cls: "today", st: "编制中", ready: "", fatal: 0, pend: 1, href: "../page-reading.html" },
{ name: "某区智慧社区平台运维服务", buyer: "某区民政局", lot: "全部", h: 53, d1: "11月5日 16:00", d2: "剩 2 天 5 小时", cls: "soon", st: "已检查", ready: "就绪", fatal: 0 },
{ name: "某市政务服务中心智慧大厅建设项目", buyer: "某市政务服务管理办公室", lot: "采购包 1", h: 408, d1: "11月20日 09:30", d2: "剩 17 天", cls: "", st: "已检查", ready: "未就绪", fatal: 3, href: "../page-reading.html" },
{ name: "某市医保信息系统等保测评服务", buyer: "某市医疗保障局", lot: "全部", h: 600, d1: "11月28日 10:00", d2: "剩 25 天", cls: "", st: "跟进中", ready: "", fatal: 0, pend: 1 },
{ name: "某区智慧校园一期", buyer: "某区教育局", lot: "全部", h: -1, d1: "已截止", d2: "", cls: "past", st: "已递交", ready: "", fatal: 0 },
{ name: "某市不动产登记信息平台升级", buyer: "某市自然资源和规划局", lot: "采购包 2", h: -2, d1: "已截止", d2: "", cls: "past", st: "已归档", ready: "", fatal: 0 }
];
const DONE = ["已递交", "已归档"];
const LEDE = {
active: ["进行中的项目", (p) => !DONE.includes(p.st)],
due7: ["7 天内截止的项目", (p) => p.h >= 0 && p.h <= 168],
fatal: ["有致命问题待处理的项目", (p) => p.fatal > 0],
pend: ["有清单条目待确认的项目", (p) => p.pend > 0]
};
let seg = "active", lede = "", q = "";
function match(p) {
const bySeg = lede ? LEDE[lede][1](p) : seg === "all" || (seg === "active" ? !DONE.includes(p.st) : p.st === seg);
return bySeg && (!q || (p.name + p.buyer).includes(q));
}
function render() {
const rows = P.filter(match);
$("[data-rows]").innerHTML = rows.map((p, i) => `<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>${p.st}</td>
<td>${p.ready === "就绪" ? '<span class="ready" tabindex="0" aria-describedby="ready-note" data-tip="只表示致命问题都已处理,不代表投标文件没有问题。">就绪</span>' : p.ready || '<span class="dash" aria-label="无">—</span>'}</td>
<td>${p.fatal ? `<span class="sev fatal">致命<span class="fig">${p.fatal}</span></span>` : '<span class="dash" aria-label="无">—</span>'}</td>
<td class="r due ${p.cls}"><span class="d1">${fig(p.d1)}</span>${p.d2 ? `<span class="d2">${p.d2}</span>` : ""}</td></tr>`).join("")
|| `<tr><td colspan="5"><div class="empty"><b>没有符合条件的项目</b>换个关键词,或切到「全部」看看。</div></td></tr>`;
}
function setSeg(v) {
seg = v; lede = "";
$$("[data-tabs] button").forEach((b) => b.setAttribute("aria-pressed", b.dataset.v === v));
$$(".n").forEach((b) => b.setAttribute("aria-pressed", "false"));
$("[data-fnote]").hidden = true;
render();
}
function setLede(f) {
if (lede === f) { setSeg("active"); return; }
lede = f;
$$("[data-tabs] button").forEach((b) => b.setAttribute("aria-pressed", "false"));
$$(".n").forEach((b) => b.setAttribute("aria-pressed", b.dataset.f === f));
$("[data-fname]").textContent = LEDE[f][0];
$("[data-fnote]").hidden = false;
render();
}
$("[data-tabs]").addEventListener("click", (e) => { const b = e.target.closest("button"); if (b) setSeg(b.dataset.v); });
$$(".n").forEach((b) => b.addEventListener("click", () => setLede(b.dataset.f)));
$("[data-clear]").addEventListener("click", () => { setSeg("active"); $("[data-tabs] button").focus(); });
$("[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 (!p.href) { e.preventDefault(); toast("(演示)只做了「读标」组合页"); return; }
if (!e.target.closest("a")) location.href = p.href;
});
document.addEventListener("click", (e) => {
const a = e.target.closest("[data-demo], [data-cite]"); if (!a) return;
e.preventDefault();
toast(a.matches("[data-cite]") ? "(演示)出处会在原文查看器里打开" : "(演示)这一页还没做");
});
$("[data-notice]").onclick = () => toast("解析完成:补遗 1 已并入清单(新增 2 条,更新 1 条)。");
$("[data-keys]").onclick = () => toast("(演示)快捷键:J / K 逐条,1 / 2 / 3 处理,? 显示说明");
render();
/* 唯一的动效:概要句数字载入时从 0 数到实际值一次 */
if (!matchMedia("(prefers-reduced-motion: reduce)").matches) {
const els = $$("[data-to]"), t0 = performance.now(), dur = 700;
const step = (t) => {
const k = Math.min(1, (t - t0) / dur), e = 1 - Math.pow(1 - k, 3);
els.forEach((el) => { el.textContent = Math.round(+el.dataset.to * e); });
if (k < 1) requestAnimationFrame(step);
};
requestAnimationFrame(step);
}
})();
</script>
</body>
</html>