- docs/design/ui-demo/:控件总览、表单、卡片、列表与表格,以及工作台、读标两个组合页 - 只用设计规范的变量和组件;新增组件样式放在 demo.css,清单见 index - index 列出 UI 精修待定问题 Q1–Q12 - design/README 与 AGENTS.md 进度同步 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
296 lines
33 KiB
HTML
296 lines
33 KiB
HTML
<!doctype html>
|
||
<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>
|
||
.flow-sub td{border-top:0}
|
||
.flow-sub td:first-child{color:var(--text-secondary)}
|
||
.fl-link{display:inline-block;width:14px;color:var(--text-secondary)}
|
||
</style>
|
||
</head>
|
||
<body class="demo">
|
||
<header data-demo-top></header>
|
||
<main class="d-page">
|
||
<div class="d-head">
|
||
<h1>④ 列表与表格</h1>
|
||
<p>筛选栏 + 表格 / 列表。表头凹陷灰底 + 2px 墨色底线,行高 40;数字用 Barlow 等宽。演示筛选、排序、批量勾选、键盘逐条处理和空结果。</p>
|
||
<nav class="d-toc" aria-label="本页目录"><a href="#s-proj">01 项目列表</a><a href="#s-list">02 清单列表</a><a href="#s-find">03 问题列表</a><a href="#s-flow">04 额度流水</a><a href="#s-param">05 参数对照表</a><a href="#s-empty">06 空结果</a></nav>
|
||
</div>
|
||
|
||
<!-- 01 项目列表 -->
|
||
<section class="d-sec" id="s-proj">
|
||
<div class="d-sec-h"><span class="no">01</span><h2>项目列表</h2><span class="ref">规范 §7.9 · 可以筛选、搜索、点表头排序</span></div>
|
||
<div class="toolbar">
|
||
<div class="seg" role="group" aria-label="按状态筛选" data-pseg><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>
|
||
<div class="pop inline" data-select data-plot><button type="button" class="select" aria-haspopup="listbox" aria-expanded="false"><span class="val">所有体系</span><i data-ic="chev-down"></i></button>
|
||
<ul class="menu" role="listbox"><li role="option" aria-selected="true" data-value="">所有体系</li><li role="option" data-value="政府采购">政府采购</li><li role="option" data-value="招标投标">招标投标</li></ul></div>
|
||
<div class="right"><label class="search"><i data-ic="search"></i><input class="input" placeholder="搜索项目、采购人、编号" aria-label="搜索项目" data-psearch></label></div>
|
||
</div>
|
||
<table class="data hover" data-ptable>
|
||
<thead><tr><th class="sortable" data-sort="name"><button class="th-sort">项目</button></th><th>所投包</th><th class="sortable" data-sort="hours" aria-sort="ascending"><button class="th-sort">投标截止</button></th><th>状态</th><th>就绪</th><th class="sortable r" data-sort="fatal"><button class="th-sort">致命</button></th><th class="r"></th></tr></thead>
|
||
<tbody data-prows></tbody>
|
||
</table>
|
||
<p class="note">默认按截止时间升序,截止临近的排在前面。「就绪」只在「已检查」显示,只表示致命问题都已处理,不代表投标文件没有问题。</p>
|
||
</section>
|
||
|
||
<!-- 02 清单列表 -->
|
||
<section class="d-sec" id="s-list">
|
||
<div class="d-sec-h"><span class="no">02</span><h2>清单列表</h2><span class="ref">规范 §7.5 · 点这块后按 J / K 移动、Enter 打开出处</span></div>
|
||
<div class="toolbar">
|
||
<div class="seg" role="group" aria-label="按分类筛选" data-cseg><button aria-pressed="true" data-v="">全部 <span class="fig">12</span></button><button aria-pressed="false" data-v="qual">资格条件</button><button aria-pressed="false" data-v="invalid">无效条款</button><button aria-pressed="false" data-v="param">技术参数</button><button aria-pressed="false" data-v="date">日期与递交</button></div>
|
||
<label class="choice"><input type="checkbox" class="ck" data-star>只看 ★ 实质性条款</label>
|
||
<div class="right"><span class="cap muted"><span class="kbd">J</span> <span class="kbd">K</span> 移动 · <span class="kbd">Enter</span> 打开出处</span></div>
|
||
</div>
|
||
<div class="filters-on" data-chips></div>
|
||
<div class="list-box" data-clist tabindex="0" aria-label="清单,按 J K 移动"></div>
|
||
</section>
|
||
|
||
<!-- 03 问题列表 -->
|
||
<section class="d-sec" id="s-find">
|
||
<div class="d-sec-h"><span class="no">03</span><h2>问题列表</h2><span class="ref">规范 §7.4 · §8.2 · 只有「提示」级可以批量</span></div>
|
||
<div class="toolbar">
|
||
<div class="seg" role="group" aria-label="按级别筛选" data-fseg>
|
||
<button aria-pressed="true" data-v="">全部 <span class="fig" data-c=""></span></button>
|
||
<button aria-pressed="false" data-v="fatal"><span class="sev fatal">致命<span class="fig" data-c="fatal"></span></span></button>
|
||
<button aria-pressed="false" data-v="major"><span class="sev major">重要<span class="fig" data-c="major"></span></span></button>
|
||
<button aria-pressed="false" data-v="hint"><span class="sev hint">提示<span class="fig" data-c="hint"></span></span></button>
|
||
</div>
|
||
<span class="cap muted">只显示待处理的问题;已处理的在列表底部。</span>
|
||
<div class="right"><button class="btn sm text" onclick="Demo.keysDialog()">快捷键 <span class="kbd">?</span></button></div>
|
||
</div>
|
||
<div class="batchbar" data-batch hidden><b>已选 <span class="fig" data-bn>0</span> 项「提示」</b><span class="cap muted">批量处理只开放给「提示」级</span><span class="r"><button class="btn sm" data-bok>批量确认无误</button><button class="btn sm text" data-bclear>取消选择</button></span></div>
|
||
<div class="results" data-flist tabindex="0" aria-label="问题列表,按 J K 移动,1 2 3 处理"></div>
|
||
</section>
|
||
|
||
<!-- 04 额度流水 -->
|
||
<section class="d-sec" id="s-flow">
|
||
<div class="d-sec-h"><span class="no">04</span><h2>额度流水</h2><span class="ref">规范 §7.10 · 红线 9 额度即货币</span></div>
|
||
<div class="toolbar">
|
||
<div class="seg" role="group" aria-label="按额度类型筛选"><button aria-pressed="true">全部</button><button aria-pressed="false">解析</button><button aria-pressed="false">检查</button></div>
|
||
<div class="pop inline" data-date data-month="2026-11"><button type="button" class="select date-trigger" aria-haspopup="dialog" aria-expanded="false" style="width:160px"><span class="val">2026-11</span><i data-ic="calendar"></i></button><div class="cal"></div></div>
|
||
<div class="right"><button class="btn sm"><i data-ic="download"></i>导出 CSV</button></div>
|
||
</div>
|
||
<table class="data flow">
|
||
<thead><tr><th>时间</th><th>类型</th><th>额度</th><th class="r">数量</th><th class="r">余额</th><th>关联项目 / 任务</th><th>说明</th></tr></thead>
|
||
<tbody>
|
||
<tr><td class="num">11-02 14:31</td><td>退回</td><td>解析</td><td class="num r plus">+1</td><td class="num r">37</td><td>智慧大厅建设项目 · 解析任务 PJ-0417</td><td class="muted">解析失败:文件已加密</td></tr>
|
||
<tr class="flow-sub"><td class="num"><span class="fl-link">└</span>11-02 14:28</td><td>预扣</td><td>解析</td><td class="num r">−1</td><td class="num r">36</td><td>智慧大厅建设项目 · 解析任务 PJ-0417</td><td class="muted">和上一行的退回对应</td></tr>
|
||
<tr><td class="num">11-02 14:20</td><td>—</td><td>检查</td><td class="num r">0</td><td class="num r">12</td><td>智慧大厅建设项目 · 第 2 次检查</td><td class="muted">24 小时内复查,不扣</td></tr>
|
||
<tr><td class="num">11-02 10:05</td><td>结算</td><td>解析</td><td class="num r">−1</td><td class="num r">37</td><td>智慧大厅建设项目 · 补遗 1</td><td></td></tr>
|
||
<tr><td class="num">11-01 16:00</td><td>结算</td><td>检查</td><td class="num r">−1</td><td class="num r">12</td><td>智慧大厅建设项目 · 第 1 次检查</td><td></td></tr>
|
||
<tr><td class="num">11-01 10:40</td><td>结算</td><td>解析</td><td class="num r">−1</td><td class="num r">38</td><td>智慧大厅建设项目 · 招标文件</td><td></td></tr>
|
||
<tr><td class="num">11-01 09:30</td><td>人工调整</td><td>检查</td><td class="num r plus">+2</td><td class="num r">13</td><td>—</td><td class="muted">运营:系统异常补偿,工单 GD-1101</td></tr>
|
||
<tr><td class="num">11-01 09:00</td><td>发放</td><td>解析 / 检查</td><td class="num r plus">+50 / +20</td><td class="num r">50 / 20</td><td>专业版开通(对公转账)</td><td class="muted">开通单 KT-1101</td></tr>
|
||
</tbody>
|
||
</table>
|
||
<div class="pagination" data-fpager></div>
|
||
<ul class="d-rules"><li>增加用定稿绿「+」(这里绿色表示「入账」,是否允许请在精修时拍板,见目录页的待定问题),扣减用墨色「−」;退回记录和对应的预扣记录相邻显示。</li></ul>
|
||
</section>
|
||
|
||
<!-- 05 参数对照表 -->
|
||
<section class="d-sec" id="s-param">
|
||
<div class="d-sec-h"><span class="no">05</span><h2>参数对照表</h2><span class="ref">规范 §7.7</span></div>
|
||
<table class="data">
|
||
<thead><tr><th>参数项</th><th>要求</th><th>投标响应</th><th>判断</th><th>级别</th><th>出处</th></tr></thead>
|
||
<tbody>
|
||
<tr class="is-fatal"><td>★ 应用服务器 · 内存</td><td class="num">≥ 512G DDR4</td><td class="num">256G DDR4</td><td><span class="verdict fatal">疑似不满足</span></td><td><span class="sev fatal">致命</span></td><td><a href="#">第 <span class="fig">41</span> 页</a> ↔ <a href="#">技术册 第 <span class="fig">12</span> 页</a></td></tr>
|
||
<tr><td>★ 应用服务器 · 系统盘</td><td class="num">2 块 ≥ 960G SSD<div class="cap">补遗 1 更新,原为 <s>≥ 480G</s></div></td><td class="num">2 块 960G SSD</td><td>满足</td><td></td><td><a href="#">补遗 1 第 <span class="fig">1</span> 页</a> ↔ <a href="#">技术册 第 <span class="fig">12</span> 页</a></td></tr>
|
||
<tr><td>★ 应用服务器 · CPU</td><td class="num">≥ 2 颗 32C 2.7GHz</td><td class="num">2 颗 7380 32C 2.7GHz</td><td>满足</td><td></td><td><a href="#">第 <span class="fig">41</span> 页</a> ↔ <a href="#">技术册 第 <span class="fig">12</span> 页</a></td></tr>
|
||
<tr><td>★ 核心交换机 · BFD 间隔</td><td class="num">≤ 50ms</td><td class="num">3.3ms</td><td>满足</td><td></td><td><a href="#">第 <span class="fig">44</span> 页</a> ↔ <a href="#">技术册 第 <span class="fig">58</span> 页</a></td></tr>
|
||
<tr><td>★ 核心交换机 · 证明材料</td><td>官网参数截图等</td><td class="muted">—</td><td><span class="verdict major">未找到响应</span></td><td><span class="sev major">重要</span></td><td><a href="#">第 <span class="fig">44</span> 页</a> ↔ <span class="cap muted">已查找:技术册、商务册附件</span></td></tr>
|
||
<tr><td>▲ 核心交换机 · 交换容量</td><td class="num">≥ 19.2Tbps</td><td class="num">19.2Tbps / 48Tbps</td><td><span class="verdict manual">需人工判断</span></td><td><span class="sev hint">提示</span></td><td><a href="#">第 <span class="fig">44</span> 页</a> ↔ <a href="#">技术册 第 <span class="fig">58</span> 页</a></td></tr>
|
||
<tr><td>排队叫号一体机 · 显示屏</td><td class="num">≥ 21.5 英寸</td><td class="num">21 英寸</td><td><span class="verdict hint">疑似不满足</span></td><td><span class="sev hint">提示</span></td><td><a href="#">第 <span class="fig">47</span> 页</a> ↔ <a href="#">技术册 第 <span class="fig">77</span> 页</a></td></tr>
|
||
</tbody>
|
||
</table>
|
||
<ul class="d-rules"><li>判断只用四个词:满足 / 疑似不满足 / 未找到响应 / 需人工判断。「满足」用墨色普通字,不用绿色。</li><li>要求和响应列用 Barlow,数值左对齐。规范的表没有「级别」列,这里加了一列标签,让判断词的颜色有形状兜底(色弱)——是否保留请拍板。</li></ul>
|
||
</section>
|
||
|
||
<!-- 06 空结果 -->
|
||
<section class="d-sec" id="s-empty">
|
||
<div class="d-sec-h"><span class="no">06</span><h2>空结果</h2><span class="ref">规范 §7.15</span></div>
|
||
<div class="d-grid c2">
|
||
<div>
|
||
<span class="d-label">筛选后没有结果:写清楚条件,给「清除筛选」</span>
|
||
<table class="data"><thead><tr><th>项目</th><th>所投包</th><th>投标截止</th><th>状态</th></tr></thead>
|
||
<tbody><tr><td colspan="4" class="empty-cell"><div class="empty sm"><b>没有符合条件的项目</b><p>「已归档」里没有名称含「医保」的项目。</p><button class="btn sm">清除筛选</button></div></td></tr></tbody></table>
|
||
</div>
|
||
<div>
|
||
<span class="d-label">问题全部处理完:说清楚意味着什么,不庆祝</span>
|
||
<div class="results"><div class="empty sm"><i data-ic="list" class="xl"></i><b>待处理的问题都已处理</b><p>致命问题已全部处理,项目为「就绪」。这只表示每一项都有人处理过,不代表投标文件没有问题。</p><button class="btn sm">导出报告</button></div>
|
||
<button class="group-h"><i data-ic="chev-right"></i>已处理(<span class="fig">9</span>)</button></div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
</main>
|
||
|
||
<script src="demo.js"></script>
|
||
<script>
|
||
const { $, $$, esc, icon, toast, dialog } = Demo;
|
||
Demo.demoTop("lists.html");
|
||
|
||
/* ---------- 01 项目列表 ---------- */
|
||
const PROJ = [
|
||
{ name: "某市政务服务中心智慧大厅建设项目", buyer: "某市政务服务管理办公室", regime: "政府采购", lot: "采购包 1", hours: 408, due: "11月20日 09:30 · 剩 17 天", cls: "", status: "已检查", ready: "未就绪", fatal: 3 },
|
||
{ name: "某区智慧社区平台运维服务", buyer: "某区民政局", regime: "政府采购", lot: "全部", hours: 53, due: "剩 2 天 5 小时", cls: "due-soon", status: "已检查", ready: "就绪", fatal: 0 },
|
||
{ name: "某县数据中心机房改造项目", buyer: "某县大数据管理局", regime: "招标投标", lot: "标段 1", hours: 6, due: "今天 17:00 截止", cls: "due-today", status: "编制中", ready: "", fatal: 0 },
|
||
{ name: "某市医保信息系统等保测评服务", buyer: "某市医疗保障局", regime: "政府采购", lot: "全部", hours: 600, due: "11月28日 10:00 · 剩 25 天", cls: "", status: "跟进中", ready: "", fatal: 0 },
|
||
{ name: "某区智慧校园一期", buyer: "某区教育局", regime: "政府采购", lot: "全部", hours: -100, due: "已截止", cls: "due-past", status: "已递交", ready: "", fatal: 0 },
|
||
{ name: "某市不动产登记信息平台升级", buyer: "某市自然资源和规划局", regime: "政府采购", lot: "采购包 2", hours: -900, due: "已截止", cls: "due-past", status: "已归档", ready: "", fatal: 0 }
|
||
];
|
||
let pf = { seg: "active", q: "", regime: "", sort: ["hours", "ascending"] };
|
||
function prows() {
|
||
let rows = PROJ.filter((p) => (pf.seg === "all" || (pf.seg === "active" ? !["已递交", "已归档"].includes(p.status) : p.status === pf.seg))
|
||
&& (!pf.regime || p.regime === pf.regime) && (!pf.q || (p.name + p.buyer).includes(pf.q)));
|
||
const [k, d] = pf.sort;
|
||
rows.sort((a, b) => (k === "name" ? a.name.localeCompare(b.name, "zh") : a[k] - b[k]) * (d === "ascending" ? 1 : -1));
|
||
const segName = { active: "进行中", all: "全部", "已递交": "已递交", "已归档": "已归档" }[pf.seg];
|
||
$("[data-prows]").innerHTML = rows.map((p) => `<tr>
|
||
<td><div class="proj-name">${p.name}</div><div class="proj-sub">${p.buyer} · ${p.regime}</div></td>
|
||
<td>${p.lot}</td><td class="nw ${p.cls}">${p.due}</td><td><span class="tag status">${p.status}</span></td>
|
||
<td class="nw">${p.ready === "就绪" ? '<span class="tag status" data-tip="只表示致命问题都已处理,不代表投标文件没有问题。">就绪</span>' : p.ready || '<span class="muted">—</span>'}</td>
|
||
<td class="r">${p.fatal ? `<span class="sev fatal">致命<span class="fig">${p.fatal}</span></span>` : '<span class="muted">—</span>'}</td>
|
||
<td class="acts"><button class="icon-btn sm tip-left" aria-label="更多操作" data-tip="更多操作">${icon("more")}</button></td></tr>`).join("")
|
||
|| `<tr><td colspan="7" class="empty-cell"><div class="empty sm"><b>没有符合条件的项目</b><p>「${segName}」里没有${pf.regime ? `「${pf.regime}」体系、` : ""}${pf.q ? `名称或采购人含「${esc(pf.q)}」的` : ""}项目。</p><button class="btn sm" data-pclear>清除筛选</button></div></td></tr>`;
|
||
}
|
||
$("[data-pseg]").addEventListener("change", (e) => { pf.seg = e.detail; prows(); });
|
||
$("[data-plot]").addEventListener("change", (e) => { pf.regime = e.detail; prows(); });
|
||
$("[data-psearch]").addEventListener("input", (e) => { pf.q = e.target.value.trim(); prows(); });
|
||
document.addEventListener("click", (e) => {
|
||
if (e.target.closest("[data-pclear]")) { pf = { ...pf, seg: "all", q: "", regime: "" }; $("[data-psearch]").value = ""; $$("[data-pseg] button").forEach((b) => b.setAttribute("aria-pressed", b.dataset.v === "all")); $("[data-plot] .val").textContent = "所有体系"; prows(); }
|
||
else if (e.target.closest("[data-prows] tr") && !e.target.closest("button")) toast("(演示)打开项目详情");
|
||
});
|
||
Demo.sortable($("[data-ptable]"), (k, d) => { pf.sort = [k, d]; prows(); });
|
||
$("[data-ptable] th[aria-sort] svg").outerHTML = icon("arrow-up");
|
||
prows();
|
||
|
||
/* ---------- 02 清单列表 ---------- */
|
||
const CL = [
|
||
{ imp: "★", t: "应用服务器:内存 ≥ 512G DDR4", cats: ["param", "invalid"], lot: "采购包 1", src: "ai", p: 41 },
|
||
{ imp: "★", t: "应用服务器:国产自主可控 CPU,提供国产化适配承诺函", cats: ["param", "invalid"], lot: "采购包 1", src: "ai", p: 41 },
|
||
{ imp: "★", t: "应用服务器:系统盘 2 块 ≥ 960G SSD", old: "2 块 ≥ 480G SSD", cats: ["param"], lot: "采购包 1", src: "add", p: 1, doc: "补遗 1" },
|
||
{ imp: "★", t: "核心交换机:支持硬件 BFD,最小发送间隔 ≤ 50ms,提供证明材料", cats: ["param", "invalid"], lot: "采购包 1", src: "ai", p: 44 },
|
||
{ imp: "▲", t: "核心交换机:交换容量 ≥ 19.2Tbps,包转发率 ≥ 2880Mpps", cats: ["param"], lot: "采购包 1", src: "ai", p: 44 },
|
||
{ imp: "", t: "营业执照等主体资格证明文件(扫描件)", cats: ["qual", "invalid"], lot: "通用", src: "ai", p: 23 },
|
||
{ imp: "", t: "财务状况报告:资产负债表 + 利润表月报表(开标前 6 个月内)", cats: ["qual", "invalid"], lot: "通用", src: "ai", p: 23 },
|
||
{ imp: "", t: "不接受联合体投标", cats: ["qual"], lot: "通用", src: "manual", p: 2, edited: true },
|
||
{ imp: "", t: "报价不超过采购包 1 最高限价 286.00 万元", cats: ["invalid"], lot: "采购包 1", src: "ai", p: 2 },
|
||
{ imp: "", t: "投标有效期不少于 90 天", cats: ["invalid", "date"], lot: "通用", src: "ai", p: 21 },
|
||
{ imp: "", t: "提交投标文件截止时间:2026年11月20日 09:30", old: "2026年11月18日 09:30", cats: ["date"], lot: "通用", src: "add", p: 1, doc: "补遗 1", confirmed: true },
|
||
{ imp: "", t: "递交方式:电子交易平台在线递交,CA 加密", cats: ["date"], lot: "通用", src: "ai", p: 6 }
|
||
];
|
||
const CAT = { qual: "资格条件", invalid: "无效条款", param: "技术参数", date: "日期与递交", score: "评分办法" };
|
||
let cf = { cat: "", star: false }, cur = 0;
|
||
function crows() {
|
||
const rows = CL.filter((c) => (!cf.cat || c.cats.includes(cf.cat)) && (!cf.star || c.imp === "★"));
|
||
const chips = [cf.cat && `<span class="chip">分类:${CAT[cf.cat]}<button aria-label="移除筛选" data-xc="cat">${icon("x")}</button></span>`, cf.star && `<span class="chip">只看 ★<button aria-label="移除筛选" data-xc="star">${icon("x")}</button></span>`].filter(Boolean);
|
||
$("[data-chips]").innerHTML = chips.length ? `<span>筛选:</span>${chips.join("")}<span>· <span class="fig m">${rows.length}</span> 条</span><button class="btn sm text" data-xc="all">清除全部</button>` : "";
|
||
cur = Math.min(cur, Math.max(rows.length - 1, 0));
|
||
$("[data-clist]").innerHTML = `<div class="lb-h"><span>要求</span><span class="r">来源 · 出处</span></div>` + (rows.map((c, i) => {
|
||
const src = c.src === "ai" ? '<span class="src-ai">AI 提取</span>' : c.src === "add" ? c.doc : "人工添加";
|
||
const upd = c.old && !c.confirmed ? `<div class="upd"><span class="flag">已被${c.doc} 更新 · 原为 <s>${c.old}</s></span><button class="btn" data-ack="${i}">已核对</button></div>` : "";
|
||
return `<div class="item ${i === cur ? "focus" : ""}" data-ci="${i}" tabindex="-1"><span class="imp">${c.imp}</span>
|
||
<div><div class="t">${c.t}</div><div class="meta">${c.cats.map((k) => `<span class="tag">${CAT[k]}</span>`).join("")}<span class="tag">${c.lot}</span>${c.edited ? '<span class="flag">已修改 · <a class="plain" href="#">查看记录</a></span>' : ""}</div>${upd}</div>
|
||
<div class="side">${src}<br><a class="cite-src" href="#" data-open>${c.doc ? c.doc + " · " : ""}第 <span class="fig">${c.p}</span> 页</a></div></div>`;
|
||
}).join("") || `<div class="empty sm"><b>这个分类下没有 ★ 条款</b><p>★ 条款都在「技术参数」和「无效条款」里。</p><button class="btn sm" data-xc="all">清除筛选</button></div>`);
|
||
}
|
||
$("[data-cseg]").addEventListener("change", (e) => { cf.cat = e.detail; crows(); });
|
||
$("[data-star]").addEventListener("change", (e) => { cf.star = e.target.checked; crows(); });
|
||
$("[data-clist]").addEventListener("click", (e) => {
|
||
const a = e.target.closest("[data-ack]"); if (a) { CL.filter((c) => (!cf.cat || c.cats.includes(cf.cat)) && (!cf.star || c.imp === "★"))[+a.dataset.ack].confirmed = true; crows(); toast("已核对补遗更新,记录已保存"); return; }
|
||
const it = e.target.closest("[data-ci]"); if (it) { cur = +it.dataset.ci; crows(); $("[data-clist]").focus(); if (e.target.closest("[data-open]")) { e.preventDefault(); toast("(演示)在原文查看器里打开这一页"); } }
|
||
});
|
||
document.addEventListener("click", (e) => { const x = e.target.closest("[data-xc]"); if (!x) return; const k = x.dataset.xc; if (k !== "star") { cf.cat = ""; $$("[data-cseg] button").forEach((b) => b.setAttribute("aria-pressed", b.dataset.v === "")); } if (k !== "cat") { cf.star = false; $("[data-star]").checked = false; } crows(); });
|
||
$("[data-clist]").addEventListener("keydown", (e) => {
|
||
const n = $$("[data-ci]").length; if (!n) return;
|
||
if (e.key === "j" || e.key === "k") { e.preventDefault(); cur = Math.max(0, Math.min(n - 1, cur + (e.key === "j" ? 1 : -1))); crows(); $(`[data-ci="${cur}"]`).scrollIntoView({ block: "nearest" }); }
|
||
if (e.key === "Enter" || e.key === "o") toast(`(演示)打开出处:${$(`[data-ci="${cur}"] .cite-src`).textContent}`);
|
||
});
|
||
crows();
|
||
|
||
/* ---------- 03 问题列表 ---------- */
|
||
const FD = [
|
||
{ sev: "fatal", t: "报价超过采购包 1 的最高限价", flag: "新增", text: ["开标一览表报价 287.60 万元,采购包 1 最高限价 286.00 万元,超出 1.60 万元。", "这一项会导致投标无效。请核对开标一览表的报价。"],
|
||
cites: [["招标文件", 2, "最高限价:418.00万元(", "采购包1:286.00万元", ",采购包2:132.00万元)。"], ["报价册 · 开标一览表", 1, "投标总价(小写):", "2876000.00 元", ""]] },
|
||
{ sev: "fatal", t: "★ 条款疑似不满足:应用服务器内存", flag: "沿用 · 上次标记为已修改,问题仍在", text: ["技术偏离表第 12 行响应「内存 256G DDR4」,要求「≥512G DDR4」。", "这一项会导致投标无效。请在技术偏离表第 12 行和对应产品参数中更正内存配置。"],
|
||
cites: [["招标文件", 41, "★ 2U机架式服务器,CPU:配置≥2颗海光架构32C 2.7GHz,", "内存: ≥512G DDR4", "……"], ["技术册", 12, "第 12 行 ", "内存:256G DDR4", " 无偏离"]] },
|
||
{ sev: "major", t: "缺少 ★ 参数的证明材料:核心交换机硬件 BFD", text: ["已响应「支持硬件 BFD」,但没有找到官网参数截图等证明材料。请在技术册补充证明材料。"],
|
||
cites: [["招标文件", 44, "★ 支持硬件BFD,最小报文发送间隔≤50ms,", "提供相关证明材料(包含但不限于产品官网公开参数截图证明等)", "。"]], missing: "证明材料未找到 · 已查找:技术册(全部章节)、商务册(附件)" },
|
||
{ sev: "major", t: "资质过期预警:ISO27001 证书在投标截止日前到期", flag: "新增", text: ["证书有效期至 2026年11月15日,早于投标截止日 11月20日。按评分办法,这一项可能不得分。", "请上传换发后的证书,或在资质库更新有效期。"],
|
||
cites: [["招标文件", 31, "体系认证(6分):……", "以投标截止日在有效期内为准", ",提供证书扫描件)。"], ["商务册", 38, "ISO/IEC 27001 信息安全管理体系认证证书 ", "有效期至:2026年11月15日", ""]] },
|
||
{ sev: "hint", t: "投标文件里出现了其他项目的名称", flag: "新增", text: ["商务册第 27 页出现「某区智慧校园一期项目」及其项目编号。如果是业绩介绍可以忽略;如果是照搬旧标书留下的,请修改。"],
|
||
cites: [["商务册", 27, "我公司承担的", "某区智慧校园一期项目(项目编号 QJ-2025-0112)", "……"]] },
|
||
{ sev: "hint", t: "一般参数疑似不满足:叫号一体机显示屏尺寸", text: ["技术册第 77 页响应「21英寸」,要求「≥21.5英寸」。一般参数负偏离每项扣 0.5 分。"],
|
||
cites: [["招标文件", 47, "", "显示屏尺寸≥21.5英寸", ",分辨率≥1920×1080"], ["技术册", 77, "显示屏:", "21英寸", " LED 背光液晶屏"]] },
|
||
{ sev: "hint", t: "投标函落款日期晚于投标截止日", flag: "沿用", text: ["投标函落款「2026年11月25日」,投标截止时间是 2026年11月20日 09:30。"],
|
||
cites: [["商务册", 3, "投标人(电子签章):南京某信息技术有限公司 ", "日期:2026年11月25日", ""]], ocr: true }
|
||
].map((f, i) => ({ ...f, id: i, st: null, sel: false }));
|
||
FD[6].st = { act: "确认无误", at: "11月1日 16:05" };
|
||
let ff = "", fcur = 0, foldOpen = false;
|
||
const SEV = { fatal: "致命", major: "重要", hint: "提示" };
|
||
function citeHTML(c, ocr) { return `<div class="cite"><a class="cite-src" href="#">${c[0]} · <span class="fig">第 ${c[1]} 页</span>${ocr ? '<span class="ocr">OCR</span>' : ""}</a><q>${esc(c[2])}<mark>${esc(c[3])}</mark>${esc(c[4])}</q></div>`; }
|
||
function findingHTML(f, focus) {
|
||
const ck = f.sev === "hint" && !f.st ? `<label class="ckw" data-tip="批量处理"><input type="checkbox" class="ck" data-fsel="${f.id}" ${f.sel ? "checked" : ""} aria-label="选择这一项"></label>`
|
||
: !f.st ? `<span class="ckw" data-tip="只有「提示」级可以批量处理"><input type="checkbox" class="ck" disabled aria-label="不可批量"></span>` : "";
|
||
const sign = f.st ? `<div class="sign ${f.st.ign ? "ign" : ""}"><span>${f.st.ign ? "–" : "✓"}</span><b>王敏</b><span class="fig">${f.st.at}</span><span>${f.st.act}</span>${f.st.note ? `<span class="muted">:${esc(f.st.note)}</span>` : ""}<a class="plain undo" href="#" data-undo="${f.id}">撤销</a></div>`
|
||
: `<div class="f-actions"><button class="btn" data-act="1" data-tip="我核对过,投标文件这里没问题(例如响应写在别处)">确认无误</button><button class="btn" data-act="2" data-tip="我已经在投标文件里改了,复查时会再查">已修改</button><button class="btn" data-act="3" data-tip="我知道这个问题,决定不处理(必须填原因)">忽略并注明原因</button><a class="plain feedback" href="#">判断有误?</a></div>`;
|
||
return `<article class="finding has-ck ${f.sev === "fatal" ? "is-fatal" : ""} ${focus ? "focus" : ""}" data-fid="${f.id}">${ck}
|
||
<div><div class="f-head"><span class="sev ${f.sev}">${SEV[f.sev]}</span><span class="f-title">${f.t}</span>${f.flag ? `<span class="flag box">${f.flag}</span>` : ""}</div>
|
||
${f.text.map((t) => `<p class="f-text">${t}</p>`).join("")}${sign}</div>
|
||
<aside>${f.cites.map((c) => citeHTML(c, f.ocr)).join("")}${f.missing ? `<div class="cite missing"><span class="cite-src">投标文件 · 未找到</span><p>${f.missing}</p></div>` : ""}</aside></article>`;
|
||
}
|
||
function frows() {
|
||
const todo = FD.filter((f) => !f.st && (!ff || f.sev === ff)), done = FD.filter((f) => f.st);
|
||
["", "fatal", "major", "hint"].forEach((k) => { $(`[data-c="${k}"]`).textContent = FD.filter((f) => !f.st && (!k || f.sev === k)).length; });
|
||
fcur = Math.min(fcur, Math.max(todo.length - 1, 0));
|
||
$("[data-flist]").innerHTML = (todo.map((f, i) => findingHTML(f, i === fcur)).join("") || `<div class="empty sm"><b>这个级别的问题都已处理</b><p>已处理的问题在下方「已处理」里,可以撤销。</p></div>`)
|
||
+ `<button class="group-h" data-fold aria-expanded="${foldOpen}">${icon(foldOpen ? "chev-down" : "chev-right")}已处理(<span class="fig">${done.length}</span>)</button>`
|
||
+ (foldOpen ? done.map((f) => findingHTML(f, false)).join("") : "");
|
||
const n = FD.filter((f) => f.sel && !f.st).length;
|
||
$("[data-batch]").hidden = !n; $("[data-bn]").textContent = n;
|
||
}
|
||
const now = () => "11月3日 " + new Date().toTimeString().slice(0, 5);
|
||
function act(f, a) {
|
||
if (a === 1 && f.sev === "fatal") return dialog({ title: "确认无误:" + f.t, wide: true, ok: "确认无误",
|
||
body: `<p>对致命问题说「没问题」,需要写一句说明。说明会同时作为误报反馈提交。</p><div class="field fill"><label for="u4">说明 <small>(必填)</small></label><textarea class="input" id="u4" placeholder="例如:报价以开标一览表大写金额为准,小写是笔误,已修改"></textarea></div>`,
|
||
onOk: (s) => { const v = $("#u4", s).value.trim(); if (v.length < 5) { const fl = $("#u4", s).closest(".field"); fl.classList.add("err"); $(".msg", fl)?.remove(); fl.insertAdjacentHTML("beforeend", '<span class="msg"><span class="ico w">!</span>至少写 5 个字</span>'); return false; } done(f, "确认无误", v); } });
|
||
if (a === 3) return dialog({ title: "忽略并注明原因", wide: true, ok: "忽略这一项",
|
||
body: `<p><span class="sev ${f.sev}">${SEV[f.sev]}</span> <b>${f.t}</b></p><div class="field fill"><label for="ir">原因 <small>(必填,至少 5 个字)</small></label><div class="chips"><button type="button" class="btn" data-chip>招标文件允许</button><button type="button" class="btn" data-chip>已在其他分册响应</button><button type="button" class="btn" data-chip>与本项目无关</button></div><textarea class="input" id="ir"></textarea></div>`,
|
||
onOpen: (s) => $$("[data-chip]", s).forEach((c) => c.onclick = () => { $("#ir", s).value = c.textContent; }),
|
||
onOk: (s) => { const v = $("#ir", s).value.trim(); if (v.length < 5) { const fl = $("#ir", s).closest(".field"); fl.classList.add("err"); $(".msg", fl)?.remove(); fl.insertAdjacentHTML("beforeend", '<span class="msg"><span class="ico w">!</span>至少写 5 个字,说明为什么不处理</span>'); return false; } done(f, "已忽略", v, true); } });
|
||
done(f, a === 1 ? "确认无误" : "已修改");
|
||
}
|
||
function done(f, actName, note, ign) { f.st = { act: actName, at: now(), note, ign }; f.sel = false; frows(); $("[data-flist]").focus(); toast(`已标记「${actName}」`, { undo: () => { f.st = null; frows(); } }); }
|
||
$("[data-fseg]").addEventListener("change", (e) => { ff = e.detail; fcur = 0; frows(); });
|
||
$("[data-flist]").addEventListener("click", (e) => {
|
||
const t = e.target, fid = t.closest("[data-fid]")?.dataset.fid, f = FD[fid];
|
||
if (t.matches("[data-fsel]")) { f.sel = t.checked; frows(); return; }
|
||
if (t.closest("[data-fold]")) { foldOpen = !foldOpen; frows(); return; }
|
||
if (t.closest("[data-undo]")) { e.preventDefault(); f.st = null; frows(); toast("已撤销", { ok: false }); return; }
|
||
if (t.closest("[data-act]")) { act(f, +t.closest("[data-act]").dataset.act); return; }
|
||
if (f && !f.st) { fcur = FD.filter((x) => !x.st && (!ff || x.sev === ff)).indexOf(f); frows(); }
|
||
});
|
||
$("[data-flist]").addEventListener("keydown", (e) => {
|
||
if (e.target.matches("input, textarea")) return;
|
||
const todo = FD.filter((f) => !f.st && (!ff || f.sev === ff)); if (!todo.length) return;
|
||
if (e.key === "j" || e.key === "k") { e.preventDefault(); fcur = Math.max(0, Math.min(todo.length - 1, fcur + (e.key === "j" ? 1 : -1))); frows(); $(".finding.focus").scrollIntoView({ block: "nearest" }); }
|
||
if (["1", "2", "3"].includes(e.key)) act(todo[fcur], +e.key);
|
||
if (e.key === "?") Demo.keysDialog();
|
||
if (e.key === "o" || e.key === "Enter") toast("(演示)在原文查看器里打开当前出处");
|
||
});
|
||
$("[data-bok]").onclick = () => { const sel = FD.filter((f) => f.sel && !f.st); sel.forEach((f) => { f.st = { act: "确认无误", at: now() }; f.sel = false; }); frows(); toast(`已将 ${sel.length} 项「提示」标记为确认无误`, { undo: () => { sel.forEach((f) => f.st = null); frows(); } }); };
|
||
$("[data-bclear]").onclick = () => { FD.forEach((f) => f.sel = false); frows(); };
|
||
frows();
|
||
|
||
/* ---------- 04 流水分页 ---------- */
|
||
$("[data-fpager]").innerHTML = `<span class="total">本月 <span class="fig">8</span> 条 · 共 <span class="fig">23</span> 条</span><button class="pg" disabled aria-label="上一页">${icon("chev-left")}</button><button class="pg" aria-current="page">1</button><button class="pg">2</button><button class="pg" aria-label="下一页">${icon("chev-right")}</button>`;
|
||
</script>
|
||
</body>
|
||
</html>
|