Compare commits

...

10 Commits

Author SHA1 Message Date
YANG JIANKUAN
3d1b634d36 docs: 记录决策 B6(应用图标印泥铺满)并更新项目进度
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 10:10:59 +08:00
YANG JIANKUAN
9aff7c499c docs(vi): 应用图标页改为 iPhone 18 Pro 主屏幕模拟
- 第 22 页右栏换成 iPhone 18 Pro 主屏幕效果图(黑色机身、Black 壁纸、4 行图标,第四行为股市和标博士),旁边是 Apple 连续曲率圆角裁切的两个尺寸图标;邮件签名移到左栏
- 新增 vi/mockup/:iphone-home.html 排版,fetch.sh 获取系统图标和壁纸,build.sh 量机身、生成遮罩并按 3 倍截图
- Apple 素材(机身、壁纸、系统图标)版权归 Apple,放在 mockup/apple/ 不进 git;机身需本人同意 Apple Design Resources 许可后手动放入,使用限制见 mockup/README.md
- 重新导出 VI 手册 PDF

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 10:10:59 +08:00
YANG JIANKUAN
05839dfe01 feat(logo): 应用图标改为印泥铺满,不留白边
- build_logo.py 新增 svg/app-icon.svg:印章实心部分撑满画布,毛边外的空隙用印章红补齐,白字和颗粒保留
- build_assets.sh 由 app-icon.svg 渲染 180 / 192 / 512 / 1024px 图标,方形、不带透明,圆角交给系统
- logo-spec.md 同步资产清单

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 10:10:59 +08:00
YANG JIANKUAN
b86e62711e docs: 记录原型阶段决策 P1–P2,同步 FR-M3-02/04 与项目进度
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 22:02:34 +08:00
YANG JIANKUAN
f4dc699041 docs(prototype): 新增 MVP 可点击原型 v0.1
- 13 个页面覆盖 J1–J5:登录、工作台、新建项目、解析、选包、读标、上传投标文件、查标、报告、项目概览、额度、资质库
- 查标页:出处批注与原文抽屉、三种处理、致命项确认需写说明(U4)、忽略原因校验、撤销、批量、键盘逐条处理
- 读标页:分类、待确认区、补遗更新核对、修改记录、原文高亮、确认清单
- 示意场景沿用 02-user-journeys,条款改写自样本 S02、S10

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 22:02:34 +08:00
YANG JIANKUAN
700514746f refactor(design): 组件样式抽成共用的 components.css
- 预览页的组件样式移到 system/components.css,本地字体加载移到 system/fonts-local.css,供预览页和原型共用
- 出处引用限 4 行、选中态加粗;链接按钮去下划线;hidden 属性优先于组件样式
- 规范补充:「已忽略」署名用辅助灰和「–」,不用确认绿

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 22:02:34 +08:00
YANG JIANKUAN
274a211ac9 docs: 记录界面规范决策 U1–U4,同步 FR-M3-07 与项目进度
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 21:24:09 +08:00
YANG JIANKUAN
cbd5c8d28d docs(design): 新增产品界面设计规范 v0.1、设计变量与组件预览
- design-system.md:原则、颜色与对比度实测、字体字阶、布局、17 个组件、交互模式、文案规范、无障碍与红线自查
- system/tokens.css:设计变量;system/components.html:用样本条款演示的组件预览
- 问题级别用实心 / 描边 / 无框区分(色弱模拟验证),红色只给致命问题

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 21:24:09 +08:00
YANG JIANKUAN
2e35c12a6f chore(gitignore): 忽略 Python 缓存,移除误提交的 pyc 文件
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 20:37:54 +08:00
YANG JIANKUAN
b993a3721a docs: 记录品牌决策 B1–B5,更新项目进度与设计资产说明
- decisions.md:视觉方向、图标与英文名、logo v5/v5.1、口号
- AGENTS.md:当前阶段、品牌与设计资产速查、目录约定,精简进度记录
- analysis/README.md:更新下一步

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 20:28:18 +08:00
44 changed files with 3383 additions and 32 deletions

7
.gitignore vendored
View File

@@ -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/

View File

@@ -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 设计精修。

View File

@@ -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)。

View File

@@ -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)。

View File

@@ -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 · 大模型策略:国产云端模型,按场景路由
- **决策**:全部使用国内云端的国产大模型,不考虑数据出境。按业务场景匹配模型能力:多模态(扫描件理解、签章检测)、快速(分类、短文本抽取、交互类)、深度思考(复杂条款推理、响应性比对)。 - **决策**:全部使用国内云端的国产大模型,不考虑数据出境。按业务场景匹配模型能力:多模态(扫描件理解、签章检测)、快速(分类、短文本抽取、交互类)、深度思考(复杂条款推理、响应性比对)。

View File

@@ -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 |
## 速查 ## 速查

View 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。

View File

@@ -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,或者是界面元素、单色印刷,用干净版。

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

After

Width:  |  Height:  |  Size: 66 KiB

View File

@@ -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

View File

@@ -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"])

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 374 KiB

View 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}
}

View 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>

View 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}

View 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; }
}

View File

@@ -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:

View 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
View 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
View 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

View 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>

View File

@@ -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
View 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 只在页眉。

View 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
View 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) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" }[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
View 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" }
]
};

View 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>

View 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
View 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
View 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
View 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>

View 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>

View 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
View 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>

View 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
View 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
View 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
View 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>

View 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>