Compare commits
10 Commits
ab7ac4307d
...
3d1b634d36
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d1b634d36 | ||
|
|
9aff7c499c | ||
|
|
05839dfe01 | ||
|
|
b86e62711e | ||
|
|
f4dc699041 | ||
|
|
700514746f | ||
|
|
274a211ac9 | ||
|
|
cbd5c8d28d | ||
|
|
2e35c12a6f | ||
|
|
b993a3721a |
7
.gitignore
vendored
@@ -21,3 +21,10 @@ docs/design/logo/image-gen/ref-texture-*
|
|||||||
|
|
||||||
# 方案对比页(可由 scripts/wordmark_compare.py 重新生成,SVG 体积大)
|
# 方案对比页(可由 scripts/wordmark_compare.py 重新生成,SVG 体积大)
|
||||||
docs/design/logo/compare/
|
docs/design/logo/compare/
|
||||||
|
|
||||||
|
# Python 缓存
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
|
||||||
|
# VI 手册 iPhone 桌面模拟用的 Apple 素材(版权归 Apple,用 docs/design/vi/mockup/fetch.sh 获取)
|
||||||
|
docs/design/vi/mockup/apple/
|
||||||
|
|||||||
37
AGENTS.md
@@ -6,7 +6,8 @@
|
|||||||
|
|
||||||
- **产品名**:标博士(代码仓库名 `bid-doctor`)
|
- **产品名**:标博士(代码仓库名 `bid-doctor`)
|
||||||
- **一句话定位**:站在**投标方**一侧,贯穿「寻标 → 读标 → 写标 → 查标 → 投标」全生命周期的 AI 辅助工具。产品是「副驾驶」,不是「自动驾驶」。
|
- **一句话定位**:站在**投标方**一侧,贯穿「寻标 → 读标 → 写标 → 查标 → 投标」全生命周期的 AI 辅助工具。产品是「副驾驶」,不是「自动驾驶」。
|
||||||
- **当前阶段**:PRD v0.9 评审稿(2026-10-21),尚无代码。后续工作流依次为:需求分析 → 主题 / 品牌设计 → 原型设计 → UI 设计 → 技术架构 → 实现 → 测试。
|
- **口号**:标博士,你的AI投标专家(固定写法,见决策 B5)。
|
||||||
|
- **当前阶段**:需求分析、品牌与 VI 已完成,产品界面设计规范 v0.1、MVP 可点击原型 v0.1 已完成,尚无代码。工作流:需求分析 ✅ → 主题 / 品牌设计 ✅ → 产品界面设计规范 ✅ → 原型设计 ✅ → UI 设计 → 技术架构 → 实现 → 测试。
|
||||||
- **需求来源**:当前有效版本是 `docs/招投标AI工具PRD-v0.9.1.html`(`docs/招投标AI工具PRD.html` 是 v0.9 原稿,保留作对照),再加上 `docs/decisions.md` 和 `docs/analysis/` 里已确认的 MVP 细化需求。三者冲突时:decisions.md > analysis > PRD。发现矛盾或缺失时,记录问题并向用户确认,不要自行假设。
|
- **需求来源**:当前有效版本是 `docs/招投标AI工具PRD-v0.9.1.html`(`docs/招投标AI工具PRD.html` 是 v0.9 原稿,保留作对照),再加上 `docs/decisions.md` 和 `docs/analysis/` 里已确认的 MVP 细化需求。三者冲突时:decisions.md > analysis > PRD。发现矛盾或缺失时,记录问题并向用户确认,不要自行假设。
|
||||||
- **工作语言**:文档、界面文案、提交说明默认使用简体中文;代码标识符、文件名使用英文。
|
- **工作语言**:文档、界面文案、提交说明默认使用简体中文;代码标识符、文件名使用英文。
|
||||||
|
|
||||||
@@ -68,15 +69,33 @@
|
|||||||
- **大模型**(T1):只用国内云端的国产大模型,数据不出境。按场景路由:多模态(扫描件、签章)/ 快速 / 深度思考。业务代码只声明需要什么能力,不直接写死供应商和模型;每个 AI 任务都要记下模型和提示词版本。
|
- **大模型**(T1):只用国内云端的国产大模型,数据不出境。按场景路由:多模态(扫描件、签章)/ 快速 / 深度思考。业务代码只声明需要什么能力,不直接写死供应商和模型;每个 AI 任务都要记下模型和提示词版本。
|
||||||
- 其余技术栈尚未选定,由「技术架构设计」阶段决定并回填到本文件。
|
- 其余技术栈尚未选定,由「技术架构设计」阶段决定并回填到本文件。
|
||||||
|
|
||||||
|
## 品牌与设计资产(已定)
|
||||||
|
|
||||||
|
设计文档索引见 `docs/design/README.md`。做界面、文案、物料时先看这几条:
|
||||||
|
|
||||||
|
- **视觉方向**:A「校样」。蓝铅笔 #2F5BD3 = AI 标注 / 出处,墨 #1D2733 = 正文 / 人工确认,校样纸 #F5F6F3 = 底色。
|
||||||
|
- **两种红色不能混用**:印章红 #D9291C **只用于 logo**;致命红 #C4122F **只用于界面里的致命问题**。
|
||||||
|
- **界面规范**:`docs/design/design-system.md`;写界面代码时颜色、字号、间距只用 `docs/design/system/tokens.css` 里的变量。
|
||||||
|
- **字体**(均为 OFL,可免费商用):思源宋体(标题、字标)、思源黑体 / 系统黑体(界面正文)、Libre Caslon(英文品牌字)、Barlow Semi Condensed(数字)。
|
||||||
|
- **Logo**:正式文件在 `docs/design/logo/svg/`、`png/`。要改 logo,改 `docs/design/logo/scripts/logo_lib.py` 里的参数,再运行 `scripts/build_assets.sh`,**不要手改 SVG**。工具环境(`logo/.work/`)不进 git,搭建方法见 `logo-spec.md` 第 6 节。
|
||||||
|
- **VI 手册**:`docs/design/vi/标博士-VI视觉识别规范-v1.1.pdf`,源文件是同目录的 `vi-manual.html`,重新导出方法见 `docs/design/vi/README.md`。logo 或口号变化后要同步重出。
|
||||||
|
- **红线**:logo 不能当作「通过 / 合格」章用在报告或任何结论上。
|
||||||
|
|
||||||
## 目录约定
|
## 目录约定
|
||||||
|
|
||||||
```
|
```
|
||||||
docs/
|
docs/
|
||||||
招投标AI工具PRD.html # PRD v0.9 原稿(只读)
|
招投标AI工具PRD.html # PRD v0.9 原稿(只读)
|
||||||
招投标AI工具PRD-v0.9.1.html # 当前有效版本;后续修订继续出新版本文件
|
招投标AI工具PRD-v0.9.1.html # 当前有效版本;后续修订继续出新版本文件
|
||||||
analysis/ # 需求分析:用户旅程、用例、数据模型草案
|
analysis/ # 需求分析:用户旅程、用例、数据模型草案、样本分析
|
||||||
design/ # 主题与品牌、设计规范(色彩、字体、组件 token)
|
design/ # 品牌与设计(索引见 design/README.md)
|
||||||
prototype/ # 原型(低保真 / 高保真 HTML)
|
brand-strategy.md # 品牌策略:定位、性格、口号、语气
|
||||||
|
brand-directions.html# 三个视觉方向对比(已选 A 校样)
|
||||||
|
logo/ # logo 正式资产、规范、构建脚本、历史版本
|
||||||
|
vi/ # VI 视觉识别规范(PDF + HTML 源文件)
|
||||||
|
design-system.md # 产品界面设计规范:原则、颜色、字体、组件、文案
|
||||||
|
system/ # tokens.css(设计变量)、components.css(组件样式)、components.html(预览)
|
||||||
|
prototype/ # MVP 可点击原型(HTML,说明见 prototype/README.md)
|
||||||
architecture/ # 技术架构、ADR(docs/architecture/adr/NNNN-*.md)
|
architecture/ # 技术架构、ADR(docs/architecture/adr/NNNN-*.md)
|
||||||
decisions.md # 产品与工程决策记录(含 D1–D4 的拍板结果)
|
decisions.md # 产品与工程决策记录(含 D1–D4 的拍板结果)
|
||||||
samples/
|
samples/
|
||||||
@@ -97,4 +116,12 @@ samples/
|
|||||||
- 2026-11-02:项目初始化,建立 AGENTS.md / CLAUDE.md。
|
- 2026-11-02:项目初始化,建立 AGENTS.md / CLAUDE.md。
|
||||||
- 2026-11-02:D1 定为 IT 集成;完成 MVP 需求分析(`docs/analysis/`)。
|
- 2026-11-02:D1 定为 IT 集成;完成 MVP 需求分析(`docs/analysis/`)。
|
||||||
- 2026-11-02:G1–G10 全部按建议确认;出 PRD v0.9.1(补充政府采购体系);大模型策略定为国产云端模型 + 按场景路由(T1)。
|
- 2026-11-02:G1–G10 全部按建议确认;出 PRD v0.9.1(补充政府采购体系);大模型策略定为国产云端模型 + 按场景路由(T1)。
|
||||||
- 2026-11-02:从苏采云公开渠道收集了 10 份 IT 集成采购文件(`samples/tenders/`),分析结论见 `docs/analysis/07-sample-findings.md`。关键结论:实质性条款不能只认 ★(有的用斜体 + 下划线标注,解析时必须保留文字格式);文件扩展名不可信;报价限价有三层。下一步:主题 / 原型设计。
|
- 2026-11-02:从苏采云公开渠道收集了 10 份 IT 集成采购文件(`samples/tenders/`),分析结论见 `docs/analysis/07-sample-findings.md`。关键结论:实质性条款不能只认 ★(有的用斜体 + 下划线标注,解析时必须保留文字格式);文件扩展名不可信;报价限价有三层。
|
||||||
|
- 2026-11-02:**品牌与 VI 完成**。
|
||||||
|
- 品牌策略:定位「投前审校员」,口号「标博士,你的AI投标专家」(B5);视觉方向选 A「校样」(B1)。
|
||||||
|
- Logo v5.1:AI 生成的白文印「标」+ 思源宋体 SemiBold / Libre Caslon 字标,比例 H2、英文视觉内缩 C2(B3、B4)。中间经历的 v1–v4(Codex 精细化、SVG 程序化模拟印泥)归档在 `docs/design/logo/archive/`,教训见 `logo/seal-research.md`:印泥质感本质上是位图,不要再用 SVG 程序化模拟。
|
||||||
|
- VI 视觉识别规范 v1.1(A4 横版 24 页,含名片、信封、信纸、报告、演示文稿、网站等应用),见 `docs/design/vi/`。
|
||||||
|
- 2026-11-02:产品界面设计规范 v0.1(`docs/design/design-system.md` + `system/tokens.css` + `system/components.html`)。要点:红色只给致命问题(表单错误、额度、截止临近都用赭色);问题级别靠实心 / 描边 / 无框区分,不只靠颜色(色弱模拟验证过);字体全部自托管;出处是固定组件。U1–U4 全部按建议确认(只做桌面 Web、图标用 Lucide、致命问题确认无误也要写说明)。
|
||||||
|
- 2026-11-02:MVP 可点击原型 v0.1(`docs/prototype/`,13 个页面,覆盖 J1–J5)。中保真,直接用设计规范的组件;查标页可逐条处理、键盘操作、看原文。原型阶段的 P1(24 小时免扣从上一次扣点的检查算起)、P2(人工核对勾选按项目保存)已按建议确认。
|
||||||
|
- 2026-11-03:应用图标改为印泥铺满、不留白边,圆角按 Apple 连续曲率圆角由系统裁(B6);VI 手册 v1.1 第 22 页改为 iPhone 18 Pro 主屏幕模拟(`docs/design/vi/mockup/`,Apple 素材不进 git,机身需本人同意 Apple Design Resources 许可后放入)。
|
||||||
|
- 下一步:用原型找种子用户走查(顺带问 G15 苏采云客户端的分册对应),再进入 UI 设计精修。
|
||||||
|
|||||||
@@ -107,7 +107,7 @@
|
|||||||
### FR-M3-02 发起检查 · P0
|
### FR-M3-02 发起检查 · P0
|
||||||
|
|
||||||
- 前提:项目至少有一份解析成功的招标文件。清单没确认时可以检查,但要提示「清单未确认,检查结果以当前清单为准」。
|
- 前提:项目至少有一份解析成功的招标文件。清单没确认时可以检查,但要提示「清单未确认,检查结果以当前清单为准」。
|
||||||
- 按 `01-scope-roles.md` §4 的计费口径扣点;同一项目 24 小时内复查不扣点。
|
- 按 `01-scope-roles.md` §4 的计费口径扣点;同一项目 24 小时内复查不扣点。24 小时从**上一次扣了额度的检查**算起,窗口内的复查不重新计时(决策 P1)。
|
||||||
- 每次检查产生一个「检查批次」,记下当时的清单版本、投标文件版本和规则版本。
|
- 每次检查产生一个「检查批次」,记下当时的清单版本、投标文件版本和规则版本。
|
||||||
|
|
||||||
### FR-M3-03 响应完整性检查 · P0
|
### FR-M3-03 响应完整性检查 · P0
|
||||||
@@ -138,7 +138,7 @@
|
|||||||
分成两类,分开显示:
|
分成两类,分开显示:
|
||||||
|
|
||||||
- **可自动检查**:投标函、授权书等格式文件是否齐全,是否改动了格式文件里的固定文字;签字 / 盖章位置**有没有**(只看有无,不鉴定真伪,界面写明);落款日期是否合理;文件里有没有出现其他项目的名称、编号或采购人(照搬旧标书留下的)。
|
- **可自动检查**:投标函、授权书等格式文件是否齐全,是否改动了格式文件里的固定文字;签字 / 盖章位置**有没有**(只看有无,不鉴定真伪,界面写明);落款日期是否合理;文件里有没有出现其他项目的名称、编号或采购人(照搬旧标书留下的)。
|
||||||
- **需人工核对**:份数、装订密封、递交方式、CA 加密等,文件里看不出来。这些生成「请核对」条目,用户勾选确认。
|
- **需人工核对**:份数、装订密封、递交方式、CA 加密等,文件里看不出来。这些生成「请核对」条目,用户勾选确认。勾选结果按项目保存,复查时沿用,报告写明勾选人和时间(决策 P2)。
|
||||||
|
|
||||||
### FR-M3-05 资质匹配检查 · P0
|
### FR-M3-05 资质匹配检查 · P0
|
||||||
|
|
||||||
@@ -159,6 +159,7 @@
|
|||||||
### FR-M3-07 人工确认 · P0
|
### FR-M3-07 人工确认 · P0
|
||||||
|
|
||||||
- 每个问题的状态:待处理 → 已确认无误 / 已修改 / 忽略(必须填原因)。
|
- 每个问题的状态:待处理 → 已确认无误 / 已修改 / 忽略(必须填原因)。
|
||||||
|
- 「致命」级问题选「已确认无误」时,也必须填一句说明(决策 U4);说明同时进入误报反馈。
|
||||||
- 还有没处理的「致命」问题时,项目就绪状态是「未就绪」,但**不阻止导出**。
|
- 还有没处理的「致命」问题时,项目就绪状态是「未就绪」,但**不阻止导出**。
|
||||||
- 支持批量处理(仅限「提示」级);每次处理都记录处理人和时间。
|
- 支持批量处理(仅限「提示」级);每次处理都记录处理人和时间。
|
||||||
- 每个问题都可以「反馈:判断有误」(J5)。
|
- 每个问题都可以「反馈:判断有误」(J5)。
|
||||||
|
|||||||
@@ -28,5 +28,5 @@
|
|||||||
## 下一步
|
## 下一步
|
||||||
|
|
||||||
1. ~~收集样本语料~~:第一批 10 份已入库(`samples/tenders/`),结论见 07。后续补扫描件、补遗、招标投标体系的样本,并人工标注实质性条款。
|
1. ~~收集样本语料~~:第一批 10 份已入库(`samples/tenders/`),结论见 07。后续补扫描件、补遗、招标投标体系的样本,并人工标注实质性条款。
|
||||||
2. 进入主题 / 品牌设计和原型设计(原型样例数据改用真实样本 S10,它有 3 个采购包)。
|
2. ~~主题 / 品牌设计~~:已完成,见 `docs/design/`。下一步是产品界面设计规范和原型设计(原型样例数据用真实样本 S10,它有 3 个采购包)。
|
||||||
3. 技术架构:模型路由层、文档规范化与出处定位、签章检测可行性(G12)。
|
3. 技术架构:模型路由层、文档规范化与出处定位、签章检测可行性(G12)。
|
||||||
|
|||||||
@@ -4,6 +4,66 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## B6 · 2026-11-03 · 应用图标改为印泥铺满
|
||||||
|
|
||||||
|
- **决策**:应用图标不再是「白底 + 印章占 80%」,改为印泥铺满整个方形、不留白边;圆角按 Apple 的连续曲率圆角(squircle)由系统裁切,图标文件保持方形、不带透明。VI 手册第 22 页用 iPhone 18 Pro 主屏幕模拟展示:Apple 官方黑色机身、iPhone 18 Pro 深色壁纸 Black、iOS 系统图标只放 4 行,第四行只有股市和标博士两个。
|
||||||
|
- **理由**:白边让图标在主屏幕上显得小而虚,和其他应用放在一起不像一枚印章;铺满后红色块面完整,辨识度更高。
|
||||||
|
- **影响**:`logo/scripts/build_logo.py` 新增 `svg/app-icon.svg`,`build_assets.sh` 由它渲染 180 / 192 / 512 / 1024px 图标;VI 手册 v1.1 第 22 页重排(邮件签名移到左栏)。Apple 素材不进 git,用 `vi/mockup/fetch.sh` 获取,机身需本人同意 Apple Design Resources 许可后手动放入。
|
||||||
|
|
||||||
|
## P1–P2 · 2026-11-02 · 原型阶段的待确认事项:全部按建议
|
||||||
|
|
||||||
|
- **P1 24 小时免扣的起算点**:从**上一次扣了额度的检查**算起;窗口内的复查不扣点,也不重新计时。理由:如果从上一次检查算起,每天复查一次就能一直不扣点,额度规则形同虚设。**影响**:FR-M3-02。
|
||||||
|
- **P2「需人工核对」的勾选按项目存**:整个项目共用一份勾选结果,复查时保留;报告写明勾选人和时间。理由:份数、CA 加密、递交回执这类事项和投标文件版本无关,每次检查重新勾选只会增加负担。**影响**:FR-M3-04。
|
||||||
|
|
||||||
|
## U1–U4 · 2026-11-02 · 产品界面设计规范 v0.1 的待确认事项:全部按建议
|
||||||
|
|
||||||
|
规范见 `docs/design/design-system.md`(第 12 节)。
|
||||||
|
|
||||||
|
- **U1 红色只给致命问题**:表单错误、系统错误、额度用完、截止临近都用赭色(小字 #8A5200),不用红。理由:让红色只代表「可能导致投标无效」;色弱模拟显示红 / 赭本来就要靠形状和文字区分,错误提示照样有文字和图标。
|
||||||
|
- **U2 只做桌面 Web**:MVP 最小宽度 1280,不做手机适配和暗色模式。理由:读标和查标需要原文并排;变量已按语义分层,以后可加暗色。
|
||||||
|
- **U3 图标用 Lucide**(ISC 许可):线性风格和校样方向一致,可商用。
|
||||||
|
- **U4 致命问题「确认无误」也要写说明**:对致命问题说「没问题」等于认为系统判断错了,说明既用于审计,也作为误报反馈。**影响**:FR-M3-07 已同步修改。
|
||||||
|
|
||||||
|
## B5 · 2026-11-02 · 口号改为「标博士,你的AI投标专家」
|
||||||
|
|
||||||
|
- **决策**:品牌口号由「每一条,都有出处。」改为 **「标博士,你的AI投标专家」**,固定写法:AI 两侧不加空格,结尾不加标点。
|
||||||
|
- **范围**:品牌策略、VI 手册(品牌概述、名片、演示文稿、网站、封底、字体样张)、方向对比板等所有出现口号的地方。
|
||||||
|
- **保留**:「每一条,都有出处」作为产品原则「出处看得见」保留(可复核是核心差异),不再作为口号。
|
||||||
|
- **注意**:「专家」只表示懂行、查得细;产品和报告里的结论仍然只用「辅助检查」「风险提示」,不承诺结果(产品红线不变)。
|
||||||
|
|
||||||
|
## B4 · 2026-11-02 · 横版比例 H2,中文字标用思源宋体 SemiBold(英文待选)
|
||||||
|
|
||||||
|
- **比例 H2**:印章的实心边缘上下对齐文字块(中文顶端到拼音底部),再放大 2%,补偿颗粒边缘显虚的问题。
|
||||||
|
- 依据:v5 实测印章只有文字块的 96%,原因是抠图时印章四周各留了 2% 的透明边。中国银行、招商银行、中华老字号等「图形 + 中英两行文字」的组合,图形高度通常和整个文字块相同;方形本身就比圆形显大,所以不需要再放大。
|
||||||
|
- 对比见 `docs/design/logo/compare/index.html`。
|
||||||
|
- **中文字标**:思源宋体 SemiBold(F2),比 Black 更清秀,和印章的轻重对比更明显。
|
||||||
|
- **英文字体**:中英文用不同字体(这是品牌组合里的常见做法)。用户选定 **E5 Libre Caslon**(OFL,Caslon 类老派衬线),候选对比见 `compare/english.html`。
|
||||||
|
- **英文视觉对齐**:几何上两端对齐时,英文看起来比中文宽。原因是「标」「士」两端只有细横画尖,而英文两端是 B、I 的粗竖画,按墨迹浓度算英文左超 6.5、右超 7.7(中文字高 = 100)。真实品牌两种做法都有:中银香港内缩了英文;清华、进出口银行只做了几何对齐。排版理论主张按视觉边缘对齐。补偿档位 C0–C3 见 `compare/alignment.html`。用户选定 **C2**(左内缩 5.0、右内缩 5.8,约为完全补偿的 3/4)。
|
||||||
|
- **已执行**:全套正式资产按 H2 + F2 + E5 + C2 重新生成(logo v5.1)。排版逻辑集中在 `docs/design/logo/scripts/logo_lib.py`,正式资产由 `build_assets.sh` 生成,对比页由 `wordmark_compare.py` 生成,三者共用同一套参数。
|
||||||
|
|
||||||
|
## B3 · 2026-11-02 · Logo 暂定 v5:AI 生成的白文印 + 矢量字标
|
||||||
|
|
||||||
|
- **决策(暂定)**:印章采用用户用 ChatGPT 图像生成的白文印「标」(`docs/design/logo/image-gen/exec-8232d0a3-…png`),经脚本处理后作为正式 logo;字标用思源宋体 Black + Cinzel(W1)。规范见 `docs/design/logo/logo-spec.md`。
|
||||||
|
- **过程**:v1 由 Codex 精细化草稿 1;v2–v4 尝试用 SVG 程序化模拟印泥质感,效果都不理想(调研见 `seal-research.md`)。结论是印泥质感本质上是位图,所以改用位图生成,再矢量化。
|
||||||
|
- **颜色调整**:印章红从 #D2282E 改为朱砂色 **#D9291C**(原图实测 #E21B0A,偏亮、超出印刷色域,略压深)。界面「致命」红 #C4122F 不变,两者一橙一洋红,可以区分。
|
||||||
|
- **小尺寸**:16px 仍用方案 B(加粗简化的「标」字),32–64px 用白色笔画加粗的印章。
|
||||||
|
- **待办**:AI 生成图形的著作权认定有不确定性,商标注册前要做检索并咨询知识产权顾问;以后可以考虑照这个设计刻一方真章,扫描后替换。
|
||||||
|
|
||||||
|
## B2 · 2026-11-02 · 16px 图标与英文名
|
||||||
|
|
||||||
|
- **16px 图标**:用方案 B,加粗简化的「标」字。红底白勾(方案 A)是待办类应用的常见图标,辨识度低。
|
||||||
|
- **英文名**:统一用拼音 **BIAOBOSHI**,不用 Bid Doctor。代码仓库名继续用 `bid-doctor`。
|
||||||
|
- **logo 第二阶段**:用户要求增加真实印章质感、重新考虑对勾(它破坏了平衡),并给字标换更有性格的开源字体。设计说明见 `docs/design/logo/BRIEF-v2.md`,先出方案对比页,用户选定后再更新正式资产。
|
||||||
|
|
||||||
|
## B1 · 2026-11-02 · 视觉方向 A「校样」+ 用户的印章 logo
|
||||||
|
|
||||||
|
- **决策**:视觉方向选 A「校样」(`docs/design/brand-directions.html`):蓝铅笔表示 AI 标注 / 出处,墨表示人工确认,红只用于问题。Logo 不用 A 方向原来的示意,改用用户提供的草稿 1(红色方形印章 + 白色「标」字 + 穿出边框的对勾 + 藏青字标),草稿 2(学士帽)不采用。
|
||||||
|
- **理由**:盖章是投标人每天都在做的事,印章比「博士帽」更贴近用户、辨识度更高,也适合做应用图标;对勾表达「逐项核对」。
|
||||||
|
- **约束**:
|
||||||
|
- 印章红只用在 logo 上;界面中的「致命」红单独定义,两者要能区分。
|
||||||
|
- 「印章 + 对勾」不能当作「通过 / 合格」章用在报告或任何结论上(产品红线:不承诺结果)。
|
||||||
|
- **执行**:由 Codex 按 `docs/design/logo/BRIEF.md` 精细化,交付物在 `docs/design/logo/`。
|
||||||
|
|
||||||
## T1 · 2026-11-02 · 大模型策略:国产云端模型,按场景路由
|
## T1 · 2026-11-02 · 大模型策略:国产云端模型,按场景路由
|
||||||
|
|
||||||
- **决策**:全部使用国内云端的国产大模型,不考虑数据出境。按业务场景匹配模型能力:多模态(扫描件理解、签章检测)、快速(分类、短文本抽取、交互类)、深度思考(复杂条款推理、响应性比对)。
|
- **决策**:全部使用国内云端的国产大模型,不考虑数据出境。按业务场景匹配模型能力:多模态(扫描件理解、签章检测)、快速(分类、短文本抽取、交互类)、深度思考(复杂条款推理、响应性比对)。
|
||||||
|
|||||||
@@ -6,7 +6,10 @@
|
|||||||
| [brand-directions.html](brand-directions.html) | 三个视觉方向对比(A 校样 / B 卷宗 / C 检验单) | 已选 A |
|
| [brand-directions.html](brand-directions.html) | 三个视觉方向对比(A 校样 / B 卷宗 / C 检验单) | 已选 A |
|
||||||
| [logo/](logo/) | Logo 正式资产、使用规范、构建脚本、历史版本 | v5.1 定稿 |
|
| [logo/](logo/) | Logo 正式资产、使用规范、构建脚本、历史版本 | v5.1 定稿 |
|
||||||
| [vi/](vi/) | VI 视觉识别规范 PDF 与 HTML 源文件 | v1.1 |
|
| [vi/](vi/) | VI 视觉识别规范 PDF 与 HTML 源文件 | v1.1 |
|
||||||
| design-system.md | 产品界面设计规范:组件、设计 token、问题级别配色 | 下一步 |
|
| [design-system.md](design-system.md) | 产品界面设计规范:原则、颜色与对比度、字体字阶、布局、17 个组件、交互模式、文案规范 | v0.1(U1–U4 已确认) |
|
||||||
|
| [system/tokens.css](system/tokens.css) | 设计变量(原型和前端直接引用) | v0.1 |
|
||||||
|
| [system/components.css](system/components.css) | 组件样式参考实现(预览页和原型共用) | v0.1 |
|
||||||
|
| [system/components.html](system/components.html) | 组件预览页(用样本条款演示) | v0.1 |
|
||||||
|
|
||||||
## 速查
|
## 速查
|
||||||
|
|
||||||
|
|||||||
512
docs/design/design-system.md
Normal file
@@ -0,0 +1,512 @@
|
|||||||
|
# 标博士 · 产品界面设计规范(v0.1 · 2026-11-02)
|
||||||
|
|
||||||
|
> 范围:MVP 的 Web 界面(读标 M2、查标 M3、项目工作台与额度 M6)和检查报告 PDF。
|
||||||
|
> 依据:视觉方向 A「校样」(决策 B1)、VI 手册 v1.1、品牌策略的语气规则、`docs/analysis/03-functional-requirements.md`。
|
||||||
|
> 配套文件:
|
||||||
|
> - `system/tokens.css`:设计变量(颜色、字体、字阶、间距、圆角、尺寸),原型和前端都直接引用;
|
||||||
|
> - `system/components.css`:组件样式的参考实现,预览页和原型(`docs/prototype/`)共用;
|
||||||
|
> - `system/fonts-local.css`:本地预览用的字体加载(从 `logo/.work/fonts` 读,不进 git);
|
||||||
|
> - `system/components.html`:组件预览页,用真实样本条款演示(起本地服务后打开,见第 13 节)。
|
||||||
|
>
|
||||||
|
> 第 12 节的 U1–U4 已于 2026-11-02 全部按建议确认(`docs/decisions.md`)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 设计原则
|
||||||
|
|
||||||
|
| 原则 | 意思 | 落到界面上 |
|
||||||
|
|---|---|---|
|
||||||
|
| **出处看得见** | 每条结论都能回到原文,这是产品原则(B5) | 出处是问题和清单项的固定组成部分,默认展开,不藏在「详情」里 |
|
||||||
|
| **颜色说明谁说的** | 蓝铅笔 = 系统(AI)说的,墨 = 人确认过的,绿 = 确认署名 | AI 产出一律带蓝色标记;人确认后变成墨色正文 + 绿色署名 |
|
||||||
|
| **红色只给致命问题** | 界面上看到红色,就意味着有可能导致投标无效的问题 | 表单错误、系统故障、额度用完、截止临近都**不用红**,用赭色 |
|
||||||
|
| **沉着,不阻断** | 截止前不添乱;决定权在用户 | 不闪烁、不弹窗打断、不用惊叹号;致命问题没处理完也能导出 |
|
||||||
|
| **密度优先** | 投标专员一次要过几十上百条 | 正文 14px,列表行紧凑,支持键盘逐条处理和批量处理 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 调研依据(为什么这样定)
|
||||||
|
|
||||||
|
| 问题 | 依据 | 结论 |
|
||||||
|
|---|---|---|
|
||||||
|
| 正文字号 | 国内 B 端设计体系的通行值:TDesign `font-body-medium` 14/22,Ant Design 主字号 14、行高 22,字阶控制在 3–5 种 | 正文 14/22;同一屏不超过 5 个字号 |
|
||||||
|
| 中文成段阅读的行高 | 中文字面满格,行高比西文需要更大;招标原文引用是成段阅读 | 引用原文用 14/24(约 1.7 倍) |
|
||||||
|
| 宋体能用多小 | 思源宋体横画细,Windows 下 ClearType 渲染小字号时横画发虚;投标人大多用 Windows | 宋体只用于 ≥20px 的标题,界面正文一律黑体 |
|
||||||
|
| 字体从哪加载 | Google Fonts 在国内访问不稳定;数据不出境(T1)的要求也延伸到前端资源 | 字体全部**自托管**;中文宋体用 Fontsource 的 `@fontsource/noto-serif-sc`(OFL,按 unicode-range 切片,页面只下载用到的字);正文黑体用系统字体,不下载 |
|
||||||
|
| 数字对齐 | 实测 Barlow Semi Condensed 的数字**默认是比例宽度**(「1」宽 309、「0」宽 507 字体单位),字体带 `tnum` 特性 | 表格、金额、页码列必须开 `font-variant-numeric: tabular-nums` |
|
||||||
|
| 对比度 | WCAG 2.x:正文 ≥ 4.5:1,大字(≥18px,或 ≥14px 粗体)≥ 3:1,控件边框等识别所需的图形 ≥ 3:1(1.4.11) | 见第 3.2 节的实测值。赭色 #A66300 在校样纸上只有 4.4:1,小字改用深一级的 #8A5200;装饰分隔线可以浅,输入框边框必须 ≥ 3:1 |
|
||||||
|
| 色弱 | 用 Machado 2009 模型模拟后算色差(ΔE,CIELAB):红色弱下「致命红 / 定稿绿」只差 11.4,绿色弱下「致命红 / 赭」只差 15.9,都接近分不出来 | **问题级别和确认状态不能只靠颜色区分**,必须同时用文字和形状(实心 / 描边 / 无框、✓ 符号),见 3.3 |
|
||||||
|
| 打印 | 检查报告会被打印给主管或老板看,常常是黑白打印 | 报告里的级别标签在灰度下也要能分辨(同样靠形状和文字) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 颜色
|
||||||
|
|
||||||
|
### 3.1 色板
|
||||||
|
|
||||||
|
| 名称 | 变量 | HEX | 用途 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 校样纸 | `--c-paper` | #F5F6F3 | 页面底色(冷灰白,不是米色) |
|
||||||
|
| 白 | `--c-white` | #FFFFFF | 卡片、表格、输入框、原文页面 |
|
||||||
|
| 凹陷灰 | `--c-sunken` | #ECEEEA | 表头、「提示」级底色、只读区 |
|
||||||
|
| 墨 | `--c-ink` | #1D2733 | 正文、标题、主按钮、人工确认后的内容、焦点环 |
|
||||||
|
| 辅助灰 | `--c-sub` | #5C6670 | 次要文字、说明、「提示」级 |
|
||||||
|
| 禁用灰 | `--c-muted` | #7A838C | **只用于**禁用文字和控件边框 |
|
||||||
|
| 分隔线 | `--c-rule` | #D9DDD8 | 装饰性分隔线 |
|
||||||
|
| 蓝铅笔 | `--c-pencil` | #2F5BD3 | AI 标注、出处、跳转原文的链接 |
|
||||||
|
| 蓝铅笔浅 | `--c-pencil-soft` | #E7EDFB | 原句高亮 |
|
||||||
|
| 致命红 | `--c-fatal` | #C4122F | **只用于「致命」级问题** |
|
||||||
|
| 赭 | `--c-ochre` | #A66300 | 「重要」级边框和图标、系统提醒、表单错误、截止临近 |
|
||||||
|
| 赭(小字) | `--c-ochre-ink` | #8A5200 | 18px 以下的赭色文字 |
|
||||||
|
| 定稿绿 | `--c-green` | #2E6B4F | 人工确认的署名和 ✓ |
|
||||||
|
|
||||||
|
每个功能色都有一个浅色底(`-soft`),只用作背景,文字仍用对应的深色。
|
||||||
|
|
||||||
|
**不进界面的颜色**:印章红 #D9291C 和藏青 #1C2554 只属于 logo(VI 手册第 03 章)。
|
||||||
|
|
||||||
|
### 3.2 对比度实测
|
||||||
|
|
||||||
|
| 文字色 | 校样纸 | 白 | 用法限制 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 墨 #1D2733 | 13.9 | 15.1 | 无 |
|
||||||
|
| 辅助灰 #5C6670 | 5.4 | 5.9 | 无 |
|
||||||
|
| 蓝铅笔 #2F5BD3 | 5.4 | 5.9 | 在高亮底 #E7EDFB 上 5.0,仍达标 |
|
||||||
|
| 致命红 #C4122F | 5.6 | 6.0 | 白字压红底 6.0 |
|
||||||
|
| 定稿绿 #2E6B4F | 5.8 | 6.3 | — |
|
||||||
|
| 赭 #A66300 | **4.4** | 4.8 | 校样纸上只能用于 ≥18px 的字、边框、图标 |
|
||||||
|
| 赭小字 #8A5200 | 5.9 | 6.4 | 18px 以下的赭色文字都用它 |
|
||||||
|
| 禁用灰 #7A838C | 3.6 | 3.9 | 只用于禁用状态和控件边框,**不能用于正文和占位符**(占位符用辅助灰) |
|
||||||
|
|
||||||
|
分隔线 #D9DDD8 只有 1.3:1,所以它只能做装饰性分隔;需要用户看清边界的控件(输入框、复选框、次按钮)用 `--border-control`(#7A838C,3.6:1)。
|
||||||
|
|
||||||
|
### 3.3 不能只靠颜色
|
||||||
|
|
||||||
|
| 要区分的东西 | 颜色 | 形状 / 文字(必须同时有) |
|
||||||
|
|---|---|---|
|
||||||
|
| 致命 | 致命红 | **实心**标签,白字「致命」 |
|
||||||
|
| 重要 | 赭 | **描边**标签(1.5px),赭色字「重要」 |
|
||||||
|
| 提示 | 辅助灰 | **无边框**,灰底灰字「提示」 |
|
||||||
|
| AI 产出 | 蓝铅笔 | 「AI」小标签或「AI 摘要 / AI 初稿」字样,左侧虚线竖线 |
|
||||||
|
| 人工确认 | 墨 + 定稿绿 | ✓ 符号 + 「王敏 11月2日 14:20 确认无误」署名行 |
|
||||||
|
| 待确认区 | 蓝铅笔 | 虚线框 + 区块标题「待确认」 |
|
||||||
|
|
||||||
|
### 3.4 颜色禁忌
|
||||||
|
|
||||||
|
- 不用红色做按钮(包括删除按钮)、表单错误、系统错误、倒计时、额度用完提示。删除这类操作靠二次确认,不靠颜色吓人。
|
||||||
|
- 不用定稿绿表示「通过 / 合格」。绿色只表示「有人确认过」,不表示结论好。
|
||||||
|
- 不用蓝铅笔做普通导航链接、主按钮。蓝色只属于系统说的话和出处。普通链接用墨色加下划线。
|
||||||
|
- 不用渐变、不用大面积彩色底。一屏之内彩色面积越少,致命红越显眼。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 字体
|
||||||
|
|
||||||
|
### 4.1 字体栈与加载
|
||||||
|
|
||||||
|
| 用途 | 变量 | 字体 | 加载方式 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 界面正文 | `--font-sans` | 苹方 → 微软雅黑 → 思源黑体(Noto Sans SC) | 系统字体,不下载 |
|
||||||
|
| 标题 | `--font-serif` | 思源宋体 SemiBold(Noto Serif SC 600) | 自托管 `@fontsource/noto-serif-sc`,只引 600 字重 |
|
||||||
|
| 数字 | `--font-figures` | Barlow Semi Condensed 500 / 600 | 自托管 `@fontsource/barlow-semi-condensed` |
|
||||||
|
|
||||||
|
- 不用 Google Fonts 等境外 CDN;字体文件随前端一起部署。
|
||||||
|
- 检查报告 PDF 在服务端生成,**嵌入**思源黑体和思源宋体,保证不同电脑打开效果一致。
|
||||||
|
- 三套字体都是 SIL OFL 1.1,许可证文件在 `logo/licenses/`。
|
||||||
|
|
||||||
|
### 4.2 字阶
|
||||||
|
|
||||||
|
| 级别 | 字号 / 行高 | 字体与字重 | 用在 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| H1 | 24 / 32 | 宋体 600 | 工作台标题、项目名、报告标题 |
|
||||||
|
| H2 | 20 / 28 | 宋体 600 | 页面标题(「采购包 2 的检查结果」) |
|
||||||
|
| Title | 16 / 24 | 黑体 600 | 问题标题、区块标题、对话框标题 |
|
||||||
|
| Body | 14 / 22 | 黑体 400 | 界面正文、表格 |
|
||||||
|
| Read | 14 / 24 | 黑体 400 | 招标原文、投标原文的引用 |
|
||||||
|
| Caption | 12 / 20 | 黑体 400 | 时间戳、脚注、表单说明;**最小字号**,正文不用 |
|
||||||
|
| Figure | 30 / 36 | Barlow 600 | 工作台统计数字、额度剩余 |
|
||||||
|
|
||||||
|
- 字重只用 400 和 600 两档(黑体在 Windows 上 500 和 400 几乎看不出区别)。
|
||||||
|
- 强调用加粗,不用斜体(中文没有真斜体)、不用彩色。
|
||||||
|
|
||||||
|
### 4.3 数字与中英文混排
|
||||||
|
|
||||||
|
- **独立出现的数字**用 Barlow:表格里的金额 / 参数值、页码「第 41 页」、倒计时、统计数字、额度。表格列加 `tabular-nums`,金额右对齐。
|
||||||
|
- **句子里的数字**跟着正文黑体走,不换 Barlow,避免一句话里字形忽宽忽窄。
|
||||||
|
- 中文与数字、英文之间加一个半角空格:「第 41 页」「≥ 512G」「采购包 2」。比较符号与数值之间也加空格。
|
||||||
|
- 招标原文引用**照录原文**,不加空格、不改标点、不改 ★ ▲ 等符号。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 间距、圆角与布局
|
||||||
|
|
||||||
|
### 5.1 基础
|
||||||
|
|
||||||
|
- 间距以 4px 为基准:4 / 8 / 12 / 16 / 20 / 24 / 32 / 40 / 48 / 64(`--sp-*`)。
|
||||||
|
- 组件内部用 4–12,组件之间 16–24,区块之间 32–48。
|
||||||
|
- 圆角:控件和卡片 2px(纸张感,接近直角),对话框和浮层 4px。不用大圆角和胶囊形。
|
||||||
|
- 层次靠分隔线和底色区分,不靠阴影。阴影只用于浮层(下拉、对话框、提示)。
|
||||||
|
|
||||||
|
### 5.2 平台与尺寸
|
||||||
|
|
||||||
|
- **MVP 只做桌面 Web**,设计基准宽度 1440,最小支持 1280(1366×768 笔记本在国内政企仍常见)。小于 1280 时页面横向滚动,不做手机适配(见 U2)。
|
||||||
|
- 浏览器:Chrome、Edge 和国产双核浏览器的极速模式(Chromium 内核)。不支持 IE 和兼容模式,检测到时提示切换。
|
||||||
|
- 不做暗色模式(U2);变量按语义分层,以后加暗色只需重新赋值语义变量。
|
||||||
|
|
||||||
|
### 5.3 页面框架
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────┬──────────────────────────────────────────────┐
|
||||||
|
│ 侧边栏 │ 顶栏:面包屑 · 项目名 · 截止倒计时 · 额度 · 账号 │
|
||||||
|
│ 216px ├──────────────────────────────────────────────┤
|
||||||
|
│ 工作台 │ │
|
||||||
|
│ 项目 │ 页面内容(最大宽度 1200px,三种主布局见下) │
|
||||||
|
│ 资质库 │ │
|
||||||
|
│ 额度 │ │
|
||||||
|
└──────────┴──────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
| 布局 | 页面 | 结构 |
|
||||||
|
|---|---|---|
|
||||||
|
| 列表 | 工作台、资质库、额度流水 | 筛选栏 + 表格,单栏 |
|
||||||
|
| 对照 | 读标(清单)、查标(问题列表) | 左:分类导航 200px;中:列表;右:**原文查看器**,宽度可拖动(默认 45%) |
|
||||||
|
| 批注 | 查标结果的默认视图 | 问题列表 + 右侧 280px 页边批注栏(出处);窄于 1440 时批注收进问题下方 |
|
||||||
|
|
||||||
|
点出处时,对照布局的右栏直接翻到对应页并高亮;批注布局下从右侧滑出原文查看器,不跳页面。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 图标
|
||||||
|
|
||||||
|
- 用 **Lucide**(ISC 许可,可商用)线性图标,描边 1.5px,尺寸 16 / 20px,颜色跟随文字色。
|
||||||
|
- 图标只做辅助,必须和文字一起出现;只有图标的按钮要有 `aria-label` 和悬停提示。
|
||||||
|
- ★ ▲ 是招标文件里的标注符号,按文字显示,不换成图标。
|
||||||
|
- 不用笑脸、奖杯、盾牌对勾这类暗示「保证」的图形。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 组件
|
||||||
|
|
||||||
|
每个组件写明:用途、结构、状态、规则。示意见 `system/components.html`。
|
||||||
|
|
||||||
|
### 7.1 按钮
|
||||||
|
|
||||||
|
| 类型 | 样式 | 用于 |
|
||||||
|
|---|---|---|
|
||||||
|
| 主按钮 | 墨底白字 | 一屏只有一个:「发起检查」「导出报告」「确认清单」 |
|
||||||
|
| 次按钮 | 白底墨字,控件边框 | 其他操作,包括问题处理三按钮 |
|
||||||
|
| 文字按钮 | 墨字,无边框,悬停出底色 | 表格行内、次要操作 |
|
||||||
|
|
||||||
|
- 高度 32(默认)/ 28(表格行内)/ 40(登录页、空状态)。左右内边距 12 / 8 / 16。
|
||||||
|
- **没有红色的危险按钮**。删除项目、删除文件用次按钮 + 确认对话框;删除项目要输入项目名才能确认。
|
||||||
|
- 会扣点的按钮,旁边直接写扣多少、还剩多少(见 8.3),不放在弹窗里才说。
|
||||||
|
- 状态:悬停(主按钮变浅 8%,次按钮出 `--bg-hover`)、按下、禁用(禁用灰字、无边框底色)、加载中(文字换成「正在…」,按钮宽度不变)。
|
||||||
|
- 按钮文字是动词短语,和结果状态用同一个词(品牌策略第 4 节)。
|
||||||
|
|
||||||
|
### 7.2 问题级别标签
|
||||||
|
|
||||||
|
```
|
||||||
|
[致命] 实心,白字压致命红
|
||||||
|
[重要] 1.5px 赭色描边,赭色小字
|
||||||
|
[提示] 无边框,凹陷灰底,辅助灰字
|
||||||
|
```
|
||||||
|
|
||||||
|
- 字号 12,字重 600,高 20,左右内边距 6,圆角 2。
|
||||||
|
- 级别由规则决定(FR-M3-03 的级别表),用户不能改级别;不同意可以「反馈:判断有误」。
|
||||||
|
- 计数徽标也用同一套形状:「致命 2」实心、「重要 5」描边、「提示 11」无框。
|
||||||
|
|
||||||
|
### 7.3 出处(页边批注)
|
||||||
|
|
||||||
|
出处是标博士最重要的组件。
|
||||||
|
|
||||||
|
```
|
||||||
|
│ 招标文件 · 第 41 页 ← 蓝铅笔,Barlow 页码,可点击
|
||||||
|
│ ★ 2U 机架式服务器,CPU:配置≥2颗… ← 原句照录,14/24,墨色字 + 蓝铅笔浅高亮
|
||||||
|
│ 投标文件 技术册 · 第 12 页 ← 第二个出处(投标方)
|
||||||
|
│ 内存:256G DDR4
|
||||||
|
```
|
||||||
|
|
||||||
|
- 左侧 2px 蓝铅笔竖线;在批注布局里,用一条虚线连到对应的问题。
|
||||||
|
- 一条结论可以有多个出处,按「招标原文 → 投标原文」排序,最多默认展开 2 条,其余「另有 N 处」。
|
||||||
|
- **「未找到」也是一种出处**:写明找过哪些分册、哪些章节,例如「已查找:商务册、技术册(全部章节)、资格证明册;未找到对应响应」。
|
||||||
|
- 引用超过 3 行时截断,「展开原文」显示全句;原句必须完整可见,不能只给页码。
|
||||||
|
- 出处无法定位的结果不进正式清单,进「待确认」区(FR-M2-06/07);界面上**不出现没有出处的结论**。
|
||||||
|
- 扫描件 OCR 识别出的原句,前面加「OCR」小字,提醒用户核对原图。
|
||||||
|
|
||||||
|
### 7.4 问题行(查标)
|
||||||
|
|
||||||
|
```
|
||||||
|
[致命] 报价超过采购包 2 的最高限价 新增
|
||||||
|
开标一览表报价 133.60 万元,最高限价 132.00 万元,超出 1.60 万元。
|
||||||
|
这一项会导致投标无效。请核对开标一览表的报价。
|
||||||
|
[确认无误] [已修改] [忽略并注明原因] 判断有误?
|
||||||
|
```
|
||||||
|
|
||||||
|
- 结构:级别标签 → 标题(结论)→ 说明(事实 + 后果 + 改法)→ 处理按钮 → 出处(右侧或下方)。
|
||||||
|
- 标题写结论,不写规则名:「报价超过采购包 2 的最高限价」,不写「报价限价规则命中」。
|
||||||
|
- 复查标记放在标题行右侧:「新增」「沿用」「本次已解决」(FR-M3-08),用辅助灰小字,不用颜色。
|
||||||
|
- 处理后:按钮收起,换成署名行「✓ 王敏 11月2日 14:20 已修改」(绿),可「撤销」。「已忽略」不是确认,署名行用辅助灰和「–」,后面带原因。
|
||||||
|
- 「致命」行左侧加 3px 致命红竖线,方便在长列表里扫读;「重要」「提示」不加。
|
||||||
|
- 已处理的问题默认折叠到列表底部「已处理(N)」,避免干扰。
|
||||||
|
- 每行都有「判断有误?」文字链接,打开反馈表单(FR-M3-07 / J5)。
|
||||||
|
- 「致命」级点「确认无误」时,展开一个必填的说明输入框(U4),说明同时作为误报反馈提交。
|
||||||
|
|
||||||
|
**三个处理动作的含义**(按钮下方悬停提示要写清楚,避免误解):
|
||||||
|
|
||||||
|
| 按钮 | 含义 | 结果状态 |
|
||||||
|
|---|---|---|
|
||||||
|
| 确认无误 | 我核对过,投标文件这里没问题(例如响应写在别处) | 已确认无误 |
|
||||||
|
| 已修改 | 我已经在投标文件里改了,复查时会再查 | 已修改 |
|
||||||
|
| 忽略并注明原因 | 我知道这个问题,决定不处理(必须填原因) | 已忽略 |
|
||||||
|
|
||||||
|
### 7.5 清单项(读标)
|
||||||
|
|
||||||
|
- 一行:重要性符号(★ / ▲ / 空)→ 标题 → 类别标签(可多个,灰色描边小标签)→ 所属包 → 来源 → 出处。
|
||||||
|
- 来源标注:「AI 提取」(蓝铅笔)、「人工添加」(墨)、「补遗 2」(墨)。
|
||||||
|
- 被用户改过的条目显示「已修改 · 查看记录」,打开修改历史(谁、何时、改前、改后)。
|
||||||
|
- 「已被补遗 N 更新」的旧值加删除线,新值旁边写来源,用户确认后去掉提示(FR-M2-10)。
|
||||||
|
- 清单整体状态显示在页头:「草稿」(辅助灰描边)/「已确认 · 王敏 11月2日」(绿色署名)。
|
||||||
|
|
||||||
|
### 7.6 待确认区
|
||||||
|
|
||||||
|
- 虚线蓝铅笔边框的独立区块,标题「待确认(N)」,说明一句:「这些结果没有找到可靠出处,或系统把握不大。确认之前,不参与检查。」
|
||||||
|
- 每条三个动作:「补上出处后采纳」「修改后采纳」「丢弃」。没有出处也要采纳时,标注「人工确认、无出处」。
|
||||||
|
- 待确认区不和正式清单混排,放在清单顶部,有条目时默认展开。
|
||||||
|
|
||||||
|
### 7.7 参数对照表
|
||||||
|
|
||||||
|
技术参数的检查结果用表格(借鉴方向 C「检验单」的写法):
|
||||||
|
|
||||||
|
| 参数项 | 要求 | 投标响应 | 判断 | 出处 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| ★ 内存 | ≥ 512G DDR4 | 256G DDR4 | **疑似不满足** | 第 41 页 ↔ 技术册 第 12 页 |
|
||||||
|
|
||||||
|
- 要求和响应列用 Barlow + `tabular-nums`,数值左对齐(参数是「数 + 单位 + 说明」,右对齐反而难读)。
|
||||||
|
- 「判断」列只用四个词:**满足 / 疑似不满足 / 未找到响应 / 需人工判断**。不用「合格 / 不合格 / 通过」。
|
||||||
|
- 「疑似不满足」用所在行的问题级别标签颜色;「满足」用墨色普通字,不用绿色(绿色只表示人工确认)。
|
||||||
|
- 表头凹陷灰底、2px 墨色底线;行间 1px 分隔线;行高 40。
|
||||||
|
|
||||||
|
### 7.8 原文查看器
|
||||||
|
|
||||||
|
- 白色页面,按规范化 PDF 显示(出处页码以它为准)。
|
||||||
|
- 高亮:原句用蓝铅笔浅底 + 1px 蓝铅笔描边框;当前选中的出处描边加粗为 2px,并自动滚动到可见区域。
|
||||||
|
- 顶部:文件名、文件类型(招标文件 / 补遗 N / 投标分册)、页码跳转「第 41 / 186 页」、上一处 / 下一处高亮。
|
||||||
|
- OCR 页面显示「扫描页 · 文字由 OCR 识别」,高亮按 OCR 坐标。
|
||||||
|
- 原文只读,不允许在查看器里改文字。
|
||||||
|
|
||||||
|
### 7.9 项目行(工作台)
|
||||||
|
|
||||||
|
| 列 | 内容 | 规则 |
|
||||||
|
|---|---|---|
|
||||||
|
| 项目 | 项目名(16/24 600)+ 采购人(辅助灰) | |
|
||||||
|
| 所投包 | 「采购包 2」或「全部」 | |
|
||||||
|
| 截止 | 倒计时 | 见下 |
|
||||||
|
| 状态 | 跟进中 / 编制中 / 已检查 / 已递交 / 已归档 | 中性描边标签,不用彩色 |
|
||||||
|
| 就绪 | 只在「已检查」显示 | 见下 |
|
||||||
|
| 致命 | 待处理的致命问题数 | 实心标签「致命 2」;为 0 时显示「—」 |
|
||||||
|
|
||||||
|
**倒计时**(以投标截止时间为准):
|
||||||
|
|
||||||
|
| 剩余时间 | 显示 | 颜色 |
|
||||||
|
|---|---|---|
|
||||||
|
| > 72 小时 | 「11月20日 09:30 · 剩 17 天」 | 墨 |
|
||||||
|
| ≤ 72 小时 | 「剩 2 天 5 小时」 | 赭小字 |
|
||||||
|
| ≤ 24 小时 | 「今天 09:30 截止」/「剩 6 小时」 | 赭小字,加粗 |
|
||||||
|
| 已过 | 「已截止」 | 辅助灰 |
|
||||||
|
|
||||||
|
**就绪状态**:「未就绪」(墨色普通字,致命数量看「致命」列)/「就绪」(墨色描边标签)。项目详情页的页头合在一起写:「未就绪 · 2 项致命问题待处理」。「就绪」悬停说明:「只表示致命问题都已处理,不代表投标文件没有问题。」
|
||||||
|
|
||||||
|
### 7.10 额度
|
||||||
|
|
||||||
|
- 顶栏常驻:「解析 37 · 检查 12」(Barlow),点开额度页。
|
||||||
|
- 额度条:已用部分墨色,80% 起变赭色并出现一行提醒,100% 时提醒「本期检查额度已用完,续费或下期发放后可继续」。**不用红色**。
|
||||||
|
- 流水表:时间、类型(发放 / 预扣 / 结算 / 退回 / 过期 / 人工调整)、数量(+ / −,Barlow 等宽)、关联项目和任务、原因。退回的记录和对应的预扣记录相邻显示。
|
||||||
|
|
||||||
|
### 7.11 异步任务进度(解析 / 检查)
|
||||||
|
|
||||||
|
- 分阶段列表:「识别文件格式 → 提取文字 → 识别分包 → 提取清单 → 核对出处」,当前阶段有进度,已完成的打 ✓。
|
||||||
|
- 明确告诉用户可以离开:「可以离开本页,完成后会发站内通知。」
|
||||||
|
- 失败时说清楚原因和扣点情况:「解析失败:文件已加密。已退回 1 次解析额度。」赭色提醒,不用红。
|
||||||
|
|
||||||
|
### 7.12 提醒与通知
|
||||||
|
|
||||||
|
| 类型 | 样式 | 例子 |
|
||||||
|
|---|---|---|
|
||||||
|
| 信息 | 白底,墨色左边线 | 「清单未确认,检查结果以当前清单为准。」 |
|
||||||
|
| 提醒 | 赭浅底,赭色左边线,赭小字标题 | 额度 80%、截止 ≤ 72 小时、解析失败、上传文件超限 |
|
||||||
|
| 完成 | 白底,绿色 ✓ | 「报告已导出」 |
|
||||||
|
|
||||||
|
- 页内提醒(Alert)放在相关内容上方,不自动消失;轻提示(Toast)只用于操作结果,4 秒后消失,可悬停暂停。
|
||||||
|
- 不用弹窗打断。只有不可撤销的操作(删除、标记已递交)才用确认对话框。
|
||||||
|
|
||||||
|
### 7.13 表单
|
||||||
|
|
||||||
|
- 标签在输入框上方,必填项在标签后写「(必填)」,不用红色星号。
|
||||||
|
- 错误提示:输入框边框变赭色 + 下方赭小字说明 + 「!」图标。说明写清楚怎么改:「手机号应为 11 位数字」。
|
||||||
|
- 校验时机:离开输入框时校验,提交时汇总。
|
||||||
|
- 「忽略并注明原因」的原因输入框:至少 5 个字,给 3 个常用原因快捷选项(「招标文件允许」「已在其他分册响应」「与本项目无关」)。
|
||||||
|
|
||||||
|
### 7.14 对话框
|
||||||
|
|
||||||
|
- 宽 480(确认)/ 640(表单),圆角 4,浮层阴影。
|
||||||
|
- 标题写要做的事:「删除项目「智慧规划资源 2025 标段三」?」;正文写后果;按钮「删除项目」「取消」,主操作在右。
|
||||||
|
- 删除项目要输入项目名才能点删除;说明删除后 30 天内可在回收站恢复(以 G10 定稿为准)。
|
||||||
|
|
||||||
|
### 7.15 空状态、加载与错误
|
||||||
|
|
||||||
|
- 空状态写下一步做什么,带一个主按钮:「还没有招标文件。上传后,标博士会逐条提取要求。」
|
||||||
|
- 加载用骨架屏,不用转圈占满屏。
|
||||||
|
- 系统错误:「出了点问题,请稍后重试。本次操作未扣额度。」带错误编号,便于客服查询。
|
||||||
|
|
||||||
|
### 7.16 AI 标识
|
||||||
|
|
||||||
|
- 系统生成的文字(摘要、说明、初稿)前面加「AI 摘要」「AI 初稿」标签:蓝铅笔 1px 描边、蓝铅笔字。
|
||||||
|
- 人修改或确认后,标签换成署名行,文字变为墨色。
|
||||||
|
|
||||||
|
### 7.17 检查报告 PDF
|
||||||
|
|
||||||
|
- A4 竖版,页边距 20mm,思源黑体正文 10.5pt / 行距 1.6,标题思源宋体。
|
||||||
|
- 页眉:左侧小号横版 logo(干净版),右侧「投标文件辅助检查报告」。**logo 只出现在页眉**,不放在任何结论、汇总数字或签名旁边(红线:logo 不能当「通过」章)。
|
||||||
|
- 首页:项目信息、投标文件版本、检查时间、规则版本、**检查覆盖范围**(查了什么 / 没查什么)、免责声明。
|
||||||
|
- 问题明细的级别标签和界面一致(实心 / 描边 / 无框),黑白打印也能分辨。
|
||||||
|
- 页脚:页码「3 / 12」+「本报告为辅助检查结果,不构成对投标结果的保证」。
|
||||||
|
- 全文不出现「通过」「合格」「保证」。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 交互模式
|
||||||
|
|
||||||
|
### 8.1 AI 产出 → 人工确认 → 留痕
|
||||||
|
|
||||||
|
所有 AI 产出都走同一个流程,视觉上一以贯之:
|
||||||
|
|
||||||
|
1. **AI 产出**:蓝铅笔标记(「AI 提取」「AI 摘要」、出处蓝色)。
|
||||||
|
2. **人处理**:三个动作之一,忽略要填原因。
|
||||||
|
3. **署名**:绿色 ✓ + 姓名 + 时间 + 动作,文字变墨色。
|
||||||
|
4. **留痕**:每条都能打开「处理记录」,看到全部历史。
|
||||||
|
|
||||||
|
### 8.2 键盘逐条处理
|
||||||
|
|
||||||
|
问题列表和清单支持键盘,方便一次过几十条:
|
||||||
|
|
||||||
|
| 键 | 动作 |
|
||||||
|
|---|---|
|
||||||
|
| J / K | 下一条 / 上一条 |
|
||||||
|
| O 或 Enter | 在原文查看器里打开当前出处 |
|
||||||
|
| 1 / 2 / 3 | 确认无误 / 已修改 / 忽略(3 会打开原因输入框) |
|
||||||
|
| U | 撤销当前条的处理 |
|
||||||
|
| ? | 显示快捷键说明 |
|
||||||
|
|
||||||
|
批量处理只开放给「提示」级(FR-M3-07):勾选后「批量确认无误」。
|
||||||
|
|
||||||
|
### 8.3 扣点前先告知
|
||||||
|
|
||||||
|
- 会扣点的按钮旁边写:「扣 1 次检查 · 剩 12 次」。24 小时内复查不扣点时写:「24 小时内复查,不扣额度」。
|
||||||
|
- 失败、超时、取消的任务,在结果里写明「已退回」。
|
||||||
|
- 额度不足时按钮仍可点,点击后说明不足并给续费入口,不提前把按钮置灰(用户需要知道原因)。
|
||||||
|
|
||||||
|
### 8.4 不阻断
|
||||||
|
|
||||||
|
- 致命问题没处理完时,「导出报告」仍可用;导出前在按钮旁提示「还有 2 项致命问题待处理,报告会如实列出」。
|
||||||
|
- 「标记已递交」同理,只提示,不拦截。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. 文案规范
|
||||||
|
|
||||||
|
### 9.1 术语跟着法律体系和采购方式走
|
||||||
|
|
||||||
|
| 概念 | 政府采购 · 公开招标 | 政府采购 · 竞争性磋商 / 谈判 | 招标投标体系 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 用户提交的文件 | 投标文件 | 响应文件 | 投标文件 |
|
||||||
|
| 被判无效 | 投标无效 | 无效响应 | 否决投标 |
|
||||||
|
| 招标方文件 | 招标文件 | 磋商文件 / 谈判文件 | 招标文件 |
|
||||||
|
| 分组 | 采购包 | 采购包 | 标段 |
|
||||||
|
|
||||||
|
界面文案用变量写(如 `{投标文件}`),按项目识别出的体系和采购方式替换,不在代码里写死。
|
||||||
|
|
||||||
|
### 9.2 禁用词
|
||||||
|
|
||||||
|
| 不用 | 原因 | 改为 |
|
||||||
|
|---|---|---|
|
||||||
|
| 保证、确保、零废标、100%、万无一失 | 产品红线 3 | 辅助检查、风险提示 |
|
||||||
|
| 通过、合格、审核通过、检查通过 | 暗示结论担保 | 已处理、就绪(附说明)、满足(仅参数对比) |
|
||||||
|
| 智能引擎、规则命中、要素、实体 | 不是投标人的话 | 用具体的业务词 |
|
||||||
|
| 建议报价、报价区间、合理报价 | 产品红线 6 | (不出现) |
|
||||||
|
| 真伪、伪造、有效印章 | 产品红线 7 | 「有无签章」「签章位置」 |
|
||||||
|
| !(惊叹号)、「请注意」「警告」开头 | 品牌性格「沉着」 | 直接写事实和后果 |
|
||||||
|
|
||||||
|
### 9.3 格式
|
||||||
|
|
||||||
|
| 内容 | 写法 |
|
||||||
|
|---|---|
|
||||||
|
| 页码 | 「第 41 页」;带文件时「招标文件 · 第 41 页」「技术册 · 第 12 页」 |
|
||||||
|
| 金额 | 沿用招标文件的单位,保留两位小数:「132.00 万元」;不换算单位 |
|
||||||
|
| 日期时间 | 界面:「11月2日 14:20」(跨年时带年份);报告和留痕:「2026-11-02 14:20」 |
|
||||||
|
| 数量 | 「致命 2 项」「共 186 页」 |
|
||||||
|
| 标点 | 中文用全角标点,引用原文照录 |
|
||||||
|
|
||||||
|
### 9.4 常用句式
|
||||||
|
|
||||||
|
| 场景 | 写法 |
|
||||||
|
|---|---|
|
||||||
|
| 未找到响应 | 「未找到对 ★ 条款的响应。已查找:商务册、技术册、资格证明册。」 |
|
||||||
|
| 疑似不满足 | 「技术偏离表第 12 行响应「内存 256G」,要求「≥512G」。」 |
|
||||||
|
| 后果 | 「这一项会导致投标无效。」(体系为招标投标时:「这一项会导致投标被否决。」) |
|
||||||
|
| 改法 | 「请在技术偏离表第 12 行补充响应。」 |
|
||||||
|
| 签章 | 「投标函第 2 页的签章位置未发现盖章。本检查只看有无,不鉴定真伪。」 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. 无障碍检查清单
|
||||||
|
|
||||||
|
- [ ] 文字对比度 ≥ 4.5:1(大字 ≥ 3:1),控件边框 ≥ 3:1 —— 见 3.2
|
||||||
|
- [ ] 级别、状态不只靠颜色 —— 见 3.3
|
||||||
|
- [ ] 所有操作可用键盘完成;焦点环 2px 墨色 + 2px 间隙,任何背景上都看得见
|
||||||
|
- [ ] 只有图标的按钮有 `aria-label`
|
||||||
|
- [ ] 原文高亮除颜色外还有描边框
|
||||||
|
- [ ] 尊重「减少动态效果」系统设置;动效时长 120–200ms,不闪烁
|
||||||
|
- [ ] 浏览器缩放到 200% 时内容不丢失(可以横向滚动)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. 产品红线自查(对应 AGENTS.md)
|
||||||
|
|
||||||
|
| 红线 | 本规范怎么落实 |
|
||||||
|
|---|---|
|
||||||
|
| 1 辅助定位四件套 | AI 标识(7.16)、清单(7.5)、处理按钮与署名(7.4、8.1)、处理记录(7.5、8.1) |
|
||||||
|
| 2 可复核 | 出处组件是固定组成部分(7.3);无出处进待确认区(7.6) |
|
||||||
|
| 3 禁用绝对化表述 | 禁用词表(9.2);报告首页覆盖范围与免责声明(7.17) |
|
||||||
|
| 4 不阻断用户 | 8.4;就绪状态只提示(7.9) |
|
||||||
|
| 5 不编造 | 资质只来自资质库;缺失时显示占位「待补录」,不生成 |
|
||||||
|
| 6 不生成报价 | 参数表与报价检查只显示投标文件读出的数字;禁用词表 |
|
||||||
|
| 7 签章只看有无 | 签章句式(9.4) |
|
||||||
|
| 9 额度即货币 | 扣点前告知、退回可见(7.10、8.3) |
|
||||||
|
| logo 不当「通过」章 | 报告 logo 只在页眉(7.17);绿色不表示「通过」(3.4) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. 已确认的事项(U1–U4,2026-11-02)
|
||||||
|
|
||||||
|
| 编号 | 事项 | 结论 | 理由 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| U1 | 表单错误、系统错误、额度用完、截止临近都不用红色,统一用赭色 | 采纳 | 让红色只代表「可能导致投标无效」;色弱测试显示红 / 赭本来就靠形状区分,错误提示照样有文字和图标 |
|
||||||
|
| U2 | MVP 只做桌面 Web(最小 1280 宽),不做手机适配和暗色模式 | 采纳 | 读标和查标需要原文并排,手机屏放不下;变量已按语义分层,以后可加 |
|
||||||
|
| U3 | 图标用 Lucide(ISC 许可) | 采纳 | 线性风格和校样方向一致,图标全,可商用 |
|
||||||
|
| U4 | 「致命」级问题点「确认无误」时,也要求写一句说明(目前需求只要求「忽略」填原因) | 采纳 | 对致命问题说「没问题」,等于认为系统判断错了:留下说明既便于审计,也是最有价值的误报反馈。会改动 FR-M3-07 |
|
||||||
|
|
||||||
|
组件库(TDesign / Ant Design / Arco 等)由技术架构阶段选定;本规范的正文 14/22、4px 间距与这些组件库一致,选定后把 `tokens.css` 映射到它的主题配置即可。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 13. 预览与维护
|
||||||
|
|
||||||
|
- 预览组件页(字体从 `logo/.work/fonts` 加载,没有该目录时会退回系统字体):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd docs/design && python3 -m http.server 8765
|
||||||
|
# 浏览器打开 http://127.0.0.1:8765/system/components.html
|
||||||
|
```
|
||||||
|
|
||||||
|
- 改颜色、字号、间距:只改 `system/tokens.css`,再同步本文的表格。
|
||||||
|
- 改组件样式:改 `system/components.css`,预览页和原型会一起变。
|
||||||
|
- 新增组件:先在本文第 7 节写用途、结构、状态、规则,再加到 `components.css` 和预览页。
|
||||||
|
- 本规范变化影响 VI 手册(颜色、字体)时,同步更新 `vi/vi-manual.html` 并重出 PDF。
|
||||||
@@ -22,8 +22,9 @@
|
|||||||
| `svg/seal-textured.svg` / `svg/seal.svg` | 只用印章:头像、水印、图标 |
|
| `svg/seal-textured.svg` / `svg/seal.svg` | 只用印章:头像、水印、图标 |
|
||||||
| `svg/seal-small.svg`、`favicon.svg` | 32–64px 专用:白色笔画已经加粗 |
|
| `svg/seal-small.svg`、`favicon.svg` | 32–64px 专用:白色笔画已经加粗 |
|
||||||
| `svg/favicon-16.svg` | 16px 专用:加粗简化的「标」字(B2 已确认的方案 B) |
|
| `svg/favicon-16.svg` | 16px 专用:加粗简化的「标」字(B2 已确认的方案 B) |
|
||||||
|
| `svg/app-icon.svg` | 应用图标:印泥铺满整个方形,不留白边(B6)。不带圆角、不带透明,圆角由系统裁 |
|
||||||
| `svg/wordmark.svg` / `wordmark-reverse.svg` | 只用字标(藏青 / 白色) |
|
| `svg/wordmark.svg` / `wordmark-reverse.svg` | 只用字标(藏青 / 白色) |
|
||||||
| `png/` | 位图导出:横版 1200 / 2400px、竖版 600px、印章 1024px(均为透明背景),以及图标 16 / 32 / 180 / 192 / 512px |
|
| `png/` | 位图导出:横版 1200 / 2400px、竖版 600px、印章 1024px(均为透明背景),以及图标 16 / 32px(透明)和应用图标 180 / 192 / 512 / 1024px(不透明,由 `app-icon.svg` 渲染) |
|
||||||
|
|
||||||
**质感版和干净版怎么选**:显示尺寸大于 64px(印章部分)时用质感版;小于 64px,或者是界面元素、单色印刷,用干净版。
|
**质感版和干净版怎么选**:显示尺寸大于 64px(印章部分)时用质感版;小于 64px,或者是界面元素、单色印刷,用干净版。
|
||||||
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 19 KiB |
BIN
docs/design/logo/png/icon-1024.png
Normal file
|
After Width: | Height: | Size: 150 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 66 KiB |
@@ -34,13 +34,10 @@ rsvg-convert -w 1024 svg/seal-textured.svg -o png/seal-1024.png
|
|||||||
# 5. PNG:图标
|
# 5. PNG:图标
|
||||||
rsvg-convert -w 16 svg/favicon-16.svg -o png/favicon-16.png
|
rsvg-convert -w 16 svg/favicon-16.svg -o png/favicon-16.png
|
||||||
rsvg-convert -w 32 svg/seal-small.svg -o png/favicon-32.png
|
rsvg-convert -w 32 svg/seal-small.svg -o png/favicon-32.png
|
||||||
# 应用图标:白底,印章占 80%(iOS 不支持透明图标)
|
# 应用图标:印泥铺满整个方形,不留白边,不带透明(iOS 不支持透明图标);圆角由系统加
|
||||||
for s in 180 192 512; do
|
for s in 192 512 1024; do
|
||||||
inner=$((s * 80 / 100))
|
rsvg-convert -w "$s" -b "$RED" svg/app-icon.svg -o "png/icon-$s.png"
|
||||||
rsvg-convert -w "$inner" svg/seal-textured.svg -o "png/.tmp-$s.png"
|
|
||||||
magick -size "${s}x${s}" xc:white "png/.tmp-$s.png" -gravity center -composite "png/icon-$s.png"
|
|
||||||
rm "png/.tmp-$s.png"
|
|
||||||
done
|
done
|
||||||
mv png/icon-180.png png/apple-touch-icon-180.png
|
rsvg-convert -w 180 -b "$RED" svg/app-icon.svg -o png/apple-touch-icon-180.png
|
||||||
|
|
||||||
ls svg png
|
ls svg png
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
用法:.work/venv/bin/python scripts/build_logo.py
|
用法:.work/venv/bin/python scripts/build_logo.py
|
||||||
通常由 scripts/build_assets.sh 调用;运行前 svg/ 里要已有 seal*.svg。
|
通常由 scripts/build_assets.sh 调用;运行前 svg/ 里要已有 seal*.svg。
|
||||||
"""
|
"""
|
||||||
from logo_lib import FINAL, FINAL_INSET, LOGO, NAVY, WHITE, lockup_h2, lockup_vertical, wordmark
|
from logo_lib import FINAL, FINAL_INSET, LOGO, NAVY, SEAL_RED, WHITE, lockup_h2, lockup_vertical, seal_group, wordmark
|
||||||
|
|
||||||
SVG = LOGO / "svg"
|
SVG = LOGO / "svg"
|
||||||
|
|
||||||
@@ -31,4 +31,10 @@ for color, name in (("#000000", "logo-mono-black.svg"), ("#FFFFFF", "logo-mono-w
|
|||||||
body, w, h = wordmark(FINAL, color, inset=FINAL_INSET)
|
body, w, h = wordmark(FINAL, color, inset=FINAL_INSET)
|
||||||
write(name, lockup_h2(body, w, h, kind="clean", seal_color=color))
|
write(name, lockup_h2(body, w, h, kind="clean", seal_color=color))
|
||||||
|
|
||||||
print("ok", sorted(p.name for p in SVG.glob("logo-*.svg")) + ["wordmark.svg", "wordmark-reverse.svg"])
|
# 应用图标:印泥铺满,不留白边。印章实心部分撑满画布,毛边外的空隙用印章红补齐;
|
||||||
|
# 印章下面垫纸色底,白字和颗粒仍显示为白色
|
||||||
|
seal, _ = seal_group("textured", 0, 0, 100, on_dark=True)
|
||||||
|
write("app-icon.svg", f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">'
|
||||||
|
f'<rect width="100" height="100" fill="{SEAL_RED}"/>{seal}</svg>')
|
||||||
|
|
||||||
|
print("ok", sorted(p.name for p in SVG.glob("logo-*.svg")) + ["wordmark.svg", "wordmark-reverse.svg", "app-icon.svg"])
|
||||||
|
|||||||
1
docs/design/logo/svg/app-icon.svg
Normal file
|
After Width: | Height: | Size: 374 KiB |
186
docs/design/system/components.css
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
/* 标博士 · 组件样式 v0.1
|
||||||
|
* 依赖 tokens.css。规范见 docs/design/design-system.md §7;组件预览 components.html 和原型 docs/prototype/ 共用本文件。
|
||||||
|
* 原型阶段的参考实现:技术架构选定组件库后,按这里的视觉去配置组件库主题,不必照搬选择器。 */
|
||||||
|
|
||||||
|
/* ---- 基础 ---- */
|
||||||
|
*{box-sizing:border-box}
|
||||||
|
body{margin:0;background:var(--bg-page);color:var(--text-primary);font:var(--fs-body)/var(--lh-body) var(--font-sans);-webkit-font-smoothing:antialiased}
|
||||||
|
:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}
|
||||||
|
.card{background:var(--bg-surface);border:var(--line) solid var(--border-subtle);border-radius:var(--radius-sm);padding:var(--sp-5) var(--sp-6)}
|
||||||
|
.row{display:flex;flex-wrap:wrap;gap:var(--sp-3);align-items:center}
|
||||||
|
.grid2{display:grid;grid-template-columns:1fr 1fr;gap:var(--sp-6)}
|
||||||
|
.fig{font-family:var(--font-figures);font-variant-numeric:tabular-nums;font-weight:600}
|
||||||
|
.note{font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--text-secondary);margin:var(--sp-3) 0 0}
|
||||||
|
|
||||||
|
|
||||||
|
/* ---- 级别标签 ---- */
|
||||||
|
.sev{display:inline-flex;align-items:center;height:20px;padding:0 6px;border-radius:var(--radius-sm);font:var(--fw-bold) var(--fs-caption)/1 var(--font-sans);white-space:nowrap}
|
||||||
|
.sev .fig{margin-left:4px;font-size:13px}
|
||||||
|
.sev.fatal{background:var(--sev-fatal);color:var(--text-on-dark)}
|
||||||
|
.sev.major{border:var(--line-sev) solid var(--sev-major);color:var(--sev-major-text)}
|
||||||
|
.sev.hint{background:var(--sev-hint-soft);color:var(--sev-hint)}
|
||||||
|
.tag{display:inline-flex;align-items:center;height:20px;padding:0 6px;border:1px solid var(--border-subtle);border-radius:var(--radius-sm);font-size:var(--fs-caption);color:var(--text-secondary);white-space:nowrap}
|
||||||
|
.tag.status{border-color:var(--border-control);color:var(--text-primary)}
|
||||||
|
.tag.ai{border-color:var(--ai);color:var(--ai)}
|
||||||
|
.flag{font-size:var(--fs-caption);color:var(--text-secondary)}
|
||||||
|
|
||||||
|
/* ---- 按钮 ---- */
|
||||||
|
.btn{display:inline-flex;align-items:center;gap:6px;height:var(--control-h);padding:0 12px;border-radius:var(--radius-sm);border:1px solid var(--border-control);background:var(--bg-surface);color:var(--text-primary);font:var(--fs-body)/1 var(--font-sans);cursor:pointer;transition:background var(--dur-fast) var(--ease)}
|
||||||
|
a.btn{text-decoration:none}
|
||||||
|
[hidden]{display:none!important}
|
||||||
|
.btn:hover{background:var(--bg-hover)}
|
||||||
|
.btn.primary{background:var(--c-ink);border-color:var(--c-ink);color:var(--text-on-dark)}
|
||||||
|
.btn.primary:hover{background:#34404D}
|
||||||
|
.btn.text{border-color:transparent;background:none}
|
||||||
|
.btn.text:hover{background:var(--bg-hover)}
|
||||||
|
.btn.sm{height:var(--control-h-sm);padding:0 8px}
|
||||||
|
.btn.lg{height:var(--control-h-lg);padding:0 16px}
|
||||||
|
.btn[disabled]{color:var(--text-disabled);background:var(--bg-sunken);border-color:var(--bg-sunken);cursor:not-allowed}
|
||||||
|
.cost{font-size:var(--fs-caption);color:var(--text-secondary)}
|
||||||
|
.cost .fig{font-weight:500;font-size:13px}
|
||||||
|
a.plain{color:var(--text-primary);text-underline-offset:3px}
|
||||||
|
|
||||||
|
/* ---- 问题行 + 页边批注 ---- */
|
||||||
|
.results{background:var(--bg-surface);border:1px solid var(--border-subtle);border-radius:var(--radius-sm)}
|
||||||
|
.results-head{display:flex;flex-wrap:wrap;align-items:baseline;gap:var(--sp-3);padding:var(--sp-5) var(--sp-6);border-bottom:1px solid var(--border-subtle)}
|
||||||
|
.results-head h3{font:var(--fw-bold) var(--fs-h2)/var(--lh-h2) var(--font-serif);margin:0}
|
||||||
|
.results-head .proj{color:var(--text-secondary)}
|
||||||
|
.results-head .counts{margin-left:auto;display:flex;gap:var(--sp-2)}
|
||||||
|
.finding{position:relative;display:grid;grid-template-columns:minmax(0,1fr) var(--margin-note-w);gap:var(--sp-8);padding:var(--sp-5) var(--sp-6);border-top:1px solid var(--border-subtle)}
|
||||||
|
.finding:first-of-type{border-top:0}
|
||||||
|
.finding.is-fatal::before{content:"";position:absolute;left:0;top:var(--sp-5);bottom:var(--sp-5);width:3px;background:var(--sev-fatal)}
|
||||||
|
.f-head{display:flex;align-items:center;gap:var(--sp-2)}
|
||||||
|
.f-title{font:var(--fw-bold) var(--fs-title)/var(--lh-title) var(--font-sans)}
|
||||||
|
.f-head .flag{margin-left:var(--sp-1);border:1px solid var(--border-subtle);border-radius:var(--radius-sm);padding:0 5px;line-height:18px}
|
||||||
|
.f-text{margin:var(--sp-2) 0 0;color:var(--text-primary)}
|
||||||
|
.f-text + .f-text{margin-top:0}
|
||||||
|
.f-actions{display:flex;flex-wrap:wrap;gap:var(--sp-2);align-items:center;margin-top:var(--sp-3)}
|
||||||
|
.f-actions .feedback{margin-left:auto;font-size:var(--fs-caption)}
|
||||||
|
.sign{margin-top:var(--sp-3);font-size:var(--fs-body);color:var(--confirmed);display:flex;gap:var(--sp-2);align-items:baseline;flex-wrap:wrap}
|
||||||
|
.sign b{font-weight:var(--fw-bold)}
|
||||||
|
.sign .fig{font-weight:500}
|
||||||
|
.sign .undo{color:var(--text-secondary);font-size:var(--fs-caption);margin-left:var(--sp-2)}
|
||||||
|
.cite{position:relative;border-left:2px solid var(--ai);padding:2px 0 2px var(--sp-3)}
|
||||||
|
.cite + .cite{margin-top:var(--sp-3)}
|
||||||
|
.finding .cite:first-child::before{content:"";position:absolute;left:calc(-1 * var(--sp-8) - 2px);top:11px;width:calc(var(--sp-8) - 4px);border-top:1.5px dashed var(--ai)}
|
||||||
|
.cite-src{display:block;color:var(--ai);font-size:var(--fs-caption);line-height:var(--lh-caption);text-decoration:none}
|
||||||
|
.cite-src:hover{color:var(--ai-strong);text-decoration:underline;text-underline-offset:3px}
|
||||||
|
.cite-src .fig{font-size:13px;font-weight:600}
|
||||||
|
.cite q{display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:4;overflow:hidden;quotes:none;margin-top:2px;line-height:var(--lh-read);color:var(--text-primary)}
|
||||||
|
.cite.on{border-left-width:4px;padding-left:calc(var(--sp-3) - 2px)}
|
||||||
|
.cite q mark{background:var(--ai-soft);color:inherit;padding:0 1px}
|
||||||
|
.cite.missing{border-left-style:dashed}
|
||||||
|
.cite.missing p{margin:2px 0 0;color:var(--text-secondary);font-size:var(--fs-caption);line-height:var(--lh-caption)}
|
||||||
|
.ocr{font:600 11px/1 var(--font-figures);color:var(--text-secondary);border:1px solid var(--border-subtle);padding:1px 3px;border-radius:2px;margin-left:4px;vertical-align:1px}
|
||||||
|
.folded{padding:var(--sp-3) var(--sp-6);border-top:1px solid var(--border-subtle);background:var(--bg-sunken);color:var(--text-secondary);font-size:var(--fs-body)}
|
||||||
|
|
||||||
|
/* ---- 清单与待确认 ---- */
|
||||||
|
.pending{border:1.5px dashed var(--ai);border-radius:var(--radius-sm);background:var(--bg-surface);padding:var(--sp-4) var(--sp-5)}
|
||||||
|
.pending h4{margin:0;font:var(--fw-bold) var(--fs-title)/var(--lh-title) var(--font-sans);color:var(--ai)}
|
||||||
|
.pending > p{margin:var(--sp-1) 0 var(--sp-3);color:var(--text-secondary)}
|
||||||
|
.item{display:grid;grid-template-columns:24px minmax(0,1fr) auto;gap:var(--sp-3);padding:var(--sp-3) 0;border-top:1px solid var(--border-subtle);align-items:start}
|
||||||
|
.item:first-of-type{border-top:0}
|
||||||
|
.imp{font-size:var(--fs-title);line-height:var(--lh-body);text-align:center}
|
||||||
|
.item .t{font-weight:var(--fw-bold)}
|
||||||
|
.item .meta{display:flex;flex-wrap:wrap;gap:6px;align-items:center;margin-top:4px}
|
||||||
|
.item .side{text-align:right;font-size:var(--fs-caption);line-height:var(--lh-caption)}
|
||||||
|
.src-ai{color:var(--ai)}
|
||||||
|
.old{text-decoration:line-through;color:var(--text-secondary)}
|
||||||
|
.list-head{display:flex;align-items:baseline;gap:var(--sp-3);margin-bottom:var(--sp-3)}
|
||||||
|
.list-head h4{margin:0;font:var(--fw-bold) var(--fs-title)/var(--lh-title) var(--font-sans)}
|
||||||
|
|
||||||
|
/* ---- 参数对照表 ---- */
|
||||||
|
table.data{width:100%;border-collapse:collapse;background:var(--bg-surface);border:1px solid var(--border-subtle)}
|
||||||
|
table.data th{background:var(--bg-sunken);text-align:left;font-weight:var(--fw-bold);font-size:var(--fs-caption);color:var(--text-secondary);padding:var(--sp-2) var(--sp-3);border-bottom:2px solid var(--border-strong);white-space:nowrap}
|
||||||
|
table.data td:first-child{white-space:nowrap}
|
||||||
|
table.data td{padding:var(--sp-2) var(--sp-3);border-bottom:1px solid var(--border-subtle);vertical-align:top;height:40px}
|
||||||
|
table.data tr:last-child td{border-bottom:0}
|
||||||
|
table.data .num{font-family:var(--font-figures);font-variant-numeric:tabular-nums;font-weight:500;font-size:15px;white-space:nowrap}
|
||||||
|
table.data .r{text-align:right}
|
||||||
|
table.data a{color:var(--ai);text-decoration:none;font-size:var(--fs-caption);white-space:nowrap}
|
||||||
|
table.data a .fig{font-size:13px}
|
||||||
|
.verdict{font-weight:var(--fw-bold);white-space:nowrap}
|
||||||
|
.verdict.fatal{color:var(--sev-fatal)}
|
||||||
|
.verdict.major{color:var(--sev-major-text)}
|
||||||
|
.verdict.manual{color:var(--text-secondary);font-weight:var(--fw-regular)}
|
||||||
|
|
||||||
|
/* ---- 原文查看器 ---- */
|
||||||
|
.viewer{background:var(--bg-sunken);border:1px solid var(--border-subtle);border-radius:var(--radius-sm);display:flex;flex-direction:column}
|
||||||
|
.viewer-bar{display:flex;align-items:center;gap:var(--sp-3);padding:var(--sp-2) var(--sp-3);background:var(--bg-surface);border-bottom:1px solid var(--border-subtle);font-size:var(--fs-caption)}
|
||||||
|
.viewer-bar b{font-size:var(--fs-body)}
|
||||||
|
.viewer-bar .pager{margin-left:auto;display:flex;gap:var(--sp-2);align-items:center}
|
||||||
|
.sheet{margin:var(--sp-4) auto;width:86%;background:#fff;box-shadow:0 1px 2px rgba(29,39,51,.12);padding:28px 32px;font:13px/1.9 "Songti SC","SimSun",var(--font-serif);color:#222}
|
||||||
|
.sheet p{margin:0 0 6px}
|
||||||
|
.sheet .hl{background:var(--ai-soft);outline:1px solid var(--ai);outline-offset:1px}
|
||||||
|
.sheet .hl.on{outline-width:2px}
|
||||||
|
.sheet .faint{color:#888}
|
||||||
|
|
||||||
|
/* ---- 工作台 ---- */
|
||||||
|
.proj-name{font-weight:var(--fw-bold);font-size:var(--fs-title);line-height:var(--lh-title)}
|
||||||
|
.proj-sub{font-size:var(--fs-caption);color:var(--text-secondary)}
|
||||||
|
.due-soon{color:var(--warn-text)}
|
||||||
|
.due-today{color:var(--warn-text);font-weight:var(--fw-bold)}
|
||||||
|
.due-past{color:var(--text-secondary)}
|
||||||
|
.ready{display:flex;gap:6px;align-items:center;flex-wrap:wrap}
|
||||||
|
|
||||||
|
/* ---- 额度 ---- */
|
||||||
|
.quota .big{font:600 var(--fs-figure)/var(--lh-figure) var(--font-figures);font-variant-numeric:tabular-nums}
|
||||||
|
.quota .unit{color:var(--text-secondary);margin-left:4px}
|
||||||
|
.bar{height:6px;background:var(--bg-sunken);border-radius:1px;overflow:hidden;margin:var(--sp-2) 0}
|
||||||
|
.bar i{display:block;height:100%;background:var(--c-ink)}
|
||||||
|
.bar.warn i{background:var(--warn)}
|
||||||
|
.flow td.plus{color:var(--confirmed)}
|
||||||
|
|
||||||
|
/* ---- 提醒 ---- */
|
||||||
|
.alert{display:flex;gap:var(--sp-3);padding:var(--sp-3) var(--sp-4);border-left:3px solid var(--c-ink);background:var(--bg-surface);border-radius:0 var(--radius-sm) var(--radius-sm) 0}
|
||||||
|
.alert + .alert{margin-top:var(--sp-3)}
|
||||||
|
.alert.warn{border-left-color:var(--warn);background:var(--warn-soft)}
|
||||||
|
.alert.warn b{color:var(--warn-text)}
|
||||||
|
.alert.done{border-left-color:var(--confirmed)}
|
||||||
|
.alert p{margin:0}
|
||||||
|
.ico{flex:none;width:18px;height:18px;border-radius:50%;display:inline-grid;place-items:center;font:700 12px/1 var(--font-sans);margin-top:2px}
|
||||||
|
.ico.i{border:1.5px solid var(--c-ink)}
|
||||||
|
.ico.w{border:1.5px solid var(--warn);color:var(--warn-text)}
|
||||||
|
.ico.ok{background:var(--confirmed);color:#fff}
|
||||||
|
|
||||||
|
/* ---- 表单 ---- */
|
||||||
|
.field{display:flex;flex-direction:column;gap:6px;max-width:360px}
|
||||||
|
.field + .field{margin-top:var(--sp-4)}
|
||||||
|
.field label{font-weight:var(--fw-bold)}
|
||||||
|
.field label small{font-weight:var(--fw-regular);color:var(--text-secondary)}
|
||||||
|
.input{height:var(--control-h);border:1px solid var(--border-control);border-radius:var(--radius-sm);padding:0 10px;font:var(--fs-body) var(--font-sans);color:var(--text-primary);background:var(--bg-surface)}
|
||||||
|
.input::placeholder{color:var(--text-secondary)}
|
||||||
|
.field.err .input{border-color:var(--warn);box-shadow:inset 0 0 0 1px var(--warn)}
|
||||||
|
.field .msg{font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--text-secondary)}
|
||||||
|
.field.err .msg{color:var(--warn-text);display:flex;gap:6px;align-items:center}
|
||||||
|
.field.err .msg .ico{width:14px;height:14px;font-size:10px;margin:0}
|
||||||
|
.chips{display:flex;gap:6px;flex-wrap:wrap}
|
||||||
|
.chips .btn{height:24px;padding:0 8px;font-size:var(--fs-caption)}
|
||||||
|
|
||||||
|
/* ---- 进度 ---- */
|
||||||
|
.steps{list-style:none;margin:0;padding:0}
|
||||||
|
.steps li{display:flex;gap:var(--sp-3);align-items:center;padding:6px 0}
|
||||||
|
.steps .dot{width:18px;height:18px;border-radius:50%;border:1.5px solid var(--border-control);flex:none;display:grid;place-items:center;font-size:11px}
|
||||||
|
.steps .done .dot{background:var(--c-ink);border-color:var(--c-ink);color:#fff}
|
||||||
|
.steps .now .dot{border-color:var(--c-ink);border-width:2px}
|
||||||
|
.steps .now{font-weight:var(--fw-bold)}
|
||||||
|
.steps .todo{color:var(--text-secondary)}
|
||||||
|
|
||||||
|
/* ---- 报告页眉 ---- */
|
||||||
|
.report{background:#fff;border:1px solid var(--border-subtle);max-width:620px;padding:28px 36px;font-size:13px;line-height:1.6}
|
||||||
|
.report-head{display:flex;justify-content:space-between;align-items:center;border-bottom:1px solid var(--c-ink);padding-bottom:10px}
|
||||||
|
.report-head img{height:26px}
|
||||||
|
.report-head span{font-size:11px;color:var(--text-secondary)}
|
||||||
|
.report h3{font:600 22px/1.4 var(--font-serif);margin:22px 0 4px}
|
||||||
|
.report dl{display:grid;grid-template-columns:96px 1fr;gap:4px 12px;margin:14px 0}
|
||||||
|
.report dt{color:var(--text-secondary)}
|
||||||
|
.report dd{margin:0}
|
||||||
|
.scope{border:1px solid var(--border-subtle);padding:10px 14px;margin-top:10px}
|
||||||
|
.scope b{display:block;margin-bottom:2px}
|
||||||
|
.report-foot{margin-top:18px;padding-top:8px;border-top:1px solid var(--border-subtle);display:flex;justify-content:space-between;font-size:11px;color:var(--text-secondary)}
|
||||||
|
|
||||||
|
|
||||||
|
@media (max-width:1100px){
|
||||||
|
.finding{grid-template-columns:1fr;gap:var(--sp-3)}
|
||||||
|
.finding .cite:first-child::before{display:none}
|
||||||
|
}
|
||||||
296
docs/design/system/components.html
Normal file
@@ -0,0 +1,296 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<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="../logo/favicon.svg">
|
||||||
|
<link rel="stylesheet" href="tokens.css">
|
||||||
|
<link rel="stylesheet" href="fonts-local.css">
|
||||||
|
<link rel="stylesheet" href="components.css">
|
||||||
|
<style>
|
||||||
|
/* 只属于预览页的排版 */
|
||||||
|
.page{max-width:1240px;margin:0 auto;padding:var(--sp-12) var(--sp-6) var(--sp-16)}
|
||||||
|
h1{font:var(--fw-bold) 32px/40px var(--font-serif);margin:0 0 var(--sp-2)}
|
||||||
|
.lede{color:var(--text-secondary);margin:0;max-width:52em}
|
||||||
|
section{margin-top:var(--sp-12)}
|
||||||
|
.sec-h{display:flex;align-items:baseline;gap:var(--sp-3);border-bottom:2px solid var(--border-strong);padding-bottom:var(--sp-2);margin-bottom:var(--sp-5)}
|
||||||
|
.sec-h h2{font:var(--fw-bold) var(--fs-h2)/var(--lh-h2) var(--font-serif);margin:0}
|
||||||
|
.sec-h .no{font:600 var(--fs-title)/1 var(--font-figures);color:var(--text-secondary)}
|
||||||
|
.sec-h .ref{margin-left:auto;font-size:var(--fs-caption);color:var(--text-secondary)}
|
||||||
|
/* ---- 色板 ---- */
|
||||||
|
.swatches{display:grid;grid-template-columns:repeat(4,1fr);gap:var(--sp-3)}
|
||||||
|
.sw{background:var(--bg-surface);border:1px solid var(--border-subtle);border-radius:var(--radius-sm);overflow:hidden;font-size:var(--fs-caption);line-height:var(--lh-caption)}
|
||||||
|
.sw i{display:block;height:56px;border-bottom:1px solid var(--border-subtle)}
|
||||||
|
.sw div{padding:var(--sp-2) var(--sp-3)}
|
||||||
|
.sw b{display:block;font-size:var(--fs-body)}
|
||||||
|
.sw .fig{font-weight:500;color:var(--text-secondary)}
|
||||||
|
|
||||||
|
/* ---- 字阶 ---- */
|
||||||
|
.type td{padding:var(--sp-3) var(--sp-4) var(--sp-3) 0;border-bottom:1px solid var(--border-subtle);vertical-align:baseline}
|
||||||
|
.type td:first-child{width:120px;color:var(--text-secondary);font-size:var(--fs-caption)}
|
||||||
|
.type table{border-collapse:collapse;width:100%}
|
||||||
|
|
||||||
|
@media (max-width:1100px){.grid2{grid-template-columns:1fr}}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main class="page">
|
||||||
|
<h1>标博士 · 组件预览</h1>
|
||||||
|
<p class="lede">配合 <code>design-system.md</code> v0.1 阅读。条款原文取自苏采云样本 S02、S10;为了演示,S10 的分包限价和 S02 的设备参数放在了同一个示意项目里。页码、投标响应值、人名都是示意数据。</p>
|
||||||
|
|
||||||
|
<!-- 01 颜色 -->
|
||||||
|
<section>
|
||||||
|
<div class="sec-h"><span class="no">01</span><h2>颜色</h2><span class="ref">规范 §3</span></div>
|
||||||
|
<div class="swatches">
|
||||||
|
<div class="sw"><i style="background:var(--c-paper)"></i><div><b>校样纸</b><span class="fig">#F5F6F3</span><br>页面底色</div></div>
|
||||||
|
<div class="sw"><i style="background:var(--c-ink)"></i><div><b>墨</b><span class="fig">#1D2733</span><br>正文、人工确认</div></div>
|
||||||
|
<div class="sw"><i style="background:var(--c-sub)"></i><div><b>辅助灰</b><span class="fig">#5C6670</span><br>次要文字、提示级</div></div>
|
||||||
|
<div class="sw"><i style="background:var(--c-pencil)"></i><div><b>蓝铅笔</b><span class="fig">#2F5BD3</span><br>AI 标注、出处</div></div>
|
||||||
|
<div class="sw"><i style="background:var(--c-fatal)"></i><div><b>致命红</b><span class="fig">#C4122F</span><br>只用于致命问题</div></div>
|
||||||
|
<div class="sw"><i style="background:var(--c-ochre)"></i><div><b>赭</b><span class="fig">#A66300 / #8A5200</span><br>重要级、提醒、错误</div></div>
|
||||||
|
<div class="sw"><i style="background:var(--c-green)"></i><div><b>定稿绿</b><span class="fig">#2E6B4F</span><br>确认署名</div></div>
|
||||||
|
<div class="sw"><i style="background:var(--c-muted)"></i><div><b>禁用灰</b><span class="fig">#7A838C</span><br>禁用、控件边框</div></div>
|
||||||
|
</div>
|
||||||
|
<p class="note">印章红 #D9291C、藏青 #1C2554 只属于 logo,不出现在界面变量里。</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- 02 字体 -->
|
||||||
|
<section class="type">
|
||||||
|
<div class="sec-h"><span class="no">02</span><h2>字体与字阶</h2><span class="ref">规范 §4</span></div>
|
||||||
|
<table>
|
||||||
|
<tr><td>H1 · 24/32 宋体</td><td style="font:600 24px/32px var(--font-serif)">智慧规划资源 2025 标段三</td></tr>
|
||||||
|
<tr><td>H2 · 20/28 宋体</td><td style="font:600 20px/28px var(--font-serif)">采购包 2 的检查结果</td></tr>
|
||||||
|
<tr><td>Title · 16/24</td><td style="font:600 16px/24px var(--font-sans)">报价超过采购包 2 的最高限价</td></tr>
|
||||||
|
<tr><td>Body · 14/22</td><td>开标一览表报价 133.60 万元,最高限价 132.00 万元,超出 1.60 万元。</td></tr>
|
||||||
|
<tr><td>Read · 14/24</td><td style="line-height:24px">★ 支持硬件BFD,最小报文发送间隔≤50ms,提供相关证明材料(包含但不限于产品官网公开参数截图证明等)。</td></tr>
|
||||||
|
<tr><td>Caption · 12/20</td><td style="font-size:12px;line-height:20px;color:var(--text-secondary)">王敏 11月2日 14:20 · 第 3 次检查</td></tr>
|
||||||
|
<tr><td>Figure · 30/36</td><td><span class="fig" style="font-size:30px;line-height:36px">132.00</span> <span class="fig" style="font-size:30px">第 41 页</span> <span class="fig" style="font-size:30px">≥ 512G</span></td></tr>
|
||||||
|
</table>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- 03 标签与按钮 -->
|
||||||
|
<section>
|
||||||
|
<div class="sec-h"><span class="no">03</span><h2>级别标签与按钮</h2><span class="ref">规范 §7.1 · §7.2</span></div>
|
||||||
|
<div class="grid2">
|
||||||
|
<div class="card">
|
||||||
|
<div class="row"><span class="sev fatal">致命</span><span class="sev major">重要</span><span class="sev hint">提示</span></div>
|
||||||
|
<div class="row" style="margin-top:var(--sp-3)"><span class="sev fatal">致命<span class="fig">2</span></span><span class="sev major">重要<span class="fig">5</span></span><span class="sev hint">提示<span class="fig">11</span></span></div>
|
||||||
|
<div class="row" style="margin-top:var(--sp-3)"><span class="tag status">编制中</span><span class="tag status">已检查</span><span class="tag ai">AI 摘要</span><span class="tag">资格条件</span><span class="tag">无效条款</span></div>
|
||||||
|
<p class="note">级别靠「实心 / 描边 / 无框」区分,色弱和黑白打印也分得清。</p>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<div class="row"><button class="btn primary">发起检查</button><span class="cost">扣 1 次检查 · 剩 <span class="fig">12</span> 次</span></div>
|
||||||
|
<div class="row" style="margin-top:var(--sp-3)"><button class="btn">导出报告</button><button class="btn text">查看处理记录</button><button class="btn" disabled>正在导出…</button></div>
|
||||||
|
<div class="row" style="margin-top:var(--sp-3)"><button class="btn sm">确认无误</button><button class="btn lg primary">上传招标文件</button><a class="plain" href="#">普通链接用墨色下划线</a></div>
|
||||||
|
<p class="note">没有红色危险按钮;删除靠确认对话框,不靠颜色。</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- 04 问题列表 -->
|
||||||
|
<section>
|
||||||
|
<div class="sec-h"><span class="no">04</span><h2>问题行与出处批注</h2><span class="ref">规范 §7.3 · §7.4</span></div>
|
||||||
|
<div class="results">
|
||||||
|
<div class="results-head">
|
||||||
|
<h3>采购包 2 的检查结果</h3><span class="proj">智慧规划资源 2025 标段三 · 第 3 次检查</span>
|
||||||
|
<div class="counts"><span class="sev fatal">致命<span class="fig">2</span></span><span class="sev major">重要<span class="fig">1</span></span><span class="sev hint">提示<span class="fig">4</span></span></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<article class="finding is-fatal">
|
||||||
|
<div>
|
||||||
|
<div class="f-head"><span class="sev fatal">致命</span><span class="f-title">报价超过采购包 2 的最高限价</span><span class="flag">新增</span></div>
|
||||||
|
<p class="f-text">开标一览表报价 133.60 万元,最高限价 132.00 万元,超出 1.60 万元。</p>
|
||||||
|
<p class="f-text">这一项会导致投标无效。请核对开标一览表的报价。</p>
|
||||||
|
<div class="f-actions"><button class="btn">确认无误</button><button class="btn">已修改</button><button class="btn">忽略并注明原因</button><a class="plain feedback" href="#">判断有误?</a></div>
|
||||||
|
</div>
|
||||||
|
<aside>
|
||||||
|
<div class="cite"><a class="cite-src" href="#">招标文件 · <span class="fig">第 2 页</span></a><q><mark>最高限价:184万元(采购包1:14万元,采购包2:132万元,采购包3:38万元)</mark></q></div>
|
||||||
|
<div class="cite"><a class="cite-src" href="#">报价册 · 开标一览表 · <span class="fig">第 1 页</span></a><q><mark>投标总价(小写):1336000.00 元</mark></q></div>
|
||||||
|
</aside>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="finding is-fatal">
|
||||||
|
<div>
|
||||||
|
<div class="f-head"><span class="sev fatal">致命</span><span class="f-title">★ 条款疑似不满足:服务器内存</span><span class="flag">沿用</span></div>
|
||||||
|
<p class="f-text">技术偏离表第 12 行响应「内存 256G」,要求「≥512G」。</p>
|
||||||
|
<div class="sign"><span>✓</span><b>王敏</b><span class="fig">11月2日 14:20</span><span>已修改</span><a class="plain undo" href="#">撤销</a></div>
|
||||||
|
</div>
|
||||||
|
<aside>
|
||||||
|
<div class="cite"><a class="cite-src" href="#">招标文件 · <span class="fig">第 41 页</span></a><q>★ 2U机架式服务器,CPU:配置≥2颗海光架构32C 2.7GHz,<mark>内存: ≥512G DDR4</mark>,系统盘: 2块≥480G SSD…</q></div>
|
||||||
|
</aside>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="finding">
|
||||||
|
<div>
|
||||||
|
<div class="f-head"><span class="sev major">重要</span><span class="f-title">缺少 ★ 参数的证明材料:硬件 BFD</span></div>
|
||||||
|
<p class="f-text">已响应「支持硬件 BFD」,但没有找到官网参数截图等证明材料。请在技术册补充证明材料。</p>
|
||||||
|
<div class="f-actions"><button class="btn">确认无误</button><button class="btn">已修改</button><button class="btn">忽略并注明原因</button><a class="plain feedback" href="#">判断有误?</a></div>
|
||||||
|
</div>
|
||||||
|
<aside>
|
||||||
|
<div class="cite"><a class="cite-src" href="#">招标文件 · <span class="fig">第 44 页</span></a><q>★ 支持硬件BFD,最小报文发送间隔≤50ms,<mark>提供相关证明材料(包含但不限于产品官网公开参数截图证明等)</mark>。</q></div>
|
||||||
|
<div class="cite missing"><span class="cite-src">投标文件 · 未找到证明材料</span><p>已查找:技术册(全部章节)、资格证明册</p></div>
|
||||||
|
</aside>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="finding">
|
||||||
|
<div>
|
||||||
|
<div class="f-head"><span class="sev hint">提示</span><span class="f-title">投标函落款日期晚于投标截止日</span></div>
|
||||||
|
<p class="f-text">投标函落款「2026年11月25日」,投标截止时间是 2026年11月20日 09:30。</p>
|
||||||
|
<div class="f-actions"><button class="btn sm">确认无误</button><button class="btn sm">已修改</button><button class="btn sm">忽略并注明原因</button><a class="plain feedback" href="#">判断有误?</a></div>
|
||||||
|
</div>
|
||||||
|
<aside>
|
||||||
|
<div class="cite"><a class="cite-src" href="#">商务册 · 投标函 · <span class="fig">第 3 页</span><span class="ocr">OCR</span></a><q><mark>2026年11月25日</mark></q></div>
|
||||||
|
</aside>
|
||||||
|
</article>
|
||||||
|
<div class="folded">已处理(<span class="fig">9</span>)· 本次已解决(<span class="fig">3</span>)</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- 05 清单 -->
|
||||||
|
<section>
|
||||||
|
<div class="sec-h"><span class="no">05</span><h2>清单项与待确认区</h2><span class="ref">规范 §7.5 · §7.6</span></div>
|
||||||
|
<div class="grid2">
|
||||||
|
<div class="pending">
|
||||||
|
<h4>待确认(<span class="fig">2</span>)</h4>
|
||||||
|
<p>这些结果没有找到可靠出处,或系统把握不大。确认之前,不参与检查。</p>
|
||||||
|
<div class="item">
|
||||||
|
<span class="imp"></span>
|
||||||
|
<div><div class="t">投标保证金:不收取</div><div class="meta"><span class="src-ai" style="font-size:12px">AI 提取 · 未找到出处</span></div>
|
||||||
|
<div class="f-actions"><button class="btn sm">补上出处后采纳</button><button class="btn sm">修改后采纳</button><button class="btn sm text">丢弃</button></div></div>
|
||||||
|
<span></span>
|
||||||
|
</div>
|
||||||
|
<div class="item">
|
||||||
|
<span class="imp">★</span>
|
||||||
|
<div><div class="t">核心产品需提供原厂授权</div><div class="meta"><span class="src-ai" style="font-size:12px">AI 提取 · 把握不大</span><a class="cite-src" href="#" style="display:inline">第 <span class="fig">38</span> 页</a></div>
|
||||||
|
<div class="f-actions"><button class="btn sm">采纳</button><button class="btn sm">修改后采纳</button><button class="btn sm text">丢弃</button></div></div>
|
||||||
|
<span></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<div class="list-head"><h4>无效条款(<span class="fig">14</span>)</h4><span class="tag status">草稿</span></div>
|
||||||
|
<div class="item">
|
||||||
|
<span class="imp">★</span>
|
||||||
|
<div><div class="t">设备采用国产自主可控 CPU 芯片,提供国产化适配承诺函</div><div class="meta"><span class="tag">无效条款</span><span class="tag">技术参数</span><span class="tag">采购包 2</span></div></div>
|
||||||
|
<div class="side"><span class="src-ai">AI 提取</span><br><a class="cite-src" href="#">第 <span class="fig">43</span> 页</a></div>
|
||||||
|
</div>
|
||||||
|
<div class="item">
|
||||||
|
<span class="imp"></span>
|
||||||
|
<div><div class="t">提交投标文件截止时间:<span class="old">2026年11月20日 09:30</span> 2026年11月27日 09:30</div><div class="meta"><span class="tag">关键日期</span><span class="flag">已被补遗 1 更新 · 待确认</span></div></div>
|
||||||
|
<div class="side">补遗 1<br><a class="cite-src" href="#">第 <span class="fig">1</span> 页</a></div>
|
||||||
|
</div>
|
||||||
|
<div class="item">
|
||||||
|
<span class="imp"></span>
|
||||||
|
<div><div class="t">联合体各方须签订《联合体协议》并明确牵头单位</div><div class="meta"><span class="tag">资格条件</span><span class="flag">已修改 · <a class="plain" href="#">查看记录</a></span></div></div>
|
||||||
|
<div class="side">人工添加</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- 06 参数表 + 原文 -->
|
||||||
|
<section>
|
||||||
|
<div class="sec-h"><span class="no">06</span><h2>参数对照表与原文查看器</h2><span class="ref">规范 §7.7 · §7.8</span></div>
|
||||||
|
<div class="grid2" style="grid-template-columns:minmax(0,1.2fr) minmax(0,1fr)">
|
||||||
|
<div>
|
||||||
|
<table class="data">
|
||||||
|
<thead><tr><th>参数项</th><th>要求</th><th>投标响应</th><th>判断</th><th>出处</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td>★ 内存</td><td class="num">≥ 512G DDR4</td><td class="num">256G DDR4</td><td><span class="verdict fatal">疑似不满足</span></td><td><a href="#">第 <span class="fig">41</span> 页 ↔ 技术册 <span class="fig">12</span></a></td></tr>
|
||||||
|
<tr><td>★ CPU</td><td class="num">≥ 2 颗 32C 2.7GHz</td><td class="num">2 颗 32C 2.7GHz</td><td><span class="verdict">满足</span></td><td><a href="#">第 <span class="fig">41</span> 页 ↔ 技术册 <span class="fig">12</span></a></td></tr>
|
||||||
|
<tr><td>▲ 存储盘</td><td class="num">≥ 4 块 3.84T SSD</td><td class="num">—</td><td><span class="verdict major">未找到响应</span></td><td><a href="#">第 <span class="fig">41</span> 页</a></td></tr>
|
||||||
|
<tr><td>万兆光口</td><td class="num">≥ 4 个 10GbE</td><td class="num">4 个 10GbE(不含光模块)</td><td><span class="verdict manual">需人工判断</span></td><td><a href="#">第 <span class="fig">41</span> 页 ↔ 技术册 <span class="fig">13</span></a></td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<p class="note">判断只用四个词:满足 / 疑似不满足 / 未找到响应 / 需人工判断。「满足」用墨色,不用绿色。</p>
|
||||||
|
</div>
|
||||||
|
<div class="viewer">
|
||||||
|
<div class="viewer-bar"><b>招标文件</b><span>JSZC-320300-XZCG-G2026-0053</span><div class="pager"><button class="btn sm">上一处</button><span class="fig">第 41 / 186 页</span><button class="btn sm">下一处</button></div></div>
|
||||||
|
<div class="sheet">
|
||||||
|
<p class="faint">采购包 2:服务器设备</p>
|
||||||
|
<p class="hl on">★ 2U机架式服务器,CPU:配置≥2颗海光架构32C 2.7GHz,内存: ≥512G DDR4,系统盘: 2块≥480G SSD,存储盘: ≥2块8T SATA、≥4块3.84T SSD……</p>
|
||||||
|
<p class="faint">阵列卡:1块8204 2G,网络:配置≥4个1GbE 接口,≥4个10GbE 光接口(含光模块)……</p>
|
||||||
|
<p>备注:加★为核心技术参数;加▲为重要技术参数;其他为通用技术参数。</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- 07 工作台 -->
|
||||||
|
<section>
|
||||||
|
<div class="sec-h"><span class="no">07</span><h2>项目工作台</h2><span class="ref">规范 §7.9</span></div>
|
||||||
|
<table class="data">
|
||||||
|
<thead><tr><th>项目</th><th>所投包</th><th>截止</th><th>状态</th><th>就绪</th><th class="r">致命</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td><div class="proj-name">智慧规划资源 2025 标段三</div><div class="proj-sub">南京市规划和自然资源局</div></td><td>采购包 2</td><td class="due-today">今天 17:00 截止</td><td><span class="tag status">已检查</span></td><td>未就绪</td><td class="r"><span class="sev fatal">致命<span class="fig">2</span></span></td></tr>
|
||||||
|
<tr><td><div class="proj-name">2026 年信息化设备采购项目</div><div class="proj-sub">国家税务总局徐州市税务局</div></td><td>全部</td><td class="due-soon">剩 2 天 5 小时</td><td><span class="tag status">已检查</span></td><td><span class="tag status" title="只表示致命问题都已处理,不代表投标文件没有问题">就绪</span></td><td class="r num">—</td></tr>
|
||||||
|
<tr><td><div class="proj-name">数据中心运维服务项目</div><div class="proj-sub">某市大数据管理局</div></td><td>标段 1</td><td><span class="fig">11月20日 09:30</span> · 剩 <span class="fig">17</span> 天</td><td><span class="tag status">编制中</span></td><td>—</td><td class="r num">—</td></tr>
|
||||||
|
<tr><td><div class="proj-name">智慧校园一期</div><div class="proj-sub">某区教育局</div></td><td>全部</td><td class="due-past">已截止</td><td><span class="tag status">已递交</span></td><td>—</td><td class="r num">—</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<p class="note">截止临近用赭色,不用红;「就绪」悬停说明:只表示致命问题都已处理,不代表投标文件没有问题。</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- 08 额度、提醒、表单、进度 -->
|
||||||
|
<section>
|
||||||
|
<div class="sec-h"><span class="no">08</span><h2>额度、提醒、表单、进度</h2><span class="ref">规范 §7.10–7.13</span></div>
|
||||||
|
<div class="grid2">
|
||||||
|
<div>
|
||||||
|
<div class="card quota">
|
||||||
|
<div class="row" style="gap:var(--sp-8)">
|
||||||
|
<div><div class="proj-sub">解析剩余</div><span class="big">37</span><span class="unit">/ 50 次</span><div class="bar"><i style="width:26%"></i></div></div>
|
||||||
|
<div><div class="proj-sub">检查剩余</div><span class="big">4</span><span class="unit">/ 20 次</span><div class="bar warn"><i style="width:80%"></i></div></div>
|
||||||
|
</div>
|
||||||
|
<table class="data flow" style="margin-top:var(--sp-4)">
|
||||||
|
<thead><tr><th>时间</th><th>类型</th><th class="r">数量</th><th>关联</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td class="num">11-02 14:31</td><td>退回</td><td class="num r plus">+1</td><td>解析失败:文件已加密</td></tr>
|
||||||
|
<tr><td class="num">11-02 14:28</td><td>预扣</td><td class="num r">−1</td><td>解析 · 补遗 1</td></tr>
|
||||||
|
<tr><td class="num">11-02 10:05</td><td>结算</td><td class="num r">−1</td><td>检查 · 智慧规划资源 2025 标段三</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="card" style="margin-top:var(--sp-4)">
|
||||||
|
<div class="field"><label>手机号 <small>(必填)</small></label><input class="input" value="1381452198"><span class="msg"><span class="ico w">!</span>手机号应为 11 位数字</span></div>
|
||||||
|
<div class="field"><label>忽略原因 <small>(必填,至少 5 个字)</small></label><input class="input" placeholder="说明为什么不处理这一项"><div class="chips"><button class="btn">招标文件允许</button><button class="btn">已在其他分册响应</button><button class="btn">与本项目无关</button></div></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="alert"><span class="ico i">i</span><p>清单未确认,检查结果以当前清单为准。</p></div>
|
||||||
|
<div class="alert warn"><span class="ico w">!</span><p><b>本期检查额度已用 80%。</b>剩 4 次,11月30日 发放下期额度。</p></div>
|
||||||
|
<div class="alert warn"><span class="ico w">!</span><p><b>解析失败:文件已加密。</b>已退回 1 次解析额度。请上传未加密的文件。</p></div>
|
||||||
|
<div class="alert done"><span class="ico ok">✓</span><p>报告已导出。</p></div>
|
||||||
|
<div class="card" style="margin-top:var(--sp-4)">
|
||||||
|
<ol class="steps">
|
||||||
|
<li class="done"><span class="dot">✓</span>识别文件格式</li>
|
||||||
|
<li class="done"><span class="dot">✓</span>提取文字</li>
|
||||||
|
<li class="now"><span class="dot"></span>识别分包(3 个采购包)</li>
|
||||||
|
<li class="todo"><span class="dot"></span>提取清单</li>
|
||||||
|
<li class="todo"><span class="dot"></span>核对出处</li>
|
||||||
|
</ol>
|
||||||
|
<p class="note">可以离开本页,完成后会发站内通知。</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- 09 报告 -->
|
||||||
|
<section>
|
||||||
|
<div class="sec-h"><span class="no">09</span><h2>检查报告首页</h2><span class="ref">规范 §7.17</span></div>
|
||||||
|
<div class="report">
|
||||||
|
<div class="report-head"><img src="../logo/svg/logo-horizontal.svg" alt="标博士"><span>投标文件辅助检查报告</span></div>
|
||||||
|
<h3>智慧规划资源 2025 标段三 · 采购包 2</h3>
|
||||||
|
<dl>
|
||||||
|
<dt>项目编号</dt><dd>JSZC-320100-JZCG-G2026-0369</dd>
|
||||||
|
<dt>投标文件</dt><dd>商务册 v3、技术册 v3、报价册 v2、资格证明册 v1</dd>
|
||||||
|
<dt>检查时间</dt><dd>2026-11-02 14:20 · 规则版本 2026.11</dd>
|
||||||
|
<dt>处理情况</dt><dd><span class="sev fatal">致命<span class="fig">2</span></span> <span class="sev major">重要<span class="fig">1</span></span> <span class="sev hint">提示<span class="fig">4</span></span> 其中待处理 1 项</dd>
|
||||||
|
</dl>
|
||||||
|
<div class="scope"><b>检查覆盖范围</b>查了:清单 86 条的响应完整性、形式要件(格式文件、签章位置有无、落款日期)、资质匹配、报价形式。<br>没查:份数、装订密封、CA 加密(见附录「需人工核对」);签章真伪;报价是否合理。</div>
|
||||||
|
<div class="report-foot"><span>本报告为辅助检查结果,不构成对投标结果的保证</span><span class="fig">1 / 12</span></div>
|
||||||
|
</div>
|
||||||
|
<p class="note">logo 只在页眉,不靠近任何结论或汇总数字。</p>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
5
docs/design/system/fonts-local.css
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
/* 本地预览用字体:从 logo/.work/fonts 加载(不进 git,搭建方法见 logo/logo-spec.md 第 6 节)。
|
||||||
|
* 没有该目录时自动退回系统字体。正式前端改用 @fontsource 自托管(design-system.md §4.1)。 */
|
||||||
|
@font-face{font-family:"Noto Serif SC";src:url("../logo/.work/fonts/NotoSerifSC%5Bwght%5D.ttf") format("truetype");font-weight:200 900}
|
||||||
|
@font-face{font-family:"Barlow Semi Condensed";src:url("../logo/.work/fonts/BarlowSemiCondensed-Medium.ttf") format("truetype");font-weight:500}
|
||||||
|
@font-face{font-family:"Barlow Semi Condensed";src:url("../logo/.work/fonts/BarlowSemiCondensed-SemiBold.ttf") format("truetype");font-weight:600}
|
||||||
105
docs/design/system/tokens.css
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
/* 标博士 · 界面设计变量(design tokens)v0.1
|
||||||
|
* 规范说明见 docs/design/design-system.md;预览见同目录 components.html。
|
||||||
|
* 只放变量,不放组件样式。技术架构选定组件库后,把这些值映射到组件库的主题配置。
|
||||||
|
* 对比度一律按 WCAG 2.x 公式计算,底色为校样纸 #F5F6F3(括号内为白底)。
|
||||||
|
*/
|
||||||
|
:root {
|
||||||
|
/* ---------- 基础色板:校样色系 ---------- */
|
||||||
|
--c-paper: #F5F6F3; /* 校样纸:页面底色 */
|
||||||
|
--c-white: #FFFFFF; /* 卡片、表格、输入框 */
|
||||||
|
--c-sunken: #ECEEEA; /* 表头、代码、凹陷区 */
|
||||||
|
--c-hover: #EEF0EC; /* 行悬停 */
|
||||||
|
--c-ink: #1D2733; /* 墨:正文、人工确认、主按钮 13.9 (15.1) */
|
||||||
|
--c-sub: #5C6670; /* 辅助灰:次要文字、「提示」级 5.4 (5.9) */
|
||||||
|
--c-muted: #7A838C; /* 只用于禁用文字与控件边框 3.6 (3.9),不能用于正文 */
|
||||||
|
--c-rule: #D9DDD8; /* 装饰分隔线(不承担识别功能) */
|
||||||
|
--c-pencil: #2F5BD3; /* 蓝铅笔:AI 标注、出处、原文链接 5.4 (5.9) */
|
||||||
|
--c-pencil-strong: #244AB0;/* 蓝铅笔悬停 / 按下 */
|
||||||
|
--c-pencil-soft: #E7EDFB; /* 原句高亮底色 */
|
||||||
|
--c-fatal: #C4122F; /* 致命红:只用于「致命」问题 5.6 (6.0) */
|
||||||
|
--c-fatal-soft: #FBE9EC;
|
||||||
|
--c-ochre: #A66300; /* 赭:「重要」级的边框、图标、≥18px 文字 4.4 (4.8) */
|
||||||
|
--c-ochre-ink: #8A5200; /* 赭色小字(<18px) 5.9 (6.4) */
|
||||||
|
--c-ochre-soft: #FBF0DC;
|
||||||
|
--c-green: #2E6B4F; /* 定稿绿:人工确认署名 5.8 (6.3) */
|
||||||
|
--c-green-soft: #E5F0EA;
|
||||||
|
/* 印章红 #D9291C、藏青 #1C2554 只属于 logo,不进界面变量 */
|
||||||
|
|
||||||
|
/* ---------- 语义色 ---------- */
|
||||||
|
--bg-page: var(--c-paper);
|
||||||
|
--bg-surface: var(--c-white);
|
||||||
|
--bg-sunken: var(--c-sunken);
|
||||||
|
--bg-hover: var(--c-hover);
|
||||||
|
--text-primary: var(--c-ink);
|
||||||
|
--text-secondary: var(--c-sub);
|
||||||
|
--text-disabled: var(--c-muted);
|
||||||
|
--text-on-dark: var(--c-white);
|
||||||
|
--border-subtle: var(--c-rule);
|
||||||
|
--border-control: var(--c-muted); /* 输入框等控件边框,≥3:1(WCAG 1.4.11) */
|
||||||
|
--border-strong: var(--c-ink);
|
||||||
|
--focus-ring: var(--c-ink);
|
||||||
|
|
||||||
|
--ai: var(--c-pencil);
|
||||||
|
--ai-strong: var(--c-pencil-strong);
|
||||||
|
--ai-soft: var(--c-pencil-soft);
|
||||||
|
--human: var(--c-ink);
|
||||||
|
--confirmed: var(--c-green);
|
||||||
|
--confirmed-soft: var(--c-green-soft);
|
||||||
|
|
||||||
|
--sev-fatal: var(--c-fatal);
|
||||||
|
--sev-fatal-soft: var(--c-fatal-soft);
|
||||||
|
--sev-major: var(--c-ochre);
|
||||||
|
--sev-major-text: var(--c-ochre-ink);
|
||||||
|
--sev-major-soft: var(--c-ochre-soft);
|
||||||
|
--sev-hint: var(--c-sub);
|
||||||
|
--sev-hint-soft: var(--c-sunken);
|
||||||
|
|
||||||
|
--warn: var(--c-ochre); /* 系统提醒、表单错误、额度预警:不用红 */
|
||||||
|
--warn-text: var(--c-ochre-ink);
|
||||||
|
--warn-soft: var(--c-ochre-soft);
|
||||||
|
|
||||||
|
/* ---------- 字体 ---------- */
|
||||||
|
--font-sans: "PingFang SC", "Microsoft YaHei", "Noto Sans SC", "Noto Sans CJK SC", "Source Han Sans SC", system-ui, sans-serif;
|
||||||
|
--font-serif: "Noto Serif SC", "Source Han Serif SC", "Songti SC", serif;
|
||||||
|
--font-figures: "Barlow Semi Condensed", var(--font-sans);
|
||||||
|
|
||||||
|
/* 字阶:字号 / 行高(px)。正文 14/22 与 TDesign、Ant Design 一致 */
|
||||||
|
--fs-caption: 12px; --lh-caption: 20px; /* 时间戳、脚注;最小字号 */
|
||||||
|
--fs-body: 14px; --lh-body: 22px; /* 界面正文 */
|
||||||
|
--lh-read: 24px; /* 原文引用等成段阅读:14/24 */
|
||||||
|
--fs-title: 16px; --lh-title: 24px; /* 问题标题、卡片标题、区块标题 */
|
||||||
|
--fs-h2: 20px; --lh-h2: 28px; /* 页面标题(宋体) */
|
||||||
|
--fs-h1: 24px; --lh-h1: 32px; /* 工作台、报告标题(宋体) */
|
||||||
|
--fs-figure: 30px; --lh-figure: 36px; /* 统计数字(Barlow) */
|
||||||
|
--fw-regular: 400;
|
||||||
|
--fw-medium: 500;
|
||||||
|
--fw-bold: 600;
|
||||||
|
|
||||||
|
/* ---------- 间距:4px 基准 ---------- */
|
||||||
|
--sp-1: 4px; --sp-2: 8px; --sp-3: 12px; --sp-4: 16px; --sp-5: 20px;
|
||||||
|
--sp-6: 24px; --sp-8: 32px; --sp-10: 40px; --sp-12: 48px; --sp-16: 64px;
|
||||||
|
|
||||||
|
/* ---------- 圆角、线、阴影 ---------- */
|
||||||
|
--radius-sm: 2px; /* 按钮、标签、输入框、卡片 */
|
||||||
|
--radius-md: 4px; /* 对话框、浮层 */
|
||||||
|
--line: 1px;
|
||||||
|
--line-sev: 1.5px; /* 「重要」标签描边 */
|
||||||
|
--shadow-overlay: 0 4px 16px rgba(29, 39, 51, .14), 0 1px 3px rgba(29, 39, 51, .10);
|
||||||
|
|
||||||
|
/* ---------- 尺寸 ---------- */
|
||||||
|
--control-h: 32px; /* 默认按钮、输入框 */
|
||||||
|
--control-h-sm: 28px; /* 表格行内 */
|
||||||
|
--control-h-lg: 40px; /* 登录、空状态主操作 */
|
||||||
|
--sidebar-w: 216px;
|
||||||
|
--margin-note-w: 280px; /* 页边批注栏 */
|
||||||
|
--content-max: 1200px;
|
||||||
|
|
||||||
|
/* ---------- 动效 ---------- */
|
||||||
|
--dur-fast: 120ms;
|
||||||
|
--dur-base: 200ms;
|
||||||
|
--ease: cubic-bezier(.2, 0, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
:root { --dur-fast: 0ms; --dur-base: 0ms; }
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
1. 准备 logo 的工具环境(见 `../logo/logo-spec.md` 第 6 节):`../logo/.work/venv` 和 `../logo/.work/fonts`。手册引用的字体(思源宋体、思源黑体、Libre Caslon、Barlow Semi Condensed)也从 `.work/fonts` 加载。
|
1. 准备 logo 的工具环境(见 `../logo/logo-spec.md` 第 6 节):`../logo/.work/venv` 和 `../logo/.work/fonts`。手册引用的字体(思源宋体、思源黑体、Libre Caslon、Barlow Semi Condensed)也从 `.work/fonts` 加载。
|
||||||
2. 重新生成标注图:在 `docs/design/logo/` 下运行 `.work/venv/bin/python scripts/vi_diagrams.py`
|
2. 重新生成标注图:在 `docs/design/logo/` 下运行 `.work/venv/bin/python scripts/vi_diagrams.py`
|
||||||
|
第 22 页的 iPhone 桌面模拟图(`mockup/apple/iphone-home.png`)用到 Apple 的素材,不进 git,生成方法见 `mockup/README.md`。
|
||||||
3. 在 `docs/design/` 下启动本地服务:`python3 -m http.server 8765 --bind 127.0.0.1`(直接打开 file:// 地址时,字体可能因浏览器安全限制加载失败)
|
3. 在 `docs/design/` 下启动本地服务:`python3 -m http.server 8765 --bind 127.0.0.1`(直接打开 file:// 地址时,字体可能因浏览器安全限制加载失败)
|
||||||
4. 导出 PDF:
|
4. 导出 PDF:
|
||||||
|
|
||||||
|
|||||||
20
docs/design/vi/mockup/README.md
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# iPhone 桌面模拟
|
||||||
|
|
||||||
|
VI 手册第 22 页「应用图标」用的 iPhone 主屏幕效果图:iPhone 18 Pro 黑色机身、iPhone 18 Pro 官方壁纸 Black(深色、干净,不和印章红冲突)、iOS 系统应用图标。只放 4 行图标,第四行只有股市和「标博士」两个,下半屏留空。
|
||||||
|
|
||||||
|
- `iphone-home.html`:主屏幕排版(按 iPhone 18 Pro 屏幕 402×874pt,1 CSS px = 1pt)
|
||||||
|
- `fetch.sh`:下载系统图标和壁纸到 `apple/`
|
||||||
|
- `build.sh`:从机身 PNG 量出屏幕位置,生成屏幕遮罩和 `apple/geometry.css`,再用 Chrome 无头模式按 3 倍截图成 `apple/iphone-home.png`(透明背景),手册引用这张图
|
||||||
|
- `apple/`:Apple 的素材和生成结果,**版权归 Apple,不进 git**
|
||||||
|
|
||||||
|
## 重新生成
|
||||||
|
|
||||||
|
1. 运行 `./fetch.sh`。系统图标来自 App Store 公开查询接口(中国区),壁纸来自 iClarified 转载的官方壁纸。
|
||||||
|
2. 放入机身。机身来自 [Apple Design Resources](https://developer.apple.com/design/resources/) 的 iPhone 18 Product Bezels(`Bezel-iPhone-18.dmg`)。打开 DMG 前要**由本人阅读并同意** Apple Design Resources 许可协议,不要用脚本自动同意。打开后把 `PNG/iPhone 18 Pro/iPhone 18 Pro - Black - Portrait.png` 复制为 `apple/bezel.png`,许可文件 `Apple Design Resources License.rtf` 也一起放进 `apple/`。
|
||||||
|
3. 运行 `./build.sh`。换机型时只要替换 `apple/bezel.png`,屏幕位置会重新量;但图标排版是按 402pt 屏宽调的,换成屏宽差别大的机型要检查一下。
|
||||||
|
|
||||||
|
## 使用限制
|
||||||
|
|
||||||
|
- Apple Design Resources 许可只允许把机身用于**只运行在 Apple 系统上的软件**的界面效果图;把效果图交给别人时,要让对方知道这些限制,也不能把素材抠出来单独使用。标博士目前是桌面 Web 产品,这张图只作为应用图标的示意。手册如果要对外发布,先确认这一点,或者把手机换回自绘的示意机身。
|
||||||
|
- 系统图标和壁纸的版权归 Apple,只作为示意,不用于宣传物料。
|
||||||
|
- 日历图标上的星期是英文「Tue」:App Store 只提供这一版,真机在中文系统上会显示「星期二」。
|
||||||
44
docs/design/vi/mockup/build.sh
Executable file
@@ -0,0 +1,44 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# 生成 VI 手册用的 iPhone 桌面模拟图 apple/iphone-home.png(透明背景,3 倍分辨率)。
|
||||||
|
# 前置:fetch.sh 已下载图标和壁纸;机身 PNG 已按 README.md 放到 apple/bezel.png;logo/.work/venv 可用。
|
||||||
|
# 用法:docs/design/vi/mockup/build.sh
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
PORT=8799
|
||||||
|
PY=../../logo/.work/venv/bin/python
|
||||||
|
|
||||||
|
# 1. 量机身:与中心连通的透明区就是屏幕(圆角形状和机身完全一致)。
|
||||||
|
# 输出屏幕遮罩 screen-mask.png,以及页面用的尺寸 geometry.css(机身 PNG 是 3 倍图,1pt = 3px)
|
||||||
|
SIZE=$("$PY" -I - <<'EOF'
|
||||||
|
import numpy as np
|
||||||
|
from PIL import Image
|
||||||
|
from scipy import ndimage
|
||||||
|
a = np.asarray(Image.open("apple/bezel.png"))[..., 3]
|
||||||
|
lab, _ = ndimage.label(a < 10)
|
||||||
|
screen = lab == lab[a.shape[0] // 2, a.shape[1] // 2]
|
||||||
|
ys, xs = np.where(screen)
|
||||||
|
x, y, w, h = xs.min(), ys.min(), xs.max() + 1 - xs.min(), ys.max() + 1 - ys.min()
|
||||||
|
# 向外扩 2px 压到机身黑边下面,避免抗锯齿露出白缝;CSS 遮罩读透明通道,所以存成 RGBA
|
||||||
|
m = ndimage.binary_dilation(screen, iterations=2)[y:y + h, x:x + w]
|
||||||
|
rgba = np.zeros(m.shape + (4,), np.uint8)
|
||||||
|
rgba[..., 3] = m * 255
|
||||||
|
Image.fromarray(rgba, "RGBA").save("apple/screen-mask.png")
|
||||||
|
H, W = a.shape
|
||||||
|
pt = lambda v: f"{v / 3:g}px"
|
||||||
|
open("apple/geometry.css", "w").write(
|
||||||
|
f":root{{--dev-w:{pt(W)};--dev-h:{pt(H)};--scr-x:{pt(x)};--scr-y:{pt(y)};--scr-w:{pt(w)};--scr-h:{pt(h)}}}\n")
|
||||||
|
print(f"{W // 3},{H // 3}")
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
cat apple/geometry.css
|
||||||
|
|
||||||
|
# 2. 临时起本地服务(file:// 下遮罩图片会被浏览器安全策略拦掉;根目录是 docs/design,页面要引用 logo/),按 3 倍截图
|
||||||
|
python3 -m http.server $PORT --bind 127.0.0.1 --directory ../.. >/dev/null 2>&1 &
|
||||||
|
SERVER=$!
|
||||||
|
trap 'kill $SERVER' EXIT
|
||||||
|
sleep 1
|
||||||
|
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" --headless=new --hide-scrollbars \
|
||||||
|
--default-background-color=00000000 --force-device-scale-factor=3 --window-size="$SIZE" \
|
||||||
|
--virtual-time-budget=5000 --screenshot="$PWD/apple/iphone-home.png" \
|
||||||
|
"http://127.0.0.1:$PORT/vi/mockup/iphone-home.html" 2>/dev/null
|
||||||
|
magick identify apple/iphone-home.png
|
||||||
58
docs/design/vi/mockup/fetch.sh
Executable file
@@ -0,0 +1,58 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# 下载 VI 手册里 iPhone 桌面模拟用到的 Apple 素材到 apple/(版权归 Apple,不进 git)。
|
||||||
|
# - 系统应用图标:App Store 公开查询接口(中国区)里 Apple 自家应用的 1024px 图标
|
||||||
|
# - 壁纸:iPhone 18 Pro 官方壁纸 Black(主屏幕版,1320×2868),iClarified 转载
|
||||||
|
# - 机身:Apple Design Resources 的 iPhone 18 Product Bezels,DMG 要先由本人同意许可协议才能打开,
|
||||||
|
# 所以不自动下载,步骤见 README.md
|
||||||
|
# 用法:docs/design/vi/mockup/fetch.sh
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
mkdir -p apple/icons
|
||||||
|
|
||||||
|
# App Store ID 文件名
|
||||||
|
APPS="
|
||||||
|
1108185179 calendar
|
||||||
|
1108187098 mail
|
||||||
|
1069513131 weather
|
||||||
|
915056765 maps
|
||||||
|
1110145109 notes
|
||||||
|
1584215688 clock
|
||||||
|
1232058109 files
|
||||||
|
364709193 books
|
||||||
|
1108187390 music
|
||||||
|
525463029 podcasts
|
||||||
|
1108187841 reminders
|
||||||
|
1069512882 stocks
|
||||||
|
1110145091 facetime
|
||||||
|
1242545199 health
|
||||||
|
1160481993 wallet
|
||||||
|
1146562112 safari
|
||||||
|
1514844618 translate
|
||||||
|
1069512134 voicememos
|
||||||
|
1110145103 home
|
||||||
|
1069509450 tips
|
||||||
|
915249334 shortcuts
|
||||||
|
6443742539 freeform
|
||||||
|
6447391597 journal
|
||||||
|
1383426740 measure
|
||||||
|
1146560473 messages
|
||||||
|
1514844621 findmy
|
||||||
|
1069511488 calculator
|
||||||
|
"
|
||||||
|
|
||||||
|
ids=$(echo "$APPS" | awk 'NF{print $1}' | paste -sd, -)
|
||||||
|
curl -sSf -m 60 "https://itunes.apple.com/lookup?country=cn&id=$ids" -o apple/lookup.json
|
||||||
|
echo "$APPS" | while read -r id name; do
|
||||||
|
[ -z "$id" ] && continue
|
||||||
|
out="apple/icons/$name.png"
|
||||||
|
[ -f "$out" ] && continue
|
||||||
|
# artworkUrl512 换成 1024px PNG
|
||||||
|
url=$(python3 -I -c "import json,sys; r=[a for a in json.load(open('apple/lookup.json'))['results'] if a['trackId']==$id]; print(r[0]['artworkUrl512'].rsplit('/',1)[0]+'/1024x1024bb.png')")
|
||||||
|
curl -sSf -m 60 -o "$out" "$url"
|
||||||
|
done
|
||||||
|
|
||||||
|
WALL=apple/wallpaper-black.jpg
|
||||||
|
[ -f "$WALL" ] || curl -sSf -m 600 -o "$WALL" \
|
||||||
|
"https://www.iclarified.com/files/ios/iClarified-iPhone-18-Pro-Wallpaper/iClarified-iPhone-18-Pro-Wallpaper-Black-Home-Screen.jpg"
|
||||||
|
|
||||||
|
ls apple apple/icons
|
||||||
85
docs/design/vi/mockup/iphone-home.html
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>iPhone 桌面模拟</title>
|
||||||
|
<!--
|
||||||
|
VI 手册「应用图标」一页用的 iPhone 桌面模拟。按 iPhone 18 Pro 屏幕 402×874pt 排版(1 CSS px = 1pt),
|
||||||
|
用 Apple 的 Product Bezels 机身、iPhone 18 Pro 官方壁纸、iOS 系统应用图标(都在 apple/,由 fetch.sh 获取,不进 git)。
|
||||||
|
机身和屏幕的位置尺寸由 build.sh 从机身 PNG 量出,写在 apple/geometry.css。
|
||||||
|
导出:运行 build.sh,按 3 倍截图成 apple/iphone-home.png,手册引用这张图。
|
||||||
|
-->
|
||||||
|
<link rel="stylesheet" href="apple/geometry.css">
|
||||||
|
<style>
|
||||||
|
:root{
|
||||||
|
/* Apple 风格连续曲率圆角(squircle):iOS 图标遮罩的贝塞尔近似,r = 边长 × 22.37% */
|
||||||
|
--squircle:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Cpath d='M34.20 0.00L65.80 0.00C75.65 0.00 80.57 0.00 85.87 1.68C91.66 3.78 96.22 8.34 98.32 14.13C100.00 19.43 100.00 24.35 100.00 34.20L100.00 65.80C100.00 75.65 100.00 80.57 98.32 85.87C96.22 91.66 91.66 96.22 85.87 98.32C80.57 100.00 75.65 100.00 65.80 100.00L34.20 100.00C24.35 100.00 19.43 100.00 14.13 98.32C8.34 96.22 3.78 91.66 1.68 85.87C0.00 80.57 0.00 75.65 0.00 65.80L0.00 34.20C0.00 24.35 0.00 19.43 1.68 14.13C3.78 8.34 8.34 3.78 14.13 1.68C19.43 0.00 24.35 0.00 34.20 0.00Z'/%3E%3C/svg%3E");
|
||||||
|
--icon:62px;
|
||||||
|
}
|
||||||
|
html,body{margin:0;background:transparent}
|
||||||
|
body{font-family:-apple-system,"SF Pro Text","PingFang SC",sans-serif;-webkit-font-smoothing:antialiased}
|
||||||
|
.device{position:relative;width:var(--dev-w);height:var(--dev-h)}
|
||||||
|
.screen{position:absolute;left:var(--scr-x);top:var(--scr-y);width:var(--scr-w);height:var(--scr-h);overflow:hidden;color:#fff;
|
||||||
|
background:url(apple/wallpaper-black.jpg) center/cover;
|
||||||
|
-webkit-mask:url(apple/screen-mask.png) 0 0/100% 100% no-repeat;mask:url(apple/screen-mask.png) 0 0/100% 100% no-repeat}
|
||||||
|
.bezel{position:absolute;left:0;top:0;width:var(--dev-w);height:var(--dev-h);pointer-events:none}
|
||||||
|
|
||||||
|
.status{position:absolute;left:0;right:0;top:0;height:58px;display:flex;align-items:center;justify-content:space-between;padding:0 32px 0 48px}
|
||||||
|
.status .time{font:600 17px/1 -apple-system,"SF Pro Text",sans-serif;letter-spacing:-.2px}
|
||||||
|
.status .right{display:flex;align-items:center;gap:6px}
|
||||||
|
|
||||||
|
/* 只放 4 行:前三行系统应用,第四行只有股市和标博士,下半屏留空 */
|
||||||
|
.grid{position:absolute;left:26px;right:26px;top:72px;display:grid;grid-template-columns:repeat(4,var(--icon));justify-content:space-between;row-gap:28px}
|
||||||
|
.app{display:flex;flex-direction:column;align-items:center;gap:6px;width:var(--icon)}
|
||||||
|
.app img,.dock img{width:var(--icon);height:var(--icon);display:block;-webkit-mask:var(--squircle) center/100% 100% no-repeat;mask:var(--squircle) center/100% 100% no-repeat}
|
||||||
|
.app span{font:500 12px/1.1 -apple-system,"PingFang SC",sans-serif;white-space:nowrap}
|
||||||
|
|
||||||
|
.search{position:absolute;left:50%;bottom:124px;transform:translateX(-50%);display:flex;align-items:center;gap:4px;padding:7px 13px;border-radius:16px;
|
||||||
|
background:rgba(255,255,255,.14);-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);font:600 13px/1 -apple-system,"PingFang SC",sans-serif}
|
||||||
|
.dock{position:absolute;left:12px;right:12px;bottom:12px;height:92px;border-radius:36px;display:grid;grid-template-columns:repeat(4,var(--icon));justify-content:space-between;align-items:center;padding:0 17px;
|
||||||
|
background:rgba(255,255,255,.12);-webkit-backdrop-filter:blur(20px) saturate(1.4);backdrop-filter:blur(20px) saturate(1.4);box-shadow:inset 0 0 0 1px rgba(255,255,255,.14)}
|
||||||
|
.homebar{position:absolute;left:50%;bottom:8px;width:140px;height:5px;margin-left:-70px;border-radius:3px;background:rgba(255,255,255,.85)}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="device">
|
||||||
|
<div class="screen">
|
||||||
|
<div class="status">
|
||||||
|
<span class="time">9:41</span>
|
||||||
|
<span class="right">
|
||||||
|
<svg width="19" height="12" viewBox="0 0 19 12"><rect x="0" y="7.5" width="3.2" height="4.5" rx="1" fill="#fff"/><rect x="5.1" y="5" width="3.2" height="7" rx="1" fill="#fff"/><rect x="10.2" y="2.5" width="3.2" height="9.5" rx="1" fill="#fff"/><rect x="15.3" y="0" width="3.2" height="12" rx="1" fill="#fff"/></svg>
|
||||||
|
<svg width="17" height="12" viewBox="0 0 17 12"><path d="M8.5 2.4c2.5 0 4.8 1 6.5 2.6l1.3-1.3A11 11 0 0 0 .7 3.7L2 5a9.3 9.3 0 0 1 6.5-2.6Zm0 3.7c1.5 0 2.9.6 4 1.6l1.3-1.3a7.5 7.5 0 0 0-10.6 0l1.3 1.3a5.6 5.6 0 0 1 4-1.6Zm0 3.6c.6 0 1.1.2 1.5.5L8.5 11.7 7 10.2c.4-.3.9-.5 1.5-.5Z" fill="#fff"/></svg>
|
||||||
|
<svg width="28" height="13" viewBox="0 0 28 13"><rect x=".5" y=".5" width="24" height="12" rx="3.8" fill="none" stroke="#fff" stroke-opacity=".4"/><rect x="2" y="2" width="21" height="9" rx="2.5" fill="#fff"/><path d="M26 4.5v4a2.2 2.2 0 0 0 0-4Z" fill="#fff" fill-opacity=".4"/></svg>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid">
|
||||||
|
<div class="app"><img src="apple/icons/facetime.png"><span>FaceTime通话</span></div>
|
||||||
|
<div class="app"><img src="apple/icons/calendar.png"><span>日历</span></div>
|
||||||
|
<div class="app"><img src="apple/icons/weather.png"><span>天气</span></div>
|
||||||
|
<div class="app"><img src="apple/icons/clock.png"><span>时钟</span></div>
|
||||||
|
|
||||||
|
<div class="app"><img src="apple/icons/maps.png"><span>地图</span></div>
|
||||||
|
<div class="app"><img src="apple/icons/notes.png"><span>备忘录</span></div>
|
||||||
|
<div class="app"><img src="apple/icons/reminders.png"><span>提醒事项</span></div>
|
||||||
|
<div class="app"><img src="apple/icons/files.png"><span>文件</span></div>
|
||||||
|
|
||||||
|
<div class="app"><img src="apple/icons/books.png"><span>图书</span></div>
|
||||||
|
<div class="app"><img src="apple/icons/podcasts.png"><span>播客</span></div>
|
||||||
|
<div class="app"><img src="apple/icons/health.png"><span>健康</span></div>
|
||||||
|
<div class="app"><img src="apple/icons/wallet.png"><span>钱包</span></div>
|
||||||
|
|
||||||
|
<div class="app"><img src="apple/icons/stocks.png"><span>股市</span></div>
|
||||||
|
<div class="app"><img src="../../logo/png/icon-512.png"><span>标博士</span></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="search"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="3" stroke-linecap="round"><circle cx="10.5" cy="10.5" r="7"/><path d="m21 21-5.5-5.5"/></svg>搜索</div>
|
||||||
|
<div class="dock">
|
||||||
|
<img src="apple/icons/messages.png"><img src="apple/icons/safari.png"><img src="apple/icons/mail.png"><img src="apple/icons/music.png">
|
||||||
|
</div>
|
||||||
|
<div class="homebar"></div>
|
||||||
|
</div>
|
||||||
|
<img class="bezel" src="apple/bezel.png" alt="">
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -610,6 +610,8 @@ th{font-weight:700;color:var(--sub);font-size:8.6pt;border-bottom:.4mm solid var
|
|||||||
|
|
||||||
<!-- ============ 21 网站与图标 ============ -->
|
<!-- ============ 21 网站与图标 ============ -->
|
||||||
<section class="page">
|
<section class="page">
|
||||||
|
<!-- Apple 风格连续曲率圆角(squircle):iOS 图标遮罩的贝塞尔近似,r = 边长 × 22.37%。用 SVG 裁切而不是 CSS 遮罩,导出 PDF 时边缘干净 -->
|
||||||
|
<svg width="0" height="0" style="position:absolute"><clipPath id="squircle"><path d="M34.20 0.00L65.80 0.00C75.65 0.00 80.57 0.00 85.87 1.68C91.66 3.78 96.22 8.34 98.32 14.13C100.00 19.43 100.00 24.35 100.00 34.20L100.00 65.80C100.00 75.65 100.00 80.57 98.32 85.87C96.22 91.66 91.66 96.22 85.87 98.32C80.57 100.00 75.65 100.00 65.80 100.00L34.20 100.00C24.35 100.00 19.43 100.00 14.13 98.32C8.34 96.22 3.78 91.66 1.68 85.87C0.00 80.57 0.00 75.65 0.00 65.80L0.00 34.20C0.00 24.35 0.00 19.43 1.68 14.13C3.78 8.34 8.34 3.78 14.13 1.68C19.43 0.00 24.35 0.00 34.20 0.00Z"/></clipPath></svg>
|
||||||
<div class="run"><b>标博士 视觉识别规范</b><span>06 应用示例</span></div>
|
<div class="run"><b>标博士 视觉识别规范</b><span>06 应用示例</span></div>
|
||||||
<div class="cols c21" style="gap:10mm">
|
<div class="cols c21" style="gap:10mm">
|
||||||
<div>
|
<div>
|
||||||
@@ -622,7 +624,7 @@ th{font-weight:700;color:var(--sub);font-size:8.6pt;border-bottom:.4mm solid var
|
|||||||
<img src="../logo/svg/logo-horizontal.svg" style="width:34mm">
|
<img src="../logo/svg/logo-horizontal.svg" style="width:34mm">
|
||||||
<div style="display:flex;gap:6mm;font-size:7.5pt;color:var(--sub)"><span>产品</span><span>价格</span><span>帮助</span><span style="color:var(--ink);font-weight:500">登录</span></div>
|
<div style="display:flex;gap:6mm;font-size:7.5pt;color:var(--sub)"><span>产品</span><span>价格</span><span>帮助</span><span style="color:var(--ink);font-weight:500">登录</span></div>
|
||||||
</div>
|
</div>
|
||||||
<div style="padding:30mm 8mm 34mm">
|
<div style="padding:15mm 8mm 17mm">
|
||||||
<div style="font:700 26pt/1.25 var(--serif)">标博士,你的AI投标专家</div>
|
<div style="font:700 26pt/1.25 var(--serif)">标博士,你的AI投标专家</div>
|
||||||
<div style="font-size:8.5pt;color:var(--sub);margin:3mm 0 5mm;max-width:110mm">上传招标文件,得到一份能逐条回到原文的检查清单;递交前核对投标文件,逐项确认后导出报告。</div>
|
<div style="font-size:8.5pt;color:var(--sub);margin:3mm 0 5mm;max-width:110mm">上传招标文件,得到一份能逐条回到原文的检查清单;递交前核对投标文件,逐项确认后导出报告。</div>
|
||||||
<span style="display:inline-block;background:var(--ink);color:#fff;font-size:8pt;padding:2mm 5mm">免费检查一个项目</span>
|
<span style="display:inline-block;background:var(--ink);color:#fff;font-size:8pt;padding:2mm 5mm">免费检查一个项目</span>
|
||||||
@@ -630,21 +632,26 @@ th{font-weight:700;color:var(--sub);font-size:8.6pt;border-bottom:.4mm solid var
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p class="cap">页头左侧放横版干净版,高度约 32–40 像素。浏览器标签使用 16 像素专用图标。</p>
|
<p class="cap">页头左侧放横版干净版,高度约 32–40 像素。浏览器标签使用 16 像素专用图标。</p>
|
||||||
|
<h3 style="margin-top:5mm">邮件签名</h3>
|
||||||
|
<div style="display:flex;gap:8mm;align-items:center;margin-top:2mm">
|
||||||
|
<div style="background:#fff;border:.25mm solid var(--rule);padding:3.5mm 5mm;width:96mm;flex:none;display:flex;justify-content:space-between;align-items:flex-end">
|
||||||
|
<div><div style="font:700 9pt var(--serif)">黄晓沁 销售专家</div>
|
||||||
|
<div style="font:500 7pt/1.6 var(--fig);color:var(--sub);margin-top:1mm">138 1452 1983<br>romy@biaoboshi.cn<br>www.biaoboshi.cn</div></div>
|
||||||
|
<img src="../logo/svg/logo-horizontal.svg" style="width:30mm">
|
||||||
|
</div>
|
||||||
|
<p class="cap" style="margin:0">签名中的标志用 PNG(logo-horizontal-1200.png 缩放显示),宽 120–160 像素。</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h2>应用图标</h2>
|
<h2>应用图标</h2>
|
||||||
<div style="display:flex;gap:6mm;align-items:flex-end;margin-top:3mm">
|
<div style="display:flex;gap:5mm;align-items:flex-start;margin-top:3mm">
|
||||||
<div style="text-align:center"><img src="../logo/png/icon-512.png" style="width:38mm;border-radius:8.4mm;box-shadow:0 .5mm 1.5mm rgba(0,0,0,.15);display:block"><span class="cap">应用图标</span></div>
|
<img src="mockup/apple/iphone-home.png" alt="iPhone 桌面" style="width:56mm;display:block;flex:none">
|
||||||
<div style="text-align:center"><img src="../logo/png/icon-192.png" style="width:19mm;border-radius:4.2mm;box-shadow:0 .5mm 1.5mm rgba(0,0,0,.15);display:block"><span class="cap">小图标</span></div>
|
<div style="display:flex;flex-direction:column;gap:5mm;padding-top:2mm">
|
||||||
|
<div><svg viewBox="0 0 100 100" style="width:20mm;display:block"><image href="../logo/png/icon-512.png" width="100" height="100" clip-path="url(#squircle)"/></svg><span class="cap">1024 / 512 px</span></div>
|
||||||
|
<div><svg viewBox="0 0 100 100" style="width:10mm;display:block"><image href="../logo/png/icon-192.png" width="100" height="100" clip-path="url(#squircle)"/></svg><span class="cap">180 / 192 px</span></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p class="small sub" style="margin-top:4mm">白底,印章占图标边长的 80%。系统会自动加圆角,图标文件本身保持方形。</p>
|
<p class="small sub" style="margin-top:3mm">印泥铺满整个图标,不留白边。图标文件保持方形、不带透明,圆角由系统按 Apple 的连续曲率圆角裁出。左图为 iPhone 18 Pro 主屏幕上的实际比例(机身、壁纸、系统图标为 Apple 素材,仅作示意)。</p>
|
||||||
<h3 style="margin-top:8mm">邮件签名</h3>
|
|
||||||
<div style="background:#fff;border:.25mm solid var(--rule);padding:4mm;margin-top:2mm">
|
|
||||||
<div style="font:700 9pt var(--serif)">黄晓沁 销售专家</div>
|
|
||||||
<div style="font:500 7pt/1.6 var(--fig);color:var(--sub);margin:1mm 0 3mm">138 1452 1983<br>romy@biaoboshi.cn<br>www.biaoboshi.cn</div>
|
|
||||||
<img src="../logo/svg/logo-horizontal.svg" style="width:30mm">
|
|
||||||
</div>
|
|
||||||
<p class="cap">签名中的标志用 PNG(logo-horizontal-1200.png 缩放显示),宽 120–160 像素。</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="folio">22</div>
|
<div class="folio">22</div>
|
||||||
@@ -665,7 +672,8 @@ th{font-weight:700;color:var(--sub);font-size:8.6pt;border-bottom:.4mm solid var
|
|||||||
<tr><td class="mono">svg/seal(-textured)(-small).svg</td><td class="small">印章</td></tr>
|
<tr><td class="mono">svg/seal(-textured)(-small).svg</td><td class="small">印章</td></tr>
|
||||||
<tr><td class="mono">svg/wordmark(-reverse).svg</td><td class="small">字标</td></tr>
|
<tr><td class="mono">svg/wordmark(-reverse).svg</td><td class="small">字标</td></tr>
|
||||||
<tr><td class="mono">svg/favicon-16.svg、favicon.svg</td><td class="small">网页图标</td></tr>
|
<tr><td class="mono">svg/favicon-16.svg、favicon.svg</td><td class="small">网页图标</td></tr>
|
||||||
<tr><td class="mono">png/</td><td class="small">横版 1200 / 2400 px、竖版 600 px、印章 1024 px,图标 16 / 32 / 180 / 192 / 512 px</td></tr>
|
<tr><td class="mono">svg/app-icon.svg</td><td class="small">应用图标(印泥铺满)</td></tr>
|
||||||
|
<tr><td class="mono">png/</td><td class="small">横版 1200 / 2400 px、竖版 600 px、印章 1024 px,图标 16 / 32 px,应用图标 180 / 192 / 512 / 1024 px</td></tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<p class="note" style="margin-top:3mm">所有文件由 scripts/build_assets.sh 生成,修改请改 scripts/logo_lib.py 的参数后重新生成,不要手改 SVG。</p>
|
<p class="note" style="margin-top:3mm">所有文件由 scripts/build_assets.sh 生成,修改请改 scripts/logo_lib.py 的参数后重新生成,不要手改 SVG。</p>
|
||||||
|
|||||||
49
docs/prototype/README.md
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
# MVP 可点击原型 v0.1(2026-11-02)
|
||||||
|
|
||||||
|
按 `docs/analysis/02-user-journeys.md` 的 J1–J5 做的 HTML 原型,覆盖 MVP 全链路:注册 → 新建项目 → 解析 → 选包 → 读标 → 上传投标文件 → 查标 → 导出报告,以及工作台、项目概览、额度、资质库。
|
||||||
|
|
||||||
|
- **保真度**:中保真。直接使用设计规范的变量和组件(`docs/design/system/`),重点验证流程、信息结构和产品红线落地;视觉细节留到 UI 设计阶段精修。
|
||||||
|
- **示意场景**:某市政务服务中心智慧大厅建设项目(政府采购 · 公开招标 · 2 个采购包,投采购包 1)。条款改写自苏采云样本 S02、S10;项目名、金额、页码、投标响应、人名都是示意。
|
||||||
|
- **入口**:`index.html`(原型目录,含页面与旅程的对应关系、模拟范围)。
|
||||||
|
|
||||||
|
## 运行
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd docs && python3 -m http.server 8766
|
||||||
|
# 打开 http://127.0.0.1:8766/prototype/
|
||||||
|
```
|
||||||
|
|
||||||
|
必须从 `docs/` 起服务,因为原型引用了 `../design/system/` 和 `../design/logo/`。宋体和数字字体从 `design/logo/.work/fonts` 加载(不进 git),没有时退回系统字体。设计基准 1440 宽,最小 1280。
|
||||||
|
|
||||||
|
## 文件
|
||||||
|
|
||||||
|
| 文件 | 内容 |
|
||||||
|
|---|---|
|
||||||
|
| `index.html` | 原型目录 |
|
||||||
|
| `login.html` | 注册 / 登录(含服务协议的围串标禁止条款) |
|
||||||
|
| `workbench.html` | 工作台 |
|
||||||
|
| `project-new.html` | 新建项目、上传招标文件;`?addendum=1` 是追加补遗 |
|
||||||
|
| `parsing.html` | 解析进度、取消、失败演示;`?addendum=1` 是补遗解析 |
|
||||||
|
| `lots.html` | 选择所投的包 |
|
||||||
|
| `checklist.html` | 读标:分类、清单、待确认区、补遗更新、修改记录、原文查看器、确认清单 |
|
||||||
|
| `bid-check.html` | 上传投标文件分册、检查范围、发起检查 |
|
||||||
|
| `results.html` | 查标:问题处理、出处批注、原文抽屉、键盘操作、批量、需人工核对 |
|
||||||
|
| `report.html` | 检查报告预览(A4) |
|
||||||
|
| `project.html` | 项目概览:状态流、AI 摘要、关键日期、文件、检查记录、递交 / 归档 / 删除 |
|
||||||
|
| `quota.html` | 额度与流水 |
|
||||||
|
| `qualifications.html` | 资质库 |
|
||||||
|
| `assets/data.js` | 示意数据(项目、文件页面、清单、检查结果) |
|
||||||
|
| `assets/app.js` | 页面框架、出处、原文查看器、对话框、轻提示 |
|
||||||
|
| `assets/app.css` | 页面框架与布局(组件样式在 `design/system/components.css`) |
|
||||||
|
|
||||||
|
## 原型阶段发现的问题(2026-11-02 已按建议确认,见 `docs/decisions.md` P1–P2)
|
||||||
|
|
||||||
|
| 编号 | 问题 | 结论 |
|
||||||
|
|---|---|---|
|
||||||
|
| P1 | 「同一项目 24 小时内复查不扣点」的 24 小时从哪次检查算起?如果从「上一次检查」算,用户每天复查一次就能一直不扣点 | 从**上一次扣点的检查**算起,窗口内的复查不扣点、也不延长窗口 |
|
||||||
|
| P2 | 「需人工核对」的勾选结果按检查批次存,还是按项目存? | 按项目存,复查时沿用;报告里写勾选人和时间 |
|
||||||
|
|
||||||
|
## 和需求、规范的对应
|
||||||
|
|
||||||
|
- 每个页面对应的 FR 编号见 `index.html` 的「其他页面」表。
|
||||||
|
- 产品红线自查:没有出处的结论只出现在「待确认」区;全站无「通过 / 合格 / 保证」字样;红色只用于致命问题;致命问题未处理也能导出和标记已递交;扣点前在按钮旁说明;报告 logo 只在页眉。
|
||||||
126
docs/prototype/assets/app.css
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
/* 原型专用:页面框架、布局和各页面的局部样式。组件样式在 design/system/components.css。 */
|
||||||
|
|
||||||
|
/* ---------- 页面框架 ---------- */
|
||||||
|
body.app{display:grid;grid-template-columns:var(--sidebar-w) minmax(0,1fr);min-height:100vh;min-width:1280px}
|
||||||
|
.sidebar{position:sticky;top:0;height:100vh;display:flex;flex-direction:column;border-right:1px solid var(--border-subtle);background:var(--bg-page);padding:var(--sp-5) var(--sp-3)}
|
||||||
|
.sidebar .brand{display:block;padding:0 var(--sp-2) var(--sp-6)}
|
||||||
|
.sidebar .brand img{height:30px;display:block}
|
||||||
|
.nav{display:flex;flex-direction:column;gap:2px}
|
||||||
|
.nav a{display:flex;align-items:center;gap:var(--sp-3);height:36px;padding:0 var(--sp-3);border-radius:var(--radius-sm);color:var(--text-primary);text-decoration:none}
|
||||||
|
.nav a:hover{background:var(--bg-hover)}
|
||||||
|
.nav a.on{background:var(--bg-surface);box-shadow:inset 2px 0 0 var(--c-ink);font-weight:var(--fw-bold)}
|
||||||
|
.nav svg,.ic{width:18px;height:18px;flex:none;fill:none;stroke:currentColor;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round}
|
||||||
|
.nav .sub{margin:var(--sp-5) var(--sp-3) var(--sp-2);font-size:var(--fs-caption);color:var(--text-secondary)}
|
||||||
|
.sidebar-foot{margin-top:auto;padding:var(--sp-3);border-top:1px solid var(--border-subtle);font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--text-secondary)}
|
||||||
|
.sidebar-foot b{display:block;color:var(--text-primary);font-size:var(--fs-body)}
|
||||||
|
|
||||||
|
.main{min-width:0;display:flex;flex-direction:column}
|
||||||
|
.top{position:sticky;top:0;z-index:20;display:flex;align-items:center;gap:var(--sp-4);height:52px;padding:0 var(--sp-8);background:var(--bg-page);border-bottom:1px solid var(--border-subtle)}
|
||||||
|
.crumbs{display:flex;gap:var(--sp-2);color:var(--text-secondary);min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
||||||
|
.crumbs a{color:var(--text-secondary);text-decoration:none}
|
||||||
|
.crumbs a:hover{color:var(--text-primary);text-decoration:underline;text-underline-offset:3px}
|
||||||
|
.crumbs b{color:var(--text-primary);font-weight:var(--fw-regular);overflow:hidden;text-overflow:ellipsis}
|
||||||
|
.top-right{margin-left:auto;display:flex;align-items:center;gap:var(--sp-4)}
|
||||||
|
.quota-chip{display:flex;gap:var(--sp-3);align-items:center;height:28px;padding:0 var(--sp-3);border:1px solid var(--border-subtle);border-radius:var(--radius-sm);color:var(--text-secondary);text-decoration:none;font-size:var(--fs-caption);background:var(--bg-surface)}
|
||||||
|
.quota-chip .fig{color:var(--text-primary);font-size:14px;margin-left:3px}
|
||||||
|
.icon-btn{display:inline-grid;place-items:center;width:32px;height:32px;border-radius:var(--radius-sm);color:var(--text-primary);background:none;border:0;cursor:pointer;position:relative}
|
||||||
|
.icon-btn:hover{background:var(--bg-hover)}
|
||||||
|
.icon-btn .dot{position:absolute;top:6px;right:6px;width:6px;height:6px;border-radius:50%;background:var(--c-ink)}
|
||||||
|
|
||||||
|
.content{padding:var(--sp-6) var(--sp-8) var(--sp-16);max-width:calc(var(--content-max) + 64px);width:100%}
|
||||||
|
.content.wide{max-width:none}
|
||||||
|
.page-title{display:flex;align-items:flex-end;gap:var(--sp-4);margin-bottom:var(--sp-5)}
|
||||||
|
.page-title h1{font:var(--fw-bold) var(--fs-h1)/var(--lh-h1) var(--font-serif);margin:0}
|
||||||
|
.page-title p{margin:var(--sp-1) 0 0;color:var(--text-secondary)}
|
||||||
|
.page-title .actions{margin-left:auto;display:flex;gap:var(--sp-2);align-items:center}
|
||||||
|
|
||||||
|
/* 项目页头 + 页签 */
|
||||||
|
.proj-head{padding:var(--sp-5) var(--sp-8) 0;border-bottom:1px solid var(--border-subtle);background:var(--bg-page)}
|
||||||
|
.proj-head .line1{display:flex;align-items:flex-start;gap:var(--sp-4)}
|
||||||
|
.proj-head h1{font:var(--fw-bold) var(--fs-h1)/var(--lh-h1) var(--font-serif);margin:0}
|
||||||
|
.proj-head .meta{margin-top:var(--sp-1);color:var(--text-secondary);display:flex;flex-wrap:wrap;gap:var(--sp-2) var(--sp-4)}
|
||||||
|
.proj-head .facts{margin-left:auto;display:flex;gap:var(--sp-6);text-align:right;white-space:nowrap}
|
||||||
|
.proj-head .facts small{display:block;font-size:var(--fs-caption);color:var(--text-secondary)}
|
||||||
|
.proj-head .facts .fig{font-size:16px}
|
||||||
|
.tabs{display:flex;gap:var(--sp-6);margin-top:var(--sp-3)}
|
||||||
|
.tabs a{padding:var(--sp-2) 0 10px;color:var(--text-secondary);text-decoration:none;border-bottom:2px solid transparent;display:flex;gap:6px;align-items:center}
|
||||||
|
.tabs a:hover{color:var(--text-primary)}
|
||||||
|
.tabs a.on{color:var(--text-primary);font-weight:var(--fw-bold);border-bottom-color:var(--c-ink)}
|
||||||
|
|
||||||
|
/* ---------- 原文查看器 ---------- */
|
||||||
|
.viewer{min-height:0}
|
||||||
|
.viewer-bar{flex-wrap:wrap}
|
||||||
|
.viewer-bar .doc-name{display:flex;gap:var(--sp-2);align-items:baseline;min-width:0}
|
||||||
|
.viewer-bar .doc-file{color:var(--text-secondary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:240px}
|
||||||
|
.viewer-body{flex:1;overflow:auto;padding:var(--sp-4) 0}
|
||||||
|
.viewer .sheet{min-height:560px;margin:0 auto;width:calc(100% - 48px);max-width:640px;position:relative}
|
||||||
|
.sheet .pno{position:absolute;bottom:14px;left:0;right:0;text-align:center;font:12px var(--font-figures);color:#999}
|
||||||
|
.sheet .h{font-weight:700;margin:4px 0 10px}
|
||||||
|
.sheet .stub{height:9px;background:#EEE;margin:12px 0;border-radius:1px}
|
||||||
|
.sheet .stub.s{width:60%}
|
||||||
|
.viewer-empty{color:var(--text-secondary);font-size:var(--fs-caption);text-align:center;margin-top:var(--sp-4)}
|
||||||
|
.viewer-note{font-size:var(--fs-caption);color:var(--text-secondary);padding:0 var(--sp-6) var(--sp-3)}
|
||||||
|
|
||||||
|
/* 抽屉(查标页从右侧滑出原文) */
|
||||||
|
.drawer{position:fixed;top:0;right:0;bottom:0;width:min(46vw,720px);z-index:40;background:var(--bg-sunken);box-shadow:var(--shadow-overlay);transform:translateX(100%);transition:transform var(--dur-base) var(--ease);display:flex;flex-direction:column}
|
||||||
|
.drawer.open{transform:none}
|
||||||
|
.drawer .viewer{flex:1;border:0;border-radius:0}
|
||||||
|
.drawer-close{margin-left:var(--sp-2)}
|
||||||
|
|
||||||
|
/* ---------- 对话框与轻提示 ---------- */
|
||||||
|
.scrim{position:fixed;inset:0;background:rgba(29,39,51,.32);z-index:50;display:grid;place-items:center}
|
||||||
|
.dialog{width:480px;max-width:calc(100vw - 32px);background:var(--bg-surface);border-radius:var(--radius-md);box-shadow:var(--shadow-overlay);padding:var(--sp-6)}
|
||||||
|
.dialog.wide{width:640px}
|
||||||
|
.dialog h2{font:var(--fw-bold) var(--fs-title)/var(--lh-title) var(--font-sans);margin:0 0 var(--sp-3)}
|
||||||
|
.dialog .body > p{margin:0 0 var(--sp-3)}
|
||||||
|
.dialog .foot{display:flex;justify-content:flex-end;gap:var(--sp-2);margin-top:var(--sp-6)}
|
||||||
|
.toast{position:fixed;left:50%;bottom:32px;transform:translateX(-50%);z-index:60;background:var(--c-ink);color:#fff;padding:10px 16px;border-radius:var(--radius-sm);box-shadow:var(--shadow-overlay);display:flex;gap:var(--sp-3);align-items:center}
|
||||||
|
.toast a{color:#fff}
|
||||||
|
textarea.input{height:auto;min-height:72px;padding:8px 10px;resize:vertical;line-height:var(--lh-body);width:100%}
|
||||||
|
.field .input{width:100%}
|
||||||
|
.field{max-width:none}
|
||||||
|
|
||||||
|
/* ---------- 通用小件 ---------- */
|
||||||
|
.muted{color:var(--text-secondary)}
|
||||||
|
.cap{font-size:var(--fs-caption);line-height:var(--lh-caption)}
|
||||||
|
.stack > * + *{margin-top:var(--sp-4)}
|
||||||
|
.section-h{display:flex;align-items:baseline;gap:var(--sp-3);margin:var(--sp-8) 0 var(--sp-3)}
|
||||||
|
.section-h h2{font:var(--fw-bold) var(--fs-title)/var(--lh-title) var(--font-sans);margin:0}
|
||||||
|
.section-h .more{margin-left:auto}
|
||||||
|
.kbd{display:inline-block;min-width:20px;padding:0 5px;border:1px solid var(--border-control);border-bottom-width:2px;border-radius:3px;font:12px/18px var(--font-figures);text-align:center;background:var(--bg-surface)}
|
||||||
|
.seg{display:inline-flex;border:1px solid var(--border-control);border-radius:var(--radius-sm);overflow:hidden;background:var(--bg-surface)}
|
||||||
|
.seg button{height:28px;padding:0 10px;border:0;background:none;font:var(--fs-body) var(--font-sans);color:var(--text-primary);cursor:pointer;display:flex;gap:6px;align-items:center}
|
||||||
|
.seg button + button{border-left:1px solid var(--border-subtle)}
|
||||||
|
.seg button.on{background:var(--c-ink);color:#fff}
|
||||||
|
.seg button.on .sev.hint{background:#fff}
|
||||||
|
.chk{display:flex;gap:var(--sp-3);align-items:flex-start;padding:var(--sp-3) 0;border-top:1px solid var(--border-subtle)}
|
||||||
|
.chk:first-child{border-top:0}
|
||||||
|
.chk input{width:16px;height:16px;margin:3px 0 0;accent-color:var(--c-ink)}
|
||||||
|
.free-badge{font-size:var(--fs-caption);border:1px solid var(--border-control);padding:0 6px;border-radius:2px}
|
||||||
|
.placeholder{border:1px dashed var(--border-control);color:var(--text-secondary);border-radius:var(--radius-sm);padding:var(--sp-4);text-align:center}
|
||||||
|
|
||||||
|
/* 状态流 */
|
||||||
|
.flowline{display:flex;align-items:center;gap:0;margin:var(--sp-2) 0}
|
||||||
|
.flowline .st{display:flex;align-items:center;gap:var(--sp-2);color:var(--text-secondary);white-space:nowrap}
|
||||||
|
.flowline .st i{width:10px;height:10px;border-radius:50%;border:1.5px solid var(--border-control);flex:none}
|
||||||
|
.flowline .st.done{color:var(--text-primary)}
|
||||||
|
.flowline .st.done i{background:var(--c-ink);border-color:var(--c-ink)}
|
||||||
|
.flowline .st.now{color:var(--text-primary);font-weight:var(--fw-bold)}
|
||||||
|
.flowline .st.now i{border:3px solid var(--c-ink)}
|
||||||
|
.flowline .ln{flex:1;min-width:24px;height:1px;background:var(--border-control);margin:0 var(--sp-3)}
|
||||||
|
|
||||||
|
/* 键值列表 */
|
||||||
|
.kv{display:grid;grid-template-columns:112px 1fr;gap:var(--sp-2) var(--sp-4);margin:0}
|
||||||
|
.kv dt{color:var(--text-secondary)}
|
||||||
|
.kv dd{margin:0}
|
||||||
|
|
||||||
|
/* 上传区 */
|
||||||
|
.drop{border:1.5px dashed var(--border-control);border-radius:var(--radius-sm);background:var(--bg-surface);padding:var(--sp-10) var(--sp-6);text-align:center;cursor:pointer}
|
||||||
|
.drop:hover{border-color:var(--c-ink)}
|
||||||
|
.drop b{display:block;font-size:var(--fs-title);margin-bottom:var(--sp-1)}
|
||||||
|
.file-row{display:grid;grid-template-columns:32px minmax(0,1fr) auto auto;gap:var(--sp-3);align-items:center;padding:var(--sp-3) var(--sp-4);background:var(--bg-surface);border:1px solid var(--border-subtle);border-radius:var(--radius-sm)}
|
||||||
|
.file-row + .file-row{margin-top:var(--sp-2)}
|
||||||
|
.file-row .ext{font:600 11px/1 var(--font-figures);border:1px solid var(--border-control);border-radius:2px;padding:4px 0;text-align:center}
|
||||||
|
.file-row select{height:28px;border:1px solid var(--border-control);border-radius:var(--radius-sm);background:#fff;font:var(--fs-body) var(--font-sans);padding:0 6px}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion:reduce){.drawer{transition:none}}
|
||||||
156
docs/prototype/assets/app.js
vendored
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
/* 原型公共脚本:页面框架、出处、原文查看器、对话框、轻提示。只模拟交互,不连后端。 */
|
||||||
|
(function () {
|
||||||
|
const D = window.PROTO;
|
||||||
|
const $ = (s, el = document) => el.querySelector(s);
|
||||||
|
const $$ = (s, el = document) => [...el.querySelectorAll(s)];
|
||||||
|
const esc = (s) => String(s).replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ }[c]));
|
||||||
|
|
||||||
|
/* 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 14l4-4"/><path d="M3.3 19a9 9 0 1 1 17.4 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.9 1.9 0 0 0 3.4 0"/>',
|
||||||
|
help: '<circle cx="12" cy="12" r="10"/><path d="M9.1 9a3 3 0 0 1 5.8 1c0 2-3 3-3 3M12 17h.01"/>',
|
||||||
|
x: '<path d="M18 6 6 18M6 6l12 12"/>',
|
||||||
|
plus: '<path d="M12 5v14M5 12h14"/>',
|
||||||
|
upload: '<path d="M12 3v12M7 8l5-5 5 5"/><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>',
|
||||||
|
file: '<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/>',
|
||||||
|
keyboard: '<rect x="2" y="5" width="20" height="14" rx="2"/><path d="M6 9h.01M10 9h.01M14 9h.01M18 9h.01M7 15h10"/>'
|
||||||
|
};
|
||||||
|
const icon = (n, cls = "ic") => `<svg class="${cls}" viewBox="0 0 24 24" aria-hidden="true">${ICONS[n]}</svg>`;
|
||||||
|
|
||||||
|
/* ---------- 页面框架 ---------- */
|
||||||
|
function shell(opt) {
|
||||||
|
const P = D.project;
|
||||||
|
const nav = [
|
||||||
|
["workbench.html", "grid", "工作台", "workbench"],
|
||||||
|
["qualifications.html", "cert", "资质库", "quals"],
|
||||||
|
["quota.html", "gauge", "额度", "quota"]
|
||||||
|
].map(([h, i, t, k]) => `<a href="${h}" class="${opt.nav === k ? "on" : ""}">${icon(i)}${t}</a>`).join("");
|
||||||
|
const side = `<aside class="sidebar">
|
||||||
|
<a class="brand" href="workbench.html"><img src="../design/logo/svg/logo-horizontal.svg" alt="标博士"></a>
|
||||||
|
<nav class="nav" aria-label="主导航">${nav}<div class="sub">原型</div><a href="index.html">${icon("help")}原型目录</a></nav>
|
||||||
|
<div class="sidebar-foot"><b>${D.user.name}</b>${D.user.company}<br>${D.user.plan} · 至 2027-10-31</div>
|
||||||
|
</aside>`;
|
||||||
|
const crumbs = (opt.crumbs || []).map((c, i, a) => i < a.length - 1 ? `<a href="${c[1]}">${c[0]}</a><span>/</span>` : `<b>${c[0]}</b>`).join("");
|
||||||
|
const top = `<header class="top">
|
||||||
|
<div class="crumbs">${crumbs}</div>
|
||||||
|
<div class="top-right">
|
||||||
|
<a class="quota-chip" href="quota.html" title="本期剩余额度">解析<span class="fig">${D.quota.parse.left}</span>检查<span class="fig">${D.quota.check.left}</span></a>
|
||||||
|
<button class="icon-btn" aria-label="通知" data-notice>${icon("bell")}<span class="dot"></span></button>
|
||||||
|
</div>
|
||||||
|
</header>`;
|
||||||
|
let head = "";
|
||||||
|
if (opt.tab) {
|
||||||
|
const tabs = [["project.html", "概览", "overview"], ["checklist.html", "读标", "checklist"], ["results.html", "查标", "results"], ["report.html", "报告", "report"]]
|
||||||
|
.map(([h, t, k]) => `<a href="${h}" class="${opt.tab === k ? "on" : ""}">${t}${k === "results" ? ' <span class="sev fatal" data-fatal-count>致命<span class="fig">3</span></span>' : ""}</a>`).join("");
|
||||||
|
head = `<div class="proj-head"><div class="line1"><div>
|
||||||
|
<h1>${P.name}</h1>
|
||||||
|
<div class="meta"><span>${P.buyer}</span><span>${P.regime} · ${P.method}</span><span>投 采购包 1(共 2 个包)</span><span class="fig" style="font-weight:500">${P.no}</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="facts">
|
||||||
|
<div><small>投标截止</small><span class="fig">${P.deadlineText}</span> · 剩 <span class="fig">${P.daysLeft}</span> 天</div>
|
||||||
|
<div><small>状态</small><span class="tag status">${P.status}</span></div>
|
||||||
|
<div><small>就绪</small><span data-ready>未就绪</span></div>
|
||||||
|
</div></div>
|
||||||
|
<nav class="tabs" aria-label="项目">${tabs}</nav></div>`;
|
||||||
|
}
|
||||||
|
document.head.insertAdjacentHTML("beforeend", '<link rel="icon" href="../design/logo/favicon.svg">');
|
||||||
|
document.body.classList.add("app");
|
||||||
|
const main = $("main");
|
||||||
|
main.classList.add("main");
|
||||||
|
document.body.insertAdjacentHTML("afterbegin", side);
|
||||||
|
main.insertAdjacentHTML("afterbegin", top + head);
|
||||||
|
$("[data-notice]").addEventListener("click", () => toast("解析完成:补遗 1 已并入清单(新增 2 条,更新 1 条)。"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- 出处 ---------- */
|
||||||
|
function citeLabel(c) {
|
||||||
|
const d = D.docs[c[0]];
|
||||||
|
return `${d.name} · <span class="fig">第 ${c[1]} 页</span>`;
|
||||||
|
}
|
||||||
|
function quote(c) {
|
||||||
|
const p = (D.pages[c[0]] || {})[c[1]] || [];
|
||||||
|
const hit = p.find((x) => x.id === c[2]);
|
||||||
|
return hit ? hit.t : "";
|
||||||
|
}
|
||||||
|
/* 段落里只标出关键原句;没有关键原句时整段标出 */
|
||||||
|
function marked(text, key, tag = "mark") {
|
||||||
|
const i = key ? text.indexOf(key) : -1;
|
||||||
|
if (i < 0) return `<${tag}>${esc(text)}</${tag.split(" ")[0]}>`;
|
||||||
|
return esc(text.slice(0, i)) + `<${tag}>${esc(key)}</${tag.split(" ")[0]}>` + esc(text.slice(i + key.length));
|
||||||
|
}
|
||||||
|
function cite(c, opt = {}) {
|
||||||
|
return `<div class="cite"><a class="cite-src" href="#" data-open="${esc(c.join("|"))}">${citeLabel(c)}</a>${opt.noQuote ? "" : `<q>${marked(quote(c), c[3])}</q>`}</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- 原文查看器 ---------- */
|
||||||
|
function viewer(el, opt = {}) {
|
||||||
|
let cur = null, list = [];
|
||||||
|
el.classList.add("viewer");
|
||||||
|
el.innerHTML = `<div class="viewer-bar">
|
||||||
|
<span class="doc-name"><b data-v-name>原文</b><span class="doc-file" data-v-file></span></span>
|
||||||
|
<div class="pager"><button class="btn sm" data-v-prev>上一处</button><span class="fig" data-v-page></span><button class="btn sm" data-v-next>下一处</button>${opt.closable ? `<button class="icon-btn drawer-close" aria-label="关闭原文" data-v-close>${icon("x")}</button>` : ""}</div>
|
||||||
|
</div>
|
||||||
|
<div class="viewer-body" data-v-body><p class="viewer-empty">点击清单项或出处,在这里查看原文并高亮原句。</p></div>`;
|
||||||
|
const body = $("[data-v-body]", el);
|
||||||
|
function render() {
|
||||||
|
const [doc, page, hl] = cur;
|
||||||
|
const d = D.docs[doc];
|
||||||
|
$("[data-v-name]", el).textContent = d.name;
|
||||||
|
$("[data-v-file]", el).textContent = d.file;
|
||||||
|
$("[data-v-page]", el).textContent = `第 ${page} / ${d.pages} 页`;
|
||||||
|
const paras = (D.pages[doc] || {})[page];
|
||||||
|
const inner = paras
|
||||||
|
? paras.map((p) => p.h ? `<p class="h">${esc(p.t)}</p>` : `<p class="${p.faint ? "faint" : ""}">${p.id === hl ? marked(p.t, cur[3], 'span class="hl on"') : esc(p.t)}</p>`).join("")
|
||||||
|
: '<div class="stub"></div><div class="stub"></div><div class="stub s"></div>';
|
||||||
|
body.innerHTML = `<div class="sheet">${inner}<div class="stub"></div><div class="stub s"></div><span class="pno">— ${page} —</span></div>
|
||||||
|
<p class="viewer-note">${doc === "B1" && page === 3 ? "扫描页 · 文字由 OCR 识别,请以原图为准。" : "页码以规范化 PDF 为准(原型只做了被引用的页面)。"}</p>`;
|
||||||
|
const on = $(".hl.on", body);
|
||||||
|
if (on) on.scrollIntoView({ block: "center", behavior: "smooth" });
|
||||||
|
const i = list.findIndex((x) => x.join("|") === cur.join("|"));
|
||||||
|
$("[data-v-prev]", el).disabled = i <= 0;
|
||||||
|
$("[data-v-next]", el).disabled = i < 0 || i >= list.length - 1;
|
||||||
|
}
|
||||||
|
function open(c, all) {
|
||||||
|
cur = c; list = all || [c];
|
||||||
|
render();
|
||||||
|
if (opt.onOpen) opt.onOpen();
|
||||||
|
}
|
||||||
|
$("[data-v-prev]", el).onclick = () => { const i = list.findIndex((x) => x.join("|") === cur.join("|")); if (i > 0) open(list[i - 1], list); };
|
||||||
|
$("[data-v-next]", el).onclick = () => { const i = list.findIndex((x) => x.join("|") === cur.join("|")); if (i < list.length - 1) open(list[i + 1], list); };
|
||||||
|
if (opt.closable) $("[data-v-close]", el).onclick = opt.onClose;
|
||||||
|
return { open };
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- 对话框与轻提示 ---------- */
|
||||||
|
function dialog({ title, body, ok = "确定", cancel = "取消", wide = false, onOk, onOpen }) {
|
||||||
|
const s = document.createElement("div");
|
||||||
|
s.className = "scrim";
|
||||||
|
s.innerHTML = `<div class="dialog ${wide ? "wide" : ""}" role="dialog" aria-modal="true" aria-labelledby="dlg-t">
|
||||||
|
<h2 id="dlg-t">${title}</h2><div class="body">${body}</div>
|
||||||
|
<div class="foot">${cancel ? `<button class="btn" data-cancel>${cancel}</button>` : ""}<button class="btn primary" data-ok>${ok}</button></div></div>`;
|
||||||
|
document.body.appendChild(s);
|
||||||
|
const close = () => { s.remove(); document.removeEventListener("keydown", onKey); };
|
||||||
|
const onKey = (e) => { if (e.key === "Escape") close(); };
|
||||||
|
document.addEventListener("keydown", onKey);
|
||||||
|
s.addEventListener("click", (e) => { if (e.target === s) close(); });
|
||||||
|
if (cancel) $("[data-cancel]", s).onclick = close;
|
||||||
|
$("[data-ok]", s).onclick = () => { if (!onOk || onOk(s) !== false) close(); };
|
||||||
|
if (onOpen) onOpen(s);
|
||||||
|
const f = $("textarea, input, [data-ok]", s);
|
||||||
|
if (f) f.focus();
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
let toastTimer;
|
||||||
|
function toast(msg) {
|
||||||
|
let t = $(".toast");
|
||||||
|
if (!t) { t = document.createElement("div"); t.className = "toast"; t.setAttribute("role", "status"); document.body.appendChild(t); }
|
||||||
|
t.innerHTML = msg;
|
||||||
|
clearTimeout(toastTimer);
|
||||||
|
toastTimer = setTimeout(() => t.remove(), 4000);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.App = { D, $, $$, esc, icon, shell, cite, citeLabel, quote, viewer, dialog, toast };
|
||||||
|
})();
|
||||||
266
docs/prototype/assets/data.js
vendored
Normal file
@@ -0,0 +1,266 @@
|
|||||||
|
/* 原型示意数据。场景见 docs/analysis/02-user-journeys.md:
|
||||||
|
* 某市政务服务中心智慧大厅建设项目(政府采购 · 公开招标 · 2 个采购包,投采购包 1)。
|
||||||
|
* 条款文字改写自苏采云样本 S02、S10;项目名、金额、页码、投标响应、人名均为示意。 */
|
||||||
|
window.PROTO = {
|
||||||
|
today: "2026-11-02",
|
||||||
|
user: { name: "王敏", company: "南京某信息技术有限公司", plan: "专业版" },
|
||||||
|
quota: { parse: { left: 37, total: 50 }, check: { left: 12, total: 20 } },
|
||||||
|
|
||||||
|
project: {
|
||||||
|
id: "p1",
|
||||||
|
name: "某市政务服务中心智慧大厅建设项目",
|
||||||
|
no: "ZC-2026-0418",
|
||||||
|
buyer: "某市政务服务管理办公室",
|
||||||
|
agent: "某市公共资源交易中心",
|
||||||
|
regime: "政府采购",
|
||||||
|
method: "公开招标",
|
||||||
|
deadline: "2026-11-20 09:30",
|
||||||
|
deadlineText: "11月20日 09:30",
|
||||||
|
daysLeft: 17,
|
||||||
|
status: "已检查",
|
||||||
|
lots: [
|
||||||
|
{ no: 1, name: "智慧大厅软硬件集成", budget: "286.00", cap: "286.00", bid: true },
|
||||||
|
{ no: 2, name: "机房网络设备更新", budget: "132.00", cap: "132.00", bid: false }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
/* 文件:role = tender 招标文件 / addendum 补遗 / bid 投标分册 */
|
||||||
|
docs: {
|
||||||
|
T: { name: "招标文件", file: "ZC-2026-0418 采购文件.pdf", role: "tender", pages: 186 },
|
||||||
|
A1: { name: "补遗 1", file: "关于 ZC-2026-0418 的更正公告.pdf", role: "addendum", pages: 2 },
|
||||||
|
B1: { name: "商务册", file: "商务册-v3.docx", role: "bid", pages: 64 },
|
||||||
|
B2: { name: "技术册", file: "技术册-v3.docx", role: "bid", pages: 212 },
|
||||||
|
B3: { name: "报价册", file: "报价册-v2.docx", role: "bid", pages: 8 }
|
||||||
|
},
|
||||||
|
|
||||||
|
/* 原文页面(只做了被引用到的页)。出处格式:[文件, 页码, 段落 id, 关键原句(可选,只高亮这一句)] */
|
||||||
|
pages: {
|
||||||
|
T: {
|
||||||
|
2: [
|
||||||
|
{ t: "一、项目基本情况", h: 1 },
|
||||||
|
{ t: "项目编号:ZC-2026-0418" },
|
||||||
|
{ t: "项目名称:某市政务服务中心智慧大厅建设项目" },
|
||||||
|
{ t: "采购方式:公开招标" },
|
||||||
|
{ id: "t2-cap", t: "预算金额:418.00万元。最高限价:418.00万元(采购包1:286.00万元,采购包2:132.00万元)。" },
|
||||||
|
{ t: "采购需求:详见第六章《采购需求》。" },
|
||||||
|
{ id: "t2-sme", t: "本项目(是/否)专门面向中小企业采购:否。对小微企业给予价格扣除优惠。" },
|
||||||
|
{ t: "合同履行期限:合同签订后 90 日历天内完成交付并通过验收。" },
|
||||||
|
{ id: "t2-joint", t: "本项目(是/否)接受联合体投标:否。" }
|
||||||
|
],
|
||||||
|
6: [
|
||||||
|
{ t: "四、提交投标文件截止时间、开标时间和地点", h: 1 },
|
||||||
|
{ id: "t6-deadline", t: "1.提交投标文件截止时间、开标时间:2026年11月20日09时30分(北京时间)。" },
|
||||||
|
{ id: "t6-place", t: "2.投标文件递交方式:通过政府采购电子交易平台在线递交加密的电子投标文件,不接受纸质投标文件。" },
|
||||||
|
{ t: "3.开标地点:某市公共资源交易中心开标室(远程不见面开标)。" },
|
||||||
|
{ id: "t6-ask", t: "4.对采购文件的询问或质疑,应在采购文件公告期限届满之日起七个工作日内以书面形式提出。" }
|
||||||
|
],
|
||||||
|
18: [
|
||||||
|
{ t: "34. 投标人存在下列情况之一的,投标无效:", h: 1 },
|
||||||
|
{ id: "t18-seal", t: "(一)投标文件未按招标文件要求签署、盖章的;" },
|
||||||
|
{ id: "t18-qual", t: "(二)不具备招标文件中规定的资格要求的;" },
|
||||||
|
{ id: "t18-cap", t: "(三)报价超过招标文件中规定的预算金额或者最高限价的;" },
|
||||||
|
{ t: "(四)投标文件含有采购人不能接受的附加条件的;" },
|
||||||
|
{ id: "t18-valid", t: "(五)投标文件中承诺的投标有效期少于招标文件中载明的投标有效期;" },
|
||||||
|
{ t: "(六)法律、法规和招标文件规定的其他无效情形。" }
|
||||||
|
],
|
||||||
|
21: [
|
||||||
|
{ t: "投标资料表", h: 1 },
|
||||||
|
{ id: "t21-valid", t: "投标有效期:90天。" },
|
||||||
|
{ id: "t21-deposit", t: "投标保证金:本项目不收取投标保证金。" },
|
||||||
|
{ id: "t21-copies", t: "投标文件份数:电子投标文件1份,须使用CA数字证书加密后上传。" },
|
||||||
|
{ id: "t21-quote", t: "20.5 投标人只能提交一个投标报价,提交2个以上投标报价的投标文件按照投标无效处理。" }
|
||||||
|
],
|
||||||
|
23: [
|
||||||
|
{ t: "二、资格条件(“1”~“7”项必须提供,否则投标无效)", h: 1 },
|
||||||
|
{ id: "t23-license", t: "1.投标人合法有效的法人或者其他组织的营业执照等证明文件。以上证明文件提供扫描件。" },
|
||||||
|
{ id: "t23-fin", t: "2.财务状况报告,至少提供:(1)本项目开标时间前6个月内任一时间截点的资产负债表的扫描件1份;(2)本项目开标时间前6个月内任何1月(不含开标当月)利润表月报表的扫描件1份。" },
|
||||||
|
{ id: "t23-tax", t: "3.本项目开标时间前6个月内任何1月的依法缴纳税收和社会保障资金的相关材料的扫描件。" },
|
||||||
|
{ id: "t23-ability", t: "4.《具备履行合同所必需的设备和专业技术能力的书面声明》(加盖电子签章)。" },
|
||||||
|
{ id: "t23-record", t: "5.参加政府采购活动前3年内在经营活动中没有重大违法记录的书面声明(加盖电子签章)。" },
|
||||||
|
{ id: "t23-credit", t: "8.投标人无被列入失信被执行人、重大税收违法案件当事人名单、政府采购严重失信行为记录名单的信用记录。" }
|
||||||
|
],
|
||||||
|
24: [
|
||||||
|
{ t: "三、符合条件(必须提供,否则投标无效)", h: 1 },
|
||||||
|
{ id: "t24-quote", t: "1.报价符合性审查:投标报价不得超过采购包最高限价;投标报价低于采购包最高限价45%的,提供与报价合理性相关的书面说明及必要的证明材料。" },
|
||||||
|
{ id: "t24-letter", t: "3.《投标函》(加盖电子签章,格式见招标文件第七章)。" },
|
||||||
|
{ id: "t24-auth", t: "4.《法定代表人授权委托书》(法定代表人签字或盖章并加盖单位电子签章)。" },
|
||||||
|
{ id: "t24-dev", t: "5.《偏离表》(加盖电子签章,格式见招标文件第七章)。" },
|
||||||
|
{ id: "t24-mark", t: "本文件中打“★”内容为实质性要求,投标人须全部满足,否则投标无效。" }
|
||||||
|
],
|
||||||
|
31: [
|
||||||
|
{ t: "第四章 评标标准(综合评分法)", h: 1 },
|
||||||
|
{ id: "t31-price", t: "价格分(10分):满足招标文件要求且投标价格最低的投标报价为评标基准价,其价格分为满分。" },
|
||||||
|
{ id: "t31-tech", t: "技术规格(40分):完全满足★和▲参数得基础分,▲参数每负偏离一项扣2分,一般参数每负偏离一项扣0.5分。" },
|
||||||
|
{ id: "t31-plan", t: "项目实施方案(20分):方案内容齐全、符合实际切实可行且无缺陷得20分,每存在一处缺陷扣2分。" },
|
||||||
|
{ id: "t31-service", t: "售后服务方案(10分):对第六章“四、售后服务要求”进行完善并详细描述,每缺少一项内容扣1分。" },
|
||||||
|
{ id: "t31-iso", t: "体系认证(6分):投标人具有有效的ISO9001、ISO27001、ISO20000认证证书,每提供一项得2分(以投标截止日在有效期内为准,提供证书扫描件)。" },
|
||||||
|
{ id: "t31-case", t: "类似业绩(8分):近三年(2023年1月1日以来)承担过政务服务大厅智能化项目的,每提供一个得2分,最多8分(提供合同关键页扫描件)。" }
|
||||||
|
],
|
||||||
|
41: [
|
||||||
|
{ t: "采购包1 · 货物需求一览表", h: 1 },
|
||||||
|
{ t: "序号 3 应用服务器 2 台", faint: true },
|
||||||
|
{ id: "t41-server", t: "★ 2U机架式服务器,CPU:配置≥2颗海光架构32C 2.7GHz,内存: ≥512G DDR4,系统盘: 2块≥480G SSD,存储盘: ≥2块8T SATA、≥4块3.84T SSD……" },
|
||||||
|
{ id: "t41-nic", t: "网络:配置≥4个1GbE 接口,≥4个10GbE 光接口(含光模块),电源:配置2块800W冗余电源。" },
|
||||||
|
{ id: "t41-cpu", t: "★ 设备采用国产自主可控CPU芯片,投标文件中提供国产化适配承诺函,格式自制。" },
|
||||||
|
{ t: "备注:加★为核心技术参数;加▲为重要技术参数;其他为通用技术参数。" }
|
||||||
|
],
|
||||||
|
44: [
|
||||||
|
{ t: "序号 5 核心交换机 1 台", faint: true },
|
||||||
|
{ id: "t44-bfd", t: "★ 支持硬件BFD,最小报文发送间隔≤50ms,提供相关证明材料(包含但不限于产品官网公开参数截图证明等)。" },
|
||||||
|
{ id: "t44-port", t: "▲ 交换容量≥19.2Tbps,包转发率≥2880Mpps,业务槽位≥4个。" }
|
||||||
|
],
|
||||||
|
47: [
|
||||||
|
{ t: "序号 9 排队叫号一体机 6 台", faint: true },
|
||||||
|
{ id: "t47-screen", t: "显示屏尺寸≥21.5英寸,分辨率≥1920×1080,支持身份证、社保卡读取。" },
|
||||||
|
{ id: "t47-ups", t: "▲ 自助服务终端须提供不少于3年原厂质保,提供原厂质保承诺函。" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
A1: {
|
||||||
|
1: [
|
||||||
|
{ t: "关于 ZC-2026-0418 的更正公告(补遗 1)", h: 1 },
|
||||||
|
{ id: "a1-deadline", t: "一、提交投标文件截止时间、开标时间更正为:2026年11月20日09时30分(北京时间)。原为2026年11月18日09时30分。" },
|
||||||
|
{ id: "a1-param", t: "二、采购包1 序号3 应用服务器“系统盘”参数更正为:2块≥960G SSD。" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
B1: {
|
||||||
|
3: [
|
||||||
|
{ t: "一、投标函", h: 1 },
|
||||||
|
{ t: "致:某市公共资源交易中心" },
|
||||||
|
{ t: "我方已仔细研究了某市政务服务中心智慧大厅建设项目(项目编号:ZC-2026-0418)招标文件的全部内容……" },
|
||||||
|
{ id: "b1-date", t: "投标人(电子签章):南京某信息技术有限公司 日期:2026年11月25日" }
|
||||||
|
],
|
||||||
|
5: [
|
||||||
|
{ t: "二、法定代表人授权委托书", h: 1 },
|
||||||
|
{ t: "本授权委托书声明:我李某某系南京某信息技术有限公司的法定代表人,现授权委托王敏为我公司代理人……" },
|
||||||
|
{ id: "b1-auth-seal", t: "法定代表人(签字或盖章):李某某 投标人(电子签章):(签章位置为空)" }
|
||||||
|
],
|
||||||
|
27: [
|
||||||
|
{ t: "九、类似项目业绩", h: 1 },
|
||||||
|
{ id: "b1-oldname", t: "我公司承担的某区智慧校园一期项目(项目编号 QJ-2025-0112)已顺利通过验收,本项目将沿用该项目的实施团队……" }
|
||||||
|
],
|
||||||
|
38: [
|
||||||
|
{ t: "十二、体系认证证书", h: 1 },
|
||||||
|
{ id: "b1-iso", t: "ISO/IEC 27001 信息安全管理体系认证证书 证书编号:01226IS0458 有效期至:2026年11月15日" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
B2: {
|
||||||
|
12: [
|
||||||
|
{ t: "技术偏离表(采购包1)", h: 1 },
|
||||||
|
{ t: "第 11 行 CPU:2颗海光 7380 32C 2.7GHz 无偏离", faint: true },
|
||||||
|
{ id: "b2-mem", t: "第 12 行 内存:256G DDR4 无偏离" },
|
||||||
|
{ t: "第 13 行 系统盘:2块960G SSD 无偏离", faint: true }
|
||||||
|
],
|
||||||
|
58: [
|
||||||
|
{ t: "核心交换机技术响应", h: 1 },
|
||||||
|
{ id: "b2-bfd", t: "支持硬件BFD,最小报文发送间隔3.3ms,满足要求。" }
|
||||||
|
],
|
||||||
|
77: [
|
||||||
|
{ t: "排队叫号一体机技术响应", h: 1 },
|
||||||
|
{ id: "b2-screen", t: "显示屏:21英寸 LED 背光液晶屏,分辨率1920×1080。" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
B3: {
|
||||||
|
1: [
|
||||||
|
{ t: "开标一览表", h: 1 },
|
||||||
|
{ t: "项目名称:某市政务服务中心智慧大厅建设项目 采购包:1" },
|
||||||
|
{ id: "b3-total", t: "投标总价(小写):2876000.00 元 (大写):贰佰捌拾柒万陆仟元整" }
|
||||||
|
],
|
||||||
|
3: [
|
||||||
|
{ t: "分项价格表(采购包1)", h: 1 },
|
||||||
|
{ id: "b3-sum", t: "合计:2868000.00 元" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/* 清单:cat = qual 资格 / invalid 无效条款 / score 评分 / date 日期与递交 / param 技术参数 */
|
||||||
|
categories: [
|
||||||
|
{ id: "qual", name: "资格条件" },
|
||||||
|
{ id: "invalid", name: "无效条款" },
|
||||||
|
{ id: "score", name: "评分办法" },
|
||||||
|
{ id: "date", name: "日期与递交" },
|
||||||
|
{ id: "param", name: "技术参数" }
|
||||||
|
],
|
||||||
|
checklist: [
|
||||||
|
{ id: "c1", cats: ["qual", "invalid"], title: "营业执照等主体资格证明文件(扫描件)", lot: 0, src: "ai", cite: ["T", 23, "t23-license"] },
|
||||||
|
{ id: "c2", cats: ["qual", "invalid"], title: "财务状况报告:资产负债表 + 利润表月报表(开标前 6 个月内)", lot: 0, src: "ai", cite: ["T", 23, "t23-fin"] },
|
||||||
|
{ id: "c3", cats: ["qual", "invalid"], title: "依法缴纳税收和社会保障资金的材料(开标前 6 个月内任 1 月)", lot: 0, src: "ai", cite: ["T", 23, "t23-tax"] },
|
||||||
|
{ id: "c4", cats: ["qual", "invalid"], title: "具备履行合同所必需的设备和专业技术能力的书面声明(加盖电子签章)", lot: 0, src: "ai", cite: ["T", 23, "t23-ability"] },
|
||||||
|
{ id: "c5", cats: ["qual", "invalid"], title: "前 3 年内无重大违法记录的书面声明(加盖电子签章)", lot: 0, src: "ai", cite: ["T", 23, "t23-record"] },
|
||||||
|
{ id: "c6", cats: ["qual"], title: "信用记录:未被列入失信被执行人等名单", lot: 0, src: "ai", cite: ["T", 23, "t23-credit"] },
|
||||||
|
{ id: "c7", cats: ["invalid"], title: "投标文件按要求签署、盖章", lot: 0, src: "ai", cite: ["T", 18, "t18-seal"] },
|
||||||
|
{ id: "c8", cats: ["invalid"], title: "报价不超过采购包 1 最高限价 286.00 万元", lot: 1, src: "ai", cite: ["T", 2, "t2-cap", "采购包1:286.00万元"] },
|
||||||
|
{ id: "c9", cats: ["invalid"], title: "只能提交一个投标报价", lot: 0, src: "ai", cite: ["T", 21, "t21-quote"] },
|
||||||
|
{ id: "c10", cats: ["invalid", "date"], title: "投标有效期不少于 90 天", lot: 0, src: "ai", cite: ["T", 21, "t21-valid"] },
|
||||||
|
{ id: "c11", cats: ["invalid"], title: "《法定代表人授权委托书》签字或盖章并加盖单位电子签章", lot: 0, src: "ai", cite: ["T", 24, "t24-auth"] },
|
||||||
|
{ id: "c12", cats: ["score"], title: "技术规格 40 分:▲ 参数每负偏离一项扣 2 分", lot: 0, src: "ai", cite: ["T", 31, "t31-tech"] },
|
||||||
|
{ id: "c13", cats: ["score"], title: "项目实施方案 20 分", lot: 0, src: "ai", cite: ["T", 31, "t31-plan"] },
|
||||||
|
{ id: "c14", cats: ["score"], title: "体系认证 6 分:ISO9001 / ISO27001 / ISO20000,投标截止日在有效期内", lot: 0, src: "ai", cite: ["T", 31, "t31-iso"] },
|
||||||
|
{ id: "c15", cats: ["score"], title: "类似业绩 8 分:2023 年以来政务服务大厅智能化项目", lot: 0, src: "ai", cite: ["T", 31, "t31-case"] },
|
||||||
|
{ id: "c16", cats: ["date"], title: "提交投标文件截止时间:2026年11月20日 09:30", old: "2026年11月18日 09:30", lot: 0, src: "addendum", srcName: "补遗 1", cite: ["A1", 1, "a1-deadline"], updated: true },
|
||||||
|
{ id: "c17", cats: ["date"], title: "递交方式:电子交易平台在线递交,CA 加密", lot: 0, src: "ai", cite: ["T", 6, "t6-place"] },
|
||||||
|
{ id: "c18", cats: ["date"], title: "投标保证金:不收取", lot: 0, src: "ai", cite: ["T", 21, "t21-deposit"] },
|
||||||
|
{ id: "c19", cats: ["param", "invalid"], imp: "★", title: "应用服务器:内存 ≥ 512G DDR4", lot: 1, src: "ai", cite: ["T", 41, "t41-server", "内存: ≥512G DDR4"] },
|
||||||
|
{ id: "c20", cats: ["param", "invalid"], imp: "★", title: "应用服务器:国产自主可控 CPU,提供国产化适配承诺函", lot: 1, src: "ai", cite: ["T", 41, "t41-cpu"] },
|
||||||
|
{ id: "c21", cats: ["param"], imp: "★", title: "应用服务器:系统盘 2 块 ≥ 960G SSD", old: "2 块 ≥ 480G SSD", lot: 1, src: "addendum", srcName: "补遗 1", cite: ["A1", 1, "a1-param"], updated: true },
|
||||||
|
{ id: "c22", cats: ["param", "invalid"], imp: "★", title: "核心交换机:支持硬件 BFD,最小发送间隔 ≤ 50ms,提供证明材料", lot: 1, src: "ai", cite: ["T", 44, "t44-bfd", "最小报文发送间隔≤50ms,提供相关证明材料"] },
|
||||||
|
{ id: "c23", cats: ["param"], imp: "▲", title: "核心交换机:交换容量 ≥ 19.2Tbps,包转发率 ≥ 2880Mpps", lot: 1, src: "ai", cite: ["T", 44, "t44-port"] },
|
||||||
|
{ id: "c24", cats: ["param"], imp: "", title: "排队叫号一体机:显示屏 ≥ 21.5 英寸", lot: 1, src: "ai", cite: ["T", 47, "t47-screen", "显示屏尺寸≥21.5英寸"] },
|
||||||
|
{ id: "c25", cats: ["param"], imp: "▲", title: "自助服务终端:不少于 3 年原厂质保,提供承诺函", lot: 1, src: "ai", cite: ["T", 47, "t47-ups"] },
|
||||||
|
{ id: "c26", cats: ["qual"], title: "不接受联合体投标", lot: 0, src: "manual", edited: true, cite: ["T", 2, "t2-joint"] }
|
||||||
|
],
|
||||||
|
hiddenLotItems: 9, /* 采购包 2 的条目数(未投,折叠) */
|
||||||
|
pending: [
|
||||||
|
{ id: "q1", title: "投标文件须胶装成册并逐页加盖骑缝章", reason: "未找到出处", src: "ai" },
|
||||||
|
{ id: "q2", imp: "★", title: "核心产品须提供原厂授权书", reason: "把握不大", src: "ai", cite: ["T", 44, "t44-port"] }
|
||||||
|
],
|
||||||
|
|
||||||
|
/* 检查结果(第 2 次检查)。sev = fatal / major / hint;status = todo / ok / fixed / ignored */
|
||||||
|
check: {
|
||||||
|
no: 2, time: "11月2日 14:20", rules: "IT 集成 2026.11",
|
||||||
|
files: "商务册 v3、技术册 v3、报价册 v2",
|
||||||
|
resolved: 2,
|
||||||
|
findings: [
|
||||||
|
{ id: "f1", sev: "fatal", item: "c8", title: "报价超过采购包 1 的最高限价", flag: "新增",
|
||||||
|
text: ["开标一览表报价 287.60 万元,采购包 1 最高限价 286.00 万元,超出 1.60 万元。", "这一项会导致投标无效。请核对开标一览表的报价。"],
|
||||||
|
cites: [["T", 2, "t2-cap", "采购包1:286.00万元"], ["B3", 1, "b3-total", "2876000.00 元"]] },
|
||||||
|
{ id: "f2", sev: "fatal", item: "c19", title: "★ 条款疑似不满足:应用服务器内存", flag: "沿用 · 上次标记为已修改,问题仍在",
|
||||||
|
text: ["技术偏离表第 12 行响应「内存 256G DDR4」,要求「≥512G DDR4」。", "这一项会导致投标无效。请在技术偏离表第 12 行和对应产品参数中更正内存配置。"],
|
||||||
|
cites: [["T", 41, "t41-server", "内存: ≥512G DDR4"], ["B2", 12, "b2-mem", "内存:256G DDR4"]] },
|
||||||
|
{ id: "f3", sev: "fatal", item: "c11", title: "授权委托书的单位签章位置未发现盖章", flag: "新增",
|
||||||
|
text: ["商务册第 5 页《法定代表人授权委托书》的「投标人(电子签章)」位置是空的。本检查只看有无,不鉴定真伪。", "这一项会导致投标无效。请在递交前补盖单位电子签章。"],
|
||||||
|
cites: [["T", 24, "t24-auth", "加盖单位电子签章"], ["B1", 5, "b1-auth-seal", "投标人(电子签章):(签章位置为空)"]] },
|
||||||
|
{ id: "f4", sev: "major", item: "c22", title: "缺少 ★ 参数的证明材料:核心交换机硬件 BFD",
|
||||||
|
text: ["已响应「支持硬件 BFD」,但没有找到官网参数截图等证明材料。请在技术册补充证明材料。"],
|
||||||
|
cites: [["T", 44, "t44-bfd", "提供相关证明材料(包含但不限于产品官网公开参数截图证明等)"], ["B2", 58, "b2-bfd", "支持硬件BFD"]], missing: "证明材料未找到 · 已查找:技术册(全部章节)、商务册(附件)" },
|
||||||
|
{ id: "f5", sev: "major", item: "c8", title: "开标一览表与分项价格表合计不一致",
|
||||||
|
text: ["开标一览表 287.60 万元,分项价格表合计 286.80 万元,相差 0.80 万元。", "按招标文件规定,两者不一致时以开标一览表为准,不作无效处理;但会影响价格评审。请核对两表。"],
|
||||||
|
cites: [["B3", 1, "b3-total", "2876000.00 元"], ["B3", 3, "b3-sum", "2868000.00 元"]] },
|
||||||
|
{ id: "f6", sev: "major", item: "c14", title: "资质过期预警:ISO27001 证书在投标截止日前到期", flag: "新增",
|
||||||
|
text: ["证书有效期至 2026年11月15日,早于投标截止日 11月20日。按评分办法,这一项可能不得分。", "请上传换发后的证书,或在资质库更新有效期。"],
|
||||||
|
cites: [["T", 31, "t31-iso", "以投标截止日在有效期内为准"], ["B1", 38, "b1-iso", "有效期至:2026年11月15日"]] },
|
||||||
|
{ id: "f7", sev: "hint", item: "c10", title: "投标函落款日期晚于投标截止日", flag: "沿用", status: "ok", by: "王敏", at: "11月1日 16:05",
|
||||||
|
text: ["投标函落款「2026年11月25日」,投标截止时间是 2026年11月20日 09:30。"],
|
||||||
|
cites: [["B1", 3, "b1-date", "日期:2026年11月25日"]] },
|
||||||
|
{ id: "f8", sev: "hint", item: "c15", title: "投标文件里出现了其他项目的名称", flag: "新增",
|
||||||
|
text: ["商务册第 27 页出现「某区智慧校园一期项目」及其项目编号。如果是业绩介绍可以忽略;如果是照搬旧标书留下的,请修改。"],
|
||||||
|
cites: [["B1", 27, "b1-oldname", "某区智慧校园一期项目(项目编号 QJ-2025-0112)"]] },
|
||||||
|
{ id: "f9", sev: "hint", item: "c24", title: "一般参数疑似不满足:叫号一体机显示屏尺寸",
|
||||||
|
text: ["技术册第 77 页响应「21英寸」,要求「≥21.5英寸」。一般参数负偏离每项扣 0.5 分。"],
|
||||||
|
cites: [["T", 47, "t47-screen", "显示屏尺寸≥21.5英寸"], ["B2", 77, "b2-screen", "21英寸"]] }
|
||||||
|
],
|
||||||
|
manual: [
|
||||||
|
{ id: "m1", title: "电子投标文件已使用 CA 数字证书加密", cite: ["T", 21, "t21-copies"] },
|
||||||
|
{ id: "m2", title: "在截止时间前通过电子交易平台上传,并取得回执", cite: ["T", 6, "t6-place"] },
|
||||||
|
{ id: "m3", title: "加密使用的 CA 证书在有效期内,开标时可正常解密", cite: ["T", 21, "t21-copies"] }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
projects: [
|
||||||
|
{ id: "p1", hoursLeft: 408, name: "某市政务服务中心智慧大厅建设项目", buyer: "某市政务服务管理办公室", lot: "采购包 1", due: "11月20日 09:30 · 剩 17 天", dueCls: "", status: "已检查", ready: "未就绪", fatal: 3, href: "results.html" },
|
||||||
|
{ id: "p2", hoursLeft: 53, name: "某区智慧社区平台运维服务", buyer: "某区民政局", lot: "全部", due: "剩 2 天 5 小时", dueCls: "due-soon", status: "已检查", ready: "就绪", fatal: 0, href: "project.html" },
|
||||||
|
{ id: "p3", hoursLeft: 7, name: "某县数据中心机房改造项目", buyer: "某县大数据管理局", lot: "标段 1", due: "今天 17:00 截止", dueCls: "due-today", status: "编制中", ready: "", fatal: 0, href: "checklist.html" },
|
||||||
|
{ id: "p4", hoursLeft: 600, name: "某市医保信息系统等保测评服务", buyer: "某市医疗保障局", lot: "全部", due: "11月28日 10:00 · 剩 25 天", dueCls: "", status: "跟进中", ready: "", fatal: 0, href: "project-new.html" },
|
||||||
|
{ id: "p5", hoursLeft: 9999, name: "某区智慧校园一期", buyer: "某区教育局", lot: "全部", due: "已截止", dueCls: "due-past", status: "已递交", ready: "", fatal: 0, href: "project.html" },
|
||||||
|
{ id: "p6", hoursLeft: 9999, name: "某市不动产登记信息平台升级", buyer: "某市自然资源和规划局", lot: "采购包 2", due: "已截止", dueCls: "due-past", status: "已归档", ready: "", fatal: 0, href: "project.html" }
|
||||||
|
]
|
||||||
|
};
|
||||||
82
docs/prototype/bid-check.html
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>上传投标文件并检查 · 标博士原型</title>
|
||||||
|
<link rel="stylesheet" href="../design/system/tokens.css">
|
||||||
|
<link rel="stylesheet" href="../design/system/fonts-local.css">
|
||||||
|
<link rel="stylesheet" href="../design/system/components.css">
|
||||||
|
<link rel="stylesheet" href="assets/app.css">
|
||||||
|
<style>
|
||||||
|
.narrow{max-width:860px}
|
||||||
|
.vol{display:grid;grid-template-columns:40px minmax(0,1fr) auto auto;gap:var(--sp-3);align-items:center;padding:var(--sp-3) var(--sp-4);background:var(--bg-surface);border:1px solid var(--border-subtle);border-radius:var(--radius-sm)}
|
||||||
|
.vol + .vol{margin-top:var(--sp-2)}
|
||||||
|
.vol.new{border-color:var(--c-ink)}
|
||||||
|
.vol.bad{border-color:var(--warn);background:var(--warn-soft)}
|
||||||
|
.vol .ext{font:600 11px/1 var(--font-figures);border:1px solid var(--border-control);border-radius:2px;padding:4px 0;text-align:center}
|
||||||
|
.scope{display:grid;grid-template-columns:1fr 1fr;gap:var(--sp-4)}
|
||||||
|
.scope ul{margin:var(--sp-1) 0 0;padding-left:1.2em}
|
||||||
|
.scope li{margin:2px 0}
|
||||||
|
.foot-bar{display:flex;align-items:center;gap:var(--sp-3);margin-top:var(--sp-6);padding-top:var(--sp-4);border-top:1px solid var(--border-subtle)}
|
||||||
|
.foot-bar .cost{margin-right:auto}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<div class="content narrow">
|
||||||
|
<div class="page-title"><div><h1>上传投标文件并检查</h1><p>只处理加密前的 Word / PDF 稿。每次重新上传都是一个新版本,检查结果关联到具体版本。</p></div></div>
|
||||||
|
<div class="alert"><span class="ico i">i</span><p>清单 v3 还是草稿(补遗 1 并入后没有重新确认),检查结果以当前清单为准。<a class="plain" href="checklist.html">去确认清单</a></p></div>
|
||||||
|
|
||||||
|
<div class="section-h"><h2>投标文件分册</h2><span class="more"><button class="btn sm" data-add>上传新版本</button></span></div>
|
||||||
|
<div data-vols></div>
|
||||||
|
|
||||||
|
<div class="section-h"><h2>这次会检查什么</h2></div>
|
||||||
|
<div class="card scope">
|
||||||
|
<div><b>会查</b><ul><li>清单 <span class="fig">26</span> 条的响应完整性(未响应 / 负偏离未说明 / 疑似不满足)</li><li>形式要件:格式文件是否齐全、签章位置有没有盖章、落款日期、其他项目名称</li><li>资质匹配:按投标截止日判断证书是否过期(用你的资质库)</li><li>报价形式:限价三层、开标一览表与分项合计、大小写</li></ul></div>
|
||||||
|
<div><b>不查</b><ul><li>份数、密封、CA 加密、递交回执(列为「需人工核对」)</li><li>签章真伪</li><li>报价是否合理、报多少</li><li>评分高低</li></ul></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="foot-bar">
|
||||||
|
<span class="cost">24 小时内复查不扣额度:从上次扣点的检查(11-01 16:00)算起,至今天 16:00</span>
|
||||||
|
<a class="btn" href="project.html">取消</a>
|
||||||
|
<button class="btn primary" data-start>开始检查</button>
|
||||||
|
</div>
|
||||||
|
<div data-run></div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
<script src="assets/data.js"></script>
|
||||||
|
<script src="assets/app.js"></script>
|
||||||
|
<script>
|
||||||
|
const { D, $, toast } = App;
|
||||||
|
App.shell({ crumbs: [["工作台", "workbench.html"], [D.project.name, "project.html"], ["上传投标文件并检查", ""]] });
|
||||||
|
const vols = [
|
||||||
|
{ ext: "DOCX", name: "商务册", file: "商务册-v3.docx", ver: "v3", info: "64 页 · 11-02 14:12" },
|
||||||
|
{ ext: "DOCX", name: "技术册", file: "技术册-v3.docx", ver: "v3", info: "212 页 · 11-02 14:12" },
|
||||||
|
{ ext: "DOCX", name: "报价册", file: "报价册-v2.docx", ver: "v2", info: "8 页 · 11-01 15:40" }
|
||||||
|
];
|
||||||
|
function render() {
|
||||||
|
$("[data-vols]").innerHTML = vols.map((v) => `<div class="vol ${v.cls || ""}"><span class="ext">${v.ext}</span>
|
||||||
|
<div><b>${v.name}</b> <span class="tag">${v.ver}</span>${v.cls === "new" ? ' <span class="flag">新版本</span>' : ""}<div class="cap ${v.cls === "bad" ? "" : "muted"}" ${v.cls === "bad" ? 'style="color:var(--warn-text)"' : ""}>${v.file} · ${v.info}</div></div>
|
||||||
|
<select class="input" style="width:120px;height:28px" aria-label="分册类型" ${v.cls === "bad" ? "disabled" : ""}><option>${v.name}</option><option>商务册</option><option>技术册</option><option>报价册</option><option>资格证明册</option></select>
|
||||||
|
<button class="btn sm text" data-rm="${v.file}">移除</button></div>`).join("");
|
||||||
|
}
|
||||||
|
render();
|
||||||
|
$("[data-add]").onclick = () => {
|
||||||
|
vols[2] = { ext: "DOCX", name: "报价册", file: "报价册-v3.docx", ver: "v3", info: "8 页 · 刚刚上传", cls: "new" };
|
||||||
|
vols.push({ ext: "KEDT", name: "资格证明册", file: "资格证明册.kedt", ver: "—", info: "这是电子投标客户端生成的加密文件,标博士读不了。请上传加密前的 Word 或 PDF 稿。", cls: "bad" });
|
||||||
|
render(); toast("报价册已更新为 v3;有 1 个文件无法读取");
|
||||||
|
};
|
||||||
|
document.addEventListener("click", (e) => { if (e.target.matches("[data-rm]")) { const i = vols.findIndex((v) => v.file === e.target.dataset.rm); vols.splice(i, 1); render(); } });
|
||||||
|
$("[data-start]").onclick = () => {
|
||||||
|
$("[data-start]").disabled = true; $("[data-start]").textContent = "正在检查…";
|
||||||
|
const S = ["解析投标文件分册", "逐条找响应", "形式要件与签章位置", "资质匹配", "报价形式", "沿用上次处理状态"];
|
||||||
|
let i = 0;
|
||||||
|
const draw = () => $("[data-run]").innerHTML = `<div class="card" style="margin-top:var(--sp-4)"><ol class="steps">${S.map((s, k) => `<li class="${k < i ? "done" : k === i ? "now" : "todo"}"><span class="dot">${k < i ? "✓" : ""}</span>${s}</li>`).join("")}</ol>
|
||||||
|
${i >= S.length ? `<div class="alert done" style="margin-top:var(--sp-3)"><span class="ico ok">✓</span><p><b>第 3 次检查完成,未扣额度。</b>致命 3 · 重要 3 · 提示 3;新增 0,沿用 7,本次已解决 2。</p></div><div class="row" style="margin-top:var(--sp-3)"><a class="btn primary" href="results.html">查看检查结果</a></div>` : '<p class="note">可以离开本页,完成后会发站内通知。</p>'}</div>`;
|
||||||
|
const tick = () => { draw(); if (i++ < S.length) setTimeout(tick, 700); };
|
||||||
|
tick();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
184
docs/prototype/checklist.html
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>读标 · 标博士原型</title>
|
||||||
|
<link rel="stylesheet" href="../design/system/tokens.css">
|
||||||
|
<link rel="stylesheet" href="../design/system/fonts-local.css">
|
||||||
|
<link rel="stylesheet" href="../design/system/components.css">
|
||||||
|
<link rel="stylesheet" href="assets/app.css">
|
||||||
|
<style>
|
||||||
|
.cl-bar{display:flex;align-items:center;gap:var(--sp-4);padding:var(--sp-4) var(--sp-8);border-bottom:1px solid var(--border-subtle);background:var(--bg-page)}
|
||||||
|
.cl-bar h2{font:var(--fw-bold) var(--fs-h2)/var(--lh-h2) var(--font-serif);margin:0}
|
||||||
|
.cl-bar .actions{margin-left:auto;display:flex;gap:var(--sp-2);align-items:center}
|
||||||
|
main.main{height:100vh;overflow:hidden}
|
||||||
|
.split{display:grid;grid-template-columns:200px minmax(0,1fr) minmax(420px,45%);flex:1;min-height:0}
|
||||||
|
.cats{border-right:1px solid var(--border-subtle);padding:var(--sp-4) var(--sp-3);overflow:auto}
|
||||||
|
.cats button{display:flex;width:100%;align-items:center;justify-content:space-between;height:34px;padding:0 var(--sp-3);border:0;background:none;border-radius:var(--radius-sm);font:var(--fs-body) var(--font-sans);color:var(--text-primary);cursor:pointer;text-align:left}
|
||||||
|
.cats button:hover{background:var(--bg-hover)}
|
||||||
|
.cats button.on{background:var(--bg-surface);box-shadow:inset 2px 0 0 var(--c-ink);font-weight:var(--fw-bold)}
|
||||||
|
.cats .fig{font-weight:500;color:var(--text-secondary)}
|
||||||
|
.cats hr{border:0;border-top:1px solid var(--border-subtle);margin:var(--sp-3) 0}
|
||||||
|
.cats .hint{font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--text-secondary);padding:0 var(--sp-3)}
|
||||||
|
.list{overflow:auto;padding:var(--sp-4) var(--sp-6) var(--sp-12)}
|
||||||
|
.list .alert{margin-bottom:var(--sp-4)}
|
||||||
|
.group{margin-top:var(--sp-5)}
|
||||||
|
.group > h3{font:var(--fw-bold) var(--fs-title)/var(--lh-title) var(--font-sans);margin:0 0 var(--sp-2);display:flex;gap:var(--sp-2);align-items:baseline}
|
||||||
|
.group > h3 .fig{font-weight:500;color:var(--text-secondary);font-size:14px}
|
||||||
|
.items{background:var(--bg-surface);border:1px solid var(--border-subtle);border-radius:var(--radius-sm);padding:0 var(--sp-4)}
|
||||||
|
.item{cursor:pointer;margin:0 calc(-1 * var(--sp-4));padding:var(--sp-3) var(--sp-4)}
|
||||||
|
.item:hover{background:var(--bg-hover)}
|
||||||
|
.item.sel{background:var(--ai-soft);box-shadow:inset 3px 0 0 var(--ai)}
|
||||||
|
.item .upd{margin-top:6px;display:flex;gap:var(--sp-2);align-items:center;font-size:var(--fs-caption)}
|
||||||
|
.item .upd .btn{height:24px;font-size:var(--fs-caption)}
|
||||||
|
.right{border-left:1px solid var(--border-subtle);display:flex;min-height:0}
|
||||||
|
.right .viewer{flex:1;border:0;border-radius:0}
|
||||||
|
.lot-note{font-size:var(--fs-caption);color:var(--text-secondary)}
|
||||||
|
.status-line{display:flex;gap:var(--sp-2);align-items:center}
|
||||||
|
.status-line .sign{margin:0;font-size:var(--fs-caption)}
|
||||||
|
.hist{width:100%;border-collapse:collapse;font-size:var(--fs-body)}
|
||||||
|
.hist td,.hist th{text-align:left;padding:6px 8px;border-bottom:1px solid var(--border-subtle);vertical-align:top}
|
||||||
|
.hist th{font-size:var(--fs-caption);color:var(--text-secondary);font-weight:var(--fw-bold)}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<div class="cl-bar">
|
||||||
|
<h2>项目检查清单</h2>
|
||||||
|
<div class="status-line" data-status></div>
|
||||||
|
<div class="actions">
|
||||||
|
<a class="plain cap" href="#" data-missing>反馈:有遗漏的要求</a>
|
||||||
|
<button class="btn" data-add>新增条目</button>
|
||||||
|
<button class="btn primary" data-confirm>确认清单</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="split">
|
||||||
|
<nav class="cats" aria-label="清单分类" data-cats></nav>
|
||||||
|
<div class="list" data-list></div>
|
||||||
|
<div class="right"><div data-viewer></div></div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<script src="assets/data.js"></script>
|
||||||
|
<script src="assets/app.js"></script>
|
||||||
|
<script>
|
||||||
|
const { D, $, $$, esc, dialog, toast } = App;
|
||||||
|
App.shell({ crumbs: [["工作台", "workbench.html"], [D.project.name, "project.html"], ["读标", ""]], tab: "checklist" });
|
||||||
|
|
||||||
|
const V = App.viewer($("[data-viewer]"));
|
||||||
|
const items = D.checklist.map((c) => ({ ...c }));
|
||||||
|
let pending = D.pending.map((q) => ({ ...q }));
|
||||||
|
let cat = "all", sel = null, confirmed = false;
|
||||||
|
const SRC = { ai: '<span class="src-ai">AI 提取</span>', manual: "人工添加" };
|
||||||
|
|
||||||
|
function status() {
|
||||||
|
$("[data-status]").innerHTML = confirmed
|
||||||
|
? `<span class="tag status">已确认 v3</span><span class="sign"><span>✓</span><b>${D.user.name}</b><span class="fig">11月2日 15:12</span>确认</span>`
|
||||||
|
: `<span class="tag">草稿 v3</span><span class="cap muted">补遗 1 并入后待重新确认 · 上一版 v2 已确认</span>`;
|
||||||
|
$("[data-confirm]").hidden = confirmed;
|
||||||
|
}
|
||||||
|
function cats() {
|
||||||
|
const n = (id) => items.filter((i) => i.cats.includes(id)).length;
|
||||||
|
$("[data-cats]").innerHTML =
|
||||||
|
`<button data-cat="all" class="${cat === "all" ? "on" : ""}">全部<span class="fig">${items.length}</span></button>` +
|
||||||
|
D.categories.map((c) => `<button data-cat="${c.id}" class="${cat === c.id ? "on" : ""}">${c.name}<span class="fig">${n(c.id)}</span></button>`).join("") +
|
||||||
|
`<hr><button data-cat="pending" class="${cat === "pending" ? "on" : ""}" style="color:var(--ai)">待确认<span class="fig" style="color:var(--ai)">${pending.length}</span></button>
|
||||||
|
<hr><p class="hint">一条要求可以属于多个分类,例如 ★ 参数同时在「技术参数」和「无效条款」里。</p>`;
|
||||||
|
}
|
||||||
|
function itemRow(i) {
|
||||||
|
const src = i.src === "addendum" ? i.srcName : SRC[i.src];
|
||||||
|
const upd = i.updated ? `<div class="upd"><span class="flag">已被${i.srcName}更新 · 原为 <s>${esc(i.old)}</s></span><button class="btn" data-ack="${i.id}">已核对</button></div>` : "";
|
||||||
|
const meta = i.cats.map((c) => `<span class="tag">${D.categories.find((x) => x.id === c).name}</span>`).join("") +
|
||||||
|
`<span class="tag">${i.lot ? "采购包 " + i.lot : "通用"}</span>` + (i.edited ? `<span class="flag">已修改 · <a class="plain" href="#" data-hist="${i.id}">查看记录</a></span>` : "");
|
||||||
|
return `<div class="item ${sel === i.id ? "sel" : ""}" data-item="${i.id}" tabindex="0">
|
||||||
|
<span class="imp">${i.imp || ""}</span>
|
||||||
|
<div><div class="t">${esc(i.title)}</div><div class="meta">${meta}</div>${upd}</div>
|
||||||
|
<div class="side">${src}<br><a class="cite-src" href="#">${App.citeLabel(i.cite).replace(/^.*? · /, "")}</a></div>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
function pendingBox() {
|
||||||
|
if (!pending.length) return "";
|
||||||
|
return `<div class="pending" style="margin-bottom:var(--sp-5)"><h4>待确认(<span class="fig">${pending.length}</span>)</h4>
|
||||||
|
<p>这些结果没有找到可靠出处,或系统把握不大。确认之前,不参与检查。</p>
|
||||||
|
${pending.map((q) => `<div class="item" style="cursor:default" data-q="${q.id}"><span class="imp">${q.imp || ""}</span>
|
||||||
|
<div><div class="t">${esc(q.title)}</div><div class="meta"><span class="src-ai cap">AI 提取 · ${q.reason}</span>${q.cite ? `<a class="cite-src" href="#" data-qopen="${q.id}" style="display:inline">第 <span class="fig">${q.cite[1]}</span> 页</a>` : ""}</div>
|
||||||
|
<div class="f-actions">${q.cite ? '<button class="btn sm" data-qa="adopt">采纳</button>' : '<button class="btn sm" data-qa="cite">补上出处后采纳</button>'}<button class="btn sm" data-qa="edit">修改后采纳</button><button class="btn sm text" data-qa="drop">丢弃</button></div></div><span></span></div>`).join("")}</div>`;
|
||||||
|
}
|
||||||
|
function list() {
|
||||||
|
const head = `<div class="alert"><span class="ico i">i</span><p>实质性条款的标注规则:<b>打「★」的内容为实质性要求</b>(招标文件 <a class="cite-src" href="#" data-rule style="display:inline">第 <span class="fig">24</span> 页</a>)。文件没有写明 ★ 条款的总数,已识别采购包 1 和通用部分的 ★ 条款 <span class="fig">4</span> 条,请对照原文核对有无遗漏。</p></div>`;
|
||||||
|
const lot = `<p class="lot-note" style="margin:0 0 var(--sp-3)">显示:通用 + 采购包 1 · 采购包 2 的 <span class="fig" style="font-weight:500">${D.hiddenLotItems}</span> 条已隐藏 · <a class="plain" href="lots.html">更改所投的包</a></p>`;
|
||||||
|
if (cat === "pending") return $("[data-list]").innerHTML = pendingBox() || '<p class="muted">没有待确认的条目。</p>';
|
||||||
|
const groups = cat === "all" ? D.categories : D.categories.filter((c) => c.id === cat);
|
||||||
|
const seen = new Set();
|
||||||
|
$("[data-list]").innerHTML = lot + (cat === "all" ? head + pendingBox() : "") + groups.map((g) => {
|
||||||
|
// 「全部」视图下每条只出现一次,放在它的第一个分类里
|
||||||
|
const rows = items.filter((i) => i.cats.includes(g.id) && (cat !== "all" || !seen.has(i.id)));
|
||||||
|
rows.forEach((i) => seen.add(i.id));
|
||||||
|
return rows.length ? `<section class="group"><h3>${g.name}<span class="fig">${rows.length}</span></h3><div class="items">${rows.map(itemRow).join("")}</div></section>` : "";
|
||||||
|
}).join("");
|
||||||
|
}
|
||||||
|
function render() { status(); cats(); list(); }
|
||||||
|
|
||||||
|
document.addEventListener("click", (e) => {
|
||||||
|
const t = e.target;
|
||||||
|
if (t.closest("[data-cat]")) { cat = t.closest("[data-cat]").dataset.cat; render(); return; }
|
||||||
|
if (t.matches("[data-ack]")) { const i = items.find((x) => x.id === t.dataset.ack); i.updated = false; i.edited = true; render(); toast("已核对补遗更新,记录已保存"); return; }
|
||||||
|
if (t.matches("[data-hist]")) { e.preventDefault(); history(t.dataset.hist); return; }
|
||||||
|
if (t.closest("[data-rule]")) { e.preventDefault(); V.open(["T", 24, "t24-mark"]); return; }
|
||||||
|
if (t.closest("[data-qopen]")) { e.preventDefault(); const q = pending.find((x) => x.id === t.closest("[data-qopen]").dataset.qopen); V.open(q.cite); return; }
|
||||||
|
if (t.matches("[data-qa]")) { pendingAct(t.closest("[data-q]").dataset.q, t.dataset.qa); return; }
|
||||||
|
const it = t.closest("[data-item]");
|
||||||
|
if (it) { e.preventDefault(); sel = it.dataset.item; const i = items.find((x) => x.id === sel); $$(".item.sel").forEach((x) => x.classList.remove("sel")); it.classList.add("sel"); V.open(i.cite); }
|
||||||
|
});
|
||||||
|
document.addEventListener("keydown", (e) => { if (e.key === "Enter" && e.target.matches("[data-item]")) e.target.click(); });
|
||||||
|
|
||||||
|
function pendingAct(id, act) {
|
||||||
|
const q = pending.find((x) => x.id === id);
|
||||||
|
if (act === "drop") { pending = pending.filter((x) => x !== q); render(); toast(`已丢弃「${esc(q.title)}」 · 可在修改记录中恢复`); return; }
|
||||||
|
if (act === "adopt") { adopt(q, q.cite, false); return; }
|
||||||
|
dialog({
|
||||||
|
title: act === "cite" ? "补上出处后采纳" : "修改后采纳",
|
||||||
|
body: `<div class="field"><label for="qt">要求</label><input class="input" id="qt" value="${esc(q.title)}"></div>
|
||||||
|
<div class="field" style="margin-top:var(--sp-4)"><label for="qp">出处页码 <small>(招标文件)</small></label><input class="input" id="qp" placeholder="例如 26" style="width:120px"></div>
|
||||||
|
<label class="row" style="gap:8px;margin-top:var(--sp-3)"><input type="checkbox" id="qn" style="accent-color:var(--c-ink)"> 找不到出处,按「人工确认、无出处」采纳</label>`,
|
||||||
|
ok: "采纳",
|
||||||
|
onOk: (s) => { const pg = $("#qp", s).value.trim(); q.title = $("#qt", s).value.trim() || q.title; adopt(q, pg ? ["T", +pg, ""] : null, !pg); }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function adopt(q, c, noCite) {
|
||||||
|
pending = pending.filter((x) => x !== q);
|
||||||
|
items.push({ id: "n" + q.id, cats: ["invalid"], imp: q.imp, title: q.title, lot: 0, src: "manual", edited: true, cite: c || ["T", 1, ""] });
|
||||||
|
render();
|
||||||
|
toast(noCite ? "已采纳,标注为「人工确认、无出处」" : "已采纳,进入正式清单");
|
||||||
|
}
|
||||||
|
function history(id) {
|
||||||
|
const i = items.find((x) => x.id === id);
|
||||||
|
const u = D.user.name;
|
||||||
|
const rows = id === "c26"
|
||||||
|
? [["11-01 10:42", "系统", "AI 提取", "—", "接受联合体投标"], ["11-01 11:05", u, "修改", "接受联合体投标", "不接受联合体投标"], ["11-01 11:06", u, "备注", "—", "原文写的是「否」,AI 读反了"]]
|
||||||
|
: [["11-01 10:42", "系统", "AI 提取", "—", i.old || i.title], ["11-02 14:31", "系统", "补遗 1 更新", i.old || "—", i.title], ["11-02 15:08", u, "核对补遗更新", "—", "已核对"]];
|
||||||
|
dialog({ title: "修改记录", cancel: null, ok: "关闭", wide: true,
|
||||||
|
body: `<p class="muted">${esc(i.title)}</p><table class="hist"><tr><th>时间</th><th>操作人</th><th>操作</th><th>改前</th><th>改后</th></tr>${rows.map((r) => `<tr>${r.map((c, k) => `<td${k ? "" : ' class="fig" style="font-weight:500"'}>${esc(c)}</td>`).join("")}</tr>`).join("")}</table>` });
|
||||||
|
}
|
||||||
|
$("[data-confirm]").onclick = () => dialog({
|
||||||
|
title: "确认清单 v3?",
|
||||||
|
body: `<p>确认后,查标会以这一版清单为依据。之后再修改清单,会回到草稿并保留历史,已有的检查结果不受影响。</p>${pending.length ? `<div class="alert warn"><span class="ico w">!</span><p><b>还有 ${pending.length} 条待确认。</b>它们不会进入这一版清单,也不参与检查。</p></div>` : ""}`,
|
||||||
|
ok: "确认清单",
|
||||||
|
onOk: () => { confirmed = true; render(); toast("清单 v3 已确认"); }
|
||||||
|
});
|
||||||
|
$("[data-add]").onclick = () => dialog({
|
||||||
|
title: "新增条目",
|
||||||
|
body: `<div class="field"><label for="na">要求</label><textarea class="input" id="na" placeholder="写下招标文件里的要求"></textarea></div>
|
||||||
|
<div class="field" style="margin-top:var(--sp-4)"><label for="nc">分类</label><select class="input" id="nc">${D.categories.map((c) => `<option value="${c.id}">${c.name}</option>`).join("")}</select></div>
|
||||||
|
<p class="cap muted">人工添加的条目可以没有出处,会标注「人工添加」。</p>`,
|
||||||
|
ok: "添加",
|
||||||
|
onOk: (s) => { const v = $("#na", s).value.trim(); if (!v) return false; items.push({ id: "u" + items.length, cats: [$("#nc", s).value], title: v, lot: 0, src: "manual", cite: ["T", 1, ""] }); render(); toast("已添加"); }
|
||||||
|
});
|
||||||
|
$("[data-missing]").onclick = (e) => { e.preventDefault(); dialog({ title: "反馈:有遗漏的要求", body: `<div class="field"><label for="mi">漏了什么?在第几页?</label><textarea class="input" id="mi" placeholder="例如:第 26 页要求提供原厂授权书,清单里没有"></textarea></div><p class="cap muted">反馈用于改进提取规则。如果需要把这条加进清单,请用「新增条目」。</p>`, ok: "提交反馈", onOk: () => toast("已收到反馈,谢谢") }); };
|
||||||
|
|
||||||
|
render();
|
||||||
|
V.open(items[0].cite); sel = items[0].id; list();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
82
docs/prototype/index.html
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<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="../design/logo/favicon.svg">
|
||||||
|
<link rel="stylesheet" href="../design/system/tokens.css">
|
||||||
|
<link rel="stylesheet" href="../design/system/fonts-local.css">
|
||||||
|
<link rel="stylesheet" href="../design/system/components.css">
|
||||||
|
<link rel="stylesheet" href="assets/app.css">
|
||||||
|
<style>
|
||||||
|
.page{max-width:1120px;margin:0 auto;padding:var(--sp-12) var(--sp-6) var(--sp-16)}
|
||||||
|
.page > header img{height:36px}
|
||||||
|
.page h1{font:600 32px/40px var(--font-serif);margin:var(--sp-6) 0 var(--sp-2)}
|
||||||
|
.lede{color:var(--text-secondary);max-width:52em;margin:0}
|
||||||
|
h2{font:600 var(--fs-h2)/var(--lh-h2) var(--font-serif);margin:var(--sp-12) 0 var(--sp-4);padding-bottom:var(--sp-2);border-bottom:2px solid var(--c-ink)}
|
||||||
|
.journey{display:grid;grid-template-columns:repeat(4,1fr);gap:var(--sp-3);counter-reset:s}
|
||||||
|
.journey a{display:block;background:var(--bg-surface);border:1px solid var(--border-subtle);border-radius:var(--radius-sm);padding:var(--sp-4);color:inherit;text-decoration:none;counter-increment:s;position:relative}
|
||||||
|
.journey a:hover{border-color:var(--c-ink)}
|
||||||
|
.journey a::before{content:counter(s);font:600 22px/1 var(--font-figures);color:var(--text-secondary);display:block;margin-bottom:var(--sp-2)}
|
||||||
|
.journey b{display:block;font-size:var(--fs-title)}
|
||||||
|
.journey span{font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--text-secondary);display:block;margin-top:4px}
|
||||||
|
.journey .file{position:absolute;top:var(--sp-4);right:var(--sp-4);font:12px var(--font-figures);color:var(--ai)}
|
||||||
|
table.data td{height:auto}
|
||||||
|
.two{display:grid;grid-template-columns:1fr 1fr;gap:var(--sp-6)}
|
||||||
|
.two ul{margin:0;padding-left:1.2em}
|
||||||
|
.two li{margin:4px 0}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="page">
|
||||||
|
<header><img src="../design/logo/svg/logo-horizontal.svg" alt="标博士"></header>
|
||||||
|
<h1>MVP 可点击原型 v0.1</h1>
|
||||||
|
<p class="lede">按 <code>docs/analysis/02-user-journeys.md</code> 的 J1–J5 走一遍 MVP 链路。示意场景:某市政务服务中心智慧大厅建设项目(政府采购 · 公开招标 · 2 个采购包,投采购包 1)。条款改写自苏采云样本 S02、S10,项目名、金额、页码、投标响应和人名都是示意。直接使用设计规范的变量和组件(<code>design/system/</code>),所以是中保真:重点验证流程、信息结构和红线落地,视觉细节留到 UI 设计阶段精修。</p>
|
||||||
|
|
||||||
|
<h2>J1 · 首次体验:从上传到导出报告</h2>
|
||||||
|
<div class="journey">
|
||||||
|
<a href="login.html"><span class="file">login</span><b>注册 / 登录</b><span>手机号 + 验证码,填企业名称;协议写明不得用于串通投标</span></a>
|
||||||
|
<a href="project-new.html"><span class="file">project-new</span><b>新建项目,上传招标文件</b><span>按文件头识别真实格式;加密文件直接说明原因、不扣点</span></a>
|
||||||
|
<a href="parsing.html"><span class="file">parsing</span><b>等待解析</b><span>分阶段进度,可以离开;失败自动退回额度</span></a>
|
||||||
|
<a href="lots.html"><span class="file">lots</span><b>选择所投的包</b><span>只保留通用 + 所选包的条目;分包可手工更正</span></a>
|
||||||
|
<a href="checklist.html"><span class="file">checklist</span><b>读标:浏览清单,看原文</b><span>左分类、中清单、右原文高亮;待确认区;补遗更新;确认清单</span></a>
|
||||||
|
<a href="bid-check.html"><span class="file">bid-check</span><b>上传投标文件并检查</b><span>多分册、多版本;写明查什么、不查什么;识别加密格式</span></a>
|
||||||
|
<a href="results.html"><span class="file">results</span><b>查标:逐条处理</b><span>出处批注、三种处理、致命项确认需写说明、键盘逐条处理、批量处理提示级</span></a>
|
||||||
|
<a href="report.html"><span class="file">report</span><b>导出报告</b><span>首页写覆盖范围和免责声明;logo 只在页眉</span></a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>其他页面</h2>
|
||||||
|
<table class="data">
|
||||||
|
<thead><tr><th>页面</th><th>对应</th><th>要点</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td><a class="plain" href="workbench.html">工作台</a></td><td>FR-M6-01</td><td>按截止排序;倒计时临近用赭色;就绪只在「已检查」显示;致命计数</td></tr>
|
||||||
|
<tr><td><a class="plain" href="project.html">项目概览</a></td><td>FR-M6-02、M2-09、J2</td><td>状态流;AI 摘要带出处;关键日期(补遗更新);文件与版本;标记已递交(可撤销)、归档、删除(输入项目名确认)</td></tr>
|
||||||
|
<tr><td><a class="plain" href="project-new.html?addendum=1">追加补遗</a> → <a class="plain" href="parsing.html?addendum=1">解析补遗</a></td><td>FR-M2-10、J2-1</td><td>补遗单独扣 1 次;并入清单后回到草稿,由用户核对</td></tr>
|
||||||
|
<tr><td><a class="plain" href="quota.html">额度</a></td><td>FR-M6-03、J3</td><td>扣点规则可见;流水对应到任务;预扣 / 退回成对出现;复查免扣也有记录</td></tr>
|
||||||
|
<tr><td><a class="plain" href="qualifications.html">资质库</a></td><td>FR-M6-08、J2-3</td><td>到期按项目截止日判断;缺附件显示「待补录」</td></tr>
|
||||||
|
<tr><td><a class="plain" href="../design/system/components.html">组件预览</a></td><td>设计规范</td><td>原型用到的全部组件</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h2>模拟了什么、没模拟什么</h2>
|
||||||
|
<div class="two">
|
||||||
|
<div class="card"><b>可以点、会变化</b><ul>
|
||||||
|
<li>查标:处理 / 撤销 / 忽略原因校验 / 致命项确认说明 / 批量 / 快捷键 J K O 1 2 3 U ? / 原文抽屉 / 就绪状态与页签计数联动</li>
|
||||||
|
<li>读标:分类筛选、点清单项看原文高亮、待确认区采纳与丢弃、补遗更新核对、修改记录、确认清单</li>
|
||||||
|
<li>解析与检查的进度动画、取消、失败演示</li>
|
||||||
|
<li>项目状态:标记已递交、撤销、归档、删除确认</li>
|
||||||
|
<li>报告:勾选附录、出处后实时重排</li></ul></div>
|
||||||
|
<div class="card"><b>没有做</b><ul>
|
||||||
|
<li>真实文件上传与解析(点上传区就出示例文件)</li>
|
||||||
|
<li>数据不保存,刷新后恢复初始状态</li>
|
||||||
|
<li>原文查看器只做了被引用到的页面</li>
|
||||||
|
<li>免费版的差异(报告角标、额度用完后的升级引导)只在文字里说明</li>
|
||||||
|
<li>运营后台(FR-M6-09)、通知中心、账号设置</li></ul></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>运行</h2>
|
||||||
|
<p>在 <code>docs/</code> 目录运行 <code>python3 -m http.server 8766</code>,打开 <code>http://127.0.0.1:8766/prototype/</code>。宋体和数字字体从 <code>design/logo/.work/fonts</code> 加载,没有该目录时退回系统字体。设计基准 1440 宽,最小 1280。</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
66
docs/prototype/login.html
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<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="../design/logo/favicon.svg">
|
||||||
|
<link rel="stylesheet" href="../design/system/tokens.css">
|
||||||
|
<link rel="stylesheet" href="../design/system/fonts-local.css">
|
||||||
|
<link rel="stylesheet" href="../design/system/components.css">
|
||||||
|
<link rel="stylesheet" href="assets/app.css">
|
||||||
|
<style>
|
||||||
|
body{display:grid;grid-template-columns:minmax(0,1fr) 520px;min-height:100vh;min-width:1280px}
|
||||||
|
.brand-side{padding:var(--sp-16) var(--sp-16) var(--sp-10);display:flex;flex-direction:column;border-right:1px solid var(--border-subtle)}
|
||||||
|
.brand-side img{width:200px}
|
||||||
|
.brand-side .slogan{font:600 32px/1.4 var(--font-serif);margin:var(--sp-12) 0 var(--sp-6)}
|
||||||
|
.points{list-style:none;margin:0;padding:0;max-width:34em}
|
||||||
|
.points li{padding:var(--sp-3) 0 var(--sp-3) var(--sp-4);border-left:2px solid var(--ai);margin-bottom:var(--sp-3)}
|
||||||
|
.points b{display:block;font-size:var(--fs-title)}
|
||||||
|
.points span{color:var(--text-secondary)}
|
||||||
|
.brand-side .legal{margin-top:auto;font-size:var(--fs-caption);color:var(--text-secondary)}
|
||||||
|
.form-side{background:var(--bg-surface);padding:120px var(--sp-12) var(--sp-10);display:flex;flex-direction:column}
|
||||||
|
.form-side .tabs{margin:0 0 var(--sp-8);border-bottom:1px solid var(--border-subtle)}
|
||||||
|
.code{display:flex;gap:var(--sp-2)}
|
||||||
|
.code .input{flex:1}
|
||||||
|
.agree{display:flex;gap:var(--sp-2);align-items:flex-start;font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--text-secondary);margin-top:var(--sp-5)}
|
||||||
|
.agree input{margin-top:3px;accent-color:var(--c-ink)}
|
||||||
|
.free{margin-top:var(--sp-5);padding:var(--sp-3) var(--sp-4);background:var(--bg-sunken);border-radius:var(--radius-sm);font-size:var(--fs-caption);line-height:var(--lh-caption)}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<section class="brand-side">
|
||||||
|
<img src="../design/logo/svg/logo-horizontal-textured.svg" alt="标博士 BIAOBOSHI">
|
||||||
|
<p class="slogan">标博士,你的AI投标专家</p>
|
||||||
|
<ul class="points">
|
||||||
|
<li><b>读标:每一条要求都有出处</b><span>上传招标文件,逐条提取资格、无效条款、评分、日期和 ★ 参数,点一下就跳到原文那一页。</span></li>
|
||||||
|
<li><b>查标:递交前逐条对一遍</b><span>把投标文件和清单逐条核对,找出没响应、疑似不满足、漏盖章的地方,由你逐条确认。</span></li>
|
||||||
|
<li><b>报告:谁在什么时候确认了什么</b><span>导出带处理记录的检查报告,写明查了什么、没查什么。</span></li>
|
||||||
|
</ul>
|
||||||
|
<p class="legal">辅助检查工具,不替代人工审核,也不对投标结果作任何保证。</p>
|
||||||
|
</section>
|
||||||
|
<section class="form-side">
|
||||||
|
<nav class="tabs" aria-label="登录方式"><a href="#" class="on" data-t="login">登录</a><a href="#" data-t="reg">注册</a></nav>
|
||||||
|
<div class="stack">
|
||||||
|
<div class="field"><label for="ph">手机号</label><input class="input" id="ph" inputmode="numeric" placeholder="11 位手机号" value="13800000000"></div>
|
||||||
|
<div class="field"><label for="cd">验证码</label><div class="code"><input class="input" id="cd" inputmode="numeric" placeholder="6 位数字"><button class="btn" data-send>发送验证码</button></div></div>
|
||||||
|
<div class="field" data-reg hidden><label for="co">企业名称</label><input class="input" id="co" placeholder="与开票信息一致,例如:南京某信息技术有限公司"><span class="msg">额度和数据归属企业主体,一个手机号只能属于一个企业。</span></div>
|
||||||
|
</div>
|
||||||
|
<label class="agree" data-reg hidden><input type="checkbox" id="ag"><span>我已阅读并同意《服务协议》《隐私政策》。服务协议约定:本产品只用于投标人对自己投标文件的投前自查,不得用于串通投标、帮助他人规避雷同等违法用途。</span></label>
|
||||||
|
<button class="btn primary lg" style="margin-top:var(--sp-6);width:100%;justify-content:center" data-go>登录</button>
|
||||||
|
<div class="free" data-reg hidden>注册即开通免费版:解读 <b>3</b> 次招标文件,检查 <b>1</b> 个项目(项目内复查不限次数)。不需要上传营业执照。</div>
|
||||||
|
</section>
|
||||||
|
<script src="assets/data.js"></script>
|
||||||
|
<script src="assets/app.js"></script>
|
||||||
|
<script>
|
||||||
|
const { $, $$, toast } = App;
|
||||||
|
let mode = "login";
|
||||||
|
$$("[data-t]").forEach((a) => (a.onclick = (e) => { e.preventDefault(); mode = a.dataset.t; $$("[data-t]").forEach((x) => x.classList.toggle("on", x === a)); $$("[data-reg]").forEach((x) => (x.hidden = mode !== "reg")); $("[data-go]").textContent = mode === "reg" ? "注册并开通免费版" : "登录"; }));
|
||||||
|
$("[data-send]").onclick = (e) => { let n = 60; const b = e.target; b.disabled = true; const t = setInterval(() => { b.textContent = `${--n} 秒后重发`; if (!n) { clearInterval(t); b.disabled = false; b.textContent = "发送验证码"; } }, 1000); toast("验证码已发送(原型:任意 6 位数字)"); };
|
||||||
|
$("[data-go]").onclick = () => {
|
||||||
|
if (mode === "reg" && !$("#ag").checked) { toast("请先阅读并同意服务协议"); return; }
|
||||||
|
location.href = mode === "reg" ? "project-new.html" : "workbench.html";
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
54
docs/prototype/lots.html
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>选择所投的包 · 标博士原型</title>
|
||||||
|
<link rel="stylesheet" href="../design/system/tokens.css">
|
||||||
|
<link rel="stylesheet" href="../design/system/fonts-local.css">
|
||||||
|
<link rel="stylesheet" href="../design/system/components.css">
|
||||||
|
<link rel="stylesheet" href="assets/app.css">
|
||||||
|
<style>
|
||||||
|
.narrow{max-width:760px}
|
||||||
|
.lot{display:grid;grid-template-columns:20px minmax(0,1fr) auto;gap:var(--sp-4);align-items:start;padding:var(--sp-4) var(--sp-5);background:var(--bg-surface);border:1px solid var(--border-control);border-radius:var(--radius-sm);cursor:pointer}
|
||||||
|
.lot + .lot{margin-top:var(--sp-3)}
|
||||||
|
.lot.on{border-color:var(--c-ink);box-shadow:inset 0 0 0 1px var(--c-ink)}
|
||||||
|
.lot input{width:16px;height:16px;margin:3px 0 0;accent-color:var(--c-ink)}
|
||||||
|
.lot b{font-size:var(--fs-title)}
|
||||||
|
.lot .money{text-align:right}
|
||||||
|
.lot .money .fig{font-size:20px}
|
||||||
|
.common{margin-top:var(--sp-4);padding:var(--sp-3) var(--sp-5);border:1px dashed var(--border-control);border-radius:var(--radius-sm);color:var(--text-secondary)}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<div class="content narrow">
|
||||||
|
<div class="page-title"><div><h1>选择要投的包</h1><p>识别到 2 个采购包。清单只保留通用条目和所选包的条目,没选的包折叠隐藏,以后可以改。</p></div></div>
|
||||||
|
<label class="lot on"><input type="checkbox" checked data-lot="1"><div><b>采购包 1 · 智慧大厅软硬件集成</b><div class="muted cap">8 条专属要求,其中 ★ 条款 4 条 · <a class="cite-src" href="#" style="display:inline" data-open>招标文件 第 2 页</a></div></div><div class="money"><span class="cap muted">最高限价</span><br><span class="fig">286.00</span> 万元</div></label>
|
||||||
|
<label class="lot"><input type="checkbox" data-lot="2"><div><b>采购包 2 · 机房网络设备更新</b><div class="muted cap">9 条专属要求,其中 ★ 条款 2 条 · <a class="cite-src" href="#" style="display:inline" data-open>招标文件 第 2 页</a></div></div><div class="money"><span class="cap muted">最高限价</span><br><span class="fig">132.00</span> 万元</div></label>
|
||||||
|
<div class="common">通用条目 <span class="fig">18</span> 条(资格条件、无效情形、递交要求等),所有包都适用,始终保留。</div>
|
||||||
|
<div class="row" style="margin-top:var(--sp-6)">
|
||||||
|
<a class="plain cap" href="#" data-fix>分包识别得不对?手工调整</a>
|
||||||
|
<span style="margin-left:auto"></span>
|
||||||
|
<a class="btn primary" href="checklist.html" data-go>查看清单</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
<script src="assets/data.js"></script>
|
||||||
|
<script src="assets/app.js"></script>
|
||||||
|
<script>
|
||||||
|
const { D, $, $$, dialog, toast } = App;
|
||||||
|
App.shell({ crumbs: [["工作台", "workbench.html"], [D.project.name, "project.html"], ["选择所投的包", ""]] });
|
||||||
|
$$("[data-lot]").forEach((c) => (c.onchange = () => { c.closest(".lot").classList.toggle("on", c.checked); $("[data-go]").classList.toggle("disabled", !$$("[data-lot]:checked").length); }));
|
||||||
|
$$("[data-open]").forEach((a) => (a.onclick = (e) => { e.preventDefault(); e.stopPropagation(); toast("原型:在读标页的原文查看器里打开第 2 页"); }));
|
||||||
|
$("[data-go]").onclick = (e) => { if (!$$("[data-lot]:checked").length) { e.preventDefault(); toast("至少选择一个包"); } };
|
||||||
|
$("[data-fix]").onclick = (e) => { e.preventDefault(); dialog({ title: "手工调整分包", wide: true,
|
||||||
|
body: `<p class="muted">可以增加、删除、重命名采购包,或把清单条目改到另一个包下(在读标页逐条调整)。</p>
|
||||||
|
<table class="data"><thead><tr><th>包号</th><th>名称</th><th class="r">最高限价(万元)</th><th></th></tr></thead><tbody>
|
||||||
|
<tr><td class="num">1</td><td><input class="input" value="智慧大厅软硬件集成"></td><td class="r"><input class="input" value="286.00" style="width:100px;text-align:right"></td><td><button class="btn sm text">删除</button></td></tr>
|
||||||
|
<tr><td class="num">2</td><td><input class="input" value="机房网络设备更新"></td><td class="r"><input class="input" value="132.00" style="width:100px;text-align:right"></td><td><button class="btn sm text">删除</button></td></tr>
|
||||||
|
</tbody></table><button class="btn sm" style="margin-top:var(--sp-3)">增加一个包</button>`,
|
||||||
|
ok: "保存", onOk: () => toast("分包已保存,修改已留痕") }); };
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
83
docs/prototype/parsing.html
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>解析中 · 标博士原型</title>
|
||||||
|
<link rel="stylesheet" href="../design/system/tokens.css">
|
||||||
|
<link rel="stylesheet" href="../design/system/fonts-local.css">
|
||||||
|
<link rel="stylesheet" href="../design/system/components.css">
|
||||||
|
<link rel="stylesheet" href="assets/app.css">
|
||||||
|
<style>
|
||||||
|
.narrow{max-width:760px}
|
||||||
|
.steps li{padding:10px 0}
|
||||||
|
.steps .sub{font-weight:var(--fw-regular);color:var(--text-secondary);font-size:var(--fs-caption);margin-left:auto}
|
||||||
|
.steps .now .dot{animation:pulse 1.2s ease-in-out infinite}
|
||||||
|
@keyframes pulse{50%{opacity:.35}}
|
||||||
|
@media (prefers-reduced-motion:reduce){.steps .now .dot{animation:none}}
|
||||||
|
.prog{height:4px;background:var(--bg-sunken);margin:var(--sp-4) 0 var(--sp-2)}
|
||||||
|
.prog i{display:block;height:100%;background:var(--c-ink);transition:width var(--dur-base)}
|
||||||
|
.done-box{display:none}
|
||||||
|
.finished .done-box{display:block}
|
||||||
|
.finished .run-box{display:none}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<div class="content narrow">
|
||||||
|
<div class="page-title"><div><h1 data-title>正在解析招标文件</h1><p>ZC-2026-0418 采购文件.doc · 186 页</p></div></div>
|
||||||
|
<div class="card" data-card>
|
||||||
|
<div class="run-box">
|
||||||
|
<ol class="steps" data-steps></ol>
|
||||||
|
<div class="prog"><i data-prog style="width:0"></i></div>
|
||||||
|
<p class="cap muted" style="margin:0">300 页以内一般 10 分钟内完成(原型里加速演示)。<b style="color:var(--text-primary)">可以离开本页</b>,完成或失败都会发站内通知。</p>
|
||||||
|
<div class="row" style="margin-top:var(--sp-5)">
|
||||||
|
<span class="cost">已预扣 1 次解析,成功后结算;失败、超时或取消会自动退回</span>
|
||||||
|
<span style="margin-left:auto"></span>
|
||||||
|
<a class="btn" href="workbench.html">回工作台,完成后通知我</a>
|
||||||
|
<button class="btn text" data-cancel>取消解析</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="done-box" data-done></div>
|
||||||
|
</div>
|
||||||
|
<p class="note"><a class="plain" href="#" data-fail>演示:解析失败的样子</a></p>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
<script src="assets/data.js"></script>
|
||||||
|
<script src="assets/app.js"></script>
|
||||||
|
<script>
|
||||||
|
const { D, $, dialog, toast } = App;
|
||||||
|
const addendum = new URLSearchParams(location.search).has("addendum");
|
||||||
|
App.shell({ crumbs: [["工作台", "workbench.html"], [addendum ? D.project.name : "新建项目", addendum ? "project.html" : ""], ["解析", ""]] });
|
||||||
|
if (addendum) $("[data-title]").textContent = "正在解析补遗 1";
|
||||||
|
const S = addendum
|
||||||
|
? [["识别文件格式", "PDF · 2 页"], ["提取文字", ""], ["提取变更内容", ""], ["核对出处", ""], ["并入清单", ""]]
|
||||||
|
: [["识别文件格式", "实际为 DOCX,保留斜体、下划线等格式"], ["提取文字", "186 页"], ["识别分包与法律体系", "2 个采购包 · 政府采购"], ["提取清单", "资格、无效条款、评分、日期、技术参数"], ["核对出处", "逐条定位页码和原句"]];
|
||||||
|
let i = 0, timer;
|
||||||
|
function render() {
|
||||||
|
$("[data-steps]").innerHTML = S.map(([t, sub], k) => `<li class="${k < i ? "done" : k === i ? "now" : "todo"}"><span class="dot">${k < i ? "✓" : ""}</span>${t}${k <= i && sub ? `<span class="sub">${sub}</span>` : ""}</li>`).join("");
|
||||||
|
$("[data-prog]").style.width = (i / S.length) * 100 + "%";
|
||||||
|
}
|
||||||
|
function tick() {
|
||||||
|
i++; render();
|
||||||
|
if (i < S.length) timer = setTimeout(tick, 1100);
|
||||||
|
else finish();
|
||||||
|
}
|
||||||
|
function finish() {
|
||||||
|
$("[data-card]").classList.add("finished");
|
||||||
|
$("[data-done]").innerHTML = addendum
|
||||||
|
? `<div class="alert done"><span class="ico ok">✓</span><p><b>补遗 1 解析完成,已结算 1 次解析。</b>更新了 2 条清单项(投标截止时间、服务器系统盘),新增 0 条。清单已回到草稿,请核对后重新确认。</p></div>
|
||||||
|
<div class="row" style="margin-top:var(--sp-4)"><a class="btn primary" href="checklist.html">去核对更新</a></div>`
|
||||||
|
: `<div class="alert done"><span class="ico ok">✓</span><p><b>解析完成,已结算 1 次解析。</b>提取了 <span class="fig">35</span> 条要求,其中 <span class="fig">2</span> 条没找到可靠出处,放在「待确认」里。</p></div>
|
||||||
|
<dl class="kv" style="margin-top:var(--sp-4)"><dt>项目名称</dt><dd>${D.project.name}</dd><dt>法律体系</dt><dd>政府采购 · 公开招标</dd><dt>采购包</dt><dd>识别到 <span class="fig">2</span> 个采购包,下一步选择要投的包</dd></dl>
|
||||||
|
<div class="row" style="margin-top:var(--sp-5)"><a class="btn primary" href="lots.html">选择所投的包</a></div>`;
|
||||||
|
}
|
||||||
|
render(); timer = setTimeout(tick, 900);
|
||||||
|
$("[data-cancel]").onclick = () => dialog({ title: "取消解析?", body: "<p>已预扣的 1 次解析会退回。取消后可以重新上传。</p>", ok: "取消解析", cancel: "继续解析",
|
||||||
|
onOk: () => { clearTimeout(timer); $("[data-card]").classList.add("finished"); $("[data-done]").innerHTML = `<div class="alert"><span class="ico i">i</span><p>已取消,退回 1 次解析额度。</p></div><div class="row" style="margin-top:var(--sp-4)"><a class="btn primary" href="project-new.html">重新上传</a></div>`; } });
|
||||||
|
$("[data-fail]").onclick = (e) => { e.preventDefault(); clearTimeout(timer); $("[data-card]").classList.add("finished");
|
||||||
|
$("[data-done]").innerHTML = `<div class="alert warn"><span class="ico w">!</span><p><b>解析失败:第 112–118 页是图片,OCR 没能识别出文字。</b>已退回 1 次解析额度。可以换一份更清晰的扫描件,或者重试。错误编号 PJ-20261102-0417。</p></div>
|
||||||
|
<div class="row" style="margin-top:var(--sp-4)"><button class="btn primary" onclick="location.reload()">重试(重新预扣 1 次)</button><a class="btn" href="project-new.html">换一个文件</a></div>`; };
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
76
docs/prototype/project-new.html
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>新建项目 · 标博士原型</title>
|
||||||
|
<link rel="stylesheet" href="../design/system/tokens.css">
|
||||||
|
<link rel="stylesheet" href="../design/system/fonts-local.css">
|
||||||
|
<link rel="stylesheet" href="../design/system/components.css">
|
||||||
|
<link rel="stylesheet" href="assets/app.css">
|
||||||
|
<style>
|
||||||
|
.narrow{max-width:760px}
|
||||||
|
.file-row.bad{border-color:var(--warn);background:var(--warn-soft)}
|
||||||
|
.file-row .st{font-size:var(--fs-caption);line-height:var(--lh-caption)}
|
||||||
|
.file-row .st.warn{color:var(--warn-text)}
|
||||||
|
.foot-bar{display:flex;align-items:center;gap:var(--sp-3);margin-top:var(--sp-6);padding-top:var(--sp-4);border-top:1px solid var(--border-subtle)}
|
||||||
|
.foot-bar .cost{margin-right:auto}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<div class="content narrow">
|
||||||
|
<div class="page-title"><div><h1 data-title>新建项目</h1><p data-sub>上传招标文件,标博士会逐条提取要求,每一条都带原文出处。</p></div></div>
|
||||||
|
<div class="card stack">
|
||||||
|
<div class="field" data-name-field><label for="pn">项目名称 <small>(选填,解析后自动填写,可以修改)</small></label><input class="input" id="pn" placeholder="例如:某市政务服务中心智慧大厅建设项目"></div>
|
||||||
|
<div>
|
||||||
|
<div class="drop" tabindex="0" role="button" data-drop>
|
||||||
|
<b>把招标文件拖到这里,或点击选择</b>
|
||||||
|
<span class="muted">支持 PDF(含扫描件)、Word(DOC / DOCX)、WPS。正文和附件分开的,可以一起上传。</span><br>
|
||||||
|
<span class="cap muted">专业版单个文件不超过 <span class="fig" style="font-weight:500">500</span> 页、<span class="fig" style="font-weight:500">100</span> MB(示意,以套餐页为准)</span>
|
||||||
|
</div>
|
||||||
|
<div data-files style="margin-top:var(--sp-3)"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="foot-bar">
|
||||||
|
<span class="cost" data-cost>选好文件后显示扣点</span>
|
||||||
|
<a class="btn" href="workbench.html">取消</a>
|
||||||
|
<button class="btn primary" data-start disabled>开始解析</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
<script src="assets/data.js"></script>
|
||||||
|
<script src="assets/app.js"></script>
|
||||||
|
<script>
|
||||||
|
const { D, $, toast } = App;
|
||||||
|
const addendum = new URLSearchParams(location.search).has("addendum");
|
||||||
|
App.shell({ nav: addendum ? "" : "workbench", crumbs: addendum ? [["工作台", "workbench.html"], [D.project.name, "project.html"], ["追加补遗", ""]] : [["工作台", "workbench.html"], ["新建项目", ""]] });
|
||||||
|
if (addendum) {
|
||||||
|
$("[data-title]").textContent = "追加补遗 / 澄清";
|
||||||
|
$("[data-sub]").textContent = "补遗单独解析,新提取的条目标注来源「补遗 N」并入清单;日期以最新文件为准,由你核对。MVP 不自动判断哪些旧条目被作废。";
|
||||||
|
$("[data-name-field]").hidden = true;
|
||||||
|
}
|
||||||
|
const files = addendum
|
||||||
|
? [{ ext: "PDF", name: "关于 ZC-2026-0418 的更正公告.pdf", info: "2 页 · 0.3 MB", type: "补遗", ok: true }]
|
||||||
|
: [
|
||||||
|
{ ext: "DOCX", name: "ZC-2026-0418 采购文件.doc", info: "186 页 · 4.2 MB", type: "招标文件", ok: true, note: "扩展名是 .doc,实际是 Word 2007 格式(DOCX),已按实际格式处理。" },
|
||||||
|
{ ext: "PDF", name: "附件:采购需求-技术参数.pdf", info: "页数无法读取", type: "招标文件", ok: false, note: "文件已加密,无法读取。请上传未加密的文件。这个文件不会创建任务,也不扣额度。" }
|
||||||
|
];
|
||||||
|
function render() {
|
||||||
|
$("[data-files]").innerHTML = files.map((f, i) => `<div class="file-row ${f.ok ? "" : "bad"}">
|
||||||
|
<span class="ext">${f.ext}</span>
|
||||||
|
<div><b>${f.name}</b><div class="st ${f.ok ? "muted" : "warn"}">${f.ok ? "" : '<span class="ico w" style="width:14px;height:14px;font-size:10px;display:inline-grid;margin:0 4px -2px 0">!</span>'}${f.info}${f.note ? " · " + f.note : ""}</div></div>
|
||||||
|
<select aria-label="文件类型" ${f.ok ? "" : "disabled"}><option ${f.type === "招标文件" ? "selected" : ""}>招标文件</option><option ${f.type === "补遗" ? "selected" : ""}>补遗</option><option>澄清答复</option></select>
|
||||||
|
<button class="btn sm text" data-rm="${i}">移除</button></div>`).join("");
|
||||||
|
const ok = files.filter((f) => f.ok).length;
|
||||||
|
$("[data-cost]").innerHTML = ok ? `扣 <span class="fig">${ok}</span> 次解析 · 剩 <span class="fig">${D.quota.parse.left}</span> 次 · 解析失败、超时、取消都不扣` : "选好文件后显示扣点";
|
||||||
|
$("[data-start]").disabled = !ok;
|
||||||
|
}
|
||||||
|
let shown = false;
|
||||||
|
$("[data-drop]").onclick = () => { if (!shown) { shown = true; render(); } };
|
||||||
|
$("[data-drop]").onkeydown = (e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); $("[data-drop]").click(); } };
|
||||||
|
document.addEventListener("click", (e) => { if (e.target.matches("[data-rm]")) { files.splice(+e.target.dataset.rm, 1); render(); } });
|
||||||
|
$("[data-start]").onclick = () => location.href = "parsing.html" + (addendum ? "?addendum=1" : "");
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
138
docs/prototype/project.html
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>项目概览 · 标博士原型</title>
|
||||||
|
<link rel="stylesheet" href="../design/system/tokens.css">
|
||||||
|
<link rel="stylesheet" href="../design/system/fonts-local.css">
|
||||||
|
<link rel="stylesheet" href="../design/system/components.css">
|
||||||
|
<link rel="stylesheet" href="assets/app.css">
|
||||||
|
<style>
|
||||||
|
.ov{display:grid;grid-template-columns:minmax(0,1.5fr) minmax(0,1fr);gap:var(--sp-6);align-items:start}
|
||||||
|
.card h3{font:var(--fw-bold) var(--fs-title)/var(--lh-title) var(--font-sans);margin:0 0 var(--sp-3);display:flex;gap:var(--sp-2);align-items:center}
|
||||||
|
.card h3 .right{margin-left:auto;font-weight:var(--fw-regular)}
|
||||||
|
.next{border-left:3px solid var(--c-ink)}
|
||||||
|
.next p{margin:0 0 var(--sp-3)}
|
||||||
|
.summary .kv{grid-template-columns:120px 1fr}
|
||||||
|
.summary dd .cite-src{display:inline;margin-left:6px}
|
||||||
|
.dates{list-style:none;margin:0;padding:0}
|
||||||
|
.dates li{display:grid;grid-template-columns:110px minmax(0,1fr) auto;gap:var(--sp-3);padding:var(--sp-2) 0;border-top:1px solid var(--border-subtle);align-items:baseline}
|
||||||
|
.dates li:first-child{border-top:0}
|
||||||
|
.dates .fig{font-weight:500}
|
||||||
|
.dates .past{color:var(--text-secondary)}
|
||||||
|
.docs .file-row{grid-template-columns:32px minmax(0,1fr) auto}
|
||||||
|
.runs td{height:auto}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<div class="content">
|
||||||
|
<div class="flowline" data-flow></div>
|
||||||
|
<div class="ov" style="margin-top:var(--sp-5)">
|
||||||
|
<div class="stack">
|
||||||
|
<div class="card next">
|
||||||
|
<h3>下一步</h3>
|
||||||
|
<p>第 2 次检查还有 <span class="sev fatal">致命<span class="fig">3</span></span> 项待处理。处理完不是递交的前提,决定权在你;报告会如实列出每一项的处理情况。</p>
|
||||||
|
<div class="row"><a class="btn primary" href="results.html">去处理检查结果</a><a class="btn" href="report.html">导出报告</a></div>
|
||||||
|
</div>
|
||||||
|
<div class="card summary">
|
||||||
|
<h3><span class="tag ai">AI 摘要</span>项目要点<span class="right cap muted">每一条都可以点出处核对</span></h3>
|
||||||
|
<dl class="kv">
|
||||||
|
<dt>法律体系</dt><dd>政府采购 · 公开招标(按「投标无效」口径检查)</dd>
|
||||||
|
<dt>预算 / 限价</dt><dd>采购包 1:<span class="fig">286.00</span> 万元(最高限价同预算)<a class="cite-src" href="#" data-open="T|2|t2-cap|采购包1:286.00万元">第 2 页</a></dd>
|
||||||
|
<dt>评标方法</dt><dd>综合评分法:价格 10、技术 40、实施方案 20、售后 10、认证 6、业绩 8 <a class="cite-src" href="#" data-open="T|31|t31-price">第 31 页</a></dd>
|
||||||
|
<dt>联合体</dt><dd>不接受 <span class="flag">已修改</span><a class="cite-src" href="#" data-open="T|2|t2-joint">第 2 页</a></dd>
|
||||||
|
<dt>中小企业</dt><dd>不专门面向中小企业;小微企业有价格扣除 <a class="cite-src" href="#" data-open="T|2|t2-sme">第 2 页</a></dd>
|
||||||
|
<dt>保证金</dt><dd>不收取 <a class="cite-src" href="#" data-open="T|21|t21-deposit">第 21 页</a></dd>
|
||||||
|
<dt>递交</dt><dd>电子交易平台在线递交,CA 加密,不接受纸质 <a class="cite-src" href="#" data-open="T|6|t6-place">第 6 页</a></dd>
|
||||||
|
<dt>★ 条款</dt><dd>采购包 1 和通用部分共 <span class="fig">4</span> 条 · <a class="plain" href="checklist.html">在清单里查看</a></dd>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<h3>检查记录<span class="right"><a class="btn sm" href="bid-check.html">上传新版本并检查</a></span></h3>
|
||||||
|
<table class="data runs">
|
||||||
|
<thead><tr><th>批次</th><th>时间</th><th>投标文件</th><th>结果</th><th>报告</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td>第 <span class="fig">2</span> 次</td><td class="num">11-02 14:20</td><td>商务册 v3、技术册 v3、报价册 v2</td><td><span class="sev fatal">致命<span class="fig">3</span></span> <span class="sev major">重要<span class="fig">3</span></span> <span class="sev hint">提示<span class="fig">3</span></span></td><td><a class="plain" href="results.html">查看</a></td></tr>
|
||||||
|
<tr><td>第 <span class="fig">1</span> 次</td><td class="num">11-01 16:00</td><td>商务册 v2、技术册 v2、报价册 v1</td><td><span class="sev fatal">致命<span class="fig">2</span></span> <span class="sev major">重要<span class="fig">4</span></span> <span class="sev hint">提示<span class="fig">2</span></span></td><td><a class="plain" href="report.html">11-01 报告.pdf</a></td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="stack">
|
||||||
|
<div class="card">
|
||||||
|
<h3>关键日期</h3>
|
||||||
|
<ul class="dates">
|
||||||
|
<li class="past"><span class="fig">10-28</span><span>采购文件公告发布</span><span></span></li>
|
||||||
|
<li><span class="fig">11-09</span><span>询问、质疑截止(公告期满后 7 个工作日)</span><a class="cite-src" href="#" data-open="T|6|t6-ask">第 6 页</a></li>
|
||||||
|
<li><span class="fig">11-20 09:30</span><span><b>投标截止 / 开标</b> · 剩 <span class="fig">17</span> 天<br><span class="flag">已被补遗 1 更新,原为 11-18 09:30</span></span><a class="cite-src" href="#" data-open="A1|1|a1-deadline|2026年11月20日09时30分">补遗 1</a></li>
|
||||||
|
<li><span class="fig">2027-02-18</span><span>投标有效期至(截止日起 90 天)</span><a class="cite-src" href="#" data-open="T|21|t21-valid">第 21 页</a></li>
|
||||||
|
</ul>
|
||||||
|
<p class="note">MVP 只在站内显示倒计时,不发截止提醒。</p>
|
||||||
|
</div>
|
||||||
|
<div class="card docs">
|
||||||
|
<h3>文件<span class="right"><button class="btn sm" data-addendum>追加补遗 / 澄清</button></span></h3>
|
||||||
|
<p class="cap muted" style="margin:0 0 var(--sp-2)">招标方</p>
|
||||||
|
<div class="file-row"><span class="ext">PDF</span><div><b>招标文件</b><div class="cap muted">ZC-2026-0418 采购文件.pdf · 186 页 · 解析成功</div></div><a class="plain cap" href="checklist.html">读标</a></div>
|
||||||
|
<div class="file-row"><span class="ext">PDF</span><div><b>补遗 1</b><div class="cap muted">更正公告 · 2 页 · 新增 0 条、更新 2 条</div></div><a class="plain cap" href="checklist.html">读标</a></div>
|
||||||
|
<p class="cap muted" style="margin:var(--sp-4) 0 var(--sp-2)">投标文件(加密前的 Word / PDF 稿)</p>
|
||||||
|
<div class="file-row"><span class="ext">DOCX</span><div><b>商务册 v3</b><div class="cap muted">64 页 · 11-02 14:12 上传</div></div><span></span></div>
|
||||||
|
<div class="file-row"><span class="ext">DOCX</span><div><b>技术册 v3</b><div class="cap muted">212 页 · 11-02 14:12 上传</div></div><span></span></div>
|
||||||
|
<div class="file-row"><span class="ext">DOCX</span><div><b>报价册 v2</b><div class="cap muted">8 页 · 11-01 15:40 上传</div></div><span></span></div>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<h3>项目操作</h3>
|
||||||
|
<div class="row"><button class="btn" data-submit>标记为已递交</button><button class="btn" data-archive>归档</button><button class="btn text" data-delete>删除项目</button></div>
|
||||||
|
<p class="note">标记已递交后项目变为只读,仍可导出报告;可以撤销,撤销会留下记录。</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
<aside class="drawer" aria-label="原文" data-drawer><div data-viewer></div></aside>
|
||||||
|
<script src="assets/data.js"></script>
|
||||||
|
<script src="assets/app.js"></script>
|
||||||
|
<script>
|
||||||
|
const { D, $, dialog, toast, esc } = App;
|
||||||
|
App.shell({ crumbs: [["工作台", "workbench.html"], [D.project.name, ""]], tab: "overview" });
|
||||||
|
const drawer = $("[data-drawer]");
|
||||||
|
const V = App.viewer($("[data-viewer]"), { closable: true, onClose: () => drawer.classList.remove("open"), onOpen: () => drawer.classList.add("open") });
|
||||||
|
document.addEventListener("click", (e) => {
|
||||||
|
const a = e.target.closest("[data-open]");
|
||||||
|
if (a) { e.preventDefault(); V.open(a.dataset.open.split("|").map((v, i) => i === 1 ? +v : v)); }
|
||||||
|
});
|
||||||
|
document.addEventListener("keydown", (e) => { if (e.key === "Escape") drawer.classList.remove("open"); });
|
||||||
|
|
||||||
|
let status = "已检查";
|
||||||
|
function flow() {
|
||||||
|
const S = ["跟进中", "编制中", "已检查", "已递交", "已归档"];
|
||||||
|
const i = S.indexOf(status);
|
||||||
|
$("[data-flow]").innerHTML = S.map((s, k) => `<span class="st ${k < i ? "done" : k === i ? "now" : ""}"><i></i>${s}</span>`).join('<span class="ln"></span>');
|
||||||
|
document.querySelector(".proj-head .tag.status").textContent = status;
|
||||||
|
}
|
||||||
|
flow();
|
||||||
|
$("[data-submit]").onclick = () => dialog({
|
||||||
|
title: "标记为已递交?",
|
||||||
|
body: `<p>标记后项目变为只读:不能再改清单、上传文件或处理问题,仍可以查看和导出报告。</p>
|
||||||
|
<div class="alert warn"><span class="ico w">!</span><p><b>还有 3 项致命问题待处理。</b>标记已递交不受影响,只是提醒你。</p></div>`,
|
||||||
|
ok: "标记为已递交",
|
||||||
|
onOk: () => { status = "已递交"; flow(); toast('已标记为已递交 · <a href="#" data-undo-submit>撤销</a>'); }
|
||||||
|
});
|
||||||
|
document.addEventListener("click", (e) => { if (e.target.matches("[data-undo-submit]")) { e.preventDefault(); status = "已检查"; flow(); toast("已撤销递交标记,记录已保存"); } });
|
||||||
|
$("[data-archive]").onclick = () => { status = "已归档"; flow(); toast("项目已归档,可以随时撤销归档"); };
|
||||||
|
$("[data-addendum]").onclick = () => location.href = "project-new.html?addendum=1";
|
||||||
|
$("[data-delete]").onclick = () => dialog({
|
||||||
|
title: `删除项目「${esc(D.project.name)}」?`,
|
||||||
|
body: `<p>项目下的招标文件、投标文件、清单、检查结果和报告都会删除。删除后 30 天内可以在回收站恢复,之后彻底清除。</p>
|
||||||
|
<p class="muted cap">额度流水和一条不含业务内容的审计记录会保留。</p>
|
||||||
|
<div class="field" data-field><label for="dn">输入项目名称确认</label><input class="input" id="dn" placeholder="${esc(D.project.name)}"><span class="msg"></span></div>`,
|
||||||
|
ok: "删除项目",
|
||||||
|
onOk: (s) => {
|
||||||
|
if ($("#dn", s).value.trim() !== D.project.name) { $("[data-field]", s).classList.add("err"); $(".msg", s).innerHTML = '<span class="ico w">!</span>项目名称不一致'; return false; }
|
||||||
|
location.href = "workbench.html";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
51
docs/prototype/qualifications.html
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>资质库 · 标博士原型</title>
|
||||||
|
<link rel="stylesheet" href="../design/system/tokens.css">
|
||||||
|
<link rel="stylesheet" href="../design/system/fonts-local.css">
|
||||||
|
<link rel="stylesheet" href="../design/system/components.css">
|
||||||
|
<link rel="stylesheet" href="assets/app.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<div class="content">
|
||||||
|
<div class="page-title"><div><h1>资质库</h1><p>查标时用来核对证书有没有过期、等级够不够。数据只来自你自己录入的内容,系统不从外部补数据。</p></div>
|
||||||
|
<div class="actions"><button class="btn primary" data-add>添加资质</button></div></div>
|
||||||
|
<div class="alert warn" style="margin-bottom:var(--sp-5)"><span class="ico w">!</span><p><b>1 张证书会在进行中项目的投标截止日前到期。</b>到期判断以每个项目的投标截止日为准,不以今天为准。</p></div>
|
||||||
|
<table class="data">
|
||||||
|
<thead><tr><th>名称</th><th>类型</th><th>等级</th><th>证书编号</th><th>发证机构</th><th>有效期至</th><th>附件</th></tr></thead>
|
||||||
|
<tbody data-rows></tbody>
|
||||||
|
</table>
|
||||||
|
<p class="note">MVP 只有最小资质库;业绩、人员、历史标书等完整资产库在 V1 提供。</p>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
<script src="assets/data.js"></script>
|
||||||
|
<script src="assets/app.js"></script>
|
||||||
|
<script>
|
||||||
|
const { $, dialog, toast } = App;
|
||||||
|
App.shell({ nav: "quals", crumbs: [["资质库", ""]] });
|
||||||
|
const Q = [
|
||||||
|
["ISO/IEC 27001 信息安全管理体系", "体系认证", "—", "01226IS0458", "某认证中心", "2026-11-15", '<span style="color:var(--warn-text)">早于「智慧大厅建设项目」截止日 11-20</span>', "已上传"],
|
||||||
|
["ISO 9001 质量管理体系", "体系认证", "—", "01226Q31877", "某认证中心", "2028-03-09", "", "已上传"],
|
||||||
|
["ISO/IEC 20000-1 信息技术服务管理体系", "体系认证", "—", "01226IT0921", "某认证中心", "2027-06-30", "", "已上传"],
|
||||||
|
["信息系统建设和服务能力等级证书", "资质证书", "CS3", "CS3-3201-000488", "中国电子信息行业联合会", "2027-12-31", "", "已上传"],
|
||||||
|
["电子与智能化工程专业承包", "资质证书", "二级", "D232045871", "某省住房和城乡建设厅", "2029-05-20", "", '<span class="muted">待补录</span>'],
|
||||||
|
["政务大厅排队叫号系统 V3.0", "软件著作权", "—", "2024SR0638812", "国家版权局", "长期", "", "已上传"]
|
||||||
|
];
|
||||||
|
$("[data-rows]").innerHTML = Q.map((r) => `<tr><td><b>${r[0]}</b></td><td>${r[1]}</td><td>${r[2]}</td><td class="num">${r[3]}</td><td>${r[4]}</td><td><span class="num">${r[5]}</span>${r[6] ? `<div class="cap">${r[6]}</div>` : ""}</td><td>${r[7]}</td></tr>`).join("");
|
||||||
|
$("[data-add]").onclick = () => dialog({ title: "添加资质", wide: true,
|
||||||
|
body: `<div class="grid2" style="gap:var(--sp-4)">
|
||||||
|
<div class="field"><label>名称</label><input class="input" placeholder="例如:信息系统建设和服务能力等级证书"></div>
|
||||||
|
<div class="field"><label>类型</label><select class="input"><option>资质证书</option><option>体系认证</option><option>软件著作权</option><option>其他</option></select></div>
|
||||||
|
<div class="field"><label>等级 <small>(选填)</small></label><input class="input"></div>
|
||||||
|
<div class="field"><label>证书编号</label><input class="input"></div>
|
||||||
|
<div class="field"><label>发证机构</label><input class="input"></div>
|
||||||
|
<div class="field"><label>有效期至</label><input class="input" type="date"></div></div>
|
||||||
|
<div class="drop" style="padding:var(--sp-5);margin-top:var(--sp-4)"><b style="font-size:14px">上传证书扫描件</b><span class="cap muted">PDF 或图片;可以稍后补录</span></div>`,
|
||||||
|
ok: "保存", onOk: () => toast("已保存") });
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
64
docs/prototype/quota.html
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>额度 · 标博士原型</title>
|
||||||
|
<link rel="stylesheet" href="../design/system/tokens.css">
|
||||||
|
<link rel="stylesheet" href="../design/system/fonts-local.css">
|
||||||
|
<link rel="stylesheet" href="../design/system/components.css">
|
||||||
|
<link rel="stylesheet" href="assets/app.css">
|
||||||
|
<style>
|
||||||
|
.meters{display:grid;grid-template-columns:repeat(3,1fr);gap:var(--sp-4)}
|
||||||
|
.meter small{display:block;color:var(--text-secondary);font-size:var(--fs-caption)}
|
||||||
|
.meter .big{font:600 var(--fs-figure)/var(--lh-figure) var(--font-figures);font-variant-numeric:tabular-nums}
|
||||||
|
.meter .unit{color:var(--text-secondary);margin-left:4px}
|
||||||
|
.meter p{margin:0;font-size:var(--fs-caption);color:var(--text-secondary)}
|
||||||
|
.rules td{height:auto}
|
||||||
|
td.plus{color:var(--confirmed)}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<div class="content">
|
||||||
|
<div class="page-title"><div><h1>额度</h1><p>专业版 · 本期 2026-11-01 至 2027-10-31。额度绑定企业主体,不可转让;期末未用完的额度不结转。</p></div>
|
||||||
|
<div class="actions"><button class="btn" data-contact>联系客服加额度</button></div></div>
|
||||||
|
<div class="alert" style="margin-bottom:var(--sp-5)"><span class="ico i">i</span><p>额度用到 80% 和 100% 时会站内提醒(每个周期每个档位只提醒一次),提醒用赭色,不用红色。</p></div>
|
||||||
|
<div class="meters">
|
||||||
|
<div class="card meter"><small>解析剩余</small><span class="big">37</span><span class="unit">/ 50 次</span><div class="bar"><i style="width:26%"></i></div><p>已用 13 次 · 招标文件和补遗各算 1 次</p></div>
|
||||||
|
<div class="card meter"><small>检查剩余</small><span class="big">12</span><span class="unit">/ 20 次</span><div class="bar"><i style="width:40%"></i></div><p>已用 8 次 · 同一项目 24 小时内复查不扣</p></div>
|
||||||
|
<div class="card meter"><small>按功能分布(本期)</small><p style="margin-top:6px;color:var(--text-primary);font-size:14px;line-height:22px">读标 11 次 · 补遗 2 次<br>查标 8 次 · 复查免扣 5 次</p></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section-h"><h2>扣点规则</h2><span class="cap muted">所有扣点都能在下方流水里对应到具体项目和任务</span></div>
|
||||||
|
<table class="data rules">
|
||||||
|
<thead><tr><th>动作</th><th>扣点</th><th>不扣点</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td>解读一份招标文件或补遗</td><td>1 次解析(发起时预扣,成功后结算)</td><td>解析失败、超时、取消:自动退回</td></tr>
|
||||||
|
<tr><td>对一个项目做一次全量检查</td><td>1 次检查(投标文件解析含在内)</td><td>检查中断、系统异常;同一项目 24 小时内复查</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div class="section-h"><h2>流水</h2><span class="more"><div class="seg"><button class="on">全部</button><button>解析</button><button>检查</button></div></span></div>
|
||||||
|
<table class="data">
|
||||||
|
<thead><tr><th>时间</th><th>类型</th><th>额度</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>智慧大厅建设项目 · 解析任务 PJ-0417</td><td class="muted">解析失败:OCR 未识别出文字</td></tr>
|
||||||
|
<tr><td class="num">11-02 14:28</td><td>预扣</td><td>解析</td><td class="num r">−1</td><td>智慧大厅建设项目 · 解析任务 PJ-0417</td><td></td></tr>
|
||||||
|
<tr><td class="num">11-02 14:20</td><td>—</td><td>检查</td><td class="num r">0</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>智慧大厅建设项目 · 补遗 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>智慧大厅建设项目 · 第 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>智慧大厅建设项目 · 招标文件</td><td></td></tr>
|
||||||
|
<tr><td class="num">11-01 09:00</td><td>发放</td><td>解析 / 检查</td><td class="num r plus">+50 / +20</td><td>专业版开通(对公转账)</td><td class="muted">运营:开通单 KT-1101</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
<script src="assets/data.js"></script>
|
||||||
|
<script src="assets/app.js"></script>
|
||||||
|
<script>
|
||||||
|
App.shell({ nav: "quota", crumbs: [["额度", ""]] });
|
||||||
|
App.$("[data-contact]").onclick = () => App.dialog({ title: "联系客服加额度", cancel: null, ok: "知道了", body: "<p>MVP 暂不支持线上加量包。请联系客服,确认后由运营在后台调整额度,调整会写进流水并注明原因。</p><dl class='kv'><dt>客服电话</dt><dd>(待定)</dd><dt>官网</dt><dd>www.biaoboshi.cn</dd></dl>" });
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
102
docs/prototype/report.html
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>检查报告 · 标博士原型</title>
|
||||||
|
<link rel="stylesheet" href="../design/system/tokens.css">
|
||||||
|
<link rel="stylesheet" href="../design/system/fonts-local.css">
|
||||||
|
<link rel="stylesheet" href="../design/system/components.css">
|
||||||
|
<link rel="stylesheet" href="assets/app.css">
|
||||||
|
<style>
|
||||||
|
.rp{display:grid;grid-template-columns:260px minmax(0,1fr);gap:var(--sp-8);align-items:start}
|
||||||
|
.rp-side{position:sticky;top:76px}
|
||||||
|
.rp-side .card + .card{margin-top:var(--sp-4)}
|
||||||
|
.rp-side h3{font:var(--fw-bold) var(--fs-title)/var(--lh-title) var(--font-sans);margin:0 0 var(--sp-3)}
|
||||||
|
.rp-side .chk{padding:var(--sp-2) 0;border:0}
|
||||||
|
.pages{display:flex;flex-direction:column;align-items:center;gap:var(--sp-6)}
|
||||||
|
.a4{width:794px;min-height:1123px;background:#fff;box-shadow:0 1px 3px rgba(29,39,51,.18);padding:76px 76px 64px;position:relative;font-size:13.5px;line-height:1.6;color:#1D2733}
|
||||||
|
.a4 .report-head{margin-bottom:0}
|
||||||
|
.a4 h2{font:600 26px/1.35 var(--font-serif);margin:40px 0 6px}
|
||||||
|
.a4 h4{font:600 15px/1.4 var(--font-sans);margin:26px 0 10px;padding-bottom:6px;border-bottom:1px solid var(--c-ink)}
|
||||||
|
.a4 .sub{color:var(--text-secondary);margin:0}
|
||||||
|
.a4 dl{display:grid;grid-template-columns:110px 1fr;gap:6px 14px;margin:22px 0}
|
||||||
|
.a4 dt{color:var(--text-secondary)}
|
||||||
|
.a4 dd{margin:0}
|
||||||
|
.a4 .disc{margin-top:18px;padding:12px 14px;background:var(--bg-sunken);font-size:12.5px}
|
||||||
|
.a4 table{width:100%;border-collapse:collapse;font-size:12.5px}
|
||||||
|
.a4 th{text-align:left;font-weight:600;color:var(--text-secondary);border-bottom:1.5px solid var(--c-ink);padding:6px 6px}
|
||||||
|
.a4 td{border-bottom:1px solid var(--border-subtle);padding:7px 6px;vertical-align:top}
|
||||||
|
.a4 .sumtab td,.a4 .sumtab th{text-align:center}
|
||||||
|
.a4 .sumtab td:first-child,.a4 .sumtab th:first-child{text-align:left}
|
||||||
|
.a4 .cites{color:var(--ai);font-size:11.5px}
|
||||||
|
.a4 .free{position:absolute;top:30px;right:76px;font-size:11px;border:1px solid var(--border-control);padding:1px 6px;color:var(--text-secondary)}
|
||||||
|
.a4 .report-foot{position:absolute;left:76px;right:76px;bottom:36px}
|
||||||
|
.a4 .st-todo{font-weight:600}
|
||||||
|
@media (max-width:1400px){.pages{zoom:.86}}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<div class="content wide">
|
||||||
|
<div class="rp">
|
||||||
|
<div class="rp-side">
|
||||||
|
<div class="card">
|
||||||
|
<h3>导出报告</h3>
|
||||||
|
<label class="chk"><input type="checkbox" checked disabled><span>问题明细(全部级别)</span></label>
|
||||||
|
<label class="chk"><input type="checkbox" checked data-opt="appendix"><span>附录:需人工核对</span></label>
|
||||||
|
<label class="chk"><input type="checkbox" checked data-opt="cites"><span>每条附原文出处</span></label>
|
||||||
|
<div class="alert warn" style="margin:var(--sp-3) 0"><span class="ico w">!</span><p class="cap">还有 <b>3 项致命问题</b>待处理,报告会如实列出。</p></div>
|
||||||
|
<div class="row"><button class="btn primary" data-dl>下载 PDF</button><button class="btn" onclick="window.print()">打印</button></div>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<h3>已导出的报告</h3>
|
||||||
|
<div class="cap"><a class="plain" href="#">11-01 16:12 · 第 1 次检查.pdf</a><br><span class="muted">王敏 导出 · 12 页</span></div>
|
||||||
|
<p class="note">报告生成后不再改动,随项目归档;重新导出会生成一份新的。</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="pages" data-pages></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
<script src="assets/data.js"></script>
|
||||||
|
<script src="assets/app.js"></script>
|
||||||
|
<script>
|
||||||
|
const { D, $, $$, esc, toast } = App;
|
||||||
|
App.shell({ crumbs: [["工作台", "workbench.html"], [D.project.name, "project.html"], ["报告", ""]], tab: "report" });
|
||||||
|
const C = D.check, SEV = { fatal: "致命", major: "重要", hint: "提示" };
|
||||||
|
const ST = (f) => f.status === "ok" ? `已确认无误 · ${f.by} ${f.at}` : '<span class="st-todo">待处理</span>';
|
||||||
|
const head = (n, total) => `<div class="report-head"><img src="../design/logo/svg/logo-horizontal.svg" alt="标博士"><span>投标文件辅助检查报告</span></div>`;
|
||||||
|
const foot = (n) => `<div class="report-foot"><span>本报告为辅助检查结果,不构成对投标结果的保证</span><span class="fig">${n} / 3</span></div>`;
|
||||||
|
const cnt = (s, st) => C.findings.filter((f) => f.sev === s && (st ? (f.status || "todo") === st : true)).length;
|
||||||
|
function render() {
|
||||||
|
const cites = $("[data-opt=cites]").checked, appx = $("[data-opt=appendix]").checked;
|
||||||
|
const p1 = `<section class="a4">${head()}
|
||||||
|
<h2>${D.project.name}</h2><p class="sub">采购包 1 · 智慧大厅软硬件集成</p>
|
||||||
|
<dl><dt>项目编号</dt><dd>${D.project.no}</dd><dt>采购人</dt><dd>${D.project.buyer}</dd><dt>法律体系</dt><dd>政府采购 · 公开招标</dd>
|
||||||
|
<dt>投标截止</dt><dd>2026-11-20 09:30</dd><dt>投标文件</dt><dd>${C.files}</dd><dt>检查依据</dt><dd>招标文件 + 补遗 1;清单 v2(王敏 2026-11-01 11:20 确认)</dd>
|
||||||
|
<dt>检查时间</dt><dd>2026-11-02 14:20(第 ${C.no} 次检查)</dd><dt>规则版本</dt><dd>${C.rules}</dd><dt>导出</dt><dd>王敏 · 2026-11-02 15:20</dd></dl>
|
||||||
|
<h4>检查覆盖范围</h4>
|
||||||
|
<p><b>查了:</b>清单 26 条的响应完整性;形式要件(格式文件、签章位置有无、落款日期、其他项目名称);资质匹配(以投标截止日判断有效期);报价形式(限价、开标一览表与分项合计、大小写)。</p>
|
||||||
|
<p><b>没查:</b>份数、密封、CA 加密、递交回执(见附录「需人工核对」);签章真伪;报价是否合理;评分高低。</p>
|
||||||
|
<h4>汇总</h4>
|
||||||
|
<table class="sumtab"><tr><th>级别</th><th>问题数</th><th>待处理</th><th>已确认无误</th><th>已修改</th><th>已忽略</th></tr>
|
||||||
|
${["fatal", "major", "hint"].map((s) => `<tr><td><span class="sev ${s}">${SEV[s]}</span></td><td class="fig">${cnt(s)}</td><td class="fig">${cnt(s, "todo")}</td><td class="fig">${cnt(s, "ok")}</td><td class="fig">0</td><td class="fig">0</td></tr>`).join("")}</table>
|
||||||
|
<p class="cap muted" style="margin-top:8px">本次已解决 ${C.resolved} 项(上次检查有、这次没有再发现)。</p>
|
||||||
|
<div class="disc">本报告由标博士根据招标文件和投标文件自动比对生成,经用户逐项处理后导出,用于投标前自查。检查范围以上方「检查覆盖范围」为准,结论可能存在遗漏或误判,不构成对资格审查、符合性审查或评标结果的任何保证。</div>
|
||||||
|
${foot(1)}</section>`;
|
||||||
|
const p2 = `<section class="a4">${head()}<h4 style="margin-top:28px">问题明细</h4>
|
||||||
|
<table><tr><th style="width:52px">级别</th><th>问题</th><th style="width:150px">处理</th></tr>
|
||||||
|
${C.findings.map((f) => `<tr><td><span class="sev ${f.sev}">${SEV[f.sev]}</span></td><td><b>${esc(f.title)}</b><br>${esc(f.text[0])}${cites ? `<div class="cites">${f.cites.map((c) => `${D.docs[c[0]].name} 第 ${c[1]} 页${c[3] ? `「${esc(c[3])}」` : ""}`).join(";")}</div>` : ""}</td><td>${ST(f)}</td></tr>`).join("")}</table>${foot(2)}</section>`;
|
||||||
|
const p3 = appx ? `<section class="a4">${head()}<h4 style="margin-top:28px">附录 · 需人工核对</h4>
|
||||||
|
<table><tr><th>要求</th><th style="width:130px">出处</th><th style="width:150px">核对</th></tr>
|
||||||
|
${C.manual.map((m) => `<tr><td>${m.title}</td><td class="cites">${D.docs[m.cite[0]].name} 第 ${m.cite[1]} 页</td><td>未勾选</td></tr>`).join("")}</table>
|
||||||
|
<p class="cap muted" style="margin-top:10px">「未勾选」表示导出时用户尚未在系统里确认核对。</p>${foot(3)}</section>` : "";
|
||||||
|
$("[data-pages]").innerHTML = p1 + p2 + p3;
|
||||||
|
}
|
||||||
|
$$("[data-opt]").forEach((c) => (c.onchange = render));
|
||||||
|
$("[data-dl]").onclick = () => toast("报告已导出(原型不生成文件)");
|
||||||
|
render();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
274
docs/prototype/results.html
Normal file
@@ -0,0 +1,274 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>查标 · 标博士原型</title>
|
||||||
|
<link rel="stylesheet" href="../design/system/tokens.css">
|
||||||
|
<link rel="stylesheet" href="../design/system/fonts-local.css">
|
||||||
|
<link rel="stylesheet" href="../design/system/components.css">
|
||||||
|
<link rel="stylesheet" href="assets/app.css">
|
||||||
|
<style>
|
||||||
|
.res-bar{display:flex;align-items:flex-end;gap:var(--sp-4);flex-wrap:wrap}
|
||||||
|
.res-bar h2{font:var(--fw-bold) var(--fs-h2)/var(--lh-h2) var(--font-serif);margin:0}
|
||||||
|
.res-bar .actions{margin-left:auto;display:flex;align-items:center;gap:var(--sp-3)}
|
||||||
|
.filters{display:flex;align-items:center;gap:var(--sp-3);margin:var(--sp-5) 0 var(--sp-3)}
|
||||||
|
.filters .right{margin-left:auto;display:flex;gap:var(--sp-2);align-items:center}
|
||||||
|
.finding{outline:none}
|
||||||
|
.finding.focus{box-shadow:inset 0 0 0 2px var(--c-ink)}
|
||||||
|
.finding.leaving{opacity:.3;transition:opacity var(--dur-base)}
|
||||||
|
.u4{margin-top:var(--sp-3);padding:var(--sp-3) var(--sp-4);background:var(--bg-sunken);border-radius:var(--radius-sm)}
|
||||||
|
.u4 label{font-weight:var(--fw-bold);display:block;margin-bottom:6px}
|
||||||
|
.u4 .row{margin-top:var(--sp-2)}
|
||||||
|
.group-h{display:flex;align-items:center;gap:var(--sp-2);width:100%;padding:var(--sp-3) var(--sp-6);border:0;border-top:1px solid var(--border-subtle);background:var(--bg-sunken);color:var(--text-secondary);font:var(--fs-body) var(--font-sans);cursor:pointer;text-align:left}
|
||||||
|
.group-h:hover{color:var(--text-primary)}
|
||||||
|
.group-h .chev{transition:transform var(--dur-fast)}
|
||||||
|
.group-h[aria-expanded="true"] .chev{transform:rotate(90deg)}
|
||||||
|
.processed .finding{background:#FCFCFB}
|
||||||
|
.resolved-row{display:flex;gap:var(--sp-3);padding:var(--sp-3) var(--sp-6);border-top:1px solid var(--border-subtle);color:var(--text-secondary)}
|
||||||
|
.resolved-row s{color:var(--text-primary)}
|
||||||
|
.empty-todo{padding:var(--sp-10) var(--sp-6);text-align:center;color:var(--text-secondary)}
|
||||||
|
.sign .note-text{color:var(--text-secondary)}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<div class="content wide">
|
||||||
|
<div class="res-bar">
|
||||||
|
<div>
|
||||||
|
<h2>采购包 1 的检查结果</h2>
|
||||||
|
<p class="muted cap" style="margin:4px 0 0">第 <span class="fig">2</span> 次检查 · 11月2日 14:20 · 商务册 v3、技术册 v3、报价册 v2 · 规则版本 IT 集成 2026.11 · 清单版本 v2(已确认)</p>
|
||||||
|
</div>
|
||||||
|
<div class="actions">
|
||||||
|
<span class="cost">24 小时内复查不扣额度(至今天 16:00)</span>
|
||||||
|
<a class="btn" href="bid-check.html">重新检查</a>
|
||||||
|
<a class="btn primary" href="report.html" data-export>导出报告</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p class="cap" data-export-note style="text-align:right;margin:6px 0 0;color:var(--text-secondary)"></p>
|
||||||
|
|
||||||
|
<div class="filters">
|
||||||
|
<div class="seg" role="group" aria-label="按级别筛选" data-seg>
|
||||||
|
<button class="on" data-f="all">全部 <span class="fig" data-c="all"></span></button>
|
||||||
|
<button data-f="fatal"><span class="sev fatal">致命<span class="fig" data-c="fatal"></span></span></button>
|
||||||
|
<button data-f="major"><span class="sev major">重要<span class="fig" data-c="major"></span></span></button>
|
||||||
|
<button data-f="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" data-batch hidden>批量确认无误(<span class="fig" data-batch-n></span>)</button>
|
||||||
|
<button class="btn sm text" data-keys>快捷键 <span class="kbd">?</span></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="results" data-list></div>
|
||||||
|
|
||||||
|
<div class="section-h"><h2>需人工核对</h2><span class="cap muted">这些要求从文件里看不出来,请递交前自己核对并勾选。勾选结果会写进报告附录。</span></div>
|
||||||
|
<div class="card" data-manual></div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
<aside class="drawer" aria-label="原文" data-drawer><div data-viewer></div></aside>
|
||||||
|
|
||||||
|
<script src="assets/data.js"></script>
|
||||||
|
<script src="assets/app.js"></script>
|
||||||
|
<script>
|
||||||
|
const { D, $, $$, esc, cite, dialog, toast } = App;
|
||||||
|
App.shell({ crumbs: [["工作台", "workbench.html"], [D.project.name, "project.html"], ["查标", ""]], tab: "results" });
|
||||||
|
|
||||||
|
const SEV = { fatal: "致命", major: "重要", hint: "提示" };
|
||||||
|
const ACT = { ok: "确认无误", fixed: "已修改", ignored: "已忽略" };
|
||||||
|
const F = D.check.findings.map((f) => ({ status: "todo", ...f }));
|
||||||
|
let filter = "all", focusId = null, lastHandled = null;
|
||||||
|
|
||||||
|
/* 原文抽屉 */
|
||||||
|
const drawer = $("[data-drawer]");
|
||||||
|
const V = App.viewer($("[data-viewer]"), { closable: true, onClose: () => drawer.classList.remove("open"), onOpen: () => drawer.classList.add("open") });
|
||||||
|
document.addEventListener("click", (e) => {
|
||||||
|
const a = e.target.closest("[data-open]");
|
||||||
|
if (!a) return;
|
||||||
|
e.preventDefault();
|
||||||
|
const f = F.find((x) => x.id === a.closest("[data-id]")?.dataset.id);
|
||||||
|
V.open(a.dataset.open.split("|").map((v, i) => i === 1 ? +v : v), f ? f.cites : null);
|
||||||
|
$$(".cite.on").forEach((x) => x.classList.remove("on")); a.closest(".cite")?.classList.add("on");
|
||||||
|
});
|
||||||
|
|
||||||
|
function sign(f) {
|
||||||
|
return `<div class="sign" ${f.status === "ignored" ? 'style="color:var(--text-secondary)"' : ""}><span>${f.status === "ignored" ? "–" : "✓"}</span><b>${f.by}</b><span class="fig">${f.at}</span><span>${f.status === "ok" ? "已确认无误" : f.status === "fixed" ? "已修改" : "已忽略"}</span>${f.note ? `<span class="note-text">· ${esc(f.note)}</span>` : ""}<a class="plain undo" href="#" data-undo>撤销</a></div>`;
|
||||||
|
}
|
||||||
|
function row(f) {
|
||||||
|
const cites = f.cites.map((c) => cite(c)).join("") + (f.missing ? `<div class="cite missing"><span class="cite-src">投标文件 · 未找到</span><p>${f.missing.replace(/^.*?· /, "")}</p></div>` : "");
|
||||||
|
const actions = f.status === "todo"
|
||||||
|
? `<div class="f-actions">
|
||||||
|
<button class="btn ${f.sev === "hint" ? "sm" : ""}" data-act="ok" title="我核对过,投标文件这里没问题">确认无误</button>
|
||||||
|
<button class="btn ${f.sev === "hint" ? "sm" : ""}" data-act="fixed" title="我已在投标文件里修改,复查时会再查">已修改</button>
|
||||||
|
<button class="btn ${f.sev === "hint" ? "sm" : ""}" data-act="ignored" title="知道这个问题,决定不处理,需要填原因">忽略并注明原因</button>
|
||||||
|
<a class="plain feedback" href="#" data-feedback>判断有误?</a></div>`
|
||||||
|
: sign(f);
|
||||||
|
return `<article class="finding ${f.sev === "fatal" ? "is-fatal" : ""} ${focusId === f.id ? "focus" : ""}" data-id="${f.id}" tabindex="-1">
|
||||||
|
<div>
|
||||||
|
<div class="f-head"><span class="sev ${f.sev}">${SEV[f.sev]}</span><span class="f-title">${esc(f.title)}</span>${f.flag ? `<span class="flag">${f.flag}</span>` : ""}</div>
|
||||||
|
${f.text.map((t) => `<p class="f-text">${esc(t)}</p>`).join("")}
|
||||||
|
${actions}
|
||||||
|
</div>
|
||||||
|
<aside>${cites}</aside>
|
||||||
|
</article>`;
|
||||||
|
}
|
||||||
|
let openProcessed = false, openResolved = false;
|
||||||
|
function render() {
|
||||||
|
const todo = F.filter((f) => f.status === "todo");
|
||||||
|
const shown = todo.filter((f) => filter === "all" || f.sev === filter);
|
||||||
|
const done = F.filter((f) => f.status !== "todo");
|
||||||
|
const n = (s) => todo.filter((f) => f.sev === s).length;
|
||||||
|
$("[data-c=all]").textContent = todo.length;
|
||||||
|
["fatal", "major", "hint"].forEach((s) => ($(`[data-c=${s}]`).textContent = n(s)));
|
||||||
|
$("[data-list]").innerHTML =
|
||||||
|
(shown.length ? shown.map(row).join("") : `<div class="empty-todo">${filter === "all" ? "全部问题都已处理。报告会列出每条的处理人、时间和原因。" : "这个级别没有待处理的问题。"}</div>`) +
|
||||||
|
`<button class="group-h" data-toggle="p" aria-expanded="${openProcessed}"><span class="chev">›</span>已处理(<span class="fig">${done.length}</span>)</button>
|
||||||
|
<div class="processed" ${openProcessed ? "" : "hidden"}>${done.map(row).join("")}</div>
|
||||||
|
<button class="group-h" data-toggle="r" aria-expanded="${openResolved}"><span class="chev">›</span>本次已解决(<span class="fig">${D.check.resolved}</span>)· 上次检查有、这次没有再发现的问题,自动关闭</button>
|
||||||
|
<div ${openResolved ? "" : "hidden"}>
|
||||||
|
<div class="resolved-row"><span class="sev major">重要</span><s>未找到售后服务方案中「响应时间要求」的响应</s><span class="cap">上次:已修改</span></div>
|
||||||
|
<div class="resolved-row"><span class="sev hint">提示</span><s>投标文件中项目编号写成「ZC-2026-0481」</s><span class="cap">上次:已修改</span></div>
|
||||||
|
</div>`;
|
||||||
|
// 就绪状态、页签计数、导出提示、批量
|
||||||
|
const fatal = n("fatal");
|
||||||
|
$$("[data-fatal-count]").forEach((el) => (el.outerHTML = fatal ? `<span class="sev fatal" data-fatal-count>致命<span class="fig">${fatal}</span></span>` : `<span data-fatal-count></span>`));
|
||||||
|
$("[data-ready]").innerHTML = fatal ? "未就绪" : `<span class="tag status" title="只表示致命问题都已处理,不代表投标文件没有问题">就绪</span>`;
|
||||||
|
$("[data-export-note]").textContent = fatal ? `还有 ${fatal} 项致命问题待处理,报告会如实列出。` : "";
|
||||||
|
const hints = todo.filter((f) => f.sev === "hint").length;
|
||||||
|
$("[data-batch]").hidden = !(filter === "hint" && hints > 1);
|
||||||
|
$("[data-batch-n]").textContent = hints;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handle(id, act, extra = {}) {
|
||||||
|
const f = F.find((x) => x.id === id);
|
||||||
|
const el = $(`[data-id="${id}"]`);
|
||||||
|
const apply = () => {
|
||||||
|
Object.assign(f, { status: act, by: D.user.name, at: "11月2日 15:02", ...extra });
|
||||||
|
lastHandled = id;
|
||||||
|
const next = nextTodo(id);
|
||||||
|
focusId = next;
|
||||||
|
render();
|
||||||
|
toast(`「${esc(f.title)}」已标记为${act === "ok" ? "已确认无误" : act === "fixed" ? "已修改" : "已忽略"} · <a href="#" data-toast-undo>撤销</a>`);
|
||||||
|
};
|
||||||
|
if (el) { el.classList.add("leaving"); setTimeout(apply, 160); } else apply();
|
||||||
|
}
|
||||||
|
function undo(id) {
|
||||||
|
const f = F.find((x) => x.id === id);
|
||||||
|
Object.assign(f, { status: "todo", note: "", by: "", at: "" });
|
||||||
|
focusId = id;
|
||||||
|
render();
|
||||||
|
toast(`已撤销:「${esc(f.title)}」回到待处理`);
|
||||||
|
}
|
||||||
|
function nextTodo(id) {
|
||||||
|
const list = F.filter((f) => f.status === "todo" && f.id !== id && (filter === "all" || f.sev === filter));
|
||||||
|
const all = F.filter((f) => filter === "all" || f.sev === filter);
|
||||||
|
const i = all.findIndex((f) => f.id === id);
|
||||||
|
return (list.find((f) => all.indexOf(f) > i) || list[0] || {}).id || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function confirmOk(id) {
|
||||||
|
const f = F.find((x) => x.id === id);
|
||||||
|
if (f.sev !== "fatal") return handle(id, "ok");
|
||||||
|
// U4:致命问题确认无误也要写说明
|
||||||
|
const el = $(`[data-id="${id}"] .f-actions`);
|
||||||
|
if ($(".u4", el.parentNode)) return;
|
||||||
|
el.insertAdjacentHTML("afterend", `<div class="u4"><label for="u4-${id}">说明为什么没有问题(必填)</label>
|
||||||
|
<textarea class="input" id="u4-${id}" placeholder="例如:内存配置在技术册第 15 页的配置清单里已写为 512G,偏离表是笔误,已另行更正"></textarea>
|
||||||
|
<p class="cap muted" style="margin:6px 0 0">致命问题被确认为没问题,等于系统可能判断错了。这条说明会进入处理记录,也会作为误报反馈发给我们。</p>
|
||||||
|
<div class="row"><button class="btn primary sm" data-u4-ok>确认无误</button><button class="btn sm" data-u4-cancel>取消</button><span class="cap" data-u4-err></span></div></div>`);
|
||||||
|
const box = $(".u4", el.parentNode);
|
||||||
|
$("textarea", box).focus();
|
||||||
|
$("[data-u4-cancel]", box).onclick = () => box.remove();
|
||||||
|
$("[data-u4-ok]", box).onclick = () => {
|
||||||
|
const v = $("textarea", box).value.trim();
|
||||||
|
if (v.length < 5) {
|
||||||
|
$("[data-u4-err]", box).innerHTML = '<span style="color:var(--warn-text)">! 请至少写 5 个字</span>';
|
||||||
|
$("textarea", box).style.borderColor = "var(--warn)";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
handle(id, "ok", { note: v });
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function ignore(id) {
|
||||||
|
const f = F.find((x) => x.id === id);
|
||||||
|
dialog({
|
||||||
|
title: "忽略这个问题?",
|
||||||
|
body: `<p>「${esc(f.title)}」</p>
|
||||||
|
<div class="field" data-field><label for="ig">忽略原因 <small>(必填,至少 5 个字)</small></label>
|
||||||
|
<textarea class="input" id="ig" placeholder="说明为什么不处理这一项"></textarea>
|
||||||
|
<div class="chips">${["招标文件允许", "已在其他分册响应", "与本项目无关"].map((c) => `<button class="btn" type="button" data-chip>${c}</button>`).join("")}</div>
|
||||||
|
<span class="msg">原因会写进处理记录和检查报告。</span></div>`,
|
||||||
|
ok: "忽略并记录原因",
|
||||||
|
onOpen: (s) => $$("[data-chip]", s).forEach((b) => (b.onclick = () => { $("#ig", s).value = b.textContent + ":"; $("#ig", s).focus(); })),
|
||||||
|
onOk: (s) => {
|
||||||
|
const v = $("#ig", s).value.trim();
|
||||||
|
if (v.replace(/[::]$/, "").length < 5) {
|
||||||
|
$("[data-field]", s).classList.add("err");
|
||||||
|
$(".msg", s).innerHTML = '<span class="ico w">!</span>请写清楚原因,至少 5 个字';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
handle(id, "ignored", { note: v });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function feedback(id) {
|
||||||
|
const f = F.find((x) => x.id === id);
|
||||||
|
dialog({
|
||||||
|
title: "反馈:判断有误",
|
||||||
|
body: `<p class="muted">「${esc(f.title)}」</p>
|
||||||
|
<div class="stack">${["这不是问题(误报)", "级别不对", "出处定位错了", "其他"].map((t, i) => `<label class="row" style="gap:8px"><input type="radio" name="fb" ${i ? "" : "checked"} style="accent-color:var(--c-ink)"> ${t}</label>`).join("")}</div>
|
||||||
|
<div class="field" style="margin-top:var(--sp-4)"><label for="fbt">补充说明 <small>(选填)</small></label><textarea class="input" id="fbt"></textarea></div>
|
||||||
|
<p class="cap muted">反馈只用于改进检查规则,不会改变这条问题的处理状态。</p>`,
|
||||||
|
ok: "提交反馈",
|
||||||
|
onOk: () => toast("已收到反馈,谢谢。我们会逐条复盘。")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener("click", (e) => {
|
||||||
|
const t = e.target;
|
||||||
|
const id = t.closest("[data-id]")?.dataset.id;
|
||||||
|
if (t.matches("[data-act]")) { focusId = id; const a = t.dataset.act; a === "ok" ? confirmOk(id) : a === "fixed" ? handle(id, "fixed") : ignore(id); }
|
||||||
|
else if (t.matches("[data-undo]")) { e.preventDefault(); undo(id); }
|
||||||
|
else if (t.matches("[data-toast-undo]")) { e.preventDefault(); $(".toast")?.remove(); undo(lastHandled); }
|
||||||
|
else if (t.matches("[data-feedback]")) { e.preventDefault(); feedback(id); }
|
||||||
|
else if (t.closest("[data-toggle]")) { const k = t.closest("[data-toggle]").dataset.toggle; k === "p" ? (openProcessed = !openProcessed) : (openResolved = !openResolved); render(); }
|
||||||
|
else if (t.closest("[data-f]")) { filter = t.closest("[data-f]").dataset.f; $$("[data-f]").forEach((b) => b.classList.toggle("on", b.dataset.f === filter)); focusId = null; render(); }
|
||||||
|
else if (t.closest("[data-batch]")) {
|
||||||
|
const hs = F.filter((f) => f.status === "todo" && f.sev === "hint");
|
||||||
|
dialog({ title: `批量确认 ${hs.length} 条「提示」?`, body: "<p>这些问题都会标记为「已确认无误」,处理记录里逐条写上你的名字和时间。批量处理只对「提示」级开放。</p>", ok: "批量确认无误",
|
||||||
|
onOk: () => { hs.forEach((f) => Object.assign(f, { status: "ok", by: D.user.name, at: "11月2日 15:04" })); render(); toast(`已批量确认 ${hs.length} 条提示`); } });
|
||||||
|
}
|
||||||
|
else if (t.closest("[data-keys]")) keysHelp();
|
||||||
|
});
|
||||||
|
|
||||||
|
function keysHelp() {
|
||||||
|
dialog({ title: "快捷键", cancel: null, ok: "知道了",
|
||||||
|
body: `<dl class="kv">${[["J / K", "下一条 / 上一条"], ["O 或 Enter", "打开当前问题的原文"], ["1", "确认无误"], ["2", "已修改"], ["3", "忽略并注明原因"], ["U", "撤销刚才的处理"], ["Esc", "关闭原文或对话框"]].map(([k, v]) => `<dt><span class="kbd">${k}</span></dt><dd>${v}</dd>`).join("")}</dl>` });
|
||||||
|
}
|
||||||
|
document.addEventListener("keydown", (e) => {
|
||||||
|
if (e.target.matches("input, textarea") || $(".scrim")) return;
|
||||||
|
const list = $$("[data-list] > .finding");
|
||||||
|
let i = list.findIndex((el) => el.dataset.id === focusId);
|
||||||
|
const k = e.key.toLowerCase();
|
||||||
|
if (k === "j" || k === "k") {
|
||||||
|
i = k === "j" ? Math.min(i + 1, list.length - 1) : Math.max(i - 1, 0);
|
||||||
|
focusId = list[i]?.dataset.id; list.forEach((el) => el.classList.toggle("focus", el.dataset.id === focusId));
|
||||||
|
list[i]?.scrollIntoView({ block: "nearest", behavior: "smooth" });
|
||||||
|
} else if ((k === "o" || k === "enter") && focusId) { $(`[data-id="${focusId}"] [data-open]`)?.click(); }
|
||||||
|
else if (k === "1" && focusId) confirmOk(focusId);
|
||||||
|
else if (k === "2" && focusId) handle(focusId, "fixed");
|
||||||
|
else if (k === "3" && focusId) ignore(focusId);
|
||||||
|
else if (k === "u" && lastHandled) undo(lastHandled);
|
||||||
|
else if (k === "escape") drawer.classList.remove("open");
|
||||||
|
else if (e.key === "?") keysHelp();
|
||||||
|
});
|
||||||
|
|
||||||
|
/* 需人工核对 */
|
||||||
|
$("[data-manual]").innerHTML = D.check.manual.map((m) => `<label class="chk"><input type="checkbox" data-m="${m.id}"><div style="flex:1"><div>${m.title}</div><div class="cap muted" data-m-sign></div></div><a class="cite-src" href="#" data-open="${m.cite.join("|")}">${App.citeLabel(m.cite)}</a></label>`).join("");
|
||||||
|
$$("[data-m]").forEach((c) => (c.onchange = () => { $("[data-m-sign]", c.parentNode).innerHTML = c.checked ? `<span style="color:var(--confirmed)">✓ ${D.user.name} 11月2日 15:06 已核对</span>` : ""; }));
|
||||||
|
|
||||||
|
render();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
75
docs/prototype/workbench.html
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>工作台 · 标博士原型</title>
|
||||||
|
<link rel="stylesheet" href="../design/system/tokens.css">
|
||||||
|
<link rel="stylesheet" href="../design/system/fonts-local.css">
|
||||||
|
<link rel="stylesheet" href="../design/system/components.css">
|
||||||
|
<link rel="stylesheet" href="assets/app.css">
|
||||||
|
<style>
|
||||||
|
.stats{display:grid;grid-template-columns:repeat(4,1fr);gap:var(--sp-4);margin-bottom:var(--sp-6)}
|
||||||
|
.stat{background:var(--bg-surface);border:1px solid var(--border-subtle);border-radius:var(--radius-sm);padding:var(--sp-4) var(--sp-5)}
|
||||||
|
.stat small{display:block;color:var(--text-secondary);font-size:var(--fs-caption)}
|
||||||
|
.stat .big{font:600 var(--fs-figure)/var(--lh-figure) var(--font-figures);font-variant-numeric:tabular-nums}
|
||||||
|
.stat .unit{color:var(--text-secondary);margin-left:4px}
|
||||||
|
.stat p{margin:var(--sp-1) 0 0;font-size:var(--fs-caption);color:var(--text-secondary)}
|
||||||
|
.toolbar{display:flex;align-items:center;gap:var(--sp-3);margin-bottom:var(--sp-3)}
|
||||||
|
.toolbar .right{margin-left:auto;display:flex;gap:var(--sp-2);align-items:center}
|
||||||
|
table.data tbody tr{cursor:pointer}
|
||||||
|
table.data tbody tr:hover td{background:var(--bg-hover)}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<div class="content">
|
||||||
|
<div class="page-title">
|
||||||
|
<div><h1>工作台</h1><p>按投标截止时间排序。截止临近的项目排在前面。</p></div>
|
||||||
|
<div class="actions"><a class="btn primary" href="project-new.html">新建项目</a></div>
|
||||||
|
</div>
|
||||||
|
<div class="stats">
|
||||||
|
<div class="stat"><small>进行中的项目</small><span class="big">4</span><p>已递交 1 · 已归档 1</p></div>
|
||||||
|
<div class="stat"><small>7 天内截止</small><span class="big">2</span><p class="due-soon" style="color:var(--warn-text)">最近:今天 17:00</p></div>
|
||||||
|
<div class="stat"><small>待处理的致命问题</small><span class="big">3</span><p>在 1 个项目里</p></div>
|
||||||
|
<div class="stat"><small>本期额度剩余</small><span class="big">37</span><span class="unit">次解析</span><p>检查 12 次 · <a class="plain" href="quota.html">查看明细</a></p></div>
|
||||||
|
</div>
|
||||||
|
<div class="toolbar">
|
||||||
|
<div class="seg" role="group" aria-label="按状态筛选" data-seg>
|
||||||
|
<button class="on" data-s="active">进行中</button><button data-s="all">全部</button><button data-s="已递交">已递交</button><button data-s="已归档">已归档</button>
|
||||||
|
</div>
|
||||||
|
<div class="right"><input class="input" placeholder="搜索项目名称、采购人、项目编号" style="width:280px"></div>
|
||||||
|
</div>
|
||||||
|
<table class="data">
|
||||||
|
<thead><tr><th>项目</th><th>所投包</th><th>截止</th><th>状态</th><th>就绪</th><th class="r">致命</th></tr></thead>
|
||||||
|
<tbody data-rows></tbody>
|
||||||
|
</table>
|
||||||
|
<p class="note">「就绪」只在「已检查」的项目上显示,只表示致命问题都已处理,不代表投标文件没有问题。截止临近用赭色提示,不用红色。</p>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
<script src="assets/data.js"></script>
|
||||||
|
<script src="assets/app.js"></script>
|
||||||
|
<script>
|
||||||
|
const { D, $, $$ } = App;
|
||||||
|
App.shell({ nav: "workbench", crumbs: [["工作台", ""]] });
|
||||||
|
let s = "active";
|
||||||
|
function render() {
|
||||||
|
const rows = [...D.projects].sort((a, b) => a.hoursLeft - b.hoursLeft).filter((p) => s === "all" || (s === "active" ? !["已递交", "已归档"].includes(p.status) : p.status === s));
|
||||||
|
$("[data-rows]").innerHTML = rows.map((p) => `<tr data-href="${p.href}">
|
||||||
|
<td><div class="proj-name">${p.name}</div><div class="proj-sub">${p.buyer}</div></td>
|
||||||
|
<td>${p.lot}</td><td class="${p.dueCls}">${p.due}</td>
|
||||||
|
<td><span class="tag status">${p.status}</span></td>
|
||||||
|
<td>${p.ready === "就绪" ? '<span class="tag status" title="只表示致命问题都已处理,不代表投标文件没有问题">就绪</span>' : p.ready || "—"}</td>
|
||||||
|
<td class="r">${p.fatal ? `<span class="sev fatal">致命<span class="fig">${p.fatal}</span></span>` : "—"}</td></tr>`).join("")
|
||||||
|
|| `<tr><td colspan="6" class="muted" style="text-align:center;padding:40px">没有符合条件的项目。</td></tr>`;
|
||||||
|
}
|
||||||
|
document.addEventListener("click", (e) => {
|
||||||
|
const b = e.target.closest("[data-s]");
|
||||||
|
if (b) { s = b.dataset.s; $$("[data-s]").forEach((x) => x.classList.toggle("on", x === b)); render(); }
|
||||||
|
const r = e.target.closest("[data-href]");
|
||||||
|
if (r) location.href = r.dataset.href;
|
||||||
|
});
|
||||||
|
render();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||