Files
bid-doctor/docs/design/ui-demo/page-workbench.html
YANG JIANKUAN 7f9592ad16 docs(design): 按 v0.2 重刷全部 UI demo,确认 Q1–Q13 与 U 键规则
- ui-demo:控件、表单、卡片、列表、工作台、读标六页按 v0.2 重刷;侧栏与顶栏框架、项目行、署名行、可键入日期输入、对照布局等收回 demo.css / demo.js 共用
- system:tokens 补悬停 / 按下 / 遮罩色、数字字阶与原文排版变量;components.css 按 v0.2 改写并清除裸色值
- design-system.md:写入 Q1–Q13 答案,新增开关、分页、下拉、日期选择等组件规则,类别标签、待确认区、选中态、对照布局按 v0.2 修订
- Q13:额度流水记录 0 点的「免扣」行,FR-M6-03 与领域模型同步;U 键改为撤销当前条
- decisions.md 记录 Q1–Q12、Q13 与 U 键;AGENTS.md 同步进度

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 16:07:15 +08:00

228 lines
16 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>
<!--
组合页 · 工作台(v0.2)
内容和交互以定稿样张 explore/workbench-d5.html 为准;框架来自 Demo.shell(),共用组件来自 components.css 和 demo.css,
本页 <style> 里只放工作台特有的布局(主列 + 右栏、待处理问题行);项目行(栅格、「下一步 / 更多」列)是 demo.css 的 B20。
-->
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>工作台 · 标博士 UI demo</title>
<link rel="icon" href="../logo/favicon.svg">
<link rel="stylesheet" href="../system/tokens.css">
<link rel="stylesheet" href="../system/fonts-local.css">
<link rel="stylesheet" href="../system/components.css">
<link rel="stylesheet" href="demo.css">
<style>
/* 本页布局:主列 + 右侧信息栏(--aside-w),全页一个间距 --gap-card */
.content > .alert,.content > .stats{margin-bottom:var(--gap-card)}
.wb{display:grid;grid-template-columns:minmax(0,1fr) var(--aside-w);gap:var(--gap-card);align-items:start}
/* 项目卡片头:筛选 + 已选统计格的清除按钮 */
.card-h .seg{margin-left:var(--sp-2)}
.card-h .clear{margin-left:auto}
/* 待处理的致命问题:不加左侧红竖线,靠实心「致命」标签扫读(d5) */
.todo{display:grid;grid-template-columns:auto minmax(0,1fr) auto auto;column-gap:var(--sp-4);align-items:center;padding:var(--sp-3) var(--card-x);border-top:var(--line) solid var(--border-subtle)}
.todo b{display:block;font-weight:var(--fw-bold)}
.todo .cite-link{margin-right:2px}
/* 右栏 */
.wb aside .card-h{min-height:52px;padding-bottom:var(--sp-1)}
.task-h{display:flex;align-items:baseline;gap:var(--sp-2)}
.task-h .cap{margin-left:auto}
.wb .progress{margin-top:10px}
.queue{display:flex;gap:var(--sp-2);align-items:flex-start;margin-top:14px;padding-top:var(--sp-3);border-top:var(--line) solid var(--border-subtle)}
.queue .ic{margin-top:2px;color:var(--text-secondary)}
.quota .q2 small{font-size:var(--fs-caption)}
.log{list-style:none;margin:0;padding:0}
.log li{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:var(--sp-2);padding:var(--sp-2) 0;border-top:var(--line) solid var(--border-subtle);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:var(--fs-figure-xs);white-space:nowrap}
.log .who{font-weight:var(--fw-bold)}
.log .ok{color:var(--confirmed);white-space:nowrap}
</style>
</head>
<body>
<main>
<div class="content">
<div class="page-title">
<div><h1>工作台</h1><p>下午好,王敏。今天是 11月3日,周二。</p></div>
<div class="actions">
<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 warn" role="note">
<i data-ic="clock" class="lg"></i>
<p><strong>某县数据中心机房改造项目</strong> <span class="when">今天 17:00 截止</span>,清单还没确认。</p>
<a class="btn sm" href="page-reading.html"><i data-ic="list"></i>确认清单</a>
</div>
<section class="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 flush" aria-labelledby="h-proj">
<div class="card-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 class="clear" data-chip></span>
</div>
<div class="prow-head" aria-hidden="true"><span>项目</span><span>投标截止</span><span>状态</span><span class="c">致命</span><span>下一步</span><span></span></div>
<ul class="prows" data-rows aria-label="项目列表"></ul>
</section>
<section class="card flush" aria-labelledby="h-todo">
<div class="card-h">
<h2 id="h-todo">待处理的致命问题</h2>
<span class="sev fatal">致命<span class="fig">3</span></span>
<a class="more lnk cap" style="margin-left:auto" href="#" data-demo>查看全部</a>
</div>
<ul style="list-style:none;margin:0;padding:0">
<li class="todo"><span class="sev fatal">致命</span><div><b>报价超过采购包 1 的最高限价</b><span class="psub">智慧大厅建设项目 · 开标一览表 287.60 万元,限价 286.00 万元</span></div><a class="cite-link" 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-link" 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-link" 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>
<aside class="stack" aria-label="任务、额度与记录">
<section class="card flush" aria-labelledby="h-task">
<div class="card-h"><h3 id="h-task">正在进行的任务</h3></div>
<div class="card-b">
<div class="task-h"><b>解析补遗 2</b><span class="cap muted">提取清单</span></div>
<div class="cap muted">某市医保信息系统等保测评服务</div>
<div class="progress"><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 muted">检查排队中:智慧社区平台运维,前面 1 个</span></div>
</div>
</section>
<section class="card flush quota" aria-labelledby="h-quota">
<div class="card-h"><h3 id="h-quota">本期额度</h3><span class="cap muted" style="margin-left:auto">至 2027-10-31</span></div>
<div class="card-b">
<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="card-f cap"><a class="lnk" href="#" data-demo>扣点规则</a><a class="lnk" href="#" data-demo>查看流水</a></div>
</section>
<section class="card flush" aria-labelledby="h-log">
<div class="card-h"><h3 id="h-log">最近处理</h3><a class="lnk cap" style="margin-left:auto" href="#" data-demo>全部记录</a></div>
<div class="card-b">
<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>
</aside>
</div>
</div>
</main>
<script src="demo.js"></script>
<script>
const { $, $$, icon, esc, toast } = Demo;
const DEMO = "(演示)这里只做了工作台,可以打开「某县数据中心」或「智慧大厅」看读标页";
/* 数据沿用 explore/workbench-d5.html;pend 为待确认清单条目数。next 为「下一步」,menu 为「更多」里的操作;cost 表示会扣点 */
const CHECK_COST = `扣 1 次检查 · 剩 <span class="fig">12</span> 次`;
const P = [
{ name: "某县数据中心机房改造项目", buyer: "某县大数据管理局", lot: "标段 1", h: 6, due: ["今天 17:00 截止", "剩 6 小时"], cls: "due-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: "due-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: "due-past", st: "已递交", ready: "", fatal: 0, pend: 0,
next: "归档项目", menu: [["导出检查报告", ""]] },
{ name: "某市不动产登记信息平台升级", buyer: "某市自然资源和规划局", lot: "采购包 2", h: -2, due: ["已截止", "已归档"], cls: "due-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 = "";
Demo.shell({ nav: "workbench", crumbs: [["工作台", ""]], search: { placeholder: "搜索项目、采购人", onInput: (v) => { q = v; render(); } } });
function status(p) {
let r = "";
if (p.ready === "就绪") r = '<span class="hint-u tip-below" 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="prow ${p.st === "已归档" ? "arch" : ""}" data-i="${i}">
<span class="pt"><a class="pname" href="${p.href || "#"}" title="${esc(p.name)}">${esc(p.name)}</a><span class="psub">${esc(p.buyer)} · ${esc(p.lot)}</span></span>
<span class="dl"><b class="${p.cls}">${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><button class="nx" data-next><span>${p.next}</span>${icon("chev-right")}</button></span>
<span class="more-cell"><button class="icon-btn sm" aria-label="更多操作:${esc(p.name)}" aria-haspopup="menu" aria-expanded="false" data-menu>${icon("more")}</button></span>
</li>`).join("")
|| `<li class="empty sm"><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="btn text sm" data-clear>${STAT[stat].label}${icon("x")}<span class="vh">,清除筛选</span></button>` : "";
}
$("[data-rows]").addEventListener("click", (e) => {
const row = e.target.closest(".prow"); if (!row) return;
const p = P[+row.dataset.i];
const m = e.target.closest("[data-menu]");
if (m) {
e.preventDefault(); e.stopPropagation();
const items = [{ label: "打开项目" }, "-", ...p.menu.map(([t, s]) => ({ label: t, sub: s === "cost" ? CHECK_COST : s }))];
Demo.floatMenu(m, items, (act) => { if (act === "打开项目" && p.href) location.href = p.href; else toast(`(演示)${act}:${p.name}`, { ok: false }); });
return;
}
if (e.target.closest(".hint-u")) return;
if (e.target.closest("[data-next]")) { if (p.href) location.href = p.href; else toast(`(演示)${p.next}:${p.name}`, { ok: false }); return; }
if (!p.href) { e.preventDefault(); toast(DEMO, { ok: false }); }
});
$("[data-seg]").addEventListener("change", (e) => { seg = e.detail; 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(); } });
document.addEventListener("click", (e) => { if (e.target.closest("[data-demo]")) { e.preventDefault(); toast(DEMO, { ok: false }); } });
render();
</script>
</body>
</html>