- 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>
228 lines
16 KiB
HTML
228 lines
16 KiB
HTML
<!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>
|