docs: 产品文档与交互原型(P1-P7 交付物)
一期范围:学校收集 + 自主认定(4 类材料全部有实测样本) - 主 PRD 190KB:125 条 FR / 21 条 VAL / 20 张表 DDL / 121 个接口 - 功能逻辑卷 88KB:不含代码模块 - 接口设计 116KB:58 个错误码 / 12 个 VO - 核心模块设计:M1 命名 / M2 压缩 / M3 识别 / M4 门禁 / M5 校验 / M6 导出 - 开发任务拆解:100 任务 / 387 人日 / 15 周 - 验收测试清单:36 条 Golden 断言 / 111 条功能验收 - PostgreSQL DDL:已在 PG 16.15 实测零错误 - 交互原型 31 屏:学生端 11 / 班主任端 9 / 管理端 11
This commit is contained in:
+112
@@ -0,0 +1,112 @@
|
||||
# 交付计划与进度
|
||||
|
||||
> 项目:数据信息填报系统(填报通 / CollectFlow)
|
||||
> 起始:2026-09-28
|
||||
> 状态:**进行中**
|
||||
|
||||
---
|
||||
|
||||
## 一、当前已交付
|
||||
|
||||
| # | 交付物 | 版本 | 状态 |
|
||||
|---|---|---|---|
|
||||
| — | 主 PRD(含库表设计、接口清单、15 条校验规则、18 张表 DDL) | V2.3 | ✅ |
|
||||
| — | 功能逻辑卷(纯功能运转逻辑,不含库表/接口/代码) | V1.9 | ✅ |
|
||||
| — | PostgreSQL 建表脚本 | — | ✅ **已在 PG 16 实测通过** |
|
||||
| — | 学生端原型(**11 屏**) | V1.2 | ✅ 含「申请加入名单」 |
|
||||
| — | 班主任端原型(9 屏) | V1.1 | ✅ |
|
||||
| — | 管理端 PC 原型(**11 屏**) | V1.2 | ✅ 含表单设计器/材料规则/整理器/申请审批 |
|
||||
|
||||
---
|
||||
|
||||
## 二、待执行计划
|
||||
|
||||
> 排序原则:**先解阻塞,再补设计,最后备交付**。
|
||||
> 客户确认链是最长的外部依赖,必须最先发出去。
|
||||
|
||||
### 阶段一 · 打通确认链
|
||||
|
||||
| # | 任务 | 产出 | 为什么这个顺序 | 状态 |
|
||||
|---|---|---|---|---|
|
||||
| **P1** | **客户与认定机构确认函** | `01-客户与机构确认函.md` | 外部依赖最长。**5 项高优先级阻塞开发**(300dpi 能否接受、名单外的人怎么办、社会收集名单从哪来…),越早发出越早回来 | ✅ |
|
||||
| **P2** | **需求追溯矩阵** | `02-需求追溯矩阵.md` | 自审:把 PRD 需求 ↔ 原型屏 ↔ 库表 ↔ 接口 逐条对应,找出**说了没做/做了没说**的缺口。放在接口文档之前,避免接口漏做 | ✅ |
|
||||
| **P2.5** | **管理端补充原型**(矩阵审计追加) | 追加到 `admin-web.html` | 矩阵发现 3 个 P0/P1 复杂交互页面未出原型:**表单设计器**、**材料规则配置**、**历史整理器**。不画出来开发必理解偏 | ✅ |
|
||||
|
||||
### 阶段二 · 补齐技术设计
|
||||
|
||||
| # | 任务 | 产出 | 说明 | 状态 |
|
||||
|---|---|---|---|---|
|
||||
| **P3** | **接口设计(OpenAPI)** | `03-接口设计.md` | PRD §8 已有 96 个接口清单,但缺**完整请求/响应字段定义**。补齐后前端可 mock、后端可并行开工。**含补追溯矩阵发现的 21 个缺口** | ✅ |
|
||||
| **P4** | **核心模块设计说明** | `04-核心模块设计说明.md` | 6 个模块的**伪代码级**说明:M1 命名 / M2 压缩 / M3 识别 / M4 门禁 / M5 校验 / M6 导出;含依赖图、开发顺序、Golden 基线 | ✅ |
|
||||
|
||||
### 阶段三 · 交付准备
|
||||
|
||||
| # | 任务 | 产出 | 说明 | 状态 |
|
||||
|---|---|---|---|---|
|
||||
| **P5** | **开发任务拆解(WBS)** | `05-开发任务拆解.md` | 8 个里程碑拆到可派工粒度(94 个任务 / 380 人日 / 工期 14 周),含关键路径、8 项风险、团队配置、3 个客户演示点 | ✅ |
|
||||
| **P6** | **验收测试清单** | `06-验收测试清单.md` | 29 条 Golden 断言(可直接自动化)+ 85 条功能验收 + 20 条异常边界 + 9 项性能 + 12 项安全 + 签署表 | ✅ |
|
||||
| **P7** | **文档导航(README)** | `../README.md` | 统一入口:按角色读法、三分钟看懂产品、关键决策速查、快速上手 | ✅ |
|
||||
|
||||
---
|
||||
|
||||
## 三、执行记录
|
||||
|
||||
| 时间 | 任务 | 结果 |
|
||||
|---|---|---|
|
||||
| 2026-09-28 | 立计划 | 7 项,分三阶段 |
|
||||
| 2026-09-28 | **P1 确认函** | 完成 `01-客户与机构确认函.md`:23 项待确认按**拍板方**分 4 组(认定机构 6 / 客户业务 9 / 商务法务 5 / 内部技术 7);**每项给出建议默认值**,客户不回复也能推进;附 9 条已闭环决策表防止反复讨论 |
|
||||
| 2026-09-28 | **P2 追溯矩阵** | 完成 `02-需求追溯矩阵.md`。**审计发现**:① 修正统计口径——实际 **115 条 FR**(83 P0/29 P1/3 P2)+ 17 条 VAL,此前漏算加粗编号;② **10 个接口缺口**(报考范围、整理器、字典、日志、消息中心、订阅额度、审核撤回、任务暂停…);③ **管理端 10 个原型页面缺口**,其中表单设计器为 P0;④ 4 个疑似需求问题中 **3 个是脚本正则缺陷、1 个为真**(已补 FR-616);⑤ **追加 P2.5** 补 3 个复杂交互页面原型 |
|
||||
| 2026-09-28 | **P2.5 补充原型** | 管理端 `admin-web.html` 由 7 屏扩至 **10 屏**:⑧**表单设计器**(字段库/画布/属性三栏 + 条件规则行,内置字段带锁不可删)⑨**材料规则配置**(材料清单侧栏 + 命名模板 + 档位选择 + 来源引导语)⑩**历史数据整理器**(用真实实测数字:475MB→37MB、59 项命名违规、仅 1 项需人工)。四份原型全部通过结构校验 |
|
||||
| 2026-09-28 | **P6 验收测试清单** | 完成 `06-验收测试清单.md`(25KB / 518 行)。把 P4 的 15 项基线转成 **29 条可自动化断言 G-01~G-29**;另含 3 条端到端主流程(32 步)、20 条异常边界、85 条功能验收、6 条视觉、9 项性能、12 项安全;含里程碑签署表与 **7 条 Go/No-Go 条件**;点出 3 个易漏盲区(Windows 解压中文名 / 并发绑定临界窗口 / 「无」是合法值) |
|
||||
| 2026-09-28 | **客户回执** | 客户确认全部 23 项按建议执行,**需求冻结**。识别出唯一功能增量 **B1(申请加入名单)**,其余 22 项零改动 |
|
||||
| 2026-09-28 | **D7 降级** | 重新评估「文件名识别真实准确率」这个待办:因**输出命名取自名册**、**归属靠证件号反查名册**、**识别有槽位兜底**、**失败一律放行**,该指标**不影响任何交付项**。降级为「上传页引导文案校准」——班主任在群里问两句即可,**取消样本索取**。至此**全部外部数据依赖清零** |
|
||||
| 2026-09-28 | **一期范围收窄** | 客户确认「**先做学校内的填报流程**」→ 一期只做**学校收集 + 自主认定**(4 类材料全部有实测样本);社会收集 / 社会评价 / 专项职业能力后置二期,**数据结构与枚举保留不启用**。**D4 因此不再是待办**。工期 −5 人日(392→387),关键路径不变仍 15 周 |
|
||||
| 2026-09-28 | **移除联系方式展示** | 客户指出"学生本来就有班主任联系方式,不需要系统里面体现"。删除 `GET /mp/roster/contact` 接口 + 门禁失败页「联系老师」按钮,改为只保留两条系统内动作(重新填写 / 申请加入名单)。**接口总数 122 → 121**。纯文案提示保留(不涉及联系方式数据) |
|
||||
| 2026-09-28 | **归属校验修正** | 客户指出"系统不存在名称错误"——纠正原设计中「把文件名不符自动按名册修正」的缺陷(会把传错的文件静默洗白)。改为 **VAL-21 文件归属校验 + 阻断提交**;判定优先级重排为 证件号反查名册 > OCR > 姓名(仅辅助);姓名不再作为独立规则。另补全「社会评价」定义与 `cert_type` 第三取值 |
|
||||
| 2026-09-28 | **B1 落地** | 新增 FR-509 系列 10 条 / `collect_join_request` 表(**PG 16 实测通过 + 部分唯一索引业务规则验证**)/ 5 个接口 / 错误码 40037~40042 / 学生端 STEP 03B + 管理端 STEP 11 两屏原型 / F-509 验收项 19 条 / WBS 新增 6 个任务 |
|
||||
| 2026-09-28 | **P7 文档导航** | 完成 ../README.md(10KB)。按角色给出读法、三分钟看懂产品、13 条关键决策速查、目录结构与快速上手、数据基线、两条踩坑教训 |
|
||||
| 2026-09-28 | **P5 开发任务拆解** | 完成 `05-开发任务拆解.md`(27.6KB / 468 行)。**94 个任务 / 380 人日 / 含缓冲 456**,工期 **14 周**。关键结论:**工期由关键路径(67 人日串行)决定,不能按人日总数摊人**;压缩周期只能砍范围不能加人。含 3 个客户演示点(A 第 5 周 / B 第 10 周 / C 第 14 周)与 §9 可复现核算口径 |
|
||||
| 2026-09-28 | **P4 核心模块设计** | 完成 `04-核心模块设计说明.md`(54KB / 1218 行)。六个模块全部伪代码级:M1 命名(含证件号三源裁决表)、M2 压缩(五档+二分降质+二次有损防护)、M3 识别(四层递进+成本闸)、M4 门禁(**并发绑定必须 UPDATE...WHERE bind_user_id IS NULL**)、M5 校验、M6 导出(快照先固化+中文文件名编码)。**校验中发现并修正三处真问题**,见下条教训 |
|
||||
| 2026-09-28 | **P3 接口设计** | 完成 `03-接口设计.md`(102KB / 2626 行)。**117 个接口**全部字段级定义(PRD 原 96 + 新增 21);新增 §2 枚举全集(含「无」的三条铁律)、§3 通用数据结构(12 个 VO,字段名与 DDL 列一一对应)、§15 错误码全集(50 个,新增 30);**自校验通过**:模块表合计=声称=实际唯一数=117,无真重复,引用字段名 116 个全部存在于 DDL |
|
||||
|
||||
---
|
||||
|
||||
## 三、客户回执(2026-09-28)
|
||||
|
||||
> **客户全部 23 项按建议方案确认。** 详见 `01-客户与机构确认函.md` 的客户回执章节。
|
||||
|
||||
| 组 | 项数 | 需开发 | 结论 |
|
||||
|---|:--:|:--:|---|
|
||||
| A 认定机构 | 6 | **0** | 全部与现有设计一致(当初就按实测数据设计) |
|
||||
| B 校方业务 | 9 | **1** | 仅 **B1(申请加入名单通道)** 是功能增量 |
|
||||
| C 商务法务 | 5 | 0 | C1 小程序资质按建议推进 + H5 保底 |
|
||||
| D 内部技术 | 7 | 0 | **D4 已后置二期**、**D7 已降级清除**;仅 D6(微信三条来源通道体积上限)需真机自测,**无外部依赖** |
|
||||
|
||||
**一期范围(客户确认后收窄)**:只做 **学校收集 + 自主认定**(4 类材料全部有实测样本)。
|
||||
社会收集 / 社会评价 / 专项职业能力后置二期,**数据结构与枚举保留不启用**。
|
||||
⇒ **D4(社会评价样本)因此不再是待办**;工期 **392 → 387 人日**,关键路径不变仍 **15 周**。
|
||||
|
||||
**本次变更(V1)**:新增 **FR-509 系列**(10 条)+ **1 张表**(`collect_join_request`)+ **5 个接口** + **2 屏原型**。
|
||||
|
||||
| 指标 | 变更前 | 变更后 |
|
||||
|---|:--:|:--:|
|
||||
| FR 总数 | 115 | **125** |
|
||||
| 接口总数 | 117 | **121** |
|
||||
| 库表 | 19 | **20** |
|
||||
| VAL 规则 | 20 | **21**(新增 VAL-21 归属校验) |
|
||||
| 错误码 | 50 | **57** |
|
||||
| 原型屏数 | 29 | **31** |
|
||||
| 任务数 | 94 | **100** |
|
||||
| 人日 | 380 | **392** → **387**(一期收窄 −5) |
|
||||
| 工期 | 14 周 | **15 周** |
|
||||
|
||||
---
|
||||
|
||||
## 四、已知风险(跟踪中)
|
||||
|
||||
| # | 风险 | 影响 | 对策 |
|
||||
|---|---|---|---|
|
||||
| R1 | ~~客户迟迟不确认 P1 的 5 项高优先级问题~~ | — | ✅ **已解除**:客户确认全部 23 项按建议执行,需求已冻结 |
|
||||
| R2 | ~~强特征词表未在"社会评价"批次数据上标定~~ | — | ✅ **已消除**:一期范围收窄为「学校收集 + 自主认定」,**不涉及**那两类零样本材料(D4 已后置二期) |
|
||||
| R3 | VLM 选型未定 | 中 | P4 中把 VLM 抽成**可替换接口**,不绑定供应商 |
|
||||
| R4 | **二期启动时缺少社会评价样本** | 中(二期) | 已在 PRD「一期范围声明」列出二期启动前必做 6 项;**不阻塞一期** |
|
||||
@@ -0,0 +1,376 @@
|
||||
# 确认函 · 数据信息填报系统
|
||||
|
||||
> 文档用途:汇总需各方拍板的事项,**可直接转发**
|
||||
> 发出日期:2026-09-28
|
||||
> 回复期限:建议 **5 个工作日内**
|
||||
> 版本:V1.0
|
||||
|
||||
---
|
||||
|
||||
## 致各位
|
||||
|
||||
我们已完成《数据信息填报系统》的需求与设计,进入开发前需确认若干关键事项。本文档把需确认的问题按**拍板方**分为四组:
|
||||
|
||||
| 分组 | 拍板方 | 项数 | 为什么需要确认 |
|
||||
|---|---|:--:|---|
|
||||
| **A** | 认定机构 | 6 | 直接决定**验收标准**——压缩到什么程度算合格、什么格式算合格 |
|
||||
| **B** | 校方 / 客户业务 | 9 | 决定**流程规则**——名单外的人怎么处理、预填字段能不能改 |
|
||||
| **C** | 商务 / 法务 | 5 | 决定**资质与商业模式**——小程序类目、定价、合规 |
|
||||
| **D** | 我方内部技术 | 7 | 不涉及客户,仅内部拍板 |
|
||||
|
||||
**每项都给出了「建议方案」,如无异议可直接回复"全部同意建议方案"。**
|
||||
若 5 个工作日内未收到回复,我方将按建议方案推进——**以便不阻塞开发排期**,后续如与预期不符可再调整(涉及外部技术依赖的除外,会单独再次确认)。
|
||||
|
||||
**如何回复**:只需按编号回答即可,例如——
|
||||
```
|
||||
A1 同意
|
||||
A2 选 B
|
||||
B1 选 A,但希望增加审批环节
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## A 组 · 请认定机构确认(技术规格)
|
||||
|
||||
> 这些直接决定**交付物是否合格**。我们已用真实批次(28 名学生 / 112 个文件)实测过,结论都有数据支撑。
|
||||
|
||||
### A1. 「300dpi 扫描件」是否可接受?
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **背景** | 认定机构要求单文件 ≤1M。工作证明等 A4 材料原始为 1272dpi、16MB。我们实测将 A4 等比缩至 **300dpi / 质量 85**,**28 张全部达标,平均 806KB,最大 831KB**——即**无需牺牲清晰度**。 |
|
||||
| **问题** | 输出为标准 300dpi 扫描件,是否可接受? |
|
||||
| **建议方案** | ✅ **接受 300dpi 输出**(这是文档扫描的行业标准分辨率,清晰度零损失) |
|
||||
| **需谁确认** | 认定机构 |
|
||||
| **回复** | ☐ 同意 ☐ 需更高分辨率(请注明期望值) |
|
||||
|
||||
### A2. 若某张扫描件内容特别复杂,压缩参数的下限怎么定?
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **背景** | 绝大多数材料 300dpi/q85 即可达标。但极端情况(满页手写、彩色底纹)可能需要降质。需要事先约定"最低可接受到什么程度"。 |
|
||||
| **问题** | 允许系统自动降到的最低档是多少? |
|
||||
| **建议方案** | ✅ **建议:允许自动降至 200dpi / 质量 70;低于此值则标记"强制压缩"并需人工确认后才提交** |
|
||||
| **回复** | ☐ 同意 ☐ 不允许降质,超限就驳回重传 |
|
||||
|
||||
### A3. 学籍表等截图类材料,保留 PNG 格式是否会被拒收?
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **背景** | 实测学籍表为教务系统的**屏幕截图**(约 1025×590,PNG 格式,40KB)。若强行转 JPG,文字边缘会发虚——**而截图本身已经很小,转换没有收益、只有损失**。 |
|
||||
| **问题** | 保留 PNG 是否可以? |
|
||||
| **建议方案** | ✅ **建议:截图类保留 PNG 原样输出**(命名仍按规范) |
|
||||
| **回复** | ☐ 可以保留 PNG ☐ 必须统一 JPG |
|
||||
|
||||
### A4. 分辨率下限:什么样的图判定为"不合格、需重传"?
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **背景** | 实测发现有一张身份证翻拍件仅 **558×745 / 0.08MB**,明显低于同批其他人(1279×1706)。**体积小不等于合格**,需要设下限。 |
|
||||
| **问题** | 长边低于多少像素应判定为不合格? |
|
||||
| **建议方案** | ✅ **建议:身份证等证件翻拍长边 ≥1400px;低于则提示重传(不阻断提交,由审核人决定)** |
|
||||
| **回复** | ☐ 同意 1400px ☐ 其他阈值:______ |
|
||||
|
||||
### A5. 材料「工作证明」的规范文件名,与实际内容不符,是否保留?
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **背景** | 按机构模板,**「职业技能等级个人申报表」的规范文件名却是「工作证明」**(例:`张三-身份证号-工作证明.jpg`)。这是机构的既有习惯。 |
|
||||
| **问题** | 是否按机构习惯保留这个命名? |
|
||||
| **建议方案** | ✅ **建议:保留机构习惯**(系统支持别名映射,不影响内部识别) |
|
||||
| **回复** | ☐ 保留 ☐ 改为"个人申报表" |
|
||||
|
||||
### A6. 导出包的目录结构与命名是否符合接收要求?
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **背景** | 系统默认按下述结构打包: |
|
||||
|
||||
```
|
||||
{批次号}-{任务名}/
|
||||
├── 24中技数控1班/
|
||||
│ ├── 一寸照片/ 张三-360121200010101234-一寸照片.jpg
|
||||
│ ├── 身份证/ 张三-360121200010101234-身份证.jpg
|
||||
│ ├── 学籍表/ 张三-360121200010101234-学籍表.png
|
||||
│ └── 工作证明/ 张三-360121200010101234-工作证明.jpg
|
||||
└── 考生线下报名信息导入模板(汇总).xlsx
|
||||
```
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **问题** | 这个结构是否满足接收要求? |
|
||||
| **建议方案** | ✅ **建议:按上述结构**(如需调整,请提供一份理想结构的样例) |
|
||||
| **回复** | ☐ 符合要求 ☐ 需调整为:______ |
|
||||
|
||||
---
|
||||
|
||||
## B 组 · 请校方 / 客户业务确认(流程规则)
|
||||
|
||||
### B1. 不在名单上的人想填报,怎么处理? ⚠️ **优先级最高**
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **背景** | 按现有需求,系统采用"**先导名单、姓名+身份证核验通过才能填报**"的门禁机制。名单外的人**一律不给填、只提示联系老师**。<br>但这会带来运营问题:**老师需要反复去后台手工加人**。在几百人的社会收集中会很痛苦。 |
|
||||
| **问题** | 是否给名单外的人一条"申请加入"通道? |
|
||||
| **建议方案** | ✅ **建议选 A** —— 既不破门禁,又免去老师手工加人 |
|
||||
|
||||
| 选项 | 方案 | 代价 |
|
||||
|---|---|---|
|
||||
| **A(建议)** | 失败页增加「**申请加入名单**」按钮 → 生成管理员待办 → **管理员批准后自动放行** | 需开发一个审批小流程 |
|
||||
| B | 维持现状:只提示,老师去后台加人 | 零开发,但老师负担重 |
|
||||
| C | 给每个任务配"白名单开关",允许自由填报 | 门禁形同虚设,**不推荐** |
|
||||
|
||||
**回复**:☐ 选 A ☐ 选 B ☐ 选 C
|
||||
|
||||
### B2. 社会收集的名单从哪里来?
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **背景** | 学校收集的名单可从教务系统导出,很稳定。但社会收集**同样要求"先导名单"**,如果名单来源不稳定,这套机制在社会侧很难跑起来。 |
|
||||
| **问题** | 社会收集的名单由谁提供、怎么来? |
|
||||
| **建议方案** | ✅ **建议:由报名点/机构维护预报名名单,导入后开放填报**;同时允许在填报期内**随时追加**(系统会告警范围外的报考项) |
|
||||
| **回复** | 名单来源:__________ 维护责任人:__________ |
|
||||
|
||||
### B3. 学生能不能修改老师预填的字段?
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **背景** | 实测老师交上来的名单里,21 个字段**已填满 18 个**(学历、在校生、学校、专业、入学时间、联系电话、报考三项等)。所以学生端主要是"核对 + 传材料"。<br>但老师填的东西可能出错——比如电话写错、入学时间记错。 |
|
||||
| **问题** | 哪些字段允许学生改? |
|
||||
| **建议方案** | ✅ **建议按下表三分类** |
|
||||
|
||||
| 类别 | 字段 | 学生端 |
|
||||
|---|---|---|
|
||||
| 🔒 **锁定** | 姓名、身份证号、**报考职业/工种/等级** | **只读不可改**(要改找老师) |
|
||||
| ✏️ **可核对修改** | 其余全部(学历、在校生、学校、专业、入学时间、电话…) | 预填并标注"老师已填,请核对",**可改且留痕** |
|
||||
| 📄 **学生负责** | 各类材料文件 | 上传 |
|
||||
|
||||
**回复**:☐ 同意该分类 ☐ 要求全部只读(学生只能改电话) ☐ 其他:______
|
||||
|
||||
### B4. 要不要再加一层手机号验证码?
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **背景** | 目前门禁是"姓名 + 身份证号"。理论上,**只要有人拿到这两项信息,就能代填**。 |
|
||||
| **问题** | 是否需要加手机号验证码? |
|
||||
| **建议方案** | ⚠️ **建议不加**。理由:① 名单里已有手机号,加验证等于要求一致;② 学生群体换号频繁,会制造大量"收不到验证码"的运营问题;③ 有人代填(如家长帮填)在本场景是可接受的。<br>**若机构有实名硬要求,则必须加。** |
|
||||
| **回复** | ☐ 不加 ☐ 必须加(请说明是哪个环节要求) |
|
||||
|
||||
### B5. 学生被驳回几次后应升级提醒管理员?
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **问题** | 驳回次数上限设为几次? |
|
||||
| **建议方案** | ✅ **默认 3 次**,达到后自动提醒管理员介入 |
|
||||
| **回复** | ☐ 同意 3 次 ☐ 改为 ____ 次 |
|
||||
|
||||
### B6. 导出包在服务器保留几天?
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **问题** | 导出包保留时长(超期自动清理,释放存储) |
|
||||
| **建议方案** | ✅ **默认 7 天**,管理员可随时重新导出(不重复计费) |
|
||||
| **回复** | ☐ 同意 7 天 ☐ 改为 ____ 天 |
|
||||
|
||||
### B7. 压缩质量的抽检由谁做?
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **背景** | 系统对"清晰度可能受损"的文件会打标记,需要人工抽检确认。**但实测 28 张扫描件全部达标,不产生任何待抽检文件**——这个机制可能用不上。 |
|
||||
| **问题** | 是否需要保留抽检环节?由谁执行? |
|
||||
| **建议方案** | ✅ **建议保留机制但默认不启用**(仅在出现"强制压缩"标记时才触发),由**班主任在审核时顺带确认** |
|
||||
| **回复** | ☐ 同意 ☐ 不需要该机制 |
|
||||
|
||||
### B8. 一期是否需要与认定机构的业务系统对接?
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **问题** | 是否需要把数据直接推送到机构系统,而不是导出文件包? |
|
||||
| **建议方案** | ✅ **建议一期不做**(导出标准包已可满足),**但预留开放接口**,二期按需启用 |
|
||||
| **回复** | ☐ 一期不做 ☐ 一期就必须对接(请提供对方接口文档) |
|
||||
|
||||
### B9. 社会收集是否需公安实名核验?
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **问题** | 是否调用公安实名核验接口(涉及按次收费) |
|
||||
| **建议方案** | ✅ **建议不做**。当前"名单核验 + 材料人工审核"已足够;如机构有硬要求再启用 |
|
||||
| **回复** | ☐ 不做 ☐ 必须做 |
|
||||
|
||||
---
|
||||
|
||||
## C 组 · 请商务 / 法务确认
|
||||
|
||||
| # | 事项 | 建议方案 | 回复 |
|
||||
|---|---|---|---|
|
||||
| **C1** | **微信小程序主体资质**是否齐备?教育类目可能需要办学许可或合作方资质,**这会影响上线时间** | 请法务提前确认类目与所需资质;**若小程序受阻,备选 H5 扫码填报可保底** | ☐ 资质齐备 ☐ 有问题,预计 ____ 解决 |
|
||||
| **C2** | **定价策略** | 建议:按**在册人数**计费(如 3–5 元/人/学期),免费版限 1 任务 / 60 人 | ☐ 同意 ☐ 改为 ______ |
|
||||
| **C3** | 是否向学生收取费用(如报名服务费)? | 建议**一期不做**(涉及支付资质与资金合规),仅收 B 端服务费 | ☐ 不做 ☐ 需要 |
|
||||
| **C4** | 私有化部署版本是否同期开发? | 建议 **SaaS 优先、私有化后置**(同代码,按需打包) | ☐ 同意 ☐ 同期开发 |
|
||||
| **C5** | **微信隐私保护指引**需更新(需声明收身份证号、相册权限) | 我方负责配置,**但需法务确认文案** | ☐ 由法务审文案 ☐ 我方自行处理 |
|
||||
|
||||
---
|
||||
|
||||
## D 组 · 我方内部技术决策(仅备案,不需客户回复)
|
||||
|
||||
| # | 决策项 | 建议 | 备注 |
|
||||
|---|---|---|---|
|
||||
| D1 | OCR 引擎选型 | 待比价(腾讯云 / 百度 / 本地模型) | 需同时评估**数据出境合规** |
|
||||
| D2 | 视觉大模型(VLM)选型 | 抽成**可替换接口**,不绑定供应商 | 实测触发率仅 2%~10%,成本可控 |
|
||||
| D3 | 门禁限流阈值 | 同 openid 每小时 ≤10 次 | 上线后按真实数据标定 |
|
||||
| D4 | 材料识别强特征词表 | 平台预置 + 租户可自维护 | **需在"社会评价"批次数据上补充标定** |
|
||||
| D5 | 一寸照片形态判定阈值 | 用真实样本标定(宽高比容差、背景纯净度) | — |
|
||||
| D6 | 微信三条来源通道的体积上限 | 需真机实测 | 决定超限提示与降级路径 |
|
||||
| ~~D7~~ | ~~文件名规则的真实准确率~~ | ✅ **已降级**(见下) | 客户确认后重新评估:该指标**不再影响任何交付项** |
|
||||
|
||||
---
|
||||
|
||||
## 附:已闭环的决策(无需再讨论)
|
||||
|
||||
| 决策 | 内容 | 依据 |
|
||||
|---|---|---|
|
||||
| **D1 强制 1M** | A4 扫描件**强制压到 1M,无例外、不设放宽** | 客户已确认 |
|
||||
| **压缩参数** | 默认 **300dpi / q85**,实测 28/28 达标,平均 806KB | 用真实扫描件实测 |
|
||||
| **无"拍照"入口** | 材料来源只有**聊天记录 / 相册 / 手机文件**三种 | 实测 4 类材料**无一来自现场拍照** |
|
||||
| **报考范围来源** | **从名单聚合**,不存在独立的"计划清单"文件 | 真实名单验证(三列已填 28/28) |
|
||||
| **数据库** | **PostgreSQL**(纯 PG,不依赖 Vastbase 专有语法) | 已确认 + DDL 实测通过 |
|
||||
| **软删除字段** | `deleted` | 已确认 |
|
||||
| **滑动交互** | **只做「右滑通过」**,驳回必须点按钮 | 误触后果不对称 |
|
||||
| **批量操作范围** | 批量通过**只带无问题的单**,红黄一律排除 | 防止"手快全选"放行异常 |
|
||||
| **导出前置检查** | 有不合格文件时**直接禁用导出按钮**,不允许带问题打包 | 铁律 |
|
||||
|
||||
---
|
||||
|
||||
## 客户回执(2026-09-28)
|
||||
|
||||
> **回执结论:全部 23 项按我方建议方案执行。**
|
||||
> 本文档转为**需求冻结基线**——以下各项视为已确认,不再重新讨论。
|
||||
|
||||
### A 组 · 认定机构(6 项)
|
||||
|
||||
| # | 确认结论 | 落地动作 | 状态 |
|
||||
|:--:|---|---|:--:|
|
||||
| A1 | **接受 300dpi 输出** | 无需改动。实测 28/28 达标,均值 806KB | ✅ 已一致 |
|
||||
| A2 | **允许自动降至 200dpi / q70**;低于此值标记"强制压缩"需人工确认 | 已一致:`min_quality=70`、`hardFloorDpi=200`、`markWhenForced=true` | ✅ 已一致 |
|
||||
| A3 | **截图类保留 PNG** | 已一致:`keepPngForScreenshot=true`;学籍表 1025×590 PNG 不缩放 | ✅ 已一致 |
|
||||
| A4 | **证件翻拍长边 ≥1400px** | 已一致:`min_long_side DEFAULT 1400`(仅 P2 档生效) | ✅ 已一致 |
|
||||
| A5 | **保留「工作证明」命名**(即使内容是个人申报表) | 已一致:材料别名映射,规范名不"纠正" | ✅ 已一致 |
|
||||
| A6 | **导出包结构按建议** | 已一致:`{批次号}-{任务名}/{班级}/{材料}/姓名-证件号-材料名.ext` | ✅ 已一致 |
|
||||
|
||||
> **结论:A 组 6 项零改动**——因为当初就是按实测数据设计的,确认只是把假设变成了事实。
|
||||
|
||||
### B 组 · 校方业务(9 项)
|
||||
|
||||
| # | 确认结论 | 落地动作 | 状态 |
|
||||
|:--:|---|---|:--:|
|
||||
| **B1** | **选 A:增加「申请加入名单 → 管理员审批」通道** | ⚠️ **需开发**:新增 FR-509 系列 + 1 张表 + 4 个接口 + 2 屏原型 | 🔨 **待实施** |
|
||||
| B2 | 社会收集名单**由报名点/机构维护预报名名单**,导入后开放;**填报期内可随时追加** | 已一致:FR-508 追加导入;补充说明写入 PRD §5.5 | ✅ 已一致 |
|
||||
| B3 | **字段三分类**:姓名/证件号/报考三项锁定,其余可核对修改并留痕 | 已一致:FR-502b + `changed_fields` 机制 | ✅ 已一致 |
|
||||
| B4 | **不加手机号验证码** | 无需改动(当前门禁即姓名+证件号) | ✅ 已一致 |
|
||||
| B5 | **驳回上限 3 次** | 已一致:`maxRejectCount=3`,超限升级管理员 | ✅ 已一致 |
|
||||
| B6 | **导出包保留 7 天** | 已一致:`expire_time = NOW() + 7 days` | ✅ 已一致 |
|
||||
| B7 | **保留抽检机制但默认不启用** | 已一致:FR-616,仅"强制压缩"标记触发 | ✅ 已一致 |
|
||||
| B8 | **一期不对接机构系统,预留开放接口** | 已一致:§14 开放接口已设计但不启用 | ✅ 已一致 |
|
||||
| B9 | **不做公安实名核验** | 无需改动 | ✅ 已一致 |
|
||||
|
||||
> **结论:B 组 9 项中 8 项零改动,仅 B1 需开发。**
|
||||
> B1 的建议是我方主动提出的(原需求只有"只提示"),客户采纳后**这是一次功能增量**,已按 §变更记录 追加设计与排期。
|
||||
|
||||
### C 组 · 商务法务(5 项)
|
||||
|
||||
| # | 确认结论 | 落地动作 |
|
||||
|:--:|---|---|
|
||||
| C1 | 小程序教育类目资质:**按建议准备,若受阻走 H5 扫码填报保底** | 商务/法务启动;**H5 保底方案需在 M0 期间做技术预研**(约 2 人日) |
|
||||
| C2 | 定价:**按在册人数计费**(3–5 元/人/学期),免费版 1 任务/60 人 | 已一致:`sys_tenant_package` 表已在 DDL 中 |
|
||||
| C3 | **一期不向学生收费** | 无需改动 |
|
||||
| C4 | **SaaS 优先、私有化后置**(同代码按需打包) | 已在架构中体现 |
|
||||
| C5 | 隐私保护指引:**我方配置、法务审文案** | ⏳ 待法务出文案 |
|
||||
|
||||
### D 组 · 我方内部(7 项)
|
||||
|
||||
| # | 决策 | 状态 |
|
||||
|:--:|---|---|
|
||||
| D1 | OCR 引擎选型(腾讯云/百度/本地) | ⏳ M2 结束前定 |
|
||||
| D2 | VLM 抽成可替换接口,不绑定供应商 | ✅ 已按此设计(04 文档 M3 §3.6) |
|
||||
| D3 | 门禁限流:openid 10 次/小时、IP 20 次/分钟 | ✅ 已实现口径 |
|
||||
| D4 | **需索取「社会评价」批次的原始材料样本** | ✅ **已后置为二期**(一期只做学校收集 + 自主认定,不涉及社会评价材料) |
|
||||
|
||||
#### D4 补充说明:为什么需要「社会评价」的样本
|
||||
|
||||
> 我们的实测数据集(`24中技数控1班`,28 人 112 文件)**全部是自主认定批次**,只覆盖 4 类材料。
|
||||
> 而 **`cert_type=2 社会评价`** 批次的材料清单**有两类是完全不同的**,目前**一份样本都没有**。
|
||||
|
||||
| 材料编码 | 名称 | 出现在 | 强特征词现状 | 样本 |
|
||||
|---|---|---|---|---|
|
||||
| `PHOTO1` | 一寸照片 | 两者都有 | 一寸、证件照、免冠 | ✅ 112 个实测 |
|
||||
| `IDCARD` | 身份证 | 两者都有 | 身份证、公民身份、人像面 | ✅ 实测 |
|
||||
| `WORK_PROOF` | 工作证明 | 两者都有 | 申报表、工作证明、公章 | ✅ 实测 |
|
||||
| `SCHOOL_ROLL` | 学籍表 | 仅自主认定 | 学籍 | ✅ 实测 |
|
||||
| **`XJW_BA`** | **学信网备案表** | **仅社会评价** | 学信网、备案表、电子注册 | ❌ **零样本** |
|
||||
| **`OTHER_PROOF`** | **其它佐证材料** | **仅社会评价 / 专项** | **写不出词** | ❌ **零样本** |
|
||||
|
||||
**需要样本解决的三件事**
|
||||
|
||||
| # | 要解决什么 | 现状的猜测成分 |
|
||||
|:--:|---|---|
|
||||
| 1 | **标定 `XJW_BA` 的强特征词与形态阈值** | 现有词是从**官方模板的文字要求**里抄的,不是从真实文件观察来的。备案表可能是网页截图(P4)、打印扫描(P3)、或拍屏(P2),**形态不同则识别与压缩策略完全不同** |
|
||||
| 2 | **决定 `OTHER_PROOF` 要不要做自动识别** | 它含"毕业证 / 社保权益单 / 职称证明 / 技能等级证书"四类**版式完全不同**的东西。用一个编码承载,识别引擎**无法从内容判断它"是不是其它佐证材料"**。可能应改为:**不做识别、默认识别失败放行、由学生自选槽位** |
|
||||
| 3 | **校准 P2/P3/P4 的自动判定阈值** | 现有阈值是用院校批次(学籍表截图 1025×590)标定的 |
|
||||
|
||||
**样本要求**
|
||||
|
||||
| 项 | 说明 |
|
||||
|---|---|
|
||||
| 理想 | **一个完整的社会评价批次**(1 个任务、全部人的全部材料) |
|
||||
| 退而求其次 | **10~20 份**,但**必须覆盖「学信网备案表」和「各类佐证材料」**(这两类现在零样本) |
|
||||
| 特别注意 | 记录**材料来源与格式**——社会人员可能直接从学信网下载 **PDF**(而非拍照/扫描)。若是,`file_types` 白名单、预览、压缩策略都要跟着调整 |
|
||||
|
||||
> ✅ **已后置为二期**(2026-09-28 客户确认):一期范围收窄为「**学校收集 + 自主认定**」,材料只有 4 类(一寸照片 / 身份证 / 学籍表 / 工作证明),**全部有 112 个真实文件实测**。
|
||||
> 因此 D4 **不再是待办、也不阻塞任何里程碑**。待二期做「社会评价」时再索取即可。
|
||||
| D5 | 一寸照片形态阈值用真实样本标定 | ✅ 已有实测(984×1378,5:7) |
|
||||
| D6 | **微信三条来源通道体积上限需真机实测** | ⏳ 待测 |
|
||||
| D7 | ~~文件名规则在"学生原始乱名"上测真实准确率~~ | ✅ **已降级为文案校准**(不需样本) |
|
||||
|
||||
#### D7 补充说明:为什么这个指标不再重要
|
||||
|
||||
> **原担忧**:文件名层识别实测 112/112 命中,但**这批文件是老师已经命名过的**,属有偏样本。
|
||||
> 学生的原始文件名可能是 `微信图片_20260928103015.jpg`、`IMG_20260928_103015.jpg` 这种,**完全没有姓名和证件号**。
|
||||
|
||||
**重新评估后结论:这个指标的"有偏性"不影响任何交付项。**
|
||||
|
||||
| # | 为什么不影响 | 依据 |
|
||||
|:--:|---|---|
|
||||
| 1 | **输出命名不依赖文件名** | 规范名一律取自名册(`{姓名}-{证件号}-{材料名}`),文件名只用于**判断归属**,不用于命名 |
|
||||
| 2 | **归属校验不依赖姓名** | 判据是 **证件号反查名册 > OCR > 姓名(仅辅助)**;姓名单独不构成规则 |
|
||||
| 3 | **材料类型识别有槽位兜底** | 学生在**哪个槽位**上传就默认是哪类(`confidence 0.60`),文件名只是**交叉验证**——用于发现传错槽位 |
|
||||
| 4 | **识别失败一律放行** | 兜底策略已定:识别不出来不阻断提交 |
|
||||
|
||||
> ⇒ 即使学生的文件**全是乱名**(层 1 完全失效),系统行为也只是:
|
||||
> **少了一层"传错槽位"的交叉验证**,而槽位本身的准确性由学生自己保证,且形态层(层 2)与内容层(层 3)仍能发现明显错位。
|
||||
> **不会出错,只会少一层提示。**
|
||||
|
||||
**降级后唯一残留的需求(文案问题,非算法问题)**
|
||||
|
||||
上传页的「**去哪里找**」引导语(`collect_material_def.tip_text`)要贴合学生真实看到的文件名。例如:
|
||||
|
||||
| 如果学生文件名普遍是 | 引导语应该说 |
|
||||
|---|---|
|
||||
| `微信图片_20260928103015.jpg` | "在聊天记录里往上翻,找**你发出去的那张图**" |
|
||||
| `IMG_20260928_103015.jpg` | "在相册里**按拍摄日期**找" |
|
||||
| `Screenshot_2026-09-28.png` | "在相册的**截图**分类里找" |
|
||||
|
||||
**怎么获取**:**不需要专门准备样本**——班主任在班级群里问两句,或者让学生随手截几个文件名发过来即可。
|
||||
|
||||
> ⚠️ **原样本需求已取消**,不再向客户索取"学生原始乱名样本"。
|
||||
|
||||
### 本次回执带来的变更汇总
|
||||
|
||||
| # | 变更 | 影响面 | 工作量 |
|
||||
|:--:|---|---|:--:|
|
||||
| **V1** | **新增 FR-509 系列:名单加入申请与审批** | PRD / DDL / 接口 / 模块设计 / 原型 / WBS / 测试 | **+9 人日** |
|
||||
| V2 | 社会收集名单来源补充说明(B2) | PRD §5.5 | 0.5 人日 |
|
||||
| V3 | H5 保底方案技术预研(C1) | M0 追加 | 2 人日 |
|
||||
| | | **合计** | **+11.5 人日** |
|
||||
|
||||
**修订后排期**:380 → **391.5 人日**;关键路径 **67 → 76 人日**;工期 **14 周 → 15 周**。
|
||||
(B1 落在 M2,而 M2 在关键路径上,故工期顺延 1 周。)
|
||||
@@ -0,0 +1,164 @@
|
||||
# 需求追溯矩阵(自审报告)
|
||||
|
||||
> 用途:把 PRD 的每条需求对上「**原型 / 库表 / 接口 / 验收**」四个交付面,找出**说了没做**和**做了没说**。
|
||||
> 生成日期:2026-09-28
|
||||
> 审计范围:**125 条 FR**(92 P0 / 30 P1 / 3 P2)+ **21 条 VAL 校验规则** + **121 个接口** + **20 张表**
|
||||
> 结论:**主流程全覆盖。第三节的 10 个接口缺口已于 P3 补齐(共新增 21 个接口);配置类页面缺口见 3.2**
|
||||
|
||||
---
|
||||
|
||||
## 一、总览
|
||||
|
||||
| 模块 | 条数 | P0 | 库表 | 接口 | 原型 | 判定 |
|
||||
|---|:--:|:--:|:--:|:--:|:--:|:--:|
|
||||
| FR-100 租户与组织 | 5 | 5 | ✅ | ✅ | ❌ | ⚠️ 缺平台运营端原型 |
|
||||
| FR-200 账号与角色 | 6 | 5 | ✅ | ✅ | ❌ | ⚠️ 缺登录页与用户管理页 |
|
||||
| FR-300 收集任务 | 12 | 9 | ✅ | ✅ | ⚠️ | ✅ 暂停接口已补(P3) |
|
||||
| FR-400 动态表单 | 8 | 6 | ✅ | ✅ | ❌ | ⚠️ **缺表单设计器原型** |
|
||||
| FR-500 名单管理 | 24 | 21 | ✅ | ✅ | ✅ | ✅ **全覆盖**(含客户确认新增的 FR-509 系列 10 条) |
|
||||
| FR-600 材料与规则 | 23 | 18 | ✅ | ✅ | ⚠️ | ✅ 识别结果已并入审核详情(P3) |
|
||||
| FR-800 审核工作台 | 10 | 7 | ✅ | ✅ | ✅ | ✅ 撤回接口已补(P3) |
|
||||
| FR-900 汇总导出 | 10 | 8 | ✅ | ✅ | ✅ | ✅ **全覆盖** |
|
||||
| FR-1000 通知催报 | 6 | 4 | ✅ | ✅ | ⚠️ | ✅ 消息中心与额度已补(P3) |
|
||||
| FR-1100 数据看板 | 5 | 2 | ✅ | ✅ | ✅ | ✅ **全覆盖** |
|
||||
| FR-1200 系统管理 | 6 | 5 | ⚠️ | ✅ | ❌ | ⚠️ 接口已补(P3),原型仍缺 |
|
||||
| FR-1300 SaaS 计费 | 4 | 2 | ⚠️ | ⚠️ | ❌ | ⚠️ 缺平台运营端 |
|
||||
| FR-1400 历史整理器 | 6 | 0 | ✅ | ❌ | ❌ | ❌ **三面全缺**(P1,本轮未做) |
|
||||
| **FR-700 校验引擎** | — | — | ✅ | ✅ | ✅ | ✅ 以 VAL-01~21 实现,三端均有体现 |
|
||||
|
||||
**图例**:✅ 完整覆盖 ⚠️ 部分覆盖 ❌ 未覆盖
|
||||
|
||||
---
|
||||
|
||||
## 二、覆盖亮点(这些是逐条核对过的)
|
||||
|
||||
| 能力 | 库表 | 接口 | 原型 | 备注 |
|
||||
|---|:--:|:--:|:--:|---|
|
||||
| **名单即预填报名表(18 业务列)** | ✅ | ✅ | ✅ | 已在 PG 16 实测;管理端③可视化呈现 |
|
||||
| **门禁核验(含限流防枚举)** | ✅ | ✅ | ✅ | 学生端①②③;统一失败提示已实现 |
|
||||
| **命名规范化引擎** | ✅ | ✅ | ✅ | 管理端②**试跑闸门**;学生端⑦结果卡 |
|
||||
| **压缩引擎(五档 + 内存保护)** | ✅ | ✅ | ✅ | 实测 28/28 达标;管理端⑦报压缩率 |
|
||||
| **材料识别(四层递进)** | ✅ | ✅ | ✅ | 学生端⑧;识别结果已并入审核详情响应 |
|
||||
| **批量通过只带绿色项** | ✅ | ✅ | ✅ | 班主任端④ + ⑨(滑动层灰化) |
|
||||
| **导出前置检查(禁止带问题打包)** | ✅ | ✅ | ✅ | 管理端⑦**禁用导出按钮** |
|
||||
| **报考范围聚合与体检** | ✅ | ✅ | ✅ | 管理端④;plan-scope 接口已补 |
|
||||
| **汇总 Excel 与目录树** | ✅ | ✅ | ✅ | 管理端⑦目录树预览 |
|
||||
|
||||
---
|
||||
|
||||
## 三、发现的缺口
|
||||
|
||||
### 3.1 接口缺口(**已在 P3 全部补齐**,共新增 21 个)
|
||||
|
||||
> 这些是"原型/PRD 里承诺了,但接口表里没有"的。放在 P3 接口设计里一并补齐。
|
||||
|
||||
| # | 缺口 | 对应需求 | 影响 | 建议接口 |
|
||||
|:--:|---|---|---|---|
|
||||
| I1 | **报考范围读取与确认** | FR-307~311 | 管理端④整屏无数据来源 | `GET/PUT /api/v1/tasks/{id}/plan-scope` |
|
||||
| I2 | **历史数据整理器** | FR-1401~1406 | §5.14 六条需求**完全无接口** | `POST /api/v1/cleanup/jobs` 等 5 个 |
|
||||
| I3 | **字典管理** | FR-1201 | 学历/等级/补考科目等下拉项无法维护 | `GET/POST/PUT /api/v1/dicts` |
|
||||
| I4 | **操作日志** | FR-1202 | 审计留痕无法查询 | `GET /api/v1/logs/operations` |
|
||||
| I5 | **登录日志** | FR-1203 | 同上 | `GET /api/v1/logs/logins` |
|
||||
| I6 | **消息中心** | FR-1004~1005 | 学生端/教师端"消息"屏无数据来源 | `GET /api/v1/mp/messages`、`/mp/teacher/messages` |
|
||||
| I7 | **订阅额度登记** | R12 对策 | 功能逻辑卷写 `/mp/subscribe`,**主 PRD 接口表漏登记** | `POST /api/v1/mp/subscribe` |
|
||||
| I8 | **审核撤回** | FR-808、原型 | 原型承诺"终审前可撤回",无接口 | `POST /api/v1/audit/submissions/{id}/revoke` |
|
||||
| I9 | **任务暂停/恢复** | FR-301 | FR 要求"发布、**暂停**、截止、归档",只有 publish/close/archive | `POST /api/v1/tasks/{id}/pause`、`/resume` |
|
||||
| I10 | **材料识别结果读取** | FR-614d | 审核辅助要显示"系统判定:身份证 92%",无处取 | 并入 `GET /api/v1/audit/submissions/{id}` 响应 |
|
||||
|
||||
> **合计 10 项,已在 P3 全部补齐**:I10 并入审核详情响应,其余新增 20 个接口(含 1 项登记遗漏 `/mp/files/{fileId}/process`)
|
||||
|
||||
### 3.2 原型缺口(管理端 10 个页面;**其中 3 个已在 P2.5 补齐**)
|
||||
|
||||
> 本轮原型聚焦"**高频主流程 + 设计难点**",配置类页面未出。这不是遗漏,是优先级取舍,但需要明确记录,避免开发以为"不用做"。
|
||||
|
||||
| # | 页面 | 对应需求 | 复杂度 | 建议 |
|
||||
|:--:|---|---|:--:|---|
|
||||
| P1 | **表单设计器** | FR-401~408 | 高 | ⚠️ **必须补**——拖拽式字段编排是 P0,且交互复杂,光靠文字描述开发必理解偏 |
|
||||
| P2 | **材料规则配置**(命名模板编辑器) | FR-601~609 | 高 | ⚠️ **建议补**——管理端②只做了"试跑",配置界面本身没画 |
|
||||
| P3 | 历史数据整理器 | FR-1401~1406 | 中 | 建议补(差异化获客功能,客户演示时最吸睛) |
|
||||
| P4 | 登录页 | FR-201 | 低 | 可略 |
|
||||
| P5 | 组织与用户管理 | FR-103、203~206 | 中 | 可略(标准 CRUD) |
|
||||
| P6 | 任务列表与详情 | FR-301~306 | 中 | 建议补简版(克隆、二维码、暂停) |
|
||||
| P7 | 通知模板管理 | FR-1001~1002 | 低 | 可略 |
|
||||
| P8 | 字典管理 | FR-1201 | 低 | 可略 |
|
||||
| P9 | 操作日志查询 | FR-1202~1203 | 低 | 可略 |
|
||||
| P10 | 平台运营端(租户/套餐/计费) | FR-101~102、1301~1304 | 中 | 建议二期做(一期客户是学校,不关心平台运营) |
|
||||
|
||||
### 3.3 需求条目本身的核查
|
||||
|
||||
> 审计过程中发现 4 个疑似问题,逐条核实后 **3 个是统计脚本的正则缺陷(不是文档缺陷)**,仅 1 个为真实待办。
|
||||
|
||||
| # | 疑似问题 | 核实结论 | 处理 |
|
||||
|:--:|---|---|---|
|
||||
| D1 | FR-614 主条目编号缺位 | ❌ **假警报**——FR-614 在 §5.6 存在(脚本未匹配**加粗编号**写法) | 无需处理 |
|
||||
| D2 | FR-608 未出现在抽取结果 | ❌ **假警报**——同上,且其优先级格含补充说明文字 | 无需处理 |
|
||||
| D3 | FR-1000 模块分级计数与条数不符 | ❌ **假警报**——1001~1004 为 P0、1005 为 P1、1006 为 P2,相加正确 | 无需处理 |
|
||||
| **D4** | **「压缩质量抽检机制」在功能逻辑卷有详述,主 PRD 无独立 FR** | ✅ **真实**——PRD 仅在 §6.3 与验收标准中提及,未立为需求条目 | 🟡 **补 FR-616**:清晰度分级标记与抽检机制(P1) |
|
||||
|
||||
> **教训**:脚本审计前必须先验证脚本本身。本次若直接采信 D1~D3,会在文档里"修"三个不存在的问题,反而引入错误。
|
||||
|
||||
### 3.4 已闭环(无需处理)
|
||||
|
||||
| 项 | 状态 |
|
||||
|---|---|
|
||||
| 报考范围来自名单(不存在计划文件) | ✅ 已澄清并改设计 |
|
||||
| DDL 在 PG 16 实测 | ✅ 零错误 |
|
||||
| 压缩参数实测标定 | ✅ 300dpi/q85,28/28 达标 |
|
||||
| 材料来源三通道(无拍照) | ✅ 已实现并出原型 |
|
||||
| 滑动交互规则 | ✅ 只右滑通过 |
|
||||
|
||||
---
|
||||
|
||||
## 四、结论与行动
|
||||
|
||||
### 覆盖度小结
|
||||
|
||||
```
|
||||
主流程(填报→审核→导出) ████████████████████ 100%
|
||||
规则引擎(命名/压缩/识别) ████████████████████ 100%
|
||||
配置类页面 ██████░░░░░░░░░░░░░░ 30%
|
||||
平台运营 ░░░░░░░░░░░░░░░░░░░░ 0%
|
||||
历史整理器 ░░░░░░░░░░░░░░░░░░░░ 0%(仅库表)
|
||||
```
|
||||
|
||||
### 必须行动的 3 项
|
||||
|
||||
| 优先级 | 行动 | 归属 |
|
||||
|:--:|---|---|
|
||||
| ✅ 已完成 | ~~补 10 个缺失接口(3.1)~~ | → **P3 已完成(新增 21 个)** |
|
||||
| ✅ 已完成 | ~~补**表单设计器**原型(3.2 P1)~~ | → **P2.5 已完成** |
|
||||
| ✅ 已完成 | ~~补**材料规则配置**原型(3.2 P2)~~ | → **P2.5 已完成** |
|
||||
| ✅ 已完成 | ~~补 **FR-616**「清晰度分级标记与抽检机制」~~ 及 **VAL-18~20** 三条校验规则 | → 已补入 PRD |
|
||||
|
||||
### 对计划的调整
|
||||
|
||||
发现了一个原计划未覆盖的缺口,**追加为 P2.5**:
|
||||
|
||||
| # | 任务 | 产出 | 理由 |
|
||||
|---|---|---|---|
|
||||
| **P2.5** | **管理端补充原型**:表单设计器 + 材料规则配置 + 整理器 | 补充到 `admin-web.html` | 追溯矩阵发现的缺口,三者都是**复杂交互、不画会理解偏**的页面 |
|
||||
|
||||
---
|
||||
|
||||
## 附录 · 完整需求索引(115 条)
|
||||
|
||||
| 模块 | 编号范围 | 条数 | 备注 |
|
||||
|---|---|:--:|---|
|
||||
| FR-100 | FR-101 ~ 105 | 5 | 全 P0 |
|
||||
| FR-200 | FR-201 ~ 206 | 6 | 含 FR-202 小程序登录 |
|
||||
| FR-300 | FR-301 ~ 312 | 12 | 含 FR-307~311 报考范围 |
|
||||
| FR-400 | FR-401 ~ 408 | 8 | 全为表单引擎 |
|
||||
| FR-500 | FR-501 ~ 509 | **24** | 含 502a/b、504a~d、**509a~i(客户确认新增 10 条)** |
|
||||
| FR-600 | FR-601 ~ 616 | 23 | 含 601a/b、614a~e(**编号连续,无跳号**) |
|
||||
| FR-800 | FR-801 ~ 810 | 10 | 审核 |
|
||||
| FR-900 | FR-901 ~ 910 | 10 | 导出 |
|
||||
| FR-1000 | FR-1001 ~ 1006 | 6 | 通知 |
|
||||
| FR-1100 | FR-1101 ~ 1105 | 5 | 看板 |
|
||||
| FR-1200 | FR-1201 ~ 1206 | 6 | 系统管理 |
|
||||
| FR-1300 | FR-1301 ~ 1304 | 4 | 计费 |
|
||||
| FR-1400 | FR-1401 ~ 1406 | 6 | 整理器 |
|
||||
| **合计** | | **125** | **92 P0 / 30 P1 / 3 P2** |
|
||||
|
||||
**校验规则**:VAL-01 ~ VAL-21(21 条,编号连续;18~20 为 P4 补充,**21 为客户确认后补充的归属校验**)
|
||||
**接口**:**121 个已定义**(原 96 + 补齐 21 + 客户确认新增 5 − 客户要求移除 1,见 `03-接口设计.md`)
|
||||
**库表**:**20 张**完整 DDL + 4 张"从略"标准表
|
||||
+2880
File diff suppressed because it is too large
Load Diff
+1444
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,505 @@
|
||||
# 开发任务拆解与排期(WBS)
|
||||
|
||||
> 版本:V1.0 | 日期:2026-09-28
|
||||
> 读者:项目经理、技术负责人、全体开发、测试
|
||||
> 依据:《数据信息填报系统PRD.md》V2.3+、《03-接口设计.md》、《04-核心模块设计说明.md》
|
||||
|
||||
---
|
||||
|
||||
## 一、说明
|
||||
|
||||
### 1.1 怎么用这份文档
|
||||
|
||||
| 角色 | 用法 |
|
||||
|---|---|
|
||||
| 技术负责人 | 按 §3 的任务表派工;关注 §4 关键路径与 §5 风险 |
|
||||
| 开发 | 认领任务 ID,从备注里取对应文档章节 |
|
||||
| 测试 | 按 §7 的里程碑验收标准准备用例 |
|
||||
| 项目经理 | 按 §6 团队配置排人;按 §7 卡节点 |
|
||||
|
||||
### 1.2 估算口径
|
||||
|
||||
| 项 | 说明 |
|
||||
|---|---|
|
||||
| 单位 | **人日**(1 人日 = 1 名熟练工程师 6~7 小时有效产出) |
|
||||
| 已含 | 自测、单元测试、联调、Code Review 返工 |
|
||||
| 不含 | 需求变更、外部依赖等待、生产事故处理 |
|
||||
| 缓冲 | **每个里程碑独立加 20% 缓冲**,不合并到总缓冲 |
|
||||
| 依据 | 有明确设计文档支撑的任务按下限估;**无设计文档的任务(外部依赖/选型)按上限估** |
|
||||
|
||||
### 1.3 前置假设
|
||||
|
||||
| # | 假设 | 若不成立的影响 |
|
||||
|:--:|---|---|
|
||||
| 1 | 微信小程序主体资质齐备 | **阻断上线**,见确认函 C1 |
|
||||
| 2 | 认定机构书面接受 300dpi 输出 | 需重调压缩参数,影响 M3(+3 人日) |
|
||||
| 3 | OCR 引擎可在 M2 结束时选定 | 影响 M4(+5 人日延期) |
|
||||
| 4 | VLM 选型可延后至 M4 中期 | 影响小(M3 层 1/2 已能跑通) |
|
||||
| 5 | 单租户数据量 < 5000 人 / 任务 | 超需额外分页与异步优化(+5 人日) |
|
||||
| 6 | 客户名单格式与实测样本同构 | 表头探测需扩样本,影响 M2(+3 人日) |
|
||||
|
||||
### 1.4 里程碑总览
|
||||
|
||||
```
|
||||
M0 ──→ M1 ──→ M2 ─────────────→ M3 ──→ M4 ──→ M5 ──→ M6 ──→ M7
|
||||
基础 任务 名册+门禁 填报 识别 审核 导出 收尾
|
||||
设施 配置 主流程 校验 闭环 交付 上线
|
||||
│ │ │ │ │ │ │
|
||||
└───────┴────────┴─────────────────┘ └───────┴──────┘
|
||||
▲ 里程碑A:可演示"配置→收集" ▲ 里程碑B:可演示"全流程闭环"
|
||||
(第 5 周末) (第 11 周末)
|
||||
▲ 里程碑C:可上线(第 15 周末)
|
||||
```
|
||||
|
||||
### 1.5 工作量汇总
|
||||
|
||||
| 里程碑 | 内容 | 人日 | 含缓冲 | 关键角色 |
|
||||
|:--:|---|:--:|:--:|---|
|
||||
| **M0** | 基础设施与账号体系 | 40 | 48 | 后端 + 前端 |
|
||||
| **M1** | 任务与配置闭环 | 44 | 53 | 前端(设计器重) |
|
||||
| **M2** | 名册与门禁 | 58 | 70 | 后端(算法重)+ **新增 B1** |
|
||||
| **M3** | 填报主流程 | 71 | 85 | **后端(引擎重)** |
|
||||
| **M4** | 识别与校验 | 34 | 41 | 后端 + 算法 |
|
||||
| **M5** | 审核闭环 | 56 | 67 | 前端 + 小程序 |
|
||||
| **M6** | 导出与交付 | 32 | 38 | 后端 |
|
||||
| **M7** | 收尾与上线 | 52 | 62 | 全员 |
|
||||
| | **合计** | **387** | **464** | |
|
||||
|
||||
---
|
||||
|
||||
## 一·五、一期范围(客户确认后收窄)
|
||||
|
||||
> **一期只做「学校收集 + 自主认定」一条流程。** 详见 PRD「一期范围声明」。
|
||||
|
||||
| 后置项 | 原工作量 | 一期处理 |
|
||||
|---|---|---|
|
||||
| 社会收集审核流(跳过初审) | 配置驱动,≈0 | **不启用**;代码分支不做 |
|
||||
| 社会评价材料(`XJW_BA` 学信网备案表) | 需样本标定 | **不做**;识别词表暂缺 |
|
||||
| 专项职业能力支持 | 配置驱动,≈0 | **不启用**;`FR-310` 标二期 |
|
||||
| 其它佐证材料(`OTHER_PROOF`) | 需样本标定 | **不做**;且倾向"不做识别、纯手动槽位" |
|
||||
|
||||
**受影响的 5 个任务(各减 1 人日)**
|
||||
|
||||
| 任务 | 原 | 现 | 减少原因 |
|
||||
|---|:--:|:--:|---|
|
||||
| M4-01 识别层 1(强特征词表) | 3 | **2** | 一期只标定 4 类材料 |
|
||||
| M4-02 识别层 2(形态特征) | 3 | **2** | 一期只覆盖自主认定批次形态 |
|
||||
| M5-01 待审列表 | 4 | **3** | 一期只有一条审核流 |
|
||||
| M7-08 Golden Set 回归脚本 | 4 | **3** | 一期只覆盖 4 类材料 |
|
||||
| M7-11 用户手册 / 培训 | 3 | **2** | 一期流程更简单 |
|
||||
|
||||
> ⚠️ **二期启用时必须补的**:`XJW_BA` / `OTHER_PROOF` 的识别标定与回归断言、社会收集审核流、专项职业能力字段隐藏。
|
||||
> 这些都已在文档中保留描述,只是标注为二期。
|
||||
|
||||
---
|
||||
|
||||
## 二、任务编号规则
|
||||
|
||||
```
|
||||
M{里程碑号}-{序号} 例:M3-04 表示第 3 个里程碑的第 4 个任务
|
||||
├─ 依赖:前置任务 ID,可并行则写"—"
|
||||
├─ 人日:见 1.2 口径
|
||||
└─ 备注:指向对应文档章节,开发直接翻过去
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 三、任务拆解
|
||||
|
||||
## M0 · 基础设施与账号体系(40 人日)
|
||||
|
||||
> **目标**:能登录、能建组织、能建用户。这是所有后续任务的地基。
|
||||
|
||||
| ID | 任务 | 依赖 | 人日 | 角色 | 备注 |
|
||||
|---|---|:--:|:--:|---|---|
|
||||
| M0-01 | 环境搭建:PG 16 / Redis / OSS / 对象存储策略 / 日志采集 | — | 3 | 后端 | 执行 `db/schema-postgresql.sql`(已实测) |
|
||||
| M0-02 | 后端项目骨架:分层、统一响应、异常处理、多租户拦截器 | M0-01 | 3 | 后端 | 接口设计 §1.3 |
|
||||
| M0-03 | 前端项目骨架:PC 管理端 + 小程序,路由/请求封装/权限指令 | — | 3 | 前端 | — |
|
||||
| M0-04 | CI/CD 与多环境(dev/test/prod)发布流水线 | M0-02 | 2 | 后端 | — |
|
||||
| M0-05 | 租户/组织 CRUD(后端) | M0-02 | 4 | 后端 | 接口 §6.1~§6.4 |
|
||||
| M0-06 | 用户与角色 CRUD(后端,含批量导入) | M0-05 | 4 | 后端 | 接口 §5.6~§5.9 |
|
||||
| M0-07 | PC 登录鉴权(含图形验证码、失败锁定) | M0-06 | 3 | 后端 | 接口 §5.1 |
|
||||
| M0-08 | 小程序登录 + 手机号绑定 | M0-02 | 2 | 后端 | 接口 §5.3 |
|
||||
| M0-09 | 管理端整体框架:布局/导航/表格/表单组件库 | M0-03 | 5 | 前端 | 参照 `admin-web.html` 视觉 |
|
||||
| M0-10 | 组织与用户管理页 | M0-09 | 4 | 前端 | — |
|
||||
| M0-11 | 登录页 + 权限菜单渲染 | M0-09 | 3 | 前端 | — |
|
||||
| M0-12 | 小程序框架与首页骨架 | M0-03 | 2 | 前端 | 参照 `student-app.html` |
|
||||
| ⭐ M0-13 | **H5 扫码填报保底方案技术预研**(C1 风险对策) | M0-03 | 2 | 前端 | 若小程序资质受阻,H5 可保底上线;**只预研不开发** |
|
||||
| **小计** | | | **40** | | |
|
||||
|
||||
**M0 验收标准(可演示)**
|
||||
|
||||
- [ ] 能开通租户 → 建组织树 → 导入 42 个用户 → 各角色登录看到不同菜单
|
||||
- [ ] 跨租户访问被拦截
|
||||
- [ ] `schema-postgresql.sql` 在测试环境零错误执行
|
||||
|
||||
---
|
||||
|
||||
## M1 · 任务与配置闭环(44 人日)
|
||||
|
||||
> **目标**:管理员能配出一个完整任务(含表单与材料规则),但还不能收集。
|
||||
|
||||
| ID | 任务 | 依赖 | 人日 | 角色 | 备注 |
|
||||
|---|---|:--:|:--:|---|---|
|
||||
| M1-01 | 任务 CRUD + 状态机(草稿/发布/暂停/恢复/截止/归档) | M0-05 | 6 | 后端 | 接口 §7.1~§7.5;**暂停与截止语义不同** |
|
||||
| M1-02 | 任务覆盖班级与班主任配置(`collect_task_org`) | M1-01 | 3 | 后端 | 接口 §7.6;主/副班主任 |
|
||||
| M1-03 | 表单 schema 版本化存储与冲突检测 | M1-01 | 4 | 后端 | 接口 §7.9;`baseVersion` 冲突返回 40025 |
|
||||
| M1-04 | 材料项定义 CRUD | M1-01 | 4 | 后端 | 接口 §7.10;28 个字段 |
|
||||
| M1-05 | 规则模板保存与应用 | M1-04 | 3 | 后端 | 接口 §7.13 |
|
||||
| M1-06 | 填报二维码 / 短链生成 | M1-01 | 2 | 后端 | 接口 §7.12 |
|
||||
| M1-07 | **表单设计器**(字段库/画布/属性三栏,内置字段锁定) | M0-09 | 8 | 前端 | 原型 STEP 08;内置字段不可删 |
|
||||
| M1-08 | 条件显示规则配置 UI | M1-07 | 3 | 前端 | 三条级联:补考/在校生/专项 |
|
||||
| M1-09 | **材料规则配置**(命名模板编辑器 + 档位选择) | M0-09 | 6 | 前端 | 原型 STEP 09;档位下拉写明适用条件 |
|
||||
| M1-10 | 任务向导与任务列表/详情页 | M1-07 | 5 | 前端 | 7 步向导 |
|
||||
| **小计** | | | **44** | | |
|
||||
|
||||
**M1 验收标准**
|
||||
|
||||
- [ ] 配出一个任务:21 个内置字段 + 2 个扩展字段 + 4 个材料项 + 3 条条件规则
|
||||
- [ ] 已发布任务改表单 → 自动升版本,旧填报单不受影响
|
||||
- [ ] 内置字段删除按钮**不可点**(前后端双重校验)
|
||||
|
||||
---
|
||||
|
||||
## M2 · 名册与门禁(58 人日)
|
||||
|
||||
> **目标**:能导入真实名单、能聚合报考范围、学生能通过门禁进来。
|
||||
|
||||
| ID | 任务 | 依赖 | 人日 | 角色 | 备注 |
|
||||
|---|---|:--:|:--:|---|---|
|
||||
| M2-01 | **表头探测算法**(扫前 10 行 + 已知列名打分 + 候选回传) | M1-01 | 4 | 后端 | 04 文档 M1;**实测表头在第 1 行和第 6 行都有** |
|
||||
| M2-02 | 名册导入 + 体检报告(证件号校验位/重复行/必填缺失) | M2-01 | 5 | 后端 | 接口 §8.1;`dryRun` 模式 |
|
||||
| M2-03 | 名册追加导入(按证件号去重合并)与变更留痕 | M2-02 | 3 | 后端 | FR-506/508 |
|
||||
| M2-04 | 报考范围聚合(按职业/工种/等级分组) | M2-02 | 4 | 后端 | 接口 §7.7;**不存在计划文件** |
|
||||
| M2-05 | 报考范围一致性体检(5 类 issue) | M2-04 | 3 | 后端 | 含 `SCOPE_LEVEL_MIXED` 等 |
|
||||
| M2-06 | **门禁匹配引擎**(限流/规范化/匹配/原子绑定) | M0-08 | 6 | 后端 | 04 文档 M4;**并发绑定走 UPDATE...WHERE** |
|
||||
| M2-07 | 姓名形近字/同音字确认流程(含 confirmToken) | M2-06 | 2 | 后端 | FR-504c |
|
||||
| M2-08 | 名册导入 UI(表头可视化 + 错行定位 + dryRun 确认) | M1-10 | 5 | 前端 | 原型 ③ |
|
||||
| M2-09 | 报考范围确认 UI(分组 + 体检提示 + 排除操作) | M1-10 | 4 | 前端 | 原型 ④;**必须全覆盖校验** |
|
||||
| M2-10 | 学生端门禁屏 + 统一失败页 + 形近字确认弹层 | M0-12 | 5 | 前端 | 原型 ①②③ |
|
||||
| M2-11 | 学生端"我的任务列表" | M0-12 | 3 | 前端 | 接口 §9.1 |
|
||||
| M2-12 | 班主任端班级看板 | M0-12 | 4 | 前端 | 接口 §10.1;**完成率=已提交率 82%** |
|
||||
| ⭐ M2-13 | **加入申请后端**:提交 / 限流 / 去重 / 证件号与手机号转 HMAC | M2-06 | 3 | 后端 | 接口 §8.7.1~8.7.2;**不存明文**;`40037/40038/40039` 三个分支 |
|
||||
| ⭐ M2-14 | **审批接口**:通过建模册项(行锁)/ 驳回 / 通知推送 | M2-13 | 2 | 后端 | 接口 §8.7.3~8.7.5;**`FOR UPDATE` 必须有**,事务外发通知 |
|
||||
| ⭐ M2-15 | **学生端申请入口与表单屏**(失败页新增出口) | M2-10 | 2 | 前端 | 门禁失败页加「申请加入名单」+ 申请表单 + 我的申请状态 |
|
||||
| ⭐ M2-16 | **管理端审批页**(列表 + 通过选班 + 驳回填原因) | M2-09 | 2 | 前端 | 驳回原因必填;**不提供"全选"** |
|
||||
| ⭐ M2-17 | 社会收集名单来源落地(B2):与报名点对齐预报名名单流程 | M2-02 | 1 | 后端 | 说明性工作 + 与客户确认字段口径 |
|
||||
| **小计** | | | **58** | | |
|
||||
|
||||
**M2 验收标准**
|
||||
|
||||
- [ ] 导入真实名单 `24中技数控1班`(28 人):正确识别表头在第 **6** 行
|
||||
- [ ] 导入官方空白模板(表头第 **1** 行):也能正确识别
|
||||
- [ ] 体检检出:证件号校验位失败 **1 例**、重复 **1 例**
|
||||
- [ ] 门禁:正确通过 / 错误统一提示 / 形近字确认 / **并发 100 请求抢同一名册项恰好 1 个成功**
|
||||
- [ ] 报考范围:3 组,其中专项职业能力组标 `NEED_REVIEW`
|
||||
|
||||
---
|
||||
|
||||
## M3 · 填报主流程(71 人日)⚠️ 最重里程碑
|
||||
|
||||
> **目标**:学生能真实走完"选文件 → 处理 → 填写 → 提交"。**两个核心引擎在这里落地。**
|
||||
|
||||
| ID | 任务 | 依赖 | 人日 | 角色 | 备注 |
|
||||
|---|---|:--:|:--:|---|---|
|
||||
| M3-01 | **M1 命名规范化引擎**(分层步骤 + 幂等) | M2-02 | 6 | 后端 | 04 文档 M1;**建议最先做,纯函数好测** |
|
||||
| M3-02 | 证件号抽取(18/15 位 + 形近字替换 ≤2 位) | M3-01 | 2 | 后端 | 替换次数上限是硬约束 |
|
||||
| M3-03 | 证件号三源裁决(文件名/名册/OCR) | M3-02 | 3 | 后端 | 含 `NEED_HUMAN` 不自动改 |
|
||||
| M3-04 | 姓名一致性与形近字纠错 | M3-01 | 2 | 后端 | 复用 M2-07 的相似度算法 |
|
||||
| M3-05 | **M2 压缩引擎**(五档 + EXIF + 二分降质 + 二次有损防护) | M0-01 | 12 | 后端 | 04 文档 M2;用 112 个真实文件回归 |
|
||||
| M3-06 | 体积下限保护 + 强制压缩标记 + 抽检清单 | M3-05 | 3 | 后端 | FR-616、VAL-20 |
|
||||
| M3-07 | 文件直传:upload-token + 秒传 + 端侧压缩提示 | M0-01 | 4 | 后端 | 接口 §9.5 |
|
||||
| M3-08 | 文件处理接口(同步 + 异步 + 进度轮询) | M3-07 | 4 | 后端 | 接口 §9.6 |
|
||||
| M3-09 | 批量处理(多材料一次提交) | M3-08 | 2 | 后端 | 接口 §9.7 |
|
||||
| M3-10 | 表单填写页(预填标注 + 三态只读/可改/留痕 + 级联) | M1-07 | 8 | 前端 | 原型 ④;`changedFields` 高亮 |
|
||||
| M3-11 | 草稿自动保存与并发冲突提示 | M3-10 | 3 | 前端 | 每 15 秒 |
|
||||
| M3-12 | 材料上传页(三来源 + 槽位 + 多选 + 进度) | M0-12 | 6 | 前端 | 原型 ⑤⑥;**无拍照入口** |
|
||||
| M3-13 | 处理结果卡(命名前后对比 + 体积压缩率 + 分步明细) | M3-08 | 5 | 前端 | 原型 ⑦ |
|
||||
| M3-14 | 进度与问题清单页 | M3-13 | 4 | 前端 | 接口 §9.12 |
|
||||
| M3-15 | 提交校验与阻断展示(blocking/warnings 分流) | M3-14 | 5 | 前端 | 接口 §9.10;`canForceSubmit` |
|
||||
| M3-16 | 提交 / 撤回 | M3-15 | 2 | 前端 | — |
|
||||
| **小计** | | | **71** | | |
|
||||
|
||||
**M3 验收标准(用真实数据)**
|
||||
|
||||
- [ ] **回归基线全部命中**:命名违规 **59**、双扩展名 **56**、体积超标 **31**
|
||||
- [ ] 压缩后 28 张 A4 扫描件:**均值 ≈806KB、最大 ≤831KB、0 张强制压缩**
|
||||
- [ ] 证件号异常 **5 例**(自动 3 + 人工 2)
|
||||
- [ ] 学生能在一台手机上完整走完 4 类材料的上传与提交
|
||||
- [ ] 提交时的阻断清单能定位到**具体某人的某项材料**
|
||||
|
||||
---
|
||||
|
||||
## M4 · 识别与校验(34 人日)
|
||||
|
||||
> **目标**:材料自动归类、问题自动发现。**识别失败必须放行。**
|
||||
|
||||
| ID | 任务 | 依赖 | 人日 | 角色 | 备注 |
|
||||
|---|---|:--:|:--:|---|---|
|
||||
| M4-01 | M3 识别层 1(文件名 + 强特征词表) | M3-01 | 2 | 后端 | 04 文档 M3;禁用通用词入表。**一期只标定 4 类材料**(二期再加 `XJW_BA`/`OTHER_PROOF`) |
|
||||
| M4-02 | M3 识别层 2(形态特征 + 零文字判据) | M4-01 | 2 | 后端 | 零文字才是 PHOTO1 的可靠判据。**一期只覆盖自主认定批次形态** |
|
||||
| M4-03 | **OCR 引擎接入与选型落地** | M0-01 | 3 | 后端 | 确认函 D1;需评估数据出境 |
|
||||
| M4-04 | M3 识别层 3(OCR 内容打分) | M4-03 | 2 | 后端 | — |
|
||||
| M4-05 | VLM 接入 + 成本闸 + 触发率监控 | M4-03 | 4 | 后端 | 04 文档 3.6;**置信度 <0.8 不展示** |
|
||||
| M4-06 | 冲突判定与一键移动 | M4-02 | 3 | 后端 | **只提示不自动改位** |
|
||||
| M4-07 | 散图分拣模式 | M4-06 | 3 | 后端 | 唯一允许"建议落位"的场景 |
|
||||
| M4-08 | **M5 校验引擎**(20 条规则 + 7 阶段依赖) | M3-03 | 8 | 后端 | 04 文档 M5;**VAL 编号以 PRD 为准** |
|
||||
| M4-09 | 跨文件一致性判定(VAL-04 / VAL-19) | M4-08 | 3 | 后端 | 疑似串档转人工 |
|
||||
| M4-10 | 识别结果 UI(置信度展示 + 冲突卡片 + 一键移动) | M4-06 | 4 | 前端 | 学生端 ⑧ |
|
||||
| **小计** | | | **34** | | |
|
||||
|
||||
**M4 验收标准**
|
||||
|
||||
- [ ] 112 个真实文件:文件名层 **112/112**,形态层分流派 **≈99%**
|
||||
- [ ] 零文字判据命中一寸照片
|
||||
- [ ] **"身份证号"四个字不会导致误判**(通用词禁入表)
|
||||
- [ ] 全黑图片:识别失败但**能提交**
|
||||
- [ ] VLM 触发率在 **2%~10%** 区间
|
||||
|
||||
---
|
||||
|
||||
## M5 · 审核闭环(56 人日)
|
||||
|
||||
> **目标**:班主任在手机上完成初筛,管理员在 PC 完成终审,全程可追溯。
|
||||
|
||||
| ID | 任务 | 依赖 | 人日 | 角色 | 备注 |
|
||||
|---|---|:--:|:--:|---|---|
|
||||
| M5-01 | 待审列表接口(红黄绿分级 + `batchApprovable` 服务端判定) | M4-08 | 3 | 后端 | 接口 §10.2。**一期只需处理学校收集一条审核流** |
|
||||
| M5-02 | 审核详情接口(含识别结果、变更字段、时间轴) | M5-01 | 3 | 后端 | 接口 §11.2;I10 缺口落点 |
|
||||
| M5-03 | 通过 / 驳回(含 redCount 拦截) | M5-02 | 3 | 后端 | **有红问题的单不允许通过** |
|
||||
| M5-04 | 批量通过(**只带绿色项,服务端强制**) | M5-03 | 2 | 后端 | 铁律;红黄一律跳过 |
|
||||
| M5-05 | 审核撤回(含"已导出不可撤"约束) | M5-03 | 2 | 后端 | 接口 §11.4 |
|
||||
| M5-06 | 代传材料(留痕 `source=4`) | M5-03 | 3 | 后端 | 学生端可见"由李老师代传" |
|
||||
| M5-07 | 待审卡片流(**只右滑通过**,左滑不绑定驳回) | M5-01 | 8 | 前端 | 原型 ②;`actionSource` 埋点 |
|
||||
| M5-08 | 学生详情页(材料预览 + 问题清单 + 变更高亮) | M5-02 | 6 | 前端 | 原型 ③ |
|
||||
| M5-09 | 驳回页(原因快选 + 二次确认 + 语音转文字) | M5-08 | 5 | 前端 | 原型 ⑤;**驳回必须点按钮** |
|
||||
| M5-10 | 一键通过确认页(列出将通过的名单) | M5-07 | 3 | 前端 | 原型 ④ |
|
||||
| M5-11 | 管理端终审列表与详情 | M5-02 | 5 | 前端 | 原型 ⑥ |
|
||||
| M5-12 | 通知:订阅消息 + 降级链(订阅→公众号→短信→站内信) | M0-08 | 6 | 后端 | 接口 §13.6;额度登记 |
|
||||
| M5-13 | 消息中心(学生端 + 教师端) | M5-12 | 4 | 前端 | 接口 §13.5 |
|
||||
| M5-14 | 催报(定向 + 一键 + 去重) | M5-12 | 3 | 后端 | `excludeRecentHours` 防骚扰 |
|
||||
| **小计** | | | **56** | | |
|
||||
|
||||
**M5 验收标准**
|
||||
|
||||
- [ ] 班主任在手机上:右滑通过 3 个、驳回 1 个(带原因)、一键通过剩余绿色项
|
||||
- [ ] 一键通过时**红色项被自动跳过并回报原因**
|
||||
- [ ] 撤回一条已通过的记录 → 时间轴出现"撤回"记录
|
||||
- [ ] **已导出打包的单不可撤回**(返回 40033)
|
||||
- [ ] 通知降级:额度耗尽时静默降级,学生端无异常提示
|
||||
|
||||
---
|
||||
|
||||
## M6 · 导出与交付(32 人日)
|
||||
|
||||
> **目标**:一键产出可直接上报的规范包。**有不合格文件就禁用导出按钮。**
|
||||
|
||||
| ID | 任务 | 依赖 | 人日 | 角色 | 备注 |
|
||||
|---|---|:--:|:--:|---|---|
|
||||
| M6-01 | 导出前置检查(三类 blocking) | M4-08 | 4 | 后端 | 铁律;`canExport=false` |
|
||||
| M6-02 | **导出打包引擎**(快照固化 + 目录树 + 分卷 + 断点续传) | M6-01 | 10 | 后端 | 04 文档 M6;**先固化快照再打包** |
|
||||
| M6-03 | 中文文件名编码(Windows 解压不乱码) | M6-02 | 2 | 后端 | `entryNameEncoding` |
|
||||
| M6-04 | 汇总 Excel(严格对齐官方 21 列 A–U) | M6-02 | 4 | 后端 | 客户拿到就能用 |
|
||||
| M6-05 | 导出中心 UI(进度 + 结果 + 下载) | M6-02 | 3 | 前端 | 原型 ⑦ |
|
||||
| M6-06 | **目录树预览**(导出前可见最终交付形态) | M6-01 | 3 | 前端 | 原型 ⑦ 右侧 |
|
||||
| M6-07 | 导出包过期清理与重新导出(不重复计费) | M6-02 | 2 | 后端 | 默认 7 天 |
|
||||
| M6-08 | 开放接口(token / submissions / files / callback) | M6-04 | 4 | 后端 | 接口 §14;HMAC 加签 |
|
||||
| **小计** | | | **32** | | |
|
||||
|
||||
**M6 验收标准**
|
||||
|
||||
- [ ] 真实批次导出:**原始 475MB → ≈37MB**,目录树正确
|
||||
- [ ] Windows 资源管理器解压**中文名不乱码**
|
||||
- [ ] 存在 1 个超标文件 → **导出按钮禁用**,并指出是"谁的哪一项"
|
||||
- [ ] 打包中途数据变化 → 产物与快照一致
|
||||
- [ ] 汇总 Excel 可被官方模板**直接打开并对齐 21 列**
|
||||
|
||||
---
|
||||
|
||||
## M7 · 收尾与上线(52 人日)
|
||||
|
||||
| ID | 任务 | 依赖 | 人日 | 角色 | 备注 |
|
||||
|---|---|:--:|:--:|---|---|
|
||||
| M7-01 | 数据看板(完成率/异常率/驳回率/平均时长 + 按工种统计) | M5-02 | 6 | 后端 | **`submitRate` 与 `passRate` 分开报** |
|
||||
| M7-02 | 数据看板 UI(含"来自正式列"标注) | M7-01 | 5 | 前端 | 原型 ⑤;走索引实时 |
|
||||
| M7-03 | 字典管理(6 个内置字典 + 租户自维护) | M0-05 | 4 | 后端 | **职业字典只做拼写校验,不强约束** |
|
||||
| M7-04 | 操作日志 + 登录日志(含敏感操作留痕) | M0-06 | 4 | 后端 | `VIEW_IDCARD_FULL` 单独留痕 |
|
||||
| M7-05 | 日志查询 UI | M7-04 | 2 | 前端 | — |
|
||||
| M7-06 | **历史数据整理器**(上传→解析→规范化→报告) | M6-02 | 8 | 后端 | 差异化获客功能 |
|
||||
| M7-07 | 整理器 UI(体检报告 + 结果导入) | M7-06 | 5 | 前端 | 原型 ⑩;`autoHandled: 90 / needManual: 1` |
|
||||
| M7-08 | **Golden Set 回归脚本**(15 项断言自动化) | M3-05 | 3 | 测试 | 04 文档附录 B 的基线。**一期只覆盖 4 类材料**,二期补 `XJW_BA`/`OTHER_PROOF` |
|
||||
| M7-09 | 压测与调优(门禁并发/上传并发/导出大任务) | M6-02 | 5 | 后端 | 重点关注 M4 并发绑定 |
|
||||
| M7-10 | 灰度发布 + 生产环境准备 + 数据迁移预案 | M7-09 | 5 | 全员 | — |
|
||||
| M7-11 | 用户手册 / 培训材料 / 客服话术 | M7-07 | 2 | 产品 | 一期只有学校收集一条流程,手册更简单 |
|
||||
| M7-12 | 定时任务(9 项,见 PRD 附录 E) | M6-07 | 3 | 后端 | 清理/催报/超时提醒/用量统计 |
|
||||
| **小计** | | | **52** | | |
|
||||
|
||||
> 📌 **M7 含大量非编码工作**(培训、手册、灰度、压测),与前面纯开发里程碑性质不同。
|
||||
|
||||
---
|
||||
|
||||
## 四、关键路径
|
||||
|
||||
```
|
||||
M0-01 环境 ──→ M0-02 后端骨架 ──→ M0-05 组织 ──→ M0-06 用户 ──→ M0-07 登录
|
||||
│
|
||||
▼
|
||||
M1-01 任务CRUD ──→ M2-01 表头探测 ──→ M2-02 名册导入
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
M1-07 表单设计器 M2-06 门禁引擎 M3-01 命名引擎
|
||||
│ │
|
||||
│ ▼
|
||||
│ M3-05 压缩引擎 ★
|
||||
│ │
|
||||
▼ ▼
|
||||
M2-10 门禁UI M3-08 文件处理
|
||||
│
|
||||
▼
|
||||
M4-08 校验引擎 ──→ M5-03 审核动作
|
||||
│
|
||||
▼
|
||||
M6-01 前置检查 ──→ M6-02 打包引擎 ★
|
||||
│
|
||||
▼
|
||||
M7-09 压测 ──→ M7-10 上线
|
||||
```
|
||||
|
||||
**★ 两个最长的单点(合计 22 人日,且无法靠加人压缩)**
|
||||
|
||||
| 任务 | 人日 | 为什么不能加人 |
|
||||
|---|:--:|---|
|
||||
| **M3-05 压缩引擎** | 12 | 图像参数调优是串行的验证循环,多人并行的收益是负的 |
|
||||
| **M6-02 打包引擎** | 10 | 依赖快照设计,拆开做反而要重写 |
|
||||
|
||||
**关键路径总长**:约 **6 + 5 + 4 + 6 + 12 + 4 + 8 + 3 + 4 + 10 + 5 = 67 人日**,客户确认后 **+9**(申请通道 4 个任务落在 M2)⇒ **76 人日**
|
||||
|
||||
**工期由关键路径决定,不能按人日总数摊人**:
|
||||
|
||||
| 算法 | 结果 | 说明 |
|
||||
|---|---|---|
|
||||
| 总工作量摊人 | 456 人日 ÷ 8.5 人 ≈ **11 周** | ❌ 这是**下限**,实际达不到 |
|
||||
| **关键路径串行** | 76 人日 ≈ **15.2 周** | ✅ **这才是真实工期** |
|
||||
|
||||
**为什么以关键路径为准**:这条链上的任务**互为前置**(没建好表就不能导名单,没导名单就不能做门禁,没门禁就不能跑填报),
|
||||
**加人不会缩短它**。例如"压缩引擎"的 12 人日里,大部分是"改参数 → 跑 112 张真实图 → 看结果"的验证循环,**两人并行做反而要重跑**。
|
||||
|
||||
> ⇒ **结论:约 15 周**(客户确认 B1 后由 14 周顺延)。若要压缩,唯一可行的办法是**砍范围**(把 M7 的整理器与开放接口后置),而不是加人。
|
||||
|
||||
---
|
||||
|
||||
## 五、风险与对策
|
||||
|
||||
| # | 风险 | 概率 | 影响 | 对策 | 触发信号 |
|
||||
|:--:|---|:--:|:--:|---|---|
|
||||
| R1 | 小程序教育类目资质未获批 | 中 | **极高** | **M0 就启动法务准备**;备选 H5 扫码填报可保底 | 第 2 周仍未提交类目 |
|
||||
| R2 | 认定机构不接受 300dpi | 低 | 高 | M3-05 预留参数可调;实测 28/28 达标是强证据 | 确认函 A1 回复 |
|
||||
| R3 | OCR/VLM 选型拖延 | 中 | 中 | M2 结束前必须定;**层 1/2 不依赖它,可先跑通** | 第 6 周未选型 |
|
||||
| R4 | 压缩引擎调优超期 | 中 | 高 | **M3-05 单独设 3 人日缓冲**;用 Golden Set 做回归门禁 | 回归超 2 项偏差 |
|
||||
| R5 | 门禁并发绑定出问题 | 低 | **极高** | 04 文档 M4 已给正确实现;**M7-09 压测必测 100 并发** | 压测出现重复绑定 |
|
||||
| R6 | 客户名单格式更复杂 | 中 | 中 | 表头探测已支持候选回传;M2 加 20% 缓冲 | dryRun 识别率 < 90% |
|
||||
| R7 | 需求变更(如加"申请加入名单"通道) | 高 | 中 | 已列入确认函 B1;**若确认则 M2 追加 3 人日** | 客户回复 |
|
||||
| R8 | 导出大任务内存溢出 | 中 | 中 | 分卷 + 流式写入;快照固化避免重查 | 单任务 > 5000 文件 |
|
||||
|
||||
---
|
||||
|
||||
## 六、团队配置建议
|
||||
|
||||
| 角色 | 人数 | 主要投入 | 峰值在第几周 |
|
||||
|---|:--:|---|---|
|
||||
| 技术负责人 | 1 | 架构、Code Review、M3 引擎 | 全程 |
|
||||
| 后端 | 3 | M0/M2/M3/M4/M6 | 第 5~10 周 |
|
||||
| 前端(PC) | 1.5 | M1 设计器、M5 终审、M7 看板 | 第 4~6 周、第 9~11 周 |
|
||||
| 前端(小程序) | 1 | M2 门禁、M3 填报、M5 审核 | 第 6~11 周 |
|
||||
| 测试 | 1 | Golden Set 回归、压测 | 第 8 周起 |
|
||||
| 产品/UI | 1 | 交互确认、文档、培训 | 第 1~3 周、第 13 周 |
|
||||
| **合计** | **8.5** | | |
|
||||
|
||||
**建议的并行策略**
|
||||
|
||||
| 阶段 | 并行方式 |
|
||||
|---|---|
|
||||
| 第 1~3 周 | 后端做 M0,前端同时搭框架(不等后端) |
|
||||
| 第 4~6 周 | 后端 M2 门禁与前端 M1 设计器**完全并行**(无依赖) |
|
||||
| 第 7~10 周 | **M3-05 压缩引擎单独占 1 人专职**,其余人做 M3 周边 |
|
||||
| 第 12~14 周 | M6/M7 并行;测试同步跑 Golden Set |
|
||||
| 第 15 周 | 全量回归 + 灰度 + 上线 |
|
||||
|
||||
---
|
||||
|
||||
## 七、里程碑验收与交付节奏
|
||||
|
||||
| 节点 | 时间 | 交付内容 | 验收方式 |
|
||||
|---|---|---|---|
|
||||
| **M0** | 第 3 周末 | 能登录、能建组织用户 | 内网演示 15 分钟 |
|
||||
| **M1** | 第 5 周末 | 能配出完整任务 | **里程碑 A**:向客户演示"配置→任务就绪" |
|
||||
| **M2** | 第 8 周末 | 能导入名单、能过门禁、能审批申请 | 用**真实名单**演示 |
|
||||
| **M3** | 第 11 周末 | 学生能完整填报提交 | **里程碑 B**:真机演示全流程 + Golden Set 回归 |
|
||||
| **M4** | 第 12 周末 | 识别与校验生效 | 112 个真实文件的识别报告 |
|
||||
| **M5** | 第 13 周末 | 审核闭环 | 班主任真机演示 + 管理端终审 |
|
||||
| **M6** | 第 14 周末 | 导出规范包 | 导出真实包并**用 Windows 解压验证** |
|
||||
| **M7** | 第 15 周末 | **可上线** | **里程碑 C**:压测报告 + 灰度 → 全量 |
|
||||
|
||||
### 建议的三个客户演示点
|
||||
|
||||
| 演示 | 时点 | 演示什么 | 目的 |
|
||||
|---|---|---|---|
|
||||
| **里程碑 A** | 第 5 周 | 任务配置闭环 + **历史数据整理器**(可用假数据先做简版) | 让客户看到"配置灵活",并**提前锁定需求** |
|
||||
| **里程碑 B** | 第 11 周 | 真机全流程 + **用真实 112 个文件跑出的处理报告** | 最有说服力的一次——数字都是他们自己的数据 |
|
||||
| **里程碑 C** | 第 15 周 | 压测 + 导出包 | 交付验收 |
|
||||
|
||||
> **里程碑 A 里建议提前塞进"整理器"的简版**:客户手里有历史乱数据时,**"先看到价值再谈上线"**比任何功能列表都有用。这一步能让后续需求变更更容易谈。
|
||||
|
||||
---
|
||||
|
||||
## 八、给项目经理的三条提醒
|
||||
|
||||
| # | 提醒 | 原因 |
|
||||
|:--:|---|---|
|
||||
| 1 | **不要压缩 M3-05(压缩引擎)和 M6-02(打包引擎)的工期** | 这两个是唯一无法靠加人加速的任务,压缩工期只会换来质量债 |
|
||||
| 2 | **Golden Set 回归脚本要在 M7 之前就准备好**(M7-08 建议提前到 M4 结束) | 它是后续所有改动的安全网;等到上线前才做,等于全程裸奔 |
|
||||
| 3 | **确认函里的 B1(名单外的人怎么办)要在 M2 开始前拿到答复** | 它决定 M2 要不要多做一条审批流;M2 一旦开工,改动的代价是 3 人日变 6 人日 |
|
||||
|
||||
---
|
||||
|
||||
## 九、核算口径(可复现)
|
||||
|
||||
本文档所有数字均**逐任务求和**得出,非估算。核算方法:
|
||||
|
||||
```
|
||||
① 读 §3 各里程碑表格,正则匹配每行 `| M\d-\d+ | ... | 人日 | 角色 | 备注 |`
|
||||
② 按里程碑求和 → 与标题括号内数字比对
|
||||
③ 汇总到 §1.5 → 与(含缓冲)列比对
|
||||
④ 缓冲 = 各里程碑人日 × 1.2 后四舍五入
|
||||
```
|
||||
|
||||
**核算结果(2026-09-28)**
|
||||
|
||||
| 里程碑 | 任务数 | 人日 | 含缓冲(×1.2) |
|
||||
|:--:|:--:|:--:|:--:|
|
||||
| M0 | 13 | 40 | 48 |
|
||||
| M1 | 10 | 44 | 53 |
|
||||
| M2 | 17 | 58 | 70 |
|
||||
| M3 | 16 | **71** | **85** |
|
||||
| M4 | 10 | 34 | 41 |
|
||||
| M5 | 14 | 56 | 67 |
|
||||
| M6 | 8 | 32 | 38 |
|
||||
| M7 | 12 | 52 | 62 |
|
||||
| **合计** | **100** | **387** | **464** |
|
||||
|
||||
> ⚠️ **注意**:初稿的 §1.5 汇总表曾出现"先填汇总、后填任务明细"造成的错位,已按逐任务求和值修正。
|
||||
> **任何人改动任务明细后,必须重跑一遍核算**——这是本文档唯一的自校验手段。
|
||||
>
|
||||
> **本次变更(客户确认 B1 后)**:新增 **6 个任务**(M0-13、M2-13~17),**+12 人日**;工期由 14 周顺延至 **15 周**。
|
||||
>
|
||||
> **一期范围收窄后**:**−5 人日**(社会收集 / 社会评价 / 专项职业能力后置;省下的主要是测试与联调成本),**关键路径不变,仍 15 周**。
|
||||
|
||||
---
|
||||
|
||||
## 变更记录
|
||||
|
||||
| 版本 | 日期 | 变更 |
|
||||
|---|---|---|
|
||||
| V1.0 | 2026-09-28 | 首版。8 个里程碑 M0–M7,共 **94 个任务**;合计 **380 人日**(含缓冲 **456**);工期约 **14 周**(由关键路径决定)。含关键路径分析、8 项风险、团队配置与 3 个客户演示点。**全部人日为逐任务求和的实际值**,核算口径见 §9 |
|
||||
| **V1.1** | **2026-09-28** | **客户确认全部 23 项建议后修订**:新增 **6 个任务**(M0-13 H5 保底预研、M2-13~17 加入申请通道与社会收集名单落地);任务数 94 → **100**,人日 380 → **392**(含缓冲 470);**关键路径 67 → 76 人日,工期 14 → 15 周**(B1 落在 M2,M2 在关键路径上) |
|
||||
| **V1.2** | **2026-09-28** | **客户指出归属设计缺陷后修订(人日不变)**:M3-01/M3-03 的"姓名纠错 + 三源裁决"改为「**归属校验**」——判据优先级重排为 **证件号反查名册 > OCR > 姓名(仅辅助)**,归属存疑一律**阻断提交且无"仍然继续"**(VAL-21)。<br>**工作量中性**:删掉了姓名编辑距离+拼音相似度逻辑(约 −1.5 人日),新增名册反查与阻断交互(约 +1.5 人日),**净变化 0**,排期不变 |
|
||||
@@ -0,0 +1,610 @@
|
||||
# 验收测试清单
|
||||
|
||||
> 版本:V1.0 | 日期:2026-09-28
|
||||
> 读者:测试工程师、开发(自测)、项目经理(验收)
|
||||
> 配套:《04-核心模块设计说明.md》附录 B(基线)、《05-开发任务拆解.md》(里程碑验收)
|
||||
> 核心原则:**能用真实数据断言的不靠人眼;能自动化的一次写、长期跑**
|
||||
|
||||
---
|
||||
|
||||
## 一、测试策略
|
||||
|
||||
### 1.1 四层结构
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ L4 验收测试(人工) ← §6 里程碑验收签署表 │
|
||||
│ 客户在场、用真实数据、不可自动化 │
|
||||
├─────────────────────────────────────────────┤
|
||||
│ L3 端到端(半自动) ← §3 主流程用例 │
|
||||
│ 真机 + 真实数据,脚本辅助准备与核对 │
|
||||
├─────────────────────────────────────────────┤
|
||||
│ L2 集成测试(自动) ← §4 功能验收清单 │
|
||||
│ 接口级,含边界与异常路径 │
|
||||
├─────────────────────────────────────────────┤
|
||||
│ L1 单元测试(自动) ← §2 Golden Assertions │
|
||||
│ 引擎级纯函数,**用 112 个真实文件做断言** │
|
||||
└─────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 1.2 各层覆盖目标
|
||||
|
||||
| 层 | 覆盖对象 | 目标 | 执行时机 |
|
||||
|---|---|:--:|---|
|
||||
| L1 | M1 命名引擎、M2 压缩引擎、M5 校验规则 | 行覆盖 ≥ 80%,**Golden 断言 100% 通过** | 每次提交(CI 门禁) |
|
||||
| L2 | 全部 117 个接口 | 接口覆盖 100%,异常路径 ≥ 60% | 每日构建 |
|
||||
| L3 | 学生填报、班主任审核、管理员导出 | 3 条主流程 × 平台差异 | 每周 / 里程碑 |
|
||||
| L4 | 里程碑交付物 | 按 §6 签署表 | 里程碑节点 |
|
||||
|
||||
### 1.3 Golden Test Set(唯一权威测试集)
|
||||
|
||||
| 项 | 内容 |
|
||||
|---|---|
|
||||
| 路径 | `D:\项目\202609机电填报系统\24中技数控1班\` |
|
||||
| 规模 | **28 人 × 4 类材料 = 112 个文件**,原始 **475 MB** |
|
||||
| 配套名单 | `考生线下报名信息导入模板(24中技数车1班班).xlsx`(表头**第 6 行**) |
|
||||
| 参照模板 | 官方空白模板(表头**第 1 行**),21 字段 |
|
||||
| 特点 | **不是构造数据**——含真实的乱命名、双扩展名、证件号错误、串档、低分辨率 |
|
||||
|
||||
> ⚠️ **这个数据集不可替代**。所有"实测"结论都来自它;换成构造数据,双扩展名 56/112 这种数字就复现不出来。
|
||||
> **必须纳入版本管理**(或至少保持只读副本),否则半年后无法回归。
|
||||
|
||||
---
|
||||
|
||||
## 二、Golden Assertions(L1 · 可自动化)
|
||||
|
||||
> 以下断言**直接从 P4 附录 B 的基线转写**。任何改动后必须全绿。
|
||||
|
||||
### 2.1 命名规范化(M1)
|
||||
|
||||
```python
|
||||
# G-01 命名违规总数
|
||||
assert count_files(failing_naming_rule) == 59 # 59 / 112 = 52.7%
|
||||
# G-02 双扩展名
|
||||
assert count_files(lambda f: re.search(r'\.(jpg|jpeg|png){2,}$', f.name, re.I)) == 56
|
||||
# G-03 规范化幂等性(关键!)
|
||||
for f in all_files:
|
||||
once = normalize(f.name, roster, material_def)
|
||||
twice = normalize(once.standard_name, roster, material_def)
|
||||
assert twice.standard_name == once.standard_name
|
||||
assert twice.issues == [] # 第二次不应产生任何 issue
|
||||
# G-04 双扩展名修复正确性
|
||||
sample = "曾伊豪-361029200907295110身份证.jpg.jpg"
|
||||
assert normalize(sample).standard_name == "曾伊豪-361029200907295110-身份证.jpg"
|
||||
# G-05 材料别名归一
|
||||
assert normalize("...个人申报表.jpg", slot="WORK_PROOF").material_name == "工作证明"
|
||||
# G-06 证件号抽取:不许暴力凑合法号
|
||||
assert normalize("张三-111111111111111111.jpg").idcard_found == False # 非法号不得被"修好"
|
||||
# G-07 无名册时不猜姓名
|
||||
assert normalize("乱名.jpg", roster=None).standard_name.startswith("未知姓名") is False # 用占位而非臆测
|
||||
```
|
||||
|
||||
### 2.2 压缩引擎(M2)
|
||||
|
||||
```python
|
||||
# G-08 体积上限:全部达标(D1 决策,无例外)
|
||||
for f in a4_scans: # 28 张工作证明
|
||||
out = compress(f, profile="P3")
|
||||
assert out.final_size_kb <= 1024, f"{f.name} 超标 {out.final_size_kb}KB"
|
||||
# G-09 质量未受损:均值与最大值
|
||||
sizes = [compress(f, profile="P3").final_size_kb for f in a4_scans]
|
||||
assert 700 <= mean(sizes) <= 900 # 实测均值 806KB
|
||||
assert max(sizes) <= 900 # 实测最大 831KB
|
||||
# G-10 零强制压缩
|
||||
assert count(out.forced == True for out in results) == 0
|
||||
# G-11 证件照规格化
|
||||
for f in photo_files: # 实测 984×1378 的 27 张
|
||||
out = compress(f, profile="P1")
|
||||
assert (out.width, out.height) == (295, 413)
|
||||
assert out.dpi == 300
|
||||
# G-12 截图不缩放、保持 PNG
|
||||
out = compress(school_roll_png, profile="P4") # 实测 1025×590 PNG
|
||||
assert (out.width, out.height) == (1025, 590)
|
||||
assert out.format == "PNG"
|
||||
# G-13 不放大(关键!)
|
||||
out = compress(small_idcard, profile="P2") # 实测 558×745
|
||||
assert out.width <= 558 and out.height <= 745
|
||||
assert out.issues_contains("VAL-20") # 体积下限保护
|
||||
# G-14 EXIF 方向正确(不拉扁)
|
||||
out = compress(rotated_photo, profile="P2")
|
||||
assert out.width > out.height # 或按原图判断,绝不允许宽高互换
|
||||
```
|
||||
|
||||
### 2.2b 文件归属(M1 · 归属校验)
|
||||
|
||||
```python
|
||||
# G-14b 归属:证件号 = 名册值 + 姓名错别字 → 不打扰学生
|
||||
f = "扬晨-360825200904303215-一寸照片.jpg" # 名册为「杨晨」
|
||||
r = normalize(f, roster=yangchen, material_def=photo1)
|
||||
assert r.standard_name == "杨晨-360825200904303215-一寸照片.jpg"
|
||||
assert r.student_facing_issues == [] # ★ 不产生面向学生的 issue
|
||||
assert r.logonly(NAME_DIFFERS) # 仅留痕
|
||||
|
||||
# G-14c 归属:乱名无号码 → 零提示(正常路径)
|
||||
r = normalize("微信图片_20260928103015.jpg", roster=yangchen, material_def=photo1)
|
||||
assert r.standard_name == "杨晨-360825200904303215-一寸照片.jpg"
|
||||
assert r.issues == [] # ★ 零提示
|
||||
assert r.ownership.verdict == "NO_IDCARD_IN_NAME"
|
||||
|
||||
# G-14d 归属:指向名册中的另一个人 → 阻断(最关键)
|
||||
f = "李四-360122200902044232-工作证明.jpg" # 该号码在名册里能查到「李四」
|
||||
r = normalize(f, roster=zhangsan, material_def=work_proof)
|
||||
assert r.ownership.verdict == "BELONGS_TO_OTHER"
|
||||
assert r.ownership.block_submit == True # ★ 阻断
|
||||
assert r.ownership.other_roster_name == "李四"
|
||||
assert r.ownership.can_force == False # ★ 没有"仍然继续"
|
||||
|
||||
# G-14e 归属:陌生号 + OCR 佐证 → 不阻断
|
||||
r = normalize("张三-111111111111111111-身份证.jpg", roster=zhangsan,
|
||||
ocr_idcard=zhangsan.id_card_no)
|
||||
assert r.ownership.verdict == "UNKNOWN_IDCARD"
|
||||
assert r.ownership.block_submit == False # OCR 佐证后放行
|
||||
assert r.issues_contains("VAL-02", severity=1)
|
||||
|
||||
# G-14f 归属:陌生号 + 无 OCR → 阻断
|
||||
r = normalize("张三-111111111111111111-身份证.jpg", roster=zhangsan, ocr_idcard=None)
|
||||
assert r.ownership.block_submit == True
|
||||
|
||||
# G-14g 校验位失败 → 自动改,不阻断(实测 2 例:于豪、杨晨)
|
||||
r = normalize("于豪-360103200805166169-身份证.jpg.jpg", roster=yuhuo, material_def=idcard)
|
||||
assert r.ownership.verdict == "CHECKSUM_INVALID"
|
||||
assert r.ownership.block_submit == False
|
||||
assert r.standard_name == "于豪-360103200805186619-身份证.jpg"
|
||||
|
||||
# G-14h ★ 回归:绝不把"别人的材料"静默洗白
|
||||
# 这是本设计最重要的一条断言——它防止的是数据污染,不是体验问题
|
||||
for f in all_files:
|
||||
r = normalize(f, roster=current_student, material_def=md)
|
||||
if r.ownership.verdict == "BELONGS_TO_OTHER":
|
||||
assert r.standard_name != "{当前学生名}-{当前学生证件号}-..." or r.block_submit
|
||||
# 即:凡判为他人材料,必须阻断,不得生成一份"名字正确"的输出
|
||||
```
|
||||
|
||||
### 2.3 材料识别(M3)
|
||||
|
||||
```python
|
||||
# G-15 文件名层命中率
|
||||
assert detect_by_name_accuracy(all_files) == 1.0 # 112/112
|
||||
# G-16 形态层分流派准确率
|
||||
assert detect_by_shape_accuracy(all_files) >= 0.99 # 实测 99.1%
|
||||
# G-17 零文字判据
|
||||
assert detect(photo1).material_code == "PHOTO1"
|
||||
assert detect(photo1).reason == "整图文字量 < 5 字"
|
||||
# G-18 通用词禁入表(关键!)
|
||||
assert "身份证号" not in STRONG_FEATURES
|
||||
for f in all_files:
|
||||
# 不得因为文件里出现"身份证号"四字就判成 IDCARD
|
||||
assert not (contains(f, "身份证号") and detect(f).material_code == "IDCARD"
|
||||
and detect(f).source == "NAME" and detect(f).confidence >= 0.9)
|
||||
# G-19 识别失败必须放行
|
||||
assert detect(black_image).material_code is None
|
||||
assert can_submit(submission_with(black_image)) == True # 不阻断!
|
||||
# G-20 VLM 触发率在预算内
|
||||
assert 0.02 <= vlm_trigger_rate(all_files) <= 0.10
|
||||
```
|
||||
|
||||
### 2.4 校验与串档(M5)
|
||||
|
||||
```python
|
||||
# G-21 证件号异常总数
|
||||
issues = run_validation(all_submissions)
|
||||
assert count(issues, rule="VAL-02") + count(issues, rule="VAL-01") == 5
|
||||
# G-22 自动/人工分流
|
||||
assert count(issues, rule="VAL-02", auto_fixed=True) == 3
|
||||
assert count(issues, rule="VAL-02", auto_fixed=False) == 2
|
||||
# G-23 疑似串档(最关键的一条)
|
||||
assert count(issues, rule="VAL-19") == 1
|
||||
s = the_cross_file_issue
|
||||
assert s.need_human == True and s.auto_fixed == False # 绝不自动改
|
||||
# G-24 跨文件一致性不误报
|
||||
assert count(issues, rule="VAL-04") == 0 # 除串档外无其他不一致
|
||||
# G-25 低分辨率检出
|
||||
assert count(issues, rule="VAL-08") == 1
|
||||
assert issues_for_rule("VAL-08")[0].dimension == "558×745"
|
||||
```
|
||||
|
||||
### 2.5 导出(M6)
|
||||
|
||||
```python
|
||||
# G-26 导出总量
|
||||
result = export(task_id, scope={only_approved: False})
|
||||
assert 30 <= result.size_mb <= 45 # 实测 ≈37MB
|
||||
# G-27 目录树结构
|
||||
assert result.tree["24中技数控1班"]["工作证明"][0].name \
|
||||
== "曾伊豪-361029200907295110-工作证明.jpg"
|
||||
# G-28 汇总 Excel 列对齐
|
||||
xlsx = result.summary_excel
|
||||
assert xlsx.header == OFFICIAL_21_COLUMNS # A–U 严格对齐
|
||||
# G-29 中文文件名编码
|
||||
with unzip(result.zip) as z:
|
||||
for name in z.namelist():
|
||||
assert name.encode('utf-8').decode('utf-8') == name # 无乱码
|
||||
```
|
||||
|
||||
### 2.6 断言的"红灯"处理规则
|
||||
|
||||
| 情况 | 处理 |
|
||||
|---|---|
|
||||
| G-01~G-10 任一失败 | **阻断发布**。这是产品核心承诺 |
|
||||
| G-11~G-14 失败 | **阻断发布**。直接影响认定机构接收 |
|
||||
| G-15~G-20 失败 | 视偏差幅度:>5% 阻断;≤5% 记录并评估 |
|
||||
| G-21~G-25 失败 | **阻断发布**。漏掉串档会流出错误数据 |
|
||||
| G-26~G-29 失败 | **阻断发布**。导出包是最终交付物 |
|
||||
| 基线数字本身需调整 | **必须走变更评审**,在 P4 附录 B 留版本记录 |
|
||||
|
||||
---
|
||||
|
||||
## 三、端到端主流程用例(L3)
|
||||
|
||||
### 3.1 主流程 A · 学校收集(学生 → 班主任 → 管理员)
|
||||
|
||||
| 步 | 操作 | 期望 | 对应原型 |
|
||||
|:--:|---|---|---|
|
||||
| A1 | 学生扫码进小程序 | 进入门禁页 | ① |
|
||||
| A2 | 输入名单上的姓名 + 证件号 | 通过,显示姓名与班级 | ② |
|
||||
| A3 | 刻意输错一位证件号 | **统一提示**"没有找到你的信息",不透露哪项错 | ③ |
|
||||
| A4 | 点"申请加入名单" | 进入申请表单;**系统内不展示任何联系方式** | ③B |
|
||||
| A5 | 正确登录后进填报页 | 18 个字段已预填,标注"老师已填,请核对" | ④ |
|
||||
| A6 | 修改"联系电话" | 可改,`changedFields` 记录 phone | ④ |
|
||||
| A7 | 尝试改"身份证号" | **只读**,点击提示"如有误请联系老师" | ④ |
|
||||
| A8 | 选"微信聊天记录"传工作证明(双扩展名乱名) | 上传成功,结果卡显示**命名前后对比** | ⑤⑦ |
|
||||
| A9 | 查看结果卡 | 显示标准名、体积 17.5MB→512KB、`VAL-06` 信息级"格式已转换" | ⑦ |
|
||||
| A10 | 传"学籍表"时误传工作证明 | 提示"这张图看起来是工作证明,要移过去吗?",**默认不移** | ⑧ |
|
||||
| A11 | 点"移到工作证明" | 文件移动,学籍表槽位变空 | ⑧ |
|
||||
| A12 | 故意不传必传材料直接提交 | 被拦,**列出缺哪项**,给出"去上传" | — |
|
||||
| A13 | 补齐后提交 | 成功,状态"等待李老师初审" | ⑨ |
|
||||
| A14 | 班主任打开待审列表 | 该生卡片为**绿色**(无问题) | ② |
|
||||
| A15 | **右滑该卡片** | 通过,卡片移出列表,显示"已通过" | ② |
|
||||
| A16 | 另一个学生有红色问题 | 卡片**灰色/不可滑**,点击进详情 | ②③ |
|
||||
| A17 | 尝试对该生执行"一键通过" | **被跳过并回报原因**"存在红色问题" | ④ |
|
||||
| A18 | 驳回该生(选"材料不清晰") | 需**二次确认**;提交后学生收到通知 | ⑤ |
|
||||
| A19 | 学生查看消息 | 消息中心有"你的材料被退回" | ⑩ |
|
||||
| A20 | 学生重新上传后提交 | 状态回到"待初审" | — |
|
||||
| A21 | 管理员终审 | 通过后状态"已通过" | ⑥ |
|
||||
| A22 | 管理员导出 | 前置检查通过 → 打包 → 下载 zip | ⑦ |
|
||||
| A23 | **Windows 资源管理器解压** | **中文名不乱码**,目录树正确 | — |
|
||||
|
||||
### 3.2 主流程 B · 社会收集(**二期**,一期不做)
|
||||
|
||||
> ⚠️ **一期范围收窄后,本节整体后置**(见 PRD「一期范围声明」)。保留描述供二期启用时使用。
|
||||
|
||||
| 步 | 操作 | 期望 |
|
||||
|:--:|---|---|
|
||||
| B1 | 用社会收集任务重跑 A1~A13 | 提交后状态直接是"待终审" |
|
||||
| B2 | 班主任打开待审列表 | **看不到该任务**(社会收集不经班主任) |
|
||||
| B3 | 管理员终审 → 导出 | 正常 |
|
||||
|
||||
### 3.3 主流程 C · 历史数据整理器
|
||||
|
||||
| 步 | 操作 | 期望 |
|
||||
|:--:|---|---|
|
||||
| C1 | 上传 `24中技数控1班.rar`(436MB) | 解析出 28 人 4 类 |
|
||||
| C2 | 查看体检报告 | 命名违规 **59**、体积超标 **31**、证件号异常 **5**、疑似串档 **1** |
|
||||
| C3 | 读报告结论 | "**90 个系统全自动处理,只有 1 个必须你亲自判断**" |
|
||||
| C4 | 下载规范化包 | 475MB→**37MB**,目录树正确 |
|
||||
| C5 | 点"导入为正式任务" | 生成任务 + 名册 + 112 个文件全挂上 |
|
||||
| C6 | 进该任务看数据 | 真实、无占位、可直接导出 |
|
||||
|
||||
### 3.4 异常与边界用例(23 条)
|
||||
|
||||
| # | 场景 | 期望 |
|
||||
|:--:|---|---|
|
||||
| E1 | 门禁连错 11 次 | 第 11 次返回 `40007b`,提示稍后再试 |
|
||||
| E2 | 同一 IP 20 人同时门禁 | **全部通过**(NAT 不误伤) |
|
||||
| E3 | 并发 100 请求抢同一名册项 | **恰好 1 个成功**,99 个 `40007c` |
|
||||
| E4 | 门禁失败后查库 | **无明文证件号**,只有 hash |
|
||||
| E5 | 学籍表 4 项材料证件号均与名册不符 | 标 `VAL-19` 疑似串档,**不自动改** |
|
||||
| E6 | 学生提交后撤回,再改再提交 | 正常;驳回次数不因撤回增加 |
|
||||
| E7 | 任务暂停中 | 学生看到"已暂停",**已提交的仍可继续审核** |
|
||||
| E8 | 任务截止后 | 学生不能提交,已提交的可审 |
|
||||
| E9 | 归档有未审单 | 拒绝,返回 `40023` |
|
||||
| E10 | 导出前有 1 个超标文件 | **导出按钮禁用**,指出"张三的工作证明 1.8M" |
|
||||
| E11 | 已导出后撤回审核 | 拒绝,返回 `40033` |
|
||||
| E12 | 表单版本冲突(两人同时改) | `40025` + 返回最新 schema |
|
||||
| E13 | 删除内置字段 | `40026`,提示可改标签/顺序 |
|
||||
| E14 | 报考范围未覆盖全员 | `40024` + 返回差集 |
|
||||
| E15 | 名字含罕见字(生僻字) | 不误判、不阻断 |
|
||||
| E16 | 上传 8000×10000 超大图 | 触发熔断转异步,**不超时** |
|
||||
| E17 | OCR 服务不可用 | **降级为人工填写**,不阻断 |
|
||||
| E18 | VLM 服务不可用 | 取前三层结果或放行 |
|
||||
| E19 | 微信订阅消息额度耗尽 | **静默降级**,学生端无异常提示 |
|
||||
| E20 | 网络中断后重进 | 草稿保留,续填 |
|
||||
| E21 | 申请提交后管理员未处理,任务截止 | 申请自动置为已失效,学生收到提示 |
|
||||
| E22 | 两人同时申请同一证件号 | 只有 1 条落库成功(部分唯一索引),另一条得 40038 |
|
||||
| E23 | 管理员在审批页停留很久后点通过 | 状态已变更时返回 40040,不重复建册 |
|
||||
| E24 | 学生传了同学的材料后点提交 | **被阻断**,提示文件名对应的是谁,**无"仍然继续"** |
|
||||
| E25 | 学生把文件名改成自己的名字后重传,内容仍是同学的 | 证件号/OCR 仍会暴露 → 阻断或转人工(**改名绕不过去**) |
|
||||
| E26 | 归属问题在异步 OCR 后才判定可放行 | 自动解除阻断 + 推送通知;**阶段二只解除、不新增阻断** |
|
||||
| E27 | 文件名形如 `微信图片_2026....jpg`(无姓名无号码) | 按名册补全,**零提示**(这是最常见的情况) |
|
||||
|
||||
---
|
||||
|
||||
## 四、功能验收清单(L2 · 按模块)
|
||||
|
||||
> 编号规则:`F-{模块}-{序号}`。每条必须有明确的**可观测结果**。
|
||||
|
||||
### F-100 租户与组织(FR-100)
|
||||
|
||||
| # | 验收项 | 期望 |
|
||||
|:--:|---|---|
|
||||
| F-100-1 | 开通租户 | 生成 `tenantCode`,同时创建首个管理员,返回初始密码 |
|
||||
| F-100-2 | 组织树三层 | 学校/院系/班级,`ancestorPath` 自动维护 |
|
||||
| F-100-3 | 删除被占用组织 | 拒绝,返回占用清单 |
|
||||
| F-100-4 | **租户隔离** | A 租户 token 访问 B 租户数据 → `40301` |
|
||||
| F-100-5 | 用量统计 | 学生数/存储/OCR/短信四项,超限告警 |
|
||||
|
||||
### F-200 账号与角色(FR-200)
|
||||
|
||||
| # | 验收项 | 期望 |
|
||||
|:--:|---|---|
|
||||
| F-200-1 | PC 登录 | 连续失败 2 次后**出现图形验证码** |
|
||||
| F-200-2 | 账号锁定 | 5 次失败锁定,提示剩余时间 |
|
||||
| F-200-3 | 小程序登录 | `code2session` 成功,绑定手机号 |
|
||||
| F-200-4 | 一班主任多班 | 看板聚合展示多个班 |
|
||||
| F-200-5 | 批量导入用户 | 返回体检报告 + 初始密码清单 |
|
||||
| F-200-6 | 停用有在办任务的用户 | 拒绝 `40014` |
|
||||
|
||||
### F-300 收集任务(FR-300)
|
||||
|
||||
| # | 验收项 | 期望 |
|
||||
|:--:|---|---|
|
||||
| F-300-1 | 任务 CRUD 与状态机 | 草稿→发布→暂停→恢复→截止→归档 |
|
||||
| F-300-2 | **暂停 ≠ 截止** | 暂停可恢复、审核继续;截止不可逆 |
|
||||
| F-300-3 | 发布前置校验 5 项 | 任一不通过阻断,`force` 无法绕过 |
|
||||
| F-300-4 | 任务克隆 | 名册复制但**清空绑定与提交状态** |
|
||||
| F-300-5 | 二维码/短链 | 带租户+任务+班级参数,截止后扫码提示 |
|
||||
| F-300-6 | 社会收集审核流 | **二期**。强制 `needClassAudit=false`,传 true 被忽略并告警 |
|
||||
|
||||
### F-400 动态表单(FR-400)
|
||||
|
||||
| # | 验收项 | 期望 |
|
||||
|:--:|---|---|
|
||||
| F-400-1 | 21 个内置字段 | 可改标签、可调顺序、**不可删除** |
|
||||
| F-400-2 | 扩展字段 | 存 `extras_json`,**不参与统计** |
|
||||
| F-400-3 | 条件显示 | 三条级联全部生效 |
|
||||
| F-400-4 | 表单版本化 | 改后旧单不受影响,`formVersion` 各自保存 |
|
||||
| F-400-5 | 版本冲突 | `40025` + 返回最新 schema |
|
||||
| F-400-6 | 学生三态字段 | 锁定/可改留痕/正常 三种表现正确 |
|
||||
|
||||
### F-500 名册(FR-500)
|
||||
|
||||
| # | 验收项 | 期望 |
|
||||
|:--:|---|---|
|
||||
| F-500-1 | **表头自动探测** | 第 1 行与第 6 行两份真实文件都识别正确 |
|
||||
| F-500-2 | 表头候选回传 | 多候选时返回并让用户确认 |
|
||||
| F-500-3 | 无法定位表头 | `40028` + 返回前 10 行预览 |
|
||||
| F-500-4 | 体检报告 | 证件号校验位/重复/必填缺失三项检出 |
|
||||
| F-500-5 | `dryRun` 模式 | 只体检不落库 |
|
||||
| F-500-6 | 追加导入去重 | 按证件号合并,新人立即可填 |
|
||||
| F-500-7 | 改名册证件号留痕 | 操作日志可查改前改后(**脱敏**) |
|
||||
| F-500-8 | 已绑定名册项不可删 | `40029` |
|
||||
| F-500-9 | **门禁匹配** | 正确通过 / 错误统一提示 / 形近字确认 |
|
||||
| F-500-10 | 门禁限流 | openid 10 次/小时;IP 20 次/分钟 |
|
||||
| F-500-11 | **并发绑定** | 100 并发抢同一项,恰好 1 个成功 |
|
||||
| F-500-12 | 失败不留明文 | 查库确认只有 hash |
|
||||
| F-500-13 | 已绑定提示 | `40007c`,**这一条明确提示** |
|
||||
|
||||
### F-509 名单加入申请(客户确认新增)
|
||||
|
||||
| # | 验收项 | 期望 |
|
||||
|:--:|---|---|
|
||||
| F-509-1 | 失败页有申请入口 | 「申请加入名单」在门禁失败页,视觉为**次要路径**(虚线分隔、不与"重新填写"抢注意力) |
|
||||
| F-509-2 | **不绕过门禁** | 申请期间**仍不能填报**;提交后页面明确写"审核期间还不能填报" |
|
||||
| F-509-3 | 申请表单校验 | 姓名/证件号/手机号必填;格式错误当场提示 |
|
||||
| F-509-4 | **敏感信息不落明文** | 查库 + 查日志:**无明文证件号与手机号**,只有 HMAC 与脱敏值 |
|
||||
| F-509-5 | 同 openid 第 3 次申请 | `40037`,提示联系老师 |
|
||||
| F-509-6 | 同证件号重复申请(待审中) | `40038`(**部分唯一索引**保证) |
|
||||
| F-509-7 | 证件号已在名册但姓名不符 | `40039`,引导"返回重新核验"(**不泄露新信息**) |
|
||||
| F-509-8 | 被驳回后重新申请 | **允许**(唯一索引只约束状态 0/1) |
|
||||
| F-509-9 | 审批通过 | 自动在名册**新建名册项**,`roster_id` 回填,`bind_type=3` |
|
||||
| F-509-10 | 审批通过后该 openid 再进 | **直接放行**,不要求重输 |
|
||||
| F-509-11 | **管理员双击"通过"** | **只建 1 条名册项**(`FOR UPDATE` 行锁 + 状态校验) |
|
||||
| F-509-12 | 通过时证件号已被占用 | `40041`,**不覆盖**原名册项 |
|
||||
| F-509-13 | 驳回必填原因 | 原因为空时提交被拦;原因原样推送给学生 |
|
||||
| F-509-14 | **不存在"全选"** | 批量通过需逐条勾选;**驳回不支持批量** |
|
||||
| F-509-15 | 结果通知 | 微信订阅消息 `JOIN_APPROVE` / `bizType=7`;通知失败**不回滚审批** |
|
||||
| F-509-16 | 他任务命中提示 | 证件号在其他任务名册中 → 列表显示「他任务已有」 |
|
||||
| F-509-17 | 待审 > 50 告警 | 列表返回 `alert` |
|
||||
| F-509-18 | 任务截止后 | 待审申请自动置为 `3` 已失效 |
|
||||
| F-509-19 | 留痕 | 操作日志含申请人与审批人;申请记录保留 2 年 |
|
||||
|
||||
### F-600 材料与规则(FR-600)
|
||||
|
||||
| # | 验收项 | 期望 |
|
||||
|:--:|---|---|
|
||||
| F-600-1 | 命名模板配置 | 变量可拖拽,支持自定义连接符与大小写 |
|
||||
| F-600-2 | 命名引擎 | 双扩展名/非法字符/缺分隔符/别名归一 全部修复 |
|
||||
| F-600-3 | **幂等性** | 跑两次结果相同、第二次无 issue |
|
||||
| F-600-4 | 证件号三源裁决 | 5 种组合按裁决表执行 |
|
||||
| F-600-5 | **NEED_HUMAN 不自动改** | 两票对一票场景保持原值 |
|
||||
| F-600-6 | 格式白名单 | 非白名单自动转换 |
|
||||
| F-600-7 | 体积上下限 | 超限压缩;过小提示"可能不是原图" |
|
||||
| F-600-8 | 五档压缩 | P1 固定尺寸 / P4 不缩放 各自正确 |
|
||||
| F-600-9 | 材料识别四层 | 各层命中率达标 |
|
||||
| F-600-10 | 冲突提示 | 只提示不自动移位置 |
|
||||
| F-600-11 | 散图分拣 | 唯一允许建议落位的场景 |
|
||||
| F-600-12 | 来源引导语 | 学生端可见自定义文案 |
|
||||
| F-600-13 | **无拍照入口** | 只有聊天记录/相册/手机文件三种来源 |
|
||||
| F-600-21 | **一期材料范围** | 只支持 `PHOTO1`/`IDCARD`/`SCHOOL_ROLL`/`WORK_PROOF` 四类;`XJW_BA`/`OTHER_PROOF` 的识别**二期启用** |
|
||||
| F-600-22 | **一期收集模式** | 创建 `collectMode=2` 的任务被拒绝(`40044`) |
|
||||
| F-600-14 | **归属校验:他人材料** | 文件名证件号指向名册中另一人 → **阻断提交**,提示"文件名对应的是「李四」" |
|
||||
| F-600-15 | **归属校验:无"仍然继续"** | 归属问题的提示里**不提供**"仍然使用/强制提交"按钮(`canForce=false`) |
|
||||
| F-600-16 | 归属校验:乱名无号码 | `微信图片_xxx.jpg` → 按名册补全,**零提示** |
|
||||
| F-600-17 | 归属校验:姓名错别字 | 证件号正确 + 姓名形近 → 按名册输出,**学生端无任何提示**,仅服务端留痕 |
|
||||
| F-600-18 | 归属校验:陌生号 + OCR 佐证 | OCR = 名册 → 自动放行,信息级 |
|
||||
| F-600-19 | 归属校验:校验位失败 | 校验位失败 → 自动按名册,信息级,**不阻断** |
|
||||
| F-600-20 | **姓名不做独立规则** | 构造"证件号正确但姓名完全不同"的文件 → 仍按名册输出,**不阻断**(归属由证件号定) |
|
||||
|
||||
### F-800 审核(FR-800)
|
||||
|
||||
| # | 验收项 | 期望 |
|
||||
|:--:|---|---|
|
||||
| F-800-1 | 待办分级 | 红/黄/绿三色,`batchApprovable` 服务端判定 |
|
||||
| F-800-2 | **只右滑通过** | 左滑**不绑定驳回** |
|
||||
| F-800-3 | 驳回需二次确认 | 不能误触 |
|
||||
| F-800-4 | 驳回原因快选 | 10 条字典 + R999 手填 |
|
||||
| F-800-5 | **批量通过只带绿项** | 红黄被跳过并回报原因 |
|
||||
| F-800-6 | 红色问题不可通过 | `40032` |
|
||||
| F-800-7 | 审核时间轴 | 含提交/初审/终审/撤回/代传 |
|
||||
| F-800-8 | **撤回** | 未导出可撤;已导出 `40033` |
|
||||
| F-800-9 | 代传材料留痕 | `source=4`,学生端可见 |
|
||||
| F-800-10 | 驳回超上限升级 | 达 3 次通知管理员 |
|
||||
| F-800-11 | 变更字段高亮 | 学生改过的预填字段在详情高亮 |
|
||||
|
||||
### F-900 导出(FR-900)
|
||||
|
||||
| # | 验收项 | 期望 |
|
||||
|:--:|---|---|
|
||||
| F-900-1 | **前置检查** | 有不合格文件 → `canExport=false` → 按钮禁用 |
|
||||
| F-900-2 | blocking 定位 | 精确到"谁的哪一项" |
|
||||
| F-900-3 | 目录树预览 | 导出前可见最终形态 |
|
||||
| F-900-4 | 打包总量 | ≈37MB,压缩率 ≈92% |
|
||||
| F-900-5 | **中文文件名** | Windows 解压不乱码 |
|
||||
| F-900-6 | 汇总 Excel | 严格对齐官方 21 列 |
|
||||
| F-900-7 | 分卷 | ≤50MB,不切分单个文件 |
|
||||
| F-900-8 | 快照一致性 | 打包中数据变化不影响产物 |
|
||||
| F-900-9 | 断点续传 | 失败后从断点继续 |
|
||||
| F-900-10 | 过期清理 | 7 天自动清理,重导不重复计费 |
|
||||
|
||||
### F-1000 通知(FR-1000)
|
||||
|
||||
| # | 验收项 | 期望 |
|
||||
|:--:|---|---|
|
||||
| F-1000-1 | 订阅消息额度登记 | 各节点索取授权并记录 |
|
||||
| F-1000-2 | **降级链** | 订阅→公众号→短信→站内信,**静默** |
|
||||
| F-1000-3 | 自动催报 | 截止前 7/3/1 天 + 当日,**仅催未提交者** |
|
||||
| F-1000-4 | 催报去重 | `excludeRecentHours=24` 防骚扰 |
|
||||
| F-1000-5 | 模板变量 | 白名单外的变量渲染为空并告警 |
|
||||
| F-1000-6 | 消息中心 | 学生端 + 教师端可读可标记 |
|
||||
|
||||
### F-1200 / F-1300 / F-1400
|
||||
|
||||
| # | 验收项 | 期望 |
|
||||
|:--:|---|---|
|
||||
| F-1200-1 | 字典管理 | 6 个内置字典,有下级项的不可停用 |
|
||||
| F-1200-2 | 职业字典 | **只做拼写校验,不强约束**(Excel 模板本就无下拉) |
|
||||
| F-1200-3 | 操作日志 | 9 类敏感操作全留痕,证件号脱敏存储 |
|
||||
| F-1200-4 | 查看完整身份证号 | 单独接口、单独留痕 `VIEW_IDCARD_FULL` |
|
||||
| F-1300-1 | 用量与套餐 | 四项用量 + 超限告警 |
|
||||
| F-1400-1 | 整理器上传解析 | 436MB rar → 28 人 4 类 |
|
||||
| F-1400-2 | **体检报告数字** | 59/31/5/1 全部命中 |
|
||||
| F-1400-3 | 报告结论 | autoHandled 90 / needManual 1 |
|
||||
| F-1400-4 | 结果导入为任务 | 名册 + 112 文件一次成型 |
|
||||
|
||||
---
|
||||
|
||||
## 五、交互与视觉验收(对照原型)
|
||||
|
||||
> 原型是视觉与交互的**唯一基准**。实现与原型不一致时,以原型为准(除非原型本身有错,走变更)。
|
||||
|
||||
| 原型 | 屏数 | 必须一致的要素 |
|
||||
|---|:--:|---|
|
||||
| `student-app.html` | 10 | 门禁失败页文案(不透露哪项错)、三态字段样式、结果卡命名前后对比、无拍照入口 |
|
||||
| `teacher-app.html` | 9 | 卡片红黄绿三色、**只能右滑**、驳回二次确认、按钮 44px 触达区 |
|
||||
| `admin-web.html` | 10 | 表格信息密度、目录树预览、设计器内置字段锁标记、整理器报告布局 |
|
||||
| `index.html` | — | 三端入口 |
|
||||
|
||||
### 视觉验收要点
|
||||
|
||||
| # | 项 | 标准 |
|
||||
|:--:|---|---|
|
||||
| V1 | 移动端触达区 | 主要按钮高度 ≥ 44px |
|
||||
| V2 | 色彩语义 | 红=阻断 / 黄=警告 / 绿=通过,全局一致 |
|
||||
| V3 | 无 emoji | 全部用内联 SVG 图标,1.7px 描边 |
|
||||
| V4 | 零外部依赖 | 断网可打开(原型约束,生产可放宽) |
|
||||
| V5 | 数字口径一致 | 教师端 **28 = 15+4+3+1+5**,完成率 **82% 是已提交率** |
|
||||
| V6 | 体积表述 | 475MB → **37MB**,压缩率 **92%**(不得出现 22.4MB) |
|
||||
|
||||
---
|
||||
|
||||
## 六、性能与安全测试
|
||||
|
||||
### 6.1 性能
|
||||
|
||||
| # | 场景 | 指标 | 说明 |
|
||||
|:--:|---|---|---|
|
||||
| P1 | 门禁匹配(5000 人名册) | **< 50ms** | 走 `id_card_hash` 索引 |
|
||||
| P2 | 单文件处理(A4 扫描件) | **< 3s** | 实测 PIL 约 2s,libvips 更快 |
|
||||
| P3 | 批量处理 4 个文件 | **< 8s** | 建议走异步 |
|
||||
| P4 | 导出 112 文件 | **< 60s** | — |
|
||||
| P5 | 导出 5000 文件 | **< 15min** | 分卷 + 流式 |
|
||||
| P6 | 门禁并发 100 | 全部响应 < 1s,恰好 1 个绑定成功 | **必测** |
|
||||
| P7 | 上传并发 200 | 无失败,p99 < 2s | — |
|
||||
| P8 | 任务统计查询(10 万行) | < 500ms | 走正式列索引 |
|
||||
| P9 | 页面首屏(小程序) | < 1.5s | — |
|
||||
|
||||
### 6.2 安全
|
||||
|
||||
| # | 项 | 验证方法 |
|
||||
|:--:|---|---|
|
||||
| S1 | 跨租户越权 | 用 A 租户 token 请求 B 租户资源,全部 `40301` |
|
||||
| S2 | 门禁防枚举 | 20 个不同证件号试同一 IP → 触发异常告警并拉黑 |
|
||||
| S3 | **失败不泄露** | 抓包对比"姓名错"与"号码错"的响应,**字节级一致** |
|
||||
| S4 | 失败不留明文 | 查库 + 查日志,无明文证件号 |
|
||||
| S5 | 脱敏覆盖 | 遍历所有响应,确认 `idCardNo` 从不出现,只有 `idCardMask` |
|
||||
| S6 | 签名 URL 过期 | 过期后返回 `40302` |
|
||||
| S7 | 幂等键重放 | 同 key 重复提交返回首次结果,不产生脏数据 |
|
||||
| S8 | 上传文件类型伪造 | 改扩展名的可执行文件被拒 |
|
||||
| S9 | 路径穿越 | 文件名含 `../` 被清理 |
|
||||
| S10 | 开放接口加签 | 篡改 body / 过期 timestamp / 重放 nonce 全部拒绝 |
|
||||
| S11 | SQL 注入 | 全参数化,渗透测试 |
|
||||
| S12 | 越权访问他人填报单 | 学生 A 访问学生 B 的 submissionId → 403 |
|
||||
|
||||
---
|
||||
|
||||
## 七、里程碑验收签署表(L4)
|
||||
|
||||
> 每个里程碑结束时由**项目经理 + 技术负责人 + 客户代表**三方签署。
|
||||
|
||||
| 里程碑 | 签署项 | 交付物 | 签署 |
|
||||
|:--:|---|---|:--:|
|
||||
| M0 | 登录/组织/用户可用,跨租户拦截 | 内网演示 | ☐ |
|
||||
| M1 | 能配出完整任务;内置字段不可删 | 演示 + 截图 | ☐ |
|
||||
| M2 | 真实名单导入正确;门禁并发测试通过 | **并发压测报告** | ☐ |
|
||||
| M3 | **Golden Assertions G-01~G-14 全绿** | 回归报告 | ☐ |
|
||||
| M4 | G-15~G-25 全绿;识别报告 | 112 文件识别报告 | ☐ |
|
||||
| M5 | 三端审核闭环;一键通过跳过红项 | 真机录屏 | ☐ |
|
||||
| M6 | G-26~G-29 全绿;**Windows 解压验证** | 导出包样本 | ☐ |
|
||||
| M7 | 性能 P1~P9 达标;安全 S1~S12 通过 | 压测 + 渗透报告 | ☐ |
|
||||
|
||||
### 上线前必须全部满足(Go/No-Go)
|
||||
|
||||
| # | 条件 | 不满足的后果 |
|
||||
|:--:|---|---|
|
||||
| 1 | **全部 29 条 Golden Assertions 通过** | No-Go |
|
||||
| 2 | 性能 P1、P2、P6 达标 | No-Go(P6 是安全底线) |
|
||||
| 3 | 安全 S3、S4、S5、S11 通过 | No-Go |
|
||||
| 4 | 小程序主体资质获批 | No-Go(无法上线) |
|
||||
| 5 | 认定机构书面接受 300dpi | No-Go(材料可能被拒收) |
|
||||
| 6 | 真实数据跑通一次完整导出并被客户确认 | No-Go |
|
||||
| 7 | 回滚预案就绪(能退回上一版本) | 可延期但需记录 |
|
||||
|
||||
---
|
||||
|
||||
## 八、测试环境要求
|
||||
|
||||
| 项 | 要求 |
|
||||
|---|---|
|
||||
| 数据库 | PostgreSQL 16(DDL 已实测) |
|
||||
| 图像库 | 支持 300dpi DPI 写入、EXIF 方向读取 |
|
||||
| 测试数据 | **Golden Test Set 只读副本**,纳入版本管理 |
|
||||
| 真机 | iOS + Android 各 ≥ 1 台;Windows 用于验证解压不乱码 |
|
||||
| 并发工具 | 支持 100 并发的压测工具 |
|
||||
| 字符集 | 文件名测试必须覆盖中文 + 双扩展名 + 全角字符 |
|
||||
|
||||
### 易被忽略的三个测试盲区
|
||||
|
||||
| # | 盲区 | 为什么容易漏 |
|
||||
|:--:|---|---|
|
||||
| 1 | **Windows 解压中文名** | 开发用 Mac/Linux 测试一切正常,交付到客户才炸 |
|
||||
| 2 | **并发绑定的临界窗口** | 单机测试永远不复现,只有压测才暴露 |
|
||||
| 3 | **「无」是合法值** | 测试数据里没人填"无",上线后真实数据一填就错 |
|
||||
|
||||
---
|
||||
|
||||
## 变更记录
|
||||
|
||||
| 版本 | 日期 | 变更 |
|
||||
|---|---|---|
|
||||
| **V1.1** | **2026-09-28** | **客户确认后修订:修正「文件归属」设计缺陷**。原设计把"文件名姓名/证件号与名册不符"处理为"自动按名册修正"(信息级),**会把传错的文件静默洗白**。修正为:<br>① 新增 **7 条归属断言 G-14b~G-14h**(Golden 29 → **36**);② 判定优先级改为 **证件号反查名册 > OCR 内容 > 姓名(仅辅助)**;③ 新增 **VAL-21 文件归属校验(阻断,且无"仍然继续")**;④ **姓名不再作为独立规则**(客户确认);⑤ 新增 **7 条功能验收 F-600-14~20**(104 → **111**)与 **4 条异常边界 E24~E27**(23 → **27**)<br>**修订后总计:36 条断言 / 111 条功能验收 / 27 条异常边界 / 6 条视觉 / 9 项性能 / 12 项安全** |
|
||||
| V1.0 | 2026-09-28 | 首版。**29 条 Golden Assertions**(可直接写成自动化)、3 条端到端主流程(32 步)、23 条异常边界、**104 条功能验收项**(含 F-509 共 19 条)、6 条视觉验收、9 项性能、12 项安全;含里程碑签署表与 7 条 Go/No-Go 条件 |
|
||||
@@ -0,0 +1,712 @@
|
||||
-- ============ 1) 建表 ============
|
||||
|
||||
CREATE TABLE sys_tenant (
|
||||
id bigint GENERATED BY DEFAULT AS IDENTITY NOT NULL,
|
||||
tenant_code varchar(32) NOT NULL,
|
||||
tenant_name varchar(128) NOT NULL,
|
||||
tenant_type smallint NOT NULL DEFAULT 1,
|
||||
contact_name varchar(64),
|
||||
contact_phone varchar(20),
|
||||
package_id bigint,
|
||||
expire_time timestamp,
|
||||
status smallint NOT NULL DEFAULT 1,
|
||||
logo_url varchar(512),
|
||||
config_json jsonb,
|
||||
create_by bigint,
|
||||
create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
update_by bigint,
|
||||
update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted smallint DEFAULT 0 NOT NULL,
|
||||
PRIMARY KEY (id),
|
||||
CONSTRAINT uk_tenant_code UNIQUE (tenant_code)
|
||||
);
|
||||
COMMENT ON TABLE sys_tenant IS '租户';
|
||||
COMMENT ON COLUMN sys_tenant.id IS '租户ID';
|
||||
COMMENT ON COLUMN sys_tenant.tenant_code IS '租户编码(短码,用于小程序扫码识别)';
|
||||
COMMENT ON COLUMN sys_tenant.tenant_name IS '租户名称(学校/机构)';
|
||||
COMMENT ON COLUMN sys_tenant.tenant_type IS '1学校 2认定机构 3报名点 4企业';
|
||||
COMMENT ON COLUMN sys_tenant.contact_name IS '联系人';
|
||||
COMMENT ON COLUMN sys_tenant.contact_phone IS '联系电话';
|
||||
COMMENT ON COLUMN sys_tenant.package_id IS '套餐ID';
|
||||
COMMENT ON COLUMN sys_tenant.expire_time IS '到期时间';
|
||||
COMMENT ON COLUMN sys_tenant.status IS '0停用 1正常 2试用';
|
||||
COMMENT ON COLUMN sys_tenant.logo_url IS 'Logo';
|
||||
COMMENT ON COLUMN sys_tenant.config_json IS '租户配置(默认压缩参数/文件保留天数等)';
|
||||
|
||||
CREATE TABLE sys_org (
|
||||
id bigint GENERATED BY DEFAULT AS IDENTITY NOT NULL,
|
||||
tenant_id bigint NOT NULL,
|
||||
parent_id bigint NOT NULL DEFAULT 0,
|
||||
org_name varchar(128) NOT NULL,
|
||||
org_type smallint NOT NULL DEFAULT 3,
|
||||
org_code varchar(64),
|
||||
ancestor_path varchar(512),
|
||||
leader_user_id bigint,
|
||||
sort_no integer NOT NULL DEFAULT 0,
|
||||
status smallint NOT NULL DEFAULT 1,
|
||||
create_by bigint,
|
||||
create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
update_by bigint,
|
||||
update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted smallint DEFAULT 0 NOT NULL,
|
||||
PRIMARY KEY (id)
|
||||
);
|
||||
CREATE INDEX idx_tenant_parent ON sys_org (tenant_id, parent_id);
|
||||
CREATE INDEX idx_tenant_name ON sys_org (tenant_id, org_name);
|
||||
COMMENT ON TABLE sys_org IS '组织架构';
|
||||
COMMENT ON COLUMN sys_org.parent_id IS '父级ID,0为根';
|
||||
COMMENT ON COLUMN sys_org.org_name IS '组织名称,如 24中技数控1班';
|
||||
COMMENT ON COLUMN sys_org.org_type IS '1学校 2院系/专业 3班级 4报名点';
|
||||
COMMENT ON COLUMN sys_org.org_code IS '组织编码/班号';
|
||||
COMMENT ON COLUMN sys_org.ancestor_path IS '祖先路径,如 0,1,5';
|
||||
COMMENT ON COLUMN sys_org.leader_user_id IS '负责人(班主任)';
|
||||
|
||||
CREATE TABLE sys_occupation_ref (
|
||||
id bigint GENERATED BY DEFAULT AS IDENTITY NOT NULL,
|
||||
parent_id bigint NOT NULL DEFAULT 0,
|
||||
name varchar(128) NOT NULL,
|
||||
aliases varchar(512),
|
||||
has_child smallint NOT NULL DEFAULT 0,
|
||||
status smallint NOT NULL DEFAULT 1,
|
||||
create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id)
|
||||
);
|
||||
CREATE INDEX idx_parent ON sys_occupation_ref (parent_id);
|
||||
CREATE INDEX idx_occref_name ON sys_occupation_ref (name);
|
||||
COMMENT ON TABLE sys_occupation_ref IS '职业工种参考字典(仅拼写校验,非约束)';
|
||||
COMMENT ON COLUMN sys_occupation_ref.parent_id IS '0=职业; 非0=该职业下的工种';
|
||||
COMMENT ON COLUMN sys_occupation_ref.aliases IS '常见错写/别名,用于纠错提示';
|
||||
COMMENT ON COLUMN sys_occupation_ref.has_child IS '该职业是否有工种';
|
||||
|
||||
CREATE TABLE sys_user (
|
||||
id bigint GENERATED BY DEFAULT AS IDENTITY NOT NULL,
|
||||
tenant_id bigint NOT NULL DEFAULT 0,
|
||||
org_id bigint,
|
||||
username varchar(64) NOT NULL,
|
||||
password varchar(128),
|
||||
real_name varchar(64) NOT NULL,
|
||||
phone varchar(20),
|
||||
phone_hash char(64),
|
||||
open_id varchar(64),
|
||||
union_id varchar(64),
|
||||
id_card_no varchar(128),
|
||||
id_card_hash char(64),
|
||||
avatar_url varchar(512),
|
||||
user_type smallint NOT NULL DEFAULT 1,
|
||||
status smallint NOT NULL DEFAULT 1,
|
||||
last_login_time timestamp,
|
||||
create_by bigint,
|
||||
create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
update_by bigint,
|
||||
update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted smallint DEFAULT 0 NOT NULL,
|
||||
PRIMARY KEY (id),
|
||||
CONSTRAINT uk_tenant_username UNIQUE (tenant_id, username)
|
||||
);
|
||||
CREATE INDEX idx_phone_hash ON sys_user (phone_hash);
|
||||
CREATE INDEX idx_idcard_hash ON sys_user (id_card_hash);
|
||||
CREATE INDEX idx_openid ON sys_user (open_id);
|
||||
COMMENT ON TABLE sys_user IS '用户';
|
||||
COMMENT ON COLUMN sys_user.tenant_id IS '0=平台运营';
|
||||
COMMENT ON COLUMN sys_user.org_id IS '所属组织';
|
||||
COMMENT ON COLUMN sys_user.username IS '登录名(教师)';
|
||||
COMMENT ON COLUMN sys_user.password IS 'BCrypt';
|
||||
COMMENT ON COLUMN sys_user.phone IS '手机号(AES加密存储)';
|
||||
COMMENT ON COLUMN sys_user.phone_hash IS '手机号HMAC,用于唯一检索';
|
||||
COMMENT ON COLUMN sys_user.open_id IS '微信openid';
|
||||
COMMENT ON COLUMN sys_user.id_card_no IS '证件号(AES加密存储)';
|
||||
COMMENT ON COLUMN sys_user.id_card_hash IS '证件号HMAC,用于比对与去重';
|
||||
COMMENT ON COLUMN sys_user.user_type IS '1教师/管理员 2填报人(学生) 3社会人员';
|
||||
COMMENT ON COLUMN sys_user.status IS '0停用 1正常 2待激活';
|
||||
|
||||
CREATE TABLE collect_task (
|
||||
id bigint GENERATED BY DEFAULT AS IDENTITY NOT NULL,
|
||||
tenant_id bigint NOT NULL,
|
||||
task_code varchar(64) NOT NULL,
|
||||
task_name varchar(200) NOT NULL,
|
||||
collect_mode smallint NOT NULL DEFAULT 1,
|
||||
batch_no varchar(64),
|
||||
major_name varchar(64),
|
||||
job_name varchar(64),
|
||||
job_level varchar(32),
|
||||
cert_type smallint NOT NULL DEFAULT 1,
|
||||
start_time timestamp,
|
||||
end_time timestamp,
|
||||
audit_config jsonb,
|
||||
naming_template varchar(256) NOT NULL DEFAULT '{姓名}-{证件号}-{材料名}',
|
||||
compress_config jsonb,
|
||||
roster_required smallint NOT NULL DEFAULT 1,
|
||||
status smallint NOT NULL DEFAULT 0,
|
||||
published_time timestamp,
|
||||
create_by bigint,
|
||||
create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
update_by bigint,
|
||||
update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted smallint DEFAULT 0 NOT NULL,
|
||||
PRIMARY KEY (id),
|
||||
CONSTRAINT uk_task_tenant_code UNIQUE (tenant_id, task_code)
|
||||
);
|
||||
CREATE INDEX idx_tenant_status ON collect_task (tenant_id, status);
|
||||
CREATE INDEX idx_end_time ON collect_task (end_time);
|
||||
COMMENT ON TABLE collect_task IS '收集任务';
|
||||
COMMENT ON COLUMN collect_task.task_code IS '任务编码,用于文件路径';
|
||||
COMMENT ON COLUMN collect_task.task_name IS '如 2026年9月机电专业认定材料收集';
|
||||
COMMENT ON COLUMN collect_task.collect_mode IS '1学校收集 2社会收集';
|
||||
COMMENT ON COLUMN collect_task.batch_no IS '批次号';
|
||||
COMMENT ON COLUMN collect_task.major_name IS '专业';
|
||||
COMMENT ON COLUMN collect_task.job_name IS '报考工种';
|
||||
COMMENT ON COLUMN collect_task.job_level IS '报考等级';
|
||||
COMMENT ON COLUMN collect_task.cert_type IS '1自主认定 2社会评价 3专项职业能力';
|
||||
COMMENT ON COLUMN collect_task.end_time IS '截止时间';
|
||||
COMMENT ON COLUMN collect_task.audit_config IS '审核流配置,见6.5';
|
||||
COMMENT ON COLUMN collect_task.compress_config IS '任务级压缩默认参数';
|
||||
COMMENT ON COLUMN collect_task.roster_required IS '是否需要名册: 固定为1,两种收集模式都必填(名单门禁)';
|
||||
COMMENT ON COLUMN collect_task.status IS '0草稿 1收集中 2已截止 3已归档 4暂停';
|
||||
|
||||
CREATE TABLE collect_task_org (
|
||||
id bigint GENERATED BY DEFAULT AS IDENTITY NOT NULL,
|
||||
tenant_id bigint NOT NULL,
|
||||
task_id bigint NOT NULL,
|
||||
org_id bigint NOT NULL,
|
||||
head_user_id bigint,
|
||||
deputy_user_ids varchar(256),
|
||||
expected_count integer NOT NULL DEFAULT 0,
|
||||
status smallint NOT NULL DEFAULT 1,
|
||||
create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
CONSTRAINT uk_task_org UNIQUE (task_id, org_id)
|
||||
);
|
||||
CREATE INDEX idx_tenant_head ON collect_task_org (tenant_id, head_user_id);
|
||||
COMMENT ON TABLE collect_task_org IS '任务覆盖组织与审核人绑定';
|
||||
COMMENT ON COLUMN collect_task_org.head_user_id IS '该班初审人(班主任),为空则取 sys_org.leader_user_id';
|
||||
COMMENT ON COLUMN collect_task_org.deputy_user_ids IS '副班主任/协办人,逗号分隔';
|
||||
COMMENT ON COLUMN collect_task_org.expected_count IS '应填人数(冗余,由名册汇总)';
|
||||
COMMENT ON COLUMN collect_task_org.status IS '0已移除 1生效';
|
||||
|
||||
CREATE TABLE collect_form_schema (
|
||||
id bigint GENERATED BY DEFAULT AS IDENTITY NOT NULL,
|
||||
tenant_id bigint NOT NULL,
|
||||
task_id bigint NOT NULL,
|
||||
version integer NOT NULL DEFAULT 1,
|
||||
schema_json jsonb NOT NULL,
|
||||
is_current smallint NOT NULL DEFAULT 1,
|
||||
create_by bigint, create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id)
|
||||
);
|
||||
CREATE INDEX idx_task_version ON collect_form_schema (task_id, version);
|
||||
COMMENT ON TABLE collect_form_schema IS '表单定义(版本化)';
|
||||
COMMENT ON COLUMN collect_form_schema.version IS '版本号,已发布任务修改则+1';
|
||||
COMMENT ON COLUMN collect_form_schema.schema_json IS '字段定义数组';
|
||||
|
||||
CREATE TABLE collect_material_def (
|
||||
id bigint GENERATED BY DEFAULT AS IDENTITY NOT NULL,
|
||||
tenant_id bigint NOT NULL,
|
||||
task_id bigint NOT NULL,
|
||||
material_code varchar(32) NOT NULL,
|
||||
material_name varchar(64) NOT NULL,
|
||||
aliases varchar(512),
|
||||
name_pattern varchar(256),
|
||||
required smallint NOT NULL DEFAULT 1,
|
||||
visible_when varchar(256),
|
||||
file_types varchar(128) NOT NULL DEFAULT 'jpg,jpeg,png',
|
||||
max_size_kb integer NOT NULL DEFAULT 1024,
|
||||
compress_profile varchar(8) NOT NULL DEFAULT 'AUTO',
|
||||
min_long_side integer NOT NULL DEFAULT 1400,
|
||||
min_quality smallint NOT NULL DEFAULT 70,
|
||||
target_dpi integer,
|
||||
fixed_size varchar(32),
|
||||
output_format varchar(16) NOT NULL DEFAULT 'jpg',
|
||||
max_count smallint NOT NULL DEFAULT 1,
|
||||
merge_enabled smallint NOT NULL DEFAULT 0,
|
||||
ocr_type varchar(32),
|
||||
sample_url varchar(512),
|
||||
tip_text varchar(512),
|
||||
sort_no integer NOT NULL DEFAULT 0,
|
||||
create_by bigint,
|
||||
create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
update_by bigint,
|
||||
update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted smallint DEFAULT 0 NOT NULL,
|
||||
PRIMARY KEY (id),
|
||||
CONSTRAINT uk_task_code UNIQUE (task_id, material_code)
|
||||
);
|
||||
CREATE INDEX idx_matdef_tenant ON collect_material_def (tenant_id);
|
||||
COMMENT ON TABLE collect_material_def IS '材料项定义';
|
||||
COMMENT ON COLUMN collect_material_def.material_code IS '如 IDCARD/PHOTO1/SCHOOL_ROLL/WORK_PROOF';
|
||||
COMMENT ON COLUMN collect_material_def.material_name IS '规范材料名,如 身份证';
|
||||
COMMENT ON COLUMN collect_material_def.aliases IS '别名,逗号分隔,用于历史文件名识别';
|
||||
COMMENT ON COLUMN collect_material_def.name_pattern IS '命名模板,空则继承任务级';
|
||||
COMMENT ON COLUMN collect_material_def.visible_when IS '条件显示表达式';
|
||||
COMMENT ON COLUMN collect_material_def.file_types IS '格式白名单';
|
||||
COMMENT ON COLUMN collect_material_def.max_size_kb IS '体积上限KB';
|
||||
COMMENT ON COLUMN collect_material_def.compress_profile IS '压缩档位: AUTO自动判定/P1证件照/P2证件翻拍/P3扫描件/P4截图/P5文档, 见6.3';
|
||||
COMMENT ON COLUMN collect_material_def.min_long_side IS '分辨率下限(仅P2证件翻拍档生效)';
|
||||
COMMENT ON COLUMN collect_material_def.min_quality IS '质量下限,低于65视为不可接受';
|
||||
COMMENT ON COLUMN collect_material_def.target_dpi IS '目标DPI(仅P3扫描件档生效,建议150-300)';
|
||||
COMMENT ON COLUMN collect_material_def.fixed_size IS '固定像素尺寸(仅P1证件照档,如295x413)';
|
||||
COMMENT ON COLUMN collect_material_def.output_format IS 'P4截图档固定输出png';
|
||||
COMMENT ON COLUMN collect_material_def.max_count IS '允许上传份数,身份证正反面可2';
|
||||
COMMENT ON COLUMN collect_material_def.merge_enabled IS '多份是否合并为一张';
|
||||
COMMENT ON COLUMN collect_material_def.ocr_type IS 'IDCARD/GENERAL/NONE';
|
||||
COMMENT ON COLUMN collect_material_def.sample_url IS '示例图';
|
||||
COMMENT ON COLUMN collect_material_def.tip_text IS '填报提示语';
|
||||
|
||||
CREATE TABLE collect_roster (
|
||||
id bigint GENERATED BY DEFAULT AS IDENTITY NOT NULL,
|
||||
tenant_id bigint NOT NULL,
|
||||
task_id bigint NOT NULL,
|
||||
org_id bigint,
|
||||
seq_no integer,
|
||||
student_no varchar(64),
|
||||
real_name varchar(64) NOT NULL,
|
||||
name_pinyin varchar(128),
|
||||
id_card_no varchar(128) NOT NULL,
|
||||
id_card_hash char(64) NOT NULL,
|
||||
id_card_mask varchar(32),
|
||||
phone varchar(20),
|
||||
phone_hash char(64),
|
||||
ext_json jsonb,
|
||||
bind_user_id bigint,
|
||||
bind_time timestamp,
|
||||
bind_type smallint,
|
||||
submit_status smallint NOT NULL DEFAULT 0,
|
||||
create_by bigint,
|
||||
create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
update_by bigint,
|
||||
update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted smallint DEFAULT 0 NOT NULL,
|
||||
PRIMARY KEY (id),
|
||||
CONSTRAINT uk_task_idcard UNIQUE (task_id, id_card_hash)
|
||||
);
|
||||
CREATE INDEX idx_task_org ON collect_roster (task_id, org_id);
|
||||
CREATE INDEX idx_bind_user ON collect_roster (bind_user_id);
|
||||
CREATE INDEX idx_roster_name ON collect_roster (task_id, real_name);
|
||||
COMMENT ON TABLE collect_roster IS '名册';
|
||||
COMMENT ON COLUMN collect_roster.org_id IS '班级';
|
||||
COMMENT ON COLUMN collect_roster.seq_no IS '序号(来自Excel)';
|
||||
COMMENT ON COLUMN collect_roster.student_no IS '学号(⚠️初稿遗漏,名册唯一性依据之一)';
|
||||
COMMENT ON COLUMN collect_roster.name_pinyin IS '拼音,用于近音纠错';
|
||||
COMMENT ON COLUMN collect_roster.id_card_no IS 'AES加密';
|
||||
COMMENT ON COLUMN collect_roster.id_card_hash IS 'HMAC,唯一性判断';
|
||||
COMMENT ON COLUMN collect_roster.id_card_mask IS '脱敏展示 3601**********1234';
|
||||
COMMENT ON COLUMN collect_roster.ext_json IS '名册其余字段(学历/工作单位/报考职业等)';
|
||||
COMMENT ON COLUMN collect_roster.bind_user_id IS '绑定的填报人';
|
||||
COMMENT ON COLUMN collect_roster.bind_type IS '1门禁核验匹配 2管理员指定';
|
||||
COMMENT ON COLUMN collect_roster.submit_status IS '0未提交 1草稿 2待审 3已通过 4已驳回';
|
||||
|
||||
CREATE TABLE collect_submission (
|
||||
id bigint GENERATED BY DEFAULT AS IDENTITY NOT NULL,
|
||||
tenant_id bigint NOT NULL,
|
||||
task_id bigint NOT NULL,
|
||||
roster_id bigint,
|
||||
org_id bigint,
|
||||
submitter_id bigint NOT NULL,
|
||||
form_version integer NOT NULL DEFAULT 1,
|
||||
form_data jsonb,
|
||||
-- ⚠️ 与 collect_roster 对称的 18 个业务列:存学生「确认/修改后」的最终值
|
||||
-- 需要与名单值做 diff(留痕"学生改了哪些字段"),也是统计"实际报考各工种人数"的依据
|
||||
occupation varchar(64),
|
||||
worktype varchar(64),
|
||||
job_level varchar(32),
|
||||
education varchar(32),
|
||||
is_student smallint,
|
||||
school varchar(128),
|
||||
major varchar(128),
|
||||
enroll_date date,
|
||||
work_unit varchar(128),
|
||||
is_retake smallint,
|
||||
retake_theory varchar(64),
|
||||
retake_practice varchar(64),
|
||||
retake_overall varchar(64),
|
||||
work_years smallint,
|
||||
cert_occupation varchar(128),
|
||||
cert_level varchar(32),
|
||||
cert_no varchar(64),
|
||||
extras_json jsonb DEFAULT '{}'::jsonb, -- 逃生舱
|
||||
changed_fields jsonb, -- 学生相对名单修改过的字段(留痕)
|
||||
status smallint NOT NULL DEFAULT 0,
|
||||
reject_count integer NOT NULL DEFAULT 0,
|
||||
last_reject_reason varchar(512),
|
||||
red_count integer NOT NULL DEFAULT 0,
|
||||
yellow_count integer NOT NULL DEFAULT 0,
|
||||
submit_time timestamp,
|
||||
class_audit_by bigint, class_audit_time timestamp,
|
||||
admin_audit_by bigint, admin_audit_time timestamp,
|
||||
create_by bigint,
|
||||
create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
update_by bigint,
|
||||
update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted smallint DEFAULT 0 NOT NULL,
|
||||
PRIMARY KEY (id),
|
||||
CONSTRAINT uk_task_submitter UNIQUE (task_id, submitter_id)
|
||||
);
|
||||
CREATE INDEX idx_task_status ON collect_submission (task_id, status, org_id);
|
||||
CREATE INDEX idx_submitter ON collect_submission (submitter_id);
|
||||
COMMENT ON TABLE collect_submission IS '填报单';
|
||||
COMMENT ON COLUMN collect_submission.roster_id IS '社会收集可为空';
|
||||
COMMENT ON COLUMN collect_submission.submitter_id IS '填报人user_id';
|
||||
COMMENT ON COLUMN collect_submission.form_data IS '表单数据快照(扩展字段;核心18列见上方独立列)';
|
||||
COMMENT ON COLUMN collect_submission.occupation IS '最终报考职业(学生确认后,应与名单一致)';
|
||||
COMMENT ON COLUMN collect_submission.worktype IS '最终报考工种';
|
||||
COMMENT ON COLUMN collect_submission.job_level IS '最终报考等级';
|
||||
COMMENT ON COLUMN collect_submission.is_student IS '是否在校生 0否 1是';
|
||||
COMMENT ON COLUMN collect_submission.work_unit IS '工作单位(没有填"无")';
|
||||
COMMENT ON COLUMN collect_submission.is_retake IS '是否补考 0否 1是';
|
||||
COMMENT ON COLUMN collect_submission.extras_json IS '租户/机构额外追加字段(逃生舱)';
|
||||
COMMENT ON COLUMN collect_submission.changed_fields IS '学生相对名单修改过的字段(留痕: {字段:{old,new,time}})';
|
||||
COMMENT ON COLUMN collect_submission.status IS '0草稿 1待初审 2待终审 3已通过 4已驳回 5已归档';
|
||||
COMMENT ON COLUMN collect_submission.red_count IS '红色问题数';
|
||||
COMMENT ON COLUMN collect_submission.yellow_count IS '黄色问题数';
|
||||
|
||||
CREATE TABLE collect_file (
|
||||
id bigint GENERATED BY DEFAULT AS IDENTITY NOT NULL,
|
||||
tenant_id bigint NOT NULL,
|
||||
task_id bigint NOT NULL,
|
||||
submission_id bigint NOT NULL,
|
||||
roster_id bigint,
|
||||
material_code varchar(32) NOT NULL,
|
||||
version integer NOT NULL DEFAULT 1,
|
||||
is_current smallint NOT NULL DEFAULT 1,
|
||||
original_name varchar(512) NOT NULL,
|
||||
standard_name varchar(512),
|
||||
file_ext varchar(16) NOT NULL,
|
||||
mime_type varchar(64),
|
||||
raw_size bigint NOT NULL,
|
||||
final_size bigint NOT NULL,
|
||||
file_hash char(64) NOT NULL,
|
||||
raw_path varchar(512),
|
||||
std_path varchar(512),
|
||||
raw_expire_time timestamp,
|
||||
width integer, height integer, quality smallint,
|
||||
dpi integer,
|
||||
ocr_status smallint NOT NULL DEFAULT 0,
|
||||
ocr_result jsonb,
|
||||
ocr_confidence numeric(4,3),
|
||||
detected_material_code varchar(32),
|
||||
detect_confidence numeric(4,3),
|
||||
detect_source varchar(32),
|
||||
detect_conflict smallint NOT NULL DEFAULT 0,
|
||||
need_manual_place smallint NOT NULL DEFAULT 0,
|
||||
vlm_called smallint NOT NULL DEFAULT 0,
|
||||
validate_status smallint NOT NULL DEFAULT 0,
|
||||
source smallint NOT NULL DEFAULT 1,
|
||||
upload_ip varchar(64),
|
||||
create_by bigint,
|
||||
create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
update_by bigint,
|
||||
update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted smallint DEFAULT 0 NOT NULL,
|
||||
PRIMARY KEY (id)
|
||||
);
|
||||
CREATE INDEX idx_file_submission ON collect_file (submission_id, material_code, is_current);
|
||||
CREATE INDEX idx_task_material ON collect_file (task_id, material_code);
|
||||
CREATE INDEX idx_hash ON collect_file (file_hash);
|
||||
CREATE INDEX idx_raw_expire ON collect_file (raw_expire_time);
|
||||
COMMENT ON TABLE collect_file IS '文件实体';
|
||||
COMMENT ON COLUMN collect_file.is_current IS '是否最新有效版本';
|
||||
COMMENT ON COLUMN collect_file.original_name IS '原始文件名';
|
||||
COMMENT ON COLUMN collect_file.standard_name IS '规范文件名';
|
||||
COMMENT ON COLUMN collect_file.raw_size IS '原始字节';
|
||||
COMMENT ON COLUMN collect_file.final_size IS '处理后字节';
|
||||
COMMENT ON COLUMN collect_file.file_hash IS 'SHA-256,用于查重';
|
||||
COMMENT ON COLUMN collect_file.raw_path IS '对象存储key-原始';
|
||||
COMMENT ON COLUMN collect_file.std_path IS '对象存储key-规范';
|
||||
COMMENT ON COLUMN collect_file.raw_expire_time IS '原始文件清理时间';
|
||||
COMMENT ON COLUMN collect_file.ocr_status IS '0未识别 1成功 2失败';
|
||||
COMMENT ON COLUMN collect_file.ocr_result IS 'OCR结构化结果(不存明文证件号,存hash)';
|
||||
COMMENT ON COLUMN collect_file.detected_material_code IS '材料识别结果(材料类型编码),见功能逻辑卷3.5-B';
|
||||
COMMENT ON COLUMN collect_file.detect_confidence IS '材料识别置信度';
|
||||
COMMENT ON COLUMN collect_file.detect_source IS '识别来源: FILENAME/形态KEYWORD/POSITION/VLM/MIXED';
|
||||
COMMENT ON COLUMN collect_file.detect_conflict IS '是否与槽位冲突(1=冲突)';
|
||||
COMMENT ON COLUMN collect_file.need_manual_place IS '是否需要人工归属(散图分拣用)';
|
||||
COMMENT ON COLUMN collect_file.vlm_called IS '是否触发了VLM兜底(用于成本核算)';
|
||||
COMMENT ON COLUMN collect_file.validate_status IS '0未校验 1通过 2警告 3错误';
|
||||
COMMENT ON COLUMN collect_file.source IS '1本人上传 2班主任代传 3管理员代传 4整理器导入';
|
||||
|
||||
CREATE TABLE collect_validation_issue (
|
||||
id bigint GENERATED BY DEFAULT AS IDENTITY NOT NULL,
|
||||
tenant_id bigint NOT NULL,
|
||||
task_id bigint NOT NULL,
|
||||
submission_id bigint NOT NULL,
|
||||
file_id bigint,
|
||||
rule_code varchar(32) NOT NULL,
|
||||
severity smallint NOT NULL,
|
||||
field_key varchar(64),
|
||||
title varchar(128) NOT NULL,
|
||||
detail varchar(512),
|
||||
expected_val varchar(256),
|
||||
actual_val varchar(256),
|
||||
auto_fixed smallint NOT NULL DEFAULT 0,
|
||||
resolved smallint NOT NULL DEFAULT 0,
|
||||
resolved_by bigint, resolved_time timestamp,
|
||||
create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id)
|
||||
);
|
||||
CREATE INDEX idx_issue_submission ON collect_validation_issue (submission_id, resolved);
|
||||
CREATE INDEX idx_task_severity ON collect_validation_issue (task_id, severity, resolved);
|
||||
COMMENT ON TABLE collect_validation_issue IS '校验问题';
|
||||
COMMENT ON COLUMN collect_validation_issue.file_id IS '可为空(表单级问题)';
|
||||
COMMENT ON COLUMN collect_validation_issue.rule_code IS 'VAL-01..VAL-12';
|
||||
COMMENT ON COLUMN collect_validation_issue.severity IS '1信息 2警告 3错误';
|
||||
COMMENT ON COLUMN collect_validation_issue.expected_val IS '期望值(如名册中的证件号)';
|
||||
COMMENT ON COLUMN collect_validation_issue.actual_val IS '实际值';
|
||||
COMMENT ON COLUMN collect_validation_issue.auto_fixed IS '是否已自动修复';
|
||||
|
||||
CREATE TABLE collect_audit_record (
|
||||
id bigint GENERATED BY DEFAULT AS IDENTITY NOT NULL,
|
||||
tenant_id bigint NOT NULL,
|
||||
task_id bigint NOT NULL,
|
||||
submission_id bigint NOT NULL,
|
||||
audit_node varchar(32) NOT NULL,
|
||||
auditor_id bigint NOT NULL,
|
||||
action smallint NOT NULL,
|
||||
reason_code varchar(32),
|
||||
reason_text varchar(512),
|
||||
material_codes varchar(256),
|
||||
duration_sec integer,
|
||||
create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id)
|
||||
);
|
||||
CREATE INDEX idx_audit_submission ON collect_audit_record (submission_id, create_time);
|
||||
CREATE INDEX idx_task_node ON collect_audit_record (task_id, audit_node, create_time);
|
||||
COMMENT ON TABLE collect_audit_record IS '审核记录';
|
||||
COMMENT ON COLUMN collect_audit_record.audit_node IS 'CLASS_AUDIT/ADMIN_AUDIT';
|
||||
COMMENT ON COLUMN collect_audit_record.action IS '1通过 2驳回 3批量通过 4批量驳回 5转交 6代传';
|
||||
COMMENT ON COLUMN collect_audit_record.reason_code IS '驳回原因编码';
|
||||
COMMENT ON COLUMN collect_audit_record.material_codes IS '被驳回的材料项';
|
||||
COMMENT ON COLUMN collect_audit_record.duration_sec IS '处理耗时';
|
||||
|
||||
CREATE TABLE collect_export_task (
|
||||
id bigint GENERATED BY DEFAULT AS IDENTITY NOT NULL,
|
||||
tenant_id bigint NOT NULL,
|
||||
task_id bigint NOT NULL,
|
||||
export_name varchar(256) NOT NULL,
|
||||
scope_json jsonb,
|
||||
split_size_mb integer NOT NULL DEFAULT 0,
|
||||
desensitize smallint NOT NULL DEFAULT 0,
|
||||
status smallint NOT NULL DEFAULT 0,
|
||||
progress smallint NOT NULL DEFAULT 0,
|
||||
file_count integer, total_size bigint,
|
||||
result_files jsonb,
|
||||
error_msg varchar(512),
|
||||
expire_time timestamp,
|
||||
create_by bigint, create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
finish_time timestamp,
|
||||
PRIMARY KEY (id)
|
||||
);
|
||||
CREATE INDEX idx_task ON collect_export_task (task_id, create_time);
|
||||
CREATE INDEX idx_status ON collect_export_task (status);
|
||||
COMMENT ON TABLE collect_export_task IS '导出任务';
|
||||
COMMENT ON COLUMN collect_export_task.scope_json IS '范围:orgIds/userIds/仅通过终审/是否含原图';
|
||||
COMMENT ON COLUMN collect_export_task.split_size_mb IS '0不分卷,>0按MB分卷';
|
||||
COMMENT ON COLUMN collect_export_task.desensitize IS '是否脱敏';
|
||||
COMMENT ON COLUMN collect_export_task.status IS '0排队 1处理中 2完成 3失败';
|
||||
COMMENT ON COLUMN collect_export_task.progress IS '0-100';
|
||||
COMMENT ON COLUMN collect_export_task.result_files IS '生成的包列表[{name,path,size,part}]';
|
||||
COMMENT ON COLUMN collect_export_task.expire_time IS '下载有效期';
|
||||
|
||||
CREATE TABLE collect_file_process_log (
|
||||
id bigint GENERATED BY DEFAULT AS IDENTITY NOT NULL,
|
||||
tenant_id bigint NOT NULL,
|
||||
file_id bigint NOT NULL,
|
||||
step varchar(32) NOT NULL,
|
||||
before_val varchar(512),
|
||||
after_val varchar(512),
|
||||
extra_json jsonb,
|
||||
cost_ms integer,
|
||||
success smallint NOT NULL DEFAULT 1,
|
||||
error_msg varchar(512),
|
||||
create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id)
|
||||
);
|
||||
CREATE INDEX idx_file ON collect_file_process_log (file_id, step);
|
||||
COMMENT ON TABLE collect_file_process_log IS '文件处理日志';
|
||||
COMMENT ON COLUMN collect_file_process_log.step IS 'NORMALIZE_NAME/COMPRESS/CONVERT/OCR/EXIF_FIX';
|
||||
COMMENT ON COLUMN collect_file_process_log.extra_json IS '如 {beforeSize,afterSize,attempts,quality,finalLongSide}';
|
||||
|
||||
CREATE TABLE collect_notify_record (
|
||||
id bigint GENERATED BY DEFAULT AS IDENTITY NOT NULL,
|
||||
tenant_id bigint NOT NULL,
|
||||
task_id bigint,
|
||||
channel varchar(16) NOT NULL, -- WECHAT_SUBSCRIBE / WECHAT_MP / SMS / INBOX
|
||||
biz_type varchar(32) NOT NULL, -- TASK_PUBLISH / DEADLINE_REMIND / REJECT / APPROVE / EXPORT_DONE / AUDIT_TIMEOUT
|
||||
receiver_id bigint NOT NULL, -- 接收人 sys_user.id
|
||||
receiver_role varchar(16), -- 接收人角色(便于按角色统计)
|
||||
template_code varchar(64),
|
||||
content varchar(1024),
|
||||
status smallint NOT NULL DEFAULT 0, -- 0待发 1成功 2失败 3额度不足跳过
|
||||
fail_reason varchar(512),
|
||||
send_time timestamp,
|
||||
create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id)
|
||||
);
|
||||
CREATE INDEX idx_notify_task ON collect_notify_record (task_id, biz_type, create_time);
|
||||
CREATE INDEX idx_notify_recv ON collect_notify_record (receiver_id, create_time);
|
||||
COMMENT ON TABLE collect_notify_record IS '通知记录';
|
||||
COMMENT ON COLUMN collect_notify_record.channel IS '渠道: 微信订阅消息/服务号/短信/站内';
|
||||
COMMENT ON COLUMN collect_notify_record.biz_type IS '业务类型';
|
||||
COMMENT ON COLUMN collect_notify_record.status IS '0待发 1成功 2失败 3额度不足跳过';
|
||||
COMMENT ON COLUMN collect_notify_record.fail_reason IS '失败原因;额度不足时记录(应对R12订阅消息次数限制)';
|
||||
|
||||
-- 订阅额度余额(微信一次性订阅消息,发一条消耗一条)
|
||||
CREATE TABLE collect_subscribe_quota (
|
||||
id bigint GENERATED BY DEFAULT AS IDENTITY NOT NULL,
|
||||
tenant_id bigint NOT NULL,
|
||||
user_id bigint NOT NULL,
|
||||
template_code varchar(64) NOT NULL,
|
||||
total integer NOT NULL DEFAULT 0, -- 累计授权次数
|
||||
used integer NOT NULL DEFAULT 0, -- 已消耗次数
|
||||
update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
CONSTRAINT uk_quota_user_tpl UNIQUE (user_id, template_code)
|
||||
);
|
||||
COMMENT ON TABLE collect_subscribe_quota IS '微信订阅消息额度余额';
|
||||
COMMENT ON COLUMN collect_subscribe_quota.total IS '累计授权次数';
|
||||
COMMENT ON COLUMN collect_subscribe_quota.used IS '已消耗次数;余额=total-used,不足时自动降级到服务号或短信';
|
||||
|
||||
CREATE TABLE collect_rule_template (
|
||||
id bigint GENERATED BY DEFAULT AS IDENTITY NOT NULL,
|
||||
tenant_id bigint NOT NULL,
|
||||
template_name varchar(128) NOT NULL,
|
||||
cert_type smallint,
|
||||
form_schema jsonb,
|
||||
materials jsonb,
|
||||
naming_template varchar(256),
|
||||
is_public smallint NOT NULL DEFAULT 0,
|
||||
use_count integer NOT NULL DEFAULT 0,
|
||||
create_by bigint,
|
||||
create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
update_by bigint,
|
||||
update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted smallint DEFAULT 0 NOT NULL,
|
||||
PRIMARY KEY (id)
|
||||
);
|
||||
CREATE INDEX idx_ruletpl_tenant ON collect_rule_template (tenant_id, is_public);
|
||||
COMMENT ON TABLE collect_rule_template IS '规则模板';
|
||||
COMMENT ON COLUMN collect_rule_template.tenant_id IS '0=平台公共模板';
|
||||
COMMENT ON COLUMN collect_rule_template.template_name IS '如 自主认定标准模板';
|
||||
COMMENT ON COLUMN collect_rule_template.cert_type IS '1自主认定 2社会评价 3专项职业能力';
|
||||
COMMENT ON COLUMN collect_rule_template.form_schema IS '表单字段模板';
|
||||
COMMENT ON COLUMN collect_rule_template.materials IS '材料清单模板';
|
||||
|
||||
CREATE TABLE collect_cleanup_job (
|
||||
id bigint GENERATED BY DEFAULT AS IDENTITY NOT NULL,
|
||||
tenant_id bigint NOT NULL,
|
||||
job_name varchar(128) NOT NULL,
|
||||
source_type smallint NOT NULL DEFAULT 1,
|
||||
source_path varchar(512) NOT NULL,
|
||||
target_task_id bigint,
|
||||
naming_template varchar(256) NOT NULL DEFAULT '{姓名}-{证件号}-{材料名}',
|
||||
compress_config jsonb,
|
||||
status smallint NOT NULL DEFAULT 0,
|
||||
progress smallint NOT NULL DEFAULT 0,
|
||||
report_json jsonb,
|
||||
total_files integer, fixed_files integer, failed_files integer,
|
||||
result_path varchar(512),
|
||||
create_by bigint, create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
finish_time timestamp,
|
||||
PRIMARY KEY (id)
|
||||
);
|
||||
CREATE INDEX idx_cleanup_tenant ON collect_cleanup_job (tenant_id, create_time);
|
||||
COMMENT ON TABLE collect_cleanup_job IS '历史数据整理任务';
|
||||
COMMENT ON COLUMN collect_cleanup_job.source_type IS '1上传压缩包 2对象存储目录';
|
||||
COMMENT ON COLUMN collect_cleanup_job.target_task_id IS '结果可导入某任务';
|
||||
COMMENT ON COLUMN collect_cleanup_job.status IS '0排队 1解析中 2处理中 3完成 4失败';
|
||||
COMMENT ON COLUMN collect_cleanup_job.report_json IS '体检报告:命名违规/证件号异常/体积超标/缺失/重复';
|
||||
|
||||
|
||||
-- ============ 1b) 客户确认后新增(FR-509) ============
|
||||
|
||||
CREATE TABLE collect_join_request (
|
||||
id bigint GENERATED BY DEFAULT AS IDENTITY NOT NULL,
|
||||
tenant_id bigint NOT NULL,
|
||||
task_id bigint NOT NULL,
|
||||
real_name varchar(64) NOT NULL,
|
||||
id_card_hash char(64) NOT NULL,
|
||||
id_card_mask varchar(32) NOT NULL,
|
||||
phone_hash char(64),
|
||||
phone_mask varchar(32),
|
||||
org_hint varchar(128),
|
||||
reason varchar(500),
|
||||
status smallint NOT NULL DEFAULT 0,
|
||||
reject_reason varchar(500),
|
||||
roster_id bigint,
|
||||
audit_by bigint,
|
||||
audit_time timestamp,
|
||||
applicant_openid varchar(64) NOT NULL,
|
||||
applicant_uid bigint,
|
||||
source smallint NOT NULL DEFAULT 1,
|
||||
create_by bigint,
|
||||
create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
update_by bigint,
|
||||
update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted smallint DEFAULT 0 NOT NULL,
|
||||
PRIMARY KEY (id)
|
||||
);
|
||||
CREATE UNIQUE INDEX uk_join_task_idcard ON collect_join_request (task_id, id_card_hash)
|
||||
WHERE deleted = 0 AND status IN (0, 1);
|
||||
CREATE INDEX idx_join_task_status ON collect_join_request (task_id, status, create_time);
|
||||
CREATE INDEX idx_join_openid ON collect_join_request (applicant_openid);
|
||||
COMMENT ON TABLE collect_join_request IS '名单加入申请';
|
||||
COMMENT ON COLUMN collect_join_request.real_name IS '申请人自述姓名(明文,业务必需)';
|
||||
COMMENT ON COLUMN collect_join_request.id_card_hash IS 'HMAC,与门禁一致,不存明文';
|
||||
COMMENT ON COLUMN collect_join_request.id_card_mask IS '脱敏展示';
|
||||
COMMENT ON COLUMN collect_join_request.org_hint IS '申请人自述班级(选填,可由管理员修正)';
|
||||
COMMENT ON COLUMN collect_join_request.reason IS '申请说明';
|
||||
COMMENT ON COLUMN collect_join_request.status IS '0待审 1已通过 2已驳回 3已失效(任务截止)';
|
||||
COMMENT ON COLUMN collect_join_request.reject_reason IS '驳回原因(驳回时必填)';
|
||||
COMMENT ON COLUMN collect_join_request.roster_id IS '审批通过后生成的名册项';
|
||||
COMMENT ON COLUMN collect_join_request.applicant_openid IS '用于限流(每任务<=2次)与审批通过后自动绑定';
|
||||
COMMENT ON COLUMN collect_join_request.source IS '1门禁失败页申请 2管理员代录';
|
||||
|
||||
-- ============ 2) 补列 ALTER ============
|
||||
|
||||
ALTER TABLE collect_task ADD COLUMN plan_scope_json jsonb;
|
||||
ALTER TABLE collect_task ADD COLUMN plan_scope_confirmed smallint NOT NULL DEFAULT 0;
|
||||
COMMENT ON COLUMN collect_task.plan_scope_json IS '本期报考范围快照(从名单聚合): {occupations:[{name,worktypes:[{name,levels:[]}]}], confirmed:true}';
|
||||
COMMENT ON COLUMN collect_task.plan_scope_confirmed IS '管理员是否已确认范围(未确认则追加名单不告警)';
|
||||
|
||||
-- 同学号的补齐:官方模板 21 字段中的非身份字段全部建为正式列
|
||||
-- 姓名、身份证号已有独立列(real_name / id_card_no),此处补齐其余 18 个
|
||||
ALTER TABLE collect_roster
|
||||
ADD COLUMN occupation varchar(64), -- 报考职业 G
|
||||
ADD COLUMN worktype varchar(64), -- 报考工种 H
|
||||
ADD COLUMN job_level varchar(32), -- 报考等级 I
|
||||
ADD COLUMN education varchar(32), -- 学历 D
|
||||
ADD COLUMN is_student smallint, -- 是否在校生 N 0否 1是
|
||||
ADD COLUMN school varchar(128), -- 在读学校 O
|
||||
ADD COLUMN major varchar(128), -- 所学专业 P
|
||||
ADD COLUMN enroll_date date, -- 入学时间 Q YYYY-MM-DD
|
||||
ADD COLUMN work_unit varchar(128), -- 工作单位 F 无则填"无"
|
||||
ADD COLUMN is_retake smallint, -- 是否补考 J 0否 1是
|
||||
ADD COLUMN retake_theory varchar(64), -- 补考理论 K
|
||||
ADD COLUMN retake_practice varchar(64), -- 补考实操 L
|
||||
ADD COLUMN retake_overall varchar(64), -- 补考综合 M
|
||||
ADD COLUMN work_years smallint, -- 本职业工作年限 R
|
||||
ADD COLUMN cert_occupation varchar(128), -- 已获证书职业工种 S
|
||||
ADD COLUMN cert_level varchar(32), -- 已获证书等级 T
|
||||
ADD COLUMN cert_no varchar(64), -- 已获证书编号 U
|
||||
ADD COLUMN extras_json jsonb DEFAULT '{}'::jsonb; -- 逃生舱:仅存租户/机构额外追加的字段
|
||||
|
||||
-- 报考范围聚合与按工种统计必需的索引
|
||||
CREATE INDEX idx_roster_scope ON collect_roster (tenant_id, task_id, occupation, worktype, job_level);
|
||||
CREATE INDEX idx_roster_org ON collect_roster (task_id, org_id, deleted);
|
||||
|
||||
COMMENT ON COLUMN collect_roster.occupation IS '报考职业(锁定,由老师确定)';
|
||||
COMMENT ON COLUMN collect_roster.worktype IS '报考工种(锁定;该职业无工种时填"无";专项职业能力为空)';
|
||||
COMMENT ON COLUMN collect_roster.job_level IS '报考等级(锁定;专项职业能力为空)';
|
||||
COMMENT ON COLUMN collect_roster.education IS '学历(下拉11项)';
|
||||
COMMENT ON COLUMN collect_roster.is_student IS '是否在校生 0否 1是';
|
||||
COMMENT ON COLUMN collect_roster.school IS '在读学校(在校生=是时必填)';
|
||||
COMMENT ON COLUMN collect_roster.major IS '所学专业(在校生=是时必填)';
|
||||
COMMENT ON COLUMN collect_roster.enroll_date IS '入学时间(在校生=是时必填,格式YYYY-MM-DD)';
|
||||
COMMENT ON COLUMN collect_roster.work_unit IS '工作单位(没有填"无")';
|
||||
COMMENT ON COLUMN collect_roster.is_retake IS '是否补考 0否 1是';
|
||||
COMMENT ON COLUMN collect_roster.retake_theory IS '补考理论(级联)';
|
||||
COMMENT ON COLUMN collect_roster.retake_practice IS '补考实操(级联)';
|
||||
COMMENT ON COLUMN collect_roster.retake_overall IS '补考综合(级联)';
|
||||
COMMENT ON COLUMN collect_roster.work_years IS '本职业工作年限(仅阿拉伯数字)';
|
||||
COMMENT ON COLUMN collect_roster.cert_occupation IS '已获证书职业工种(选填)';
|
||||
COMMENT ON COLUMN collect_roster.cert_level IS '已获证书等级(选填)';
|
||||
COMMENT ON COLUMN collect_roster.cert_no IS '已获证书编号(选填)';
|
||||
COMMENT ON COLUMN collect_roster.extras_json IS '租户/机构额外追加的字段(逃生舱,不参与核心统计)';
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,129 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>填报通 · 原型索引</title>
|
||||
<style>
|
||||
:root{
|
||||
--ink:#1b2430; --ink2:#4a5768; --ink3:#8b95a3;
|
||||
--paper:#f4f1ea; --paper2:#fbfaf7; --card:#fff;
|
||||
--line:#e0dbd0; --line2:#cfc8b8;
|
||||
--seal:#c0392b; --seal-soft:#f9ece9;
|
||||
--jade:#2f6b4f; --jade-soft:#e8f0eb;
|
||||
--font-display:"Songti SC","STSong","SimSun",serif;
|
||||
--font-body:"PingFang SC","Hiragino Sans GB","Microsoft YaHei",sans-serif;
|
||||
--font-mono:"SF Mono","JetBrains Mono",Consolas,monospace;
|
||||
}
|
||||
*{box-sizing:border-box;margin:0;padding:0}
|
||||
body{
|
||||
font-family:var(--font-body);color:var(--ink);background:var(--paper);
|
||||
background-image:linear-gradient(rgba(207,200,184,.28) 1px,transparent 1px),
|
||||
linear-gradient(90deg,rgba(207,200,184,.28) 1px,transparent 1px);
|
||||
background-size:26px 26px;-webkit-font-smoothing:antialiased;
|
||||
display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:100vh;padding:48px 24px;
|
||||
}
|
||||
.ico{width:1em;height:1em;display:inline-block;vertical-align:-.14em;fill:none;stroke:currentColor;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}
|
||||
.head{text-align:center;margin-bottom:38px}
|
||||
.kicker{font-size:11px;letter-spacing:.24em;text-transform:uppercase;color:var(--ink3);font-family:var(--font-mono);margin-bottom:12px}
|
||||
h1{font-family:var(--font-display);font-size:36px;font-weight:600;letter-spacing:.05em}
|
||||
h1 em{font-style:normal;color:var(--seal)}
|
||||
.head p{font-size:13.5px;color:var(--ink2);line-height:1.8;margin-top:12px}
|
||||
.cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(272px,1fr));gap:20px;max-width:1040px;width:100%}
|
||||
a.card{
|
||||
display:block;text-decoration:none;color:inherit;background:var(--card);
|
||||
border:1px solid var(--line);border-radius:16px;padding:24px;transition:.26s;position:relative;overflow:hidden;
|
||||
}
|
||||
a.card::before{
|
||||
content:"";position:absolute;inset:0;opacity:0;transition:.26s;
|
||||
background:repeating-linear-gradient(45deg,transparent,transparent 9px,rgba(192,57,43,.03) 9px,rgba(192,57,43,.03) 18px);
|
||||
}
|
||||
a.card:hover{transform:translateY(-5px);border-color:var(--line2);box-shadow:0 22px 40px -18px rgba(27,36,48,.34)}
|
||||
a.card:hover::before{opacity:1}
|
||||
a.card .ic{
|
||||
width:46px;height:46px;border-radius:12px;display:flex;align-items:center;justify-content:center;font-size:23px;
|
||||
background:var(--paper);border:1px solid var(--line);color:var(--ink2);margin-bottom:16px;position:relative;
|
||||
}
|
||||
a.card.tch .ic{background:#eef1f4;border-color:var(--line2);color:var(--ink)}
|
||||
a.card.adm .ic{background:var(--jade-soft);border-color:#cfe0d6;color:var(--jade)}
|
||||
a.card h2{font-family:var(--font-display);font-size:20px;font-weight:600;letter-spacing:.04em;position:relative}
|
||||
a.card .meta{font-size:11.5px;font-family:var(--font-mono);color:var(--seal);letter-spacing:.06em;margin-top:7px;position:relative}
|
||||
a.card ul{list-style:none;margin-top:14px;position:relative}
|
||||
a.card li{font-size:12.5px;color:var(--ink2);line-height:1.75;padding-left:15px;position:relative}
|
||||
a.card li::before{content:"";position:absolute;left:0;top:9px;width:5px;height:5px;border-radius:50%;background:var(--line2)}
|
||||
a.card .go{
|
||||
display:flex;align-items:center;gap:6px;margin-top:18px;font-size:12.5px;color:var(--ink3);position:relative;
|
||||
}
|
||||
a.card:hover .go{color:var(--seal)}
|
||||
.foot{margin-top:40px;font-size:11.5px;color:var(--ink3);text-align:center;line-height:1.9;font-family:var(--font-mono)}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<svg width="0" height="0" style="position:absolute" aria-hidden="true">
|
||||
<symbol id="i-user" viewBox="0 0 24 24"><circle cx="12" cy="8.4" r="3.6"/><path d="M5 19.6c0-3.4 3.1-5.6 7-5.6s7 2.2 7 5.6"/></symbol>
|
||||
<symbol id="i-users" viewBox="0 0 24 24"><circle cx="9" cy="8.4" r="3.4"/><path d="M3 19.2c0-3.1 2.7-5.2 6-5.2s6 2.1 6 5.2"/><path d="M16.2 5.6a3.4 3.4 0 0 1 0 6.4M17.6 14.4c2 .6 3.4 2.3 3.4 4.8"/></symbol>
|
||||
<symbol id="i-arrow" viewBox="0 0 24 24"><path d="M5 12h13M13.5 7.5L18 12l-4.5 4.5"/></symbol>
|
||||
<symbol id="i-desktop" viewBox="0 0 24 24"><rect x="2.8" y="4.2" width="18.4" height="12.4" rx="1.8"/><path d="M8.5 20.2h7M12 16.6v3.6"/></symbol>
|
||||
</svg>
|
||||
|
||||
<div class="head">
|
||||
<div class="kicker">Prototype Index · V1.0</div>
|
||||
<h1>填报通 · <em>交互原型</em></h1>
|
||||
<p>职业技能等级认定报名材料收集系统 · 两端原型<br>设计语言:「档案室 / 电子卷宗」</p>
|
||||
</div>
|
||||
|
||||
<div class="cards">
|
||||
<a class="card" href="student-app.html">
|
||||
<div class="ic"><svg class="ico"><use href="#i-user"/></svg></div>
|
||||
<h2>学生填报端</h2>
|
||||
<div class="meta">11 SCREENS</div>
|
||||
<ul>
|
||||
<li>门禁核验 · 形近字确认 · 统一失败提示</li>
|
||||
<li><b>申请加入名单</b>(门禁失败后的受控出口)</li>
|
||||
<li>填报主页(字段三类规则:锁定/可改/材料)</li>
|
||||
<li>材料来源三通道(聊天记录/相册/文件,无拍照)</li>
|
||||
<li>上传即规范化 · 压缩率可视化 · 传错位置提示</li>
|
||||
<li>提交汇总 · 审核进度时间轴</li>
|
||||
</ul>
|
||||
<div class="go">打开原型 <svg class="ico"><use href="#i-arrow"/></svg></div>
|
||||
</a>
|
||||
|
||||
<a class="card tch" href="teacher-app.html">
|
||||
<div class="ic"><svg class="ico"><use href="#i-users"/></svg></div>
|
||||
<h2>班主任端</h2>
|
||||
<div class="meta">9 SCREENS</div>
|
||||
<ul>
|
||||
<li>电子花名册看板(一屏扫完 28 人)</li>
|
||||
<li>待审列表 · 异常优先 · 材料状态缩略条</li>
|
||||
<li>学生详情(给出判断依据)</li>
|
||||
<li>批量通过(红黄一律排除并展示)</li>
|
||||
<li><b>右滑通过 + 二次确认</b>(驳回须点按钮)</li>
|
||||
<li>催报(额度透明)· 消息 · 代传材料</li>
|
||||
</ul>
|
||||
<div class="go">打开原型 <svg class="ico"><use href="#i-arrow"/></svg></div>
|
||||
</a>
|
||||
|
||||
<a class="card adm" href="admin-web.html">
|
||||
<div class="ic"><svg class="ico"><use href="#i-desktop"/></svg></div>
|
||||
<h2>管理端(PC)</h2>
|
||||
<div class="meta">11 SCREENS · 1180px</div>
|
||||
<ul>
|
||||
<li>工作台(结论优先 + 待办排序)</li>
|
||||
<li><b>命名规则试跑</b>——发布前的唯一闸门</li>
|
||||
<li>名单导入(表头定位可视化)</li>
|
||||
<li>报考范围确认(聚合 + 一致性体检)</li>
|
||||
<li>数据看板 · 终审 · 导出中心(目录树预览)</li>
|
||||
<li><b>表单设计器 · 材料规则配置 · 历史数据整理器</b></li>
|
||||
<li><b>名单加入申请审批</b>(客户确认新增)</li>
|
||||
<li>支持 100% / 82% / 66% 缩放查看</li>
|
||||
</ul>
|
||||
<div class="go">打开原型 <svg class="ico"><use href="#i-arrow"/></svg></div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="foot">
|
||||
docs/prototype/ · 单文件自包含 · 零外部依赖 · 无 emoji<br>
|
||||
数据取自真实批次「24中技数控1班」28 名学生
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,909 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>填报通 · 学生端交互原型</title>
|
||||
<style>
|
||||
:root{
|
||||
--ink:#1b2430; --ink2:#4a5768; --ink3:#8b95a3;
|
||||
--paper:#f4f1ea; --paper2:#fbfaf7; --card:#ffffff;
|
||||
--line:#e0dbd0; --line2:#cfc8b8;
|
||||
--seal:#c0392b; --seal-soft:#f9ece9;
|
||||
--jade:#2f6b4f; --jade-soft:#e8f0eb;
|
||||
--amber:#a9761f; --amber-soft:#f8f1de;
|
||||
--font-display:"Songti SC","STSong","SimSun","Noto Serif SC",serif;
|
||||
--font-body:"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Source Han Sans SC",sans-serif;
|
||||
--font-mono:"SF Mono","JetBrains Mono","Cascadia Mono",Consolas,monospace;
|
||||
--r:14px;
|
||||
}
|
||||
*{box-sizing:border-box;margin:0;padding:0}
|
||||
html{scroll-behavior:smooth}
|
||||
body{
|
||||
font-family:var(--font-body);
|
||||
color:var(--ink);
|
||||
background:var(--paper);
|
||||
background-image:
|
||||
linear-gradient(rgba(207,200,184,.28) 1px,transparent 1px),
|
||||
linear-gradient(90deg,rgba(207,200,184,.28) 1px,transparent 1px);
|
||||
background-size:26px 26px;
|
||||
-webkit-font-smoothing:antialiased;
|
||||
padding:0 0 80px;
|
||||
}
|
||||
/* ── 图标系统:统一描边、随字号缩放 ── */
|
||||
.ico{
|
||||
width:1em;height:1em;flex:none;display:inline-block;vertical-align:-.14em;
|
||||
fill:none;stroke:currentColor;stroke-width:1.7;
|
||||
stroke-linecap:round;stroke-linejoin:round;
|
||||
}
|
||||
.ico.lg{width:1.35em;height:1.35em;vertical-align:-.28em}
|
||||
.ico.xl{width:1.9em;height:1.9em;vertical-align:-.42em}
|
||||
/* ── 页头 ── */
|
||||
.masthead{
|
||||
padding:38px 40px 26px;
|
||||
border-bottom:1px solid var(--line2);
|
||||
background:linear-gradient(180deg,var(--paper2),transparent);
|
||||
position:relative;
|
||||
}
|
||||
.masthead::after{content:"";position:absolute;left:0;right:0;bottom:3px;height:1px;background:var(--line)}
|
||||
.kicker{font-size:11px;letter-spacing:.22em;text-transform:uppercase;color:var(--ink3);font-family:var(--font-mono);margin-bottom:10px}
|
||||
h1{font-family:var(--font-display);font-size:32px;font-weight:600;letter-spacing:.04em;line-height:1.25}
|
||||
h1 em{font-style:normal;color:var(--seal)}
|
||||
.sub{margin-top:10px;color:var(--ink2);font-size:13.5px;line-height:1.75;max-width:780px}
|
||||
.sub code{font-family:var(--font-mono);font-size:12px;background:#fff;border:1px solid var(--line);border-radius:4px;padding:1px 5px}
|
||||
.legend{display:flex;flex-wrap:wrap;gap:18px;margin-top:18px;font-size:12px;color:var(--ink2)}
|
||||
.legend span{display:flex;align-items:center;gap:7px}
|
||||
.dot{width:9px;height:9px;border-radius:50%;flex:none}
|
||||
.dot.lock{background:var(--ink)} .dot.edit{background:var(--amber)}
|
||||
.dot.doc{background:var(--jade)} .dot.seal{background:var(--seal)}
|
||||
/* ── 画廊 ── */
|
||||
.gallery{
|
||||
display:grid;grid-template-columns:repeat(auto-fit,minmax(348px,1fr));
|
||||
gap:36px 30px;padding:44px 40px;max-width:1560px;margin:0 auto;
|
||||
}
|
||||
figure{margin:0;display:flex;flex-direction:column;align-items:center}
|
||||
figcaption{margin-top:16px;max-width:340px;text-align:center;font-size:12.5px;line-height:1.7;color:var(--ink2)}
|
||||
figcaption b{display:block;font-family:var(--font-display);font-size:15px;color:var(--ink);letter-spacing:.03em;margin-bottom:5px;font-weight:600}
|
||||
figcaption .tag{
|
||||
display:inline-block;font-family:var(--font-mono);font-size:10px;letter-spacing:.1em;
|
||||
color:var(--seal);border:1px solid var(--seal);border-radius:3px;padding:1px 6px;margin-bottom:7px;opacity:.85;
|
||||
}
|
||||
/* ── 手机外壳 ── */
|
||||
.device{
|
||||
width:348px;height:752px;flex:none;background:#0e1319;border-radius:44px;padding:11px;
|
||||
box-shadow:0 26px 48px -18px rgba(27,36,48,.42),0 2px 6px rgba(27,36,48,.14);
|
||||
position:relative;transition:transform .4s cubic-bezier(.2,.8,.3,1),box-shadow .4s;
|
||||
}
|
||||
.device:hover{transform:translateY(-6px);box-shadow:0 36px 62px -20px rgba(27,36,48,.5)}
|
||||
.screen{width:100%;height:100%;background:var(--paper2);border-radius:34px;overflow:hidden;display:flex;flex-direction:column;position:relative}
|
||||
/* 状态栏 */
|
||||
.statusbar{
|
||||
height:40px;flex:none;display:flex;align-items:center;justify-content:space-between;
|
||||
padding:0 20px;font-size:12px;font-family:var(--font-mono);color:var(--ink);font-weight:500;
|
||||
}
|
||||
.statusbar .icons{display:flex;gap:5px;align-items:center;opacity:.85;font-size:13px}
|
||||
.bat{width:20px;height:10px;border:1.2px solid var(--ink);border-radius:2.5px;position:relative;flex:none}
|
||||
.bat::after{content:"";position:absolute;left:1.5px;top:1.5px;bottom:1.5px;width:11px;background:var(--ink);border-radius:1px}
|
||||
.bat::before{content:"";position:absolute;right:-3px;top:3px;width:2px;height:4px;background:var(--ink);border-radius:0 1px 1px 0}
|
||||
/* 导航条 */
|
||||
.navbar{
|
||||
flex:none;height:46px;display:flex;align-items:center;gap:10px;padding:0 18px;
|
||||
border-bottom:1px solid var(--line);font-size:15px;font-weight:600;letter-spacing:.02em;background:var(--paper2);
|
||||
}
|
||||
.navbar .back{width:17px;height:17px;flex:none;opacity:.55}
|
||||
.navbar .back .ico{stroke-width:2.4}
|
||||
/* 内容区 */
|
||||
.body{flex:1;overflow-y:auto;padding:18px;scrollbar-width:thin}
|
||||
.body::-webkit-scrollbar{width:4px}
|
||||
.body::-webkit-scrollbar-thumb{background:var(--line2);border-radius:2px}
|
||||
/* ── 通用元件 ── */
|
||||
.panel{background:var(--card);border:1px solid var(--line);border-radius:var(--r);padding:15px;margin-bottom:13px}
|
||||
.panel.flush{padding:0;overflow:hidden}
|
||||
.ptitle{
|
||||
display:flex;align-items:center;gap:7px;font-size:12px;color:var(--ink3);
|
||||
letter-spacing:.06em;margin-bottom:11px;font-family:var(--font-mono);
|
||||
}
|
||||
.ptitle .badge{
|
||||
font-size:10px;padding:1.5px 6px;border-radius:3px;letter-spacing:.04em;
|
||||
font-family:var(--font-body);font-weight:600;display:inline-flex;align-items:center;gap:4px;
|
||||
}
|
||||
.badge .ico{font-size:11px}
|
||||
.badge.lock{background:#eef1f4;color:var(--ink2);border:1px solid var(--line2)}
|
||||
.badge.edit{background:var(--amber-soft);color:var(--amber);border:1px solid #e8d9b5}
|
||||
.badge.doc{background:var(--jade-soft);color:var(--jade);border:1px solid #cfe0d6}
|
||||
/* 数据行 */
|
||||
.row{display:flex;justify-content:space-between;align-items:baseline;gap:12px;padding:8px 0;border-bottom:1px dashed var(--line)}
|
||||
.row:last-child{border-bottom:0}
|
||||
.row .k{font-size:13px;color:var(--ink2);flex:none}
|
||||
.row .v{font-size:13.5px;color:var(--ink);text-align:right;font-weight:500;word-break:break-all}
|
||||
.row .v.mono{font-family:var(--font-mono);font-size:12.5px;letter-spacing:.02em}
|
||||
.row .v.dim{color:var(--ink3);font-weight:400}
|
||||
/* 按钮 */
|
||||
.btn{
|
||||
display:block;width:100%;border:0;border-radius:11px;
|
||||
font-family:var(--font-body);font-size:15.5px;font-weight:600;letter-spacing:.05em;
|
||||
padding:14px;cursor:pointer;transition:.22s;text-align:center;
|
||||
}
|
||||
.btn.primary{background:var(--ink);color:#fff}
|
||||
.btn.primary:hover{background:#2a3646}
|
||||
.btn.seal{background:var(--seal);color:#fff}
|
||||
.btn.seal:hover{background:#a93022}
|
||||
.btn.ghost{background:transparent;color:var(--ink2);border:1px solid var(--line2)}
|
||||
.btn.ghost:hover{background:#fff;color:var(--ink)}
|
||||
.btn:disabled{background:#c9cdd3;color:#8f959d;cursor:not-allowed}
|
||||
.btnrow{display:flex;gap:10px}
|
||||
.btnrow .btn{margin:0}
|
||||
.foot{flex:none;padding:13px 18px 20px;border-top:1px solid var(--line);background:var(--paper2)}
|
||||
/* 输入框 */
|
||||
.field{margin-bottom:15px}
|
||||
.field label{display:block;font-size:12.5px;color:var(--ink2);margin-bottom:7px;letter-spacing:.04em;font-weight:500}
|
||||
.field label .req{color:var(--seal);margin-left:3px}
|
||||
.input{
|
||||
width:100%;height:52px;border:1.5px solid var(--line2);border-radius:11px;background:var(--card);
|
||||
padding:0 15px;font-size:16px;color:var(--ink);font-family:var(--font-body);letter-spacing:.02em;transition:.2s;
|
||||
}
|
||||
.input.mono{font-family:var(--font-mono);letter-spacing:.13em;font-size:16.5px}
|
||||
.input:focus{outline:0;border-color:var(--ink);box-shadow:0 0 0 3px rgba(27,36,48,.07)}
|
||||
.input::placeholder{color:#b3b9c1;letter-spacing:normal;font-size:15px}
|
||||
/* 申请加入名单入口(次要路径,视觉上不与主操作竞争) */
|
||||
.apply-entry{border-top:1px dashed var(--line2);padding-top:12px;margin-bottom:6px;text-align:center}
|
||||
.apply-entry .ae-t{font-size:12px;color:var(--ink3);margin-bottom:9px}
|
||||
.apply-entry .ae-btn{width:100%;justify-content:center}
|
||||
.apply-hint{font-size:11.5px;color:var(--ink3);line-height:1.7;margin-top:9px;text-align:left}
|
||||
.stepbox{display:flex;gap:10px;margin-bottom:15px}
|
||||
.stepbox .sb{flex:1;display:flex;align-items:center;gap:8px;font-size:11.5px;color:var(--ink3)}
|
||||
.stepbox .sb i{width:19px;height:19px;border-radius:50%;background:var(--line);color:var(--ink3);display:flex;align-items:center;justify-content:center;font-style:normal;font-size:11px;font-family:var(--font-mono);flex:none}
|
||||
.stepbox .sb.on{color:var(--ink);font-weight:600}
|
||||
.stepbox .sb.on i{background:var(--ink);color:#fff}
|
||||
.hint{font-size:11.5px;color:var(--ink3);margin-top:6px;line-height:1.6}
|
||||
.hint.warn{color:var(--amber)}
|
||||
/* 页头说明块 */
|
||||
.pagehead{
|
||||
padding:22px 20px 20px;text-align:center;
|
||||
background:linear-gradient(180deg,#fff,var(--paper2));border-bottom:1px solid var(--line);
|
||||
}
|
||||
.pagehead .glyph{
|
||||
width:52px;height:52px;margin:0 auto 13px;border-radius:14px;
|
||||
display:flex;align-items:center;justify-content:center;font-size:22px;
|
||||
background:var(--seal-soft);border:1px solid #eed5cf;color:var(--seal);
|
||||
}
|
||||
.pagehead .glyph.ok{background:var(--jade-soft);border-color:#cfe0d6;color:var(--jade)}
|
||||
.pagehead .glyph.gray{background:#eef1f4;border-color:var(--line2);color:var(--ink3)}
|
||||
.pagehead h2{font-family:var(--font-display);font-size:21px;font-weight:600;letter-spacing:.05em}
|
||||
.pagehead p{font-size:12.5px;color:var(--ink2);line-height:1.75;margin-top:9px;padding:0 6px}
|
||||
.pagehead p b{color:var(--seal);font-weight:600}
|
||||
/* 任务条 */
|
||||
.taskbar{padding:13px 18px;background:#fff;border-bottom:1px solid var(--line)}
|
||||
.taskbar .tname{font-size:14.5px;font-weight:600;letter-spacing:.02em}
|
||||
.taskbar .tmeta{font-size:11.5px;color:var(--ink3);margin-top:5px;display:flex;gap:12px;font-family:var(--font-mono)}
|
||||
/* 进度环 */
|
||||
.ring{width:52px;height:52px;border-radius:50%;flex:none;position:relative;background:conic-gradient(var(--jade) calc(var(--p)*1%),#e8e4db 0)}
|
||||
.ring::after{content:"";position:absolute;inset:5px;border-radius:50%;background:var(--card)}
|
||||
.ring span{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-size:12.5px;font-family:var(--font-mono);font-weight:600;color:var(--jade);z-index:1}
|
||||
/* 材料项 */
|
||||
.mat{display:flex;align-items:center;gap:13px;padding:14px 15px;border-bottom:1px solid var(--line);cursor:pointer;transition:.2s;background:var(--card)}
|
||||
.mat:last-child{border-bottom:0}
|
||||
.mat:hover{background:#fdfcfa}
|
||||
.mat .thumb{
|
||||
width:46px;height:46px;border-radius:9px;flex:none;background:var(--paper);border:1px solid var(--line);
|
||||
display:flex;align-items:center;justify-content:center;font-size:21px;color:var(--ink2);
|
||||
}
|
||||
.mat .thumb.ok{background:var(--jade-soft);border-color:#cfe0d6;color:var(--jade)}
|
||||
.mat .info{flex:1;min-width:0}
|
||||
.mat .mname{font-size:14px;font-weight:600;letter-spacing:.02em;display:flex;align-items:center;gap:7px}
|
||||
.mat .mdesc{font-size:11px;color:var(--ink3);margin-top:4px;line-height:1.55;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
||||
.mat .mdesc.done{font-family:var(--font-mono);color:var(--jade)}
|
||||
.mat .state{flex:none;font-size:11px;padding:3px 9px;border-radius:20px;font-weight:600;letter-spacing:.03em}
|
||||
.state.pass{background:var(--jade-soft);color:var(--jade)}
|
||||
.state.warn{background:var(--amber-soft);color:var(--amber)}
|
||||
.state.none{background:#eef1f4;color:var(--ink3)}
|
||||
.state.crit{background:var(--seal-soft);color:var(--seal)}
|
||||
/* 弹层 */
|
||||
.sheet{
|
||||
position:absolute;left:0;right:0;bottom:0;z-index:20;background:var(--paper2);
|
||||
border-radius:22px 22px 34px 34px;box-shadow:0 -14px 40px -10px rgba(27,36,48,.28);
|
||||
padding:9px 16px 22px;animation:sheetUp .34s cubic-bezier(.2,.85,.3,1);
|
||||
}
|
||||
@keyframes sheetUp{from{transform:translateY(102%)}to{transform:translateY(0)}}
|
||||
.sheet .grab{width:38px;height:4px;border-radius:2px;background:var(--line2);margin:0 auto 15px}
|
||||
.sheet h3{font-size:14.5px;font-weight:600;margin-bottom:4px;letter-spacing:.03em}
|
||||
.sheet .stip{font-size:11.5px;color:var(--ink3);margin-bottom:14px;line-height:1.6}
|
||||
.scard{
|
||||
display:flex;align-items:center;gap:13px;padding:14px;background:var(--card);
|
||||
border:1.5px solid var(--line);border-radius:12px;margin-bottom:9px;cursor:pointer;transition:.2s;
|
||||
}
|
||||
.scard:hover{border-color:var(--ink);transform:translateX(2px)}
|
||||
.scard.rec{border-color:var(--seal);background:var(--seal-soft)}
|
||||
.scard .sicon{
|
||||
width:40px;height:40px;border-radius:10px;background:var(--paper);flex:none;border:1px solid var(--line);
|
||||
display:flex;align-items:center;justify-content:center;font-size:20px;color:var(--ink2);
|
||||
}
|
||||
.scard.rec .sicon{background:#fff;border-color:#eed5cf;color:var(--seal)}
|
||||
.scard .stxt{flex:1}
|
||||
.scard .st{font-size:14px;font-weight:600;letter-spacing:.02em;display:flex;align-items:center;gap:7px}
|
||||
.scard .sd{font-size:11px;color:var(--ink3);margin-top:3px;line-height:1.5}
|
||||
.recommend{font-size:9.5px;background:var(--seal);color:#fff;padding:1.5px 6px;border-radius:3px;letter-spacing:.06em;font-weight:600}
|
||||
.mask{position:absolute;inset:0;background:rgba(27,36,48,.42);z-index:15}
|
||||
/* ── 文件处理卡(核心)── */
|
||||
.filecard{
|
||||
background:var(--card);border:1px solid var(--line);border-radius:var(--r);
|
||||
padding:16px;margin-bottom:13px;position:relative;overflow:hidden;
|
||||
}
|
||||
.filecard::before{
|
||||
content:"";position:absolute;inset:0;pointer-events:none;
|
||||
background:repeating-linear-gradient(45deg,transparent,transparent 9px,rgba(192,57,43,.028) 9px,rgba(192,57,43,.028) 18px);
|
||||
}
|
||||
.fc-label{font-size:10.5px;font-family:var(--font-mono);letter-spacing:.12em;color:var(--ink3);margin-bottom:7px}
|
||||
.fname{font-family:var(--font-mono);font-size:12.5px;line-height:1.65;word-break:break-all;letter-spacing:.01em}
|
||||
.fname.old{color:var(--ink3);position:relative;display:inline-block}
|
||||
.fname.old .strike{
|
||||
position:absolute;left:-1px;right:-1px;top:52%;height:1.5px;background:var(--seal);
|
||||
transform-origin:left;transform:scaleX(0);animation:draw .5s .5s cubic-bezier(.6,0,.3,1) forwards;
|
||||
}
|
||||
@keyframes draw{to{transform:scaleX(1)}}
|
||||
.fname.old .fix{background:rgba(192,57,43,.14);border-bottom:1.5px solid var(--seal);border-radius:2px;padding:0 1px}
|
||||
.fc-arrow{display:flex;align-items:center;gap:9px;margin:13px 0;font-size:11.5px;color:var(--jade);font-weight:600;letter-spacing:.06em}
|
||||
.fc-arrow .line{flex:1;height:1px;background:linear-gradient(90deg,var(--seal),var(--jade));opacity:.4}
|
||||
.fname.new{color:var(--ink);font-weight:500}
|
||||
.fname.new .ok{background:var(--jade-soft);border-bottom:1.5px solid var(--jade);border-radius:2px;padding:0 1px}
|
||||
/* 印章 */
|
||||
.stamp{
|
||||
position:absolute;right:13px;top:13px;width:60px;height:60px;border-radius:50%;
|
||||
border:2.2px solid var(--seal);color:var(--seal);
|
||||
display:flex;align-items:center;justify-content:center;
|
||||
font-family:var(--font-display);font-size:12px;font-weight:700;letter-spacing:.06em;
|
||||
transform:rotate(-13deg) scale(0);opacity:.82;
|
||||
animation:stampIn .5s 1.05s cubic-bezier(.3,1.5,.5,1) forwards;
|
||||
text-align:center;line-height:1.15;padding:4px;
|
||||
background:radial-gradient(circle,rgba(192,57,43,.05),transparent 70%);
|
||||
}
|
||||
.stamp.jade{border-color:var(--jade);color:var(--jade);background:radial-gradient(circle,rgba(47,107,79,.05),transparent 70%)}
|
||||
@keyframes stampIn{
|
||||
0%{transform:rotate(-13deg) scale(2.4);opacity:0}
|
||||
60%{transform:rotate(-13deg) scale(.92);opacity:.9}
|
||||
100%{transform:rotate(-13deg) scale(1);opacity:.82}
|
||||
}
|
||||
/* 体积对比 */
|
||||
.vol{display:flex;align-items:center;gap:10px;margin-top:5px;font-family:var(--font-mono);font-size:12px}
|
||||
.vol .from{color:var(--ink3);text-decoration:line-through;text-decoration-color:var(--seal)}
|
||||
.vol .to{color:var(--jade);font-weight:600;font-size:13.5px}
|
||||
.vol .pct{margin-left:auto;font-size:10.5px;background:var(--jade-soft);color:var(--jade);padding:2px 8px;border-radius:20px;font-weight:600}
|
||||
/* 校验条目 */
|
||||
.vitem{display:flex;gap:9px;align-items:flex-start;padding:9px 0;border-top:1px dashed var(--line);font-size:12.5px;line-height:1.6}
|
||||
.vitem .vi{flex:none;font-size:15px;margin-top:1px;color:var(--ink3)}
|
||||
.vitem .vi.ok{color:var(--jade)} .vitem .vi.work{color:var(--seal)} .vitem .vi.wait{color:var(--line2)}
|
||||
.vitem .vt{flex:1;color:var(--ink2)}
|
||||
.vitem .vt b{color:var(--ink);font-weight:600}
|
||||
/* 时间轴 */
|
||||
.tl{padding-left:6px}
|
||||
.tl .node{display:flex;gap:13px;position:relative;padding-bottom:22px}
|
||||
.tl .node:last-child{padding-bottom:0}
|
||||
.tl .node::before{content:"";position:absolute;left:9px;top:22px;bottom:0;width:1.5px;background:var(--line)}
|
||||
.tl .node:last-child::before{display:none}
|
||||
.tl .nd{
|
||||
width:19px;height:19px;border-radius:50%;flex:none;z-index:1;
|
||||
display:flex;align-items:center;justify-content:center;font-size:10px;color:#fff;
|
||||
background:var(--line2);font-family:var(--font-mono);
|
||||
}
|
||||
.tl .nd .ico{font-size:11px;stroke-width:2.6}
|
||||
.tl .nd.done{background:var(--jade)}
|
||||
.tl .nd.now{background:var(--seal);box-shadow:0 0 0 4px var(--seal-soft)}
|
||||
.tl .nc{flex:1;padding-top:1px}
|
||||
.tl .nt{font-size:13.5px;font-weight:600;letter-spacing:.02em}
|
||||
.tl .nt.dim{color:var(--ink3);font-weight:400}
|
||||
.tl .ns{font-size:11.5px;color:var(--ink3);margin-top:4px;line-height:1.6}
|
||||
/* 提醒条 */
|
||||
.notice{display:flex;gap:10px;padding:12px 14px;border-radius:11px;font-size:12.5px;line-height:1.65;margin-bottom:13px;align-items:flex-start}
|
||||
.notice .ni{flex:none;font-size:16px;margin-top:1px}
|
||||
.notice.crit{background:var(--seal-soft);border:1px solid #eed5cf;color:#8e2d20}
|
||||
.notice.warn{background:var(--amber-soft);border:1px solid #e8d9b5;color:#7d5815}
|
||||
.notice.info{background:#eef1f4;border:1px solid var(--line2);color:var(--ink2)}
|
||||
.notice.ok{background:var(--jade-soft);border:1px solid #cfe0d6;color:#245341}
|
||||
.notice b{font-weight:600}
|
||||
/* 小链接 */
|
||||
.link{font-size:12px;color:var(--ink3);text-decoration:underline;text-underline-offset:3px;cursor:pointer}
|
||||
.link:hover{color:var(--seal)}
|
||||
.center-note{text-align:center;font-size:11.5px;color:var(--ink3);margin-top:14px;line-height:1.7}
|
||||
/* 说明区 */
|
||||
.apron{max-width:1560px;margin:0 auto;padding:0 40px}
|
||||
.apron h3{font-family:var(--font-display);font-size:19px;letter-spacing:.05em;margin:44px 0 16px;padding-bottom:10px;border-bottom:1px solid var(--line2)}
|
||||
.aside-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(290px,1fr));gap:20px}
|
||||
.aside-card{background:var(--card);border:1px solid var(--line);border-radius:var(--r);padding:17px;font-size:12.5px;line-height:1.8;color:var(--ink2)}
|
||||
.aside-card h4{font-size:13.5px;color:var(--ink);margin-bottom:9px;letter-spacing:.03em;display:flex;align-items:center;gap:8px;font-weight:600}
|
||||
.aside-card h4 .n{font-family:var(--font-mono);font-size:10.5px;color:var(--seal);border:1px solid var(--seal);border-radius:3px;padding:0 5px}
|
||||
.aside-card code{font-family:var(--font-mono);font-size:11px;background:var(--paper);border:1px solid var(--line);border-radius:3px;padding:1px 4px}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- ══ 内联 SVG 图标库(线性 · 1.7 描边 · currentColor)══ -->
|
||||
<svg width="0" height="0" style="position:absolute" aria-hidden="true">
|
||||
<symbol id="i-back" viewBox="0 0 24 24"><path d="M15 18l-6-6 6-6"/></symbol>
|
||||
<symbol id="i-lock" viewBox="0 0 24 24"><rect x="4.5" y="10.5" width="15" height="10" rx="2.2"/><path d="M8 10.5V7.5a4 4 0 0 1 8 0v3"/><path d="M12 14.5v2.5"/></symbol>
|
||||
<symbol id="i-pencil" viewBox="0 0 24 24"><path d="M4.5 19.5h3.2L18.4 8.8a2.3 2.3 0 0 0-3.2-3.2L4.5 16.3z"/><path d="M14.2 6.6l3.2 3.2"/></symbol>
|
||||
<symbol id="i-file" viewBox="0 0 24 24"><path d="M13.5 3.5H7A1.8 1.8 0 0 0 5.2 5.3v13.4A1.8 1.8 0 0 0 7 20.5h10a1.8 1.8 0 0 0 1.8-1.8V9z"/><path d="M13.5 3.5V9h5.3"/></symbol>
|
||||
<symbol id="i-image" viewBox="0 0 24 24"><rect x="3.5" y="4.5" width="17" height="15" rx="2.2"/><circle cx="8.8" cy="9.8" r="1.6"/><path d="M4 17.5l4.5-4.5 3.5 3.5 3-3 4.5 4.5"/></symbol>
|
||||
<symbol id="i-idcard" viewBox="0 0 24 24"><rect x="2.5" y="5" width="19" height="14" rx="2.2"/><circle cx="8.5" cy="11.4" r="2.1"/><path d="M5.2 16.4c.6-1.5 1.8-2.3 3.3-2.3s2.7.8 3.3 2.3"/><path d="M14.5 10.2h4.3M14.5 13.6h4.3"/></symbol>
|
||||
<symbol id="i-stamp" viewBox="0 0 24 24"><circle cx="12" cy="8.8" r="5.4"/><path d="M9.4 13.4L8 20.5h8l-1.4-7.1"/></symbol>
|
||||
<symbol id="i-shield" viewBox="0 0 24 24"><path d="M12 3.5l7 2.8v5.4c0 4.2-2.9 7.6-7 8.8-4.1-1.2-7-4.6-7-8.8V6.3z"/></symbol>
|
||||
<symbol id="i-bulb" viewBox="0 0 24 24"><path d="M12 3.5a5.4 5.4 0 0 0-3.2 9.8c.5.4.8 1 .8 1.6v1.1h4.8v-1.1c0-.6.3-1.2.8-1.6A5.4 5.4 0 0 0 12 3.5z"/><path d="M9.8 18.6h4.4M10.4 21h3.2"/></symbol>
|
||||
<symbol id="i-pin" viewBox="0 0 24 24"><path d="M12 21s6.4-5.4 6.4-10.4A6.4 6.4 0 0 0 5.6 10.6C5.6 15.6 12 21 12 21z"/><circle cx="12" cy="10.5" r="2.3"/></symbol>
|
||||
<symbol id="i-sparkle" viewBox="0 0 24 24"><path d="M11 3.8l1.7 4.5 4.5 1.7-4.5 1.7L11 16.2 9.3 11.7 4.8 10l4.5-1.7z"/><path d="M18 15.4l.7 1.9 1.9.7-1.9.7-.7 1.9-.7-1.9-1.9-.7 1.9-.7z"/></symbol>
|
||||
<symbol id="i-search" viewBox="0 0 24 24"><circle cx="11" cy="11" r="6.4"/><path d="M15.7 15.7l4.8 4.8"/></symbol>
|
||||
<symbol id="i-help" viewBox="0 0 24 24"><circle cx="12" cy="12" r="8.5"/><path d="M9.7 9.6a2.4 2.4 0 0 1 4.7.7c0 1.6-2.4 2.2-2.4 3.6"/><path d="M12 17v.5"/></symbol>
|
||||
<symbol id="i-bell" viewBox="0 0 24 24"><path d="M17.4 16.4V11a5.4 5.4 0 0 0-10.8 0v5.4L5.2 18.4h13.6z"/><path d="M10 18.4a2 2 0 0 0 4 0"/></symbol>
|
||||
<symbol id="i-check" viewBox="0 0 24 24"><path d="M5 12.6l4.5 4.5L19 7.4"/></symbol>
|
||||
<symbol id="i-check-circle" viewBox="0 0 24 24"><circle cx="12" cy="12" r="8.5"/><path d="M8.2 12.2l2.6 2.6 5-5.2"/></symbol>
|
||||
<symbol id="i-refresh" viewBox="0 0 24 24"><path d="M19.6 12a7.6 7.6 0 1 1-2.3-5.4"/><path d="M19.6 4.6v4.9h-4.9"/></symbol>
|
||||
<symbol id="i-clock" viewBox="0 0 24 24"><circle cx="12" cy="12" r="8.5"/><path d="M12 7.4V12l3 2"/></symbol>
|
||||
<symbol id="i-sliders" viewBox="0 0 24 24"><path d="M4 8.5h8.5M17.5 8.5h2.5M4 15.5h2.5M11.5 15.5h8.5"/><circle cx="15" cy="8.5" r="2.2"/><circle cx="9" cy="15.5" r="2.2"/></symbol>
|
||||
<symbol id="i-chat" viewBox="0 0 24 24"><path d="M20 12.3c0 3.5-3.6 6.4-8 6.4-1 0-2-.2-2.9-.5L4.6 19.8l1-3.1A6.2 6.2 0 0 1 4 12.3C4 8.8 7.6 5.9 12 5.9s8 2.9 8 6.4z"/></symbol>
|
||||
<symbol id="i-folder" viewBox="0 0 24 24"><path d="M3.5 6.6A1.9 1.9 0 0 1 5.4 4.7h3.3l2.1 2.6h8A1.9 1.9 0 0 1 20.7 9.2v8.4a1.9 1.9 0 0 1-1.9 1.9H5.4a1.9 1.9 0 0 1-1.9-1.9z"/></symbol>
|
||||
<symbol id="i-alert" viewBox="0 0 24 24"><path d="M12 4.3l8.8 15.2H3.2z"/><path d="M12 10v3.9M12 16.9v.5"/></symbol>
|
||||
<symbol id="i-signal" viewBox="0 0 24 24"><path d="M4 18v-2M9 18v-5M14 18v-8M19 18V7"/></symbol>
|
||||
<symbol id="i-wifi" viewBox="0 0 24 24"><path d="M4 9.6a12.5 12.5 0 0 1 16 0M7 13a8 8 0 0 1 10 0"/><circle cx="12" cy="17.2" r="1.1"/></symbol>
|
||||
<symbol id="i-user-plus" viewBox="0 0 24 24"><circle cx="9.5" cy="8.2" r="3.6"/><path d="M3.4 20.2c.7-3.6 3.1-5.6 6.1-5.6s5.4 2 6.1 5.6"/><path d="M18.6 7.6v6M15.6 10.6h6"/></symbol>
|
||||
</svg>
|
||||
|
||||
<header class="masthead">
|
||||
<div class="kicker">Prototype · 学生端 · V1.2</div>
|
||||
<h1>填报通 · <em>学生填报端</em>交互原型</h1>
|
||||
<p class="sub">
|
||||
11 屏完整流程:<strong>门禁核验 → 信息核对 → 选材上传 → 自动规范化 → 提交审核</strong>。<br>
|
||||
<span style="font-size:12.5px;color:var(--ink3)">新增 STEP 03B「申请加入名单」——门禁失败后的受控出口,客户确认后加入。</span><br>
|
||||
设计方向为「档案室 / 电子卷宗」——本系统替代的是纸质材料袋与红章审批,界面应让人感到<strong>有秩序、可信、像一份正经公文</strong>。<br>
|
||||
图标为内联 SVG 线性图标(统一 1.7px 描边、继承文字色),不用 emoji——避免跨平台渲染不一致,也避免消费级观感。<br>
|
||||
全部文案遵循「零术语 · 指出位置 · 给出动作」三原则;数据取自真实批次 <code>24中技数控1班</code> 的 28 名学生。
|
||||
</p>
|
||||
<div class="legend">
|
||||
<span><i class="dot lock"></i><svg class="ico"><use href="#i-lock"/></svg> 锁定:只读,名单为空则阻断发布</span>
|
||||
<span><i class="dot edit"></i><svg class="ico"><use href="#i-pencil"/></svg> 预填可改:老师已填,可核对修改</span>
|
||||
<span><i class="dot doc"></i><svg class="ico"><use href="#i-file"/></svg> 材料:学生唯一的重活</span>
|
||||
<span><i class="dot seal"></i>朱红:印章 / 需修正</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="gallery">
|
||||
|
||||
<!-- ① 门禁核验 -->
|
||||
<figure>
|
||||
<div class="device"><div class="screen">
|
||||
<div class="statusbar"><span>9:41</span><span class="icons"><svg class="ico"><use href="#i-signal"/></svg><svg class="ico"><use href="#i-wifi"/></svg><i class="bat"></i></span></div>
|
||||
<div class="navbar"><span class="back"><svg class="ico"><use href="#i-back"/></svg></span>身份核验</div>
|
||||
<div class="body">
|
||||
<div class="notice info">
|
||||
<span class="ni"><svg class="ico lg"><use href="#i-shield"/></svg></span>
|
||||
<div>为保护你的信息,请先填写<b>姓名</b>和<b>身份证号</b>,与老师提供的名单核对后才能填报。</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>姓名<span class="req">*</span></label>
|
||||
<input class="input" placeholder="请填写真实姓名" value="曾依豪">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>身份证号<span class="req">*</span></label>
|
||||
<input class="input mono" value="361029200907295110" readonly>
|
||||
<div class="hint">共 18 位,最后一位可能是字母 X(会自动转为大写)</div>
|
||||
</div>
|
||||
<div class="notice ok" style="margin-top:18px">
|
||||
<span class="ni"><svg class="ico lg"><use href="#i-check-circle"/></svg></span>
|
||||
<div>格式检查通过</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="foot">
|
||||
<button class="btn primary">下一步</button>
|
||||
<div class="center-note">填写即表示同意《个人信息处理告知同意书》</div>
|
||||
</div>
|
||||
</div></div>
|
||||
<figcaption>
|
||||
<span class="tag">STEP 01</span>
|
||||
<b>门禁核验</b>
|
||||
进入填报的唯一入口。身份证号用<span style="font-family:var(--font-mono)">等宽字体+字距</span>呈现,因为它是"数据"不是"文字"。<br>
|
||||
<b style="display:inline;font-size:12.5px;color:var(--seal)">先在本地校验位数与校验位</b>,不合格当场提示、不发请求——既省流量,也防刷接口。
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
<!-- ② 形近字确认 -->
|
||||
<figure>
|
||||
<div class="device"><div class="screen">
|
||||
<div class="statusbar"><span>9:41</span><span class="icons"><svg class="ico"><use href="#i-signal"/></svg><svg class="ico"><use href="#i-wifi"/></svg><i class="bat"></i></span></div>
|
||||
<div class="navbar"><span class="back"><svg class="ico"><use href="#i-back"/></svg></span>确认姓名</div>
|
||||
<div class="pagehead">
|
||||
<div class="glyph"><svg class="ico xl"><use href="#i-help"/></svg></div>
|
||||
<h2>你是不是曾伊豪?</h2>
|
||||
<p>身份证号已和名单匹配上,但姓名不太一样。<br>请确认一下,这关系到你后续的证书信息。</p>
|
||||
</div>
|
||||
<div class="body">
|
||||
<div class="panel">
|
||||
<div class="row"><span class="k">你填写的</span><span class="v">曾依豪</span></div>
|
||||
<div class="row"><span class="k">老师名单</span><span class="v" style="color:var(--jade)">曾伊豪</span></div>
|
||||
</div>
|
||||
<div class="notice warn">
|
||||
<span class="ni"><svg class="ico lg"><use href="#i-bulb"/></svg></span>
|
||||
<div>「依」和「伊」读音相同、字形相近,是最常见的写法差异。</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="foot">
|
||||
<div class="btnrow" style="margin-bottom:10px">
|
||||
<button class="btn primary">是的,是我</button>
|
||||
<button class="btn ghost">不是</button>
|
||||
</div>
|
||||
<div class="center-note">确认后姓名将按名单锁定,不可再改</div>
|
||||
</div>
|
||||
</div></div>
|
||||
<figcaption>
|
||||
<span class="tag">STEP 02</span>
|
||||
<b>形近字确认</b>
|
||||
不走"报错 → 重填",而是<span style="color:var(--seal)">把系统判断摆出来让用户裁决</span>。<br>
|
||||
这个纠错能力来自真实数据——实测名单里就有「扬晨 ↔ 杨晨」的实例(差 1 字且拼音相同,命中规则但必须经人确认)。本屏用同类型的「曾依豪 ↔ 曾伊豪」示例,保持主角一致。
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
<!-- ③ 门禁失败 -->
|
||||
<figure>
|
||||
<div class="device"><div class="screen">
|
||||
<div class="statusbar"><span>9:41</span><span class="icons"><svg class="ico"><use href="#i-signal"/></svg><svg class="ico"><use href="#i-wifi"/></svg><i class="bat"></i></span></div>
|
||||
<div class="navbar"><span class="back"><svg class="ico"><use href="#i-back"/></svg></span>身份核验</div>
|
||||
<div class="pagehead">
|
||||
<div class="glyph gray"><svg class="ico xl"><use href="#i-search"/></svg></div>
|
||||
<h2>没有找到你的信息</h2>
|
||||
<p>请核对姓名和身份证号是否与老师提供的名单一致。<br>如果确实已报名,可以申请加入名单,由老师审核。</p>
|
||||
</div>
|
||||
<div class="body">
|
||||
<div class="panel">
|
||||
<div class="ptitle">本次填写 <span class="badge lock">已提交</span></div>
|
||||
<div class="row"><span class="k">姓名</span><span class="v">曾伊豪</span></div>
|
||||
<div class="row"><span class="k">身份证号</span><span class="v mono">361029********5511</span></div>
|
||||
</div>
|
||||
<div class="notice info">
|
||||
<span class="ni"><svg class="ico lg"><use href="#i-shield"/></svg></span>
|
||||
<div>出于隐私保护,系统<b>不会提示</b>具体是哪一项不符。</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="foot">
|
||||
<button class="btn primary" style="width:100%;margin-bottom:13px">重新填写</button>
|
||||
<div class="apply-entry">
|
||||
<div class="ae-t">名单里确实没有你?</div>
|
||||
<button class="btn ghost ae-btn"><svg class="ico"><use href="#i-user-plus"/></svg>申请加入名单</button>
|
||||
</div>
|
||||
<div class="center-note">连续 10 次未匹配将暂时锁定,1 小时后可重试</div>
|
||||
</div>
|
||||
</div></div>
|
||||
<figcaption>
|
||||
<span class="tag">STEP 03</span>
|
||||
<b>门禁失败(统一提示)</b>
|
||||
<span style="color:var(--seal)">故意不区分失败原因</span>——无论"号码不在名单"还是"姓名不符",文案完全一致。<br>
|
||||
否则这个接口就能被用来<b style="display:inline">逐个探测身份证号是否在名单里</b>。这一屏是安全设计,不是文案设计。
|
||||
<span class="note">底部只有两条出路,都是<b>系统内的动作</b>:<br>
|
||||
① <b>重新填写</b>(主路径)——大多数情况只是名字打错了;<br>
|
||||
② <b>申请加入名单</b>(FR-509)——确实不在名单里时自助申请,由管理员逐条审批。<br>
|
||||
<b>系统内不展示任何联系方式</b>——学生在班级群里本来就有班主任的联系方式,系统再放一份既不必要,也带来维护与隐私成本。</span>
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
<!-- ③B 申请加入名单(客户确认新增) -->
|
||||
<figure>
|
||||
<div class="device"><div class="screen">
|
||||
<div class="statusbar"><span>9:41</span><span class="icons"><svg class="ico"><use href="#i-signal"/></svg><svg class="ico"><use href="#i-wifi"/></svg><i class="bat"></i></span></div>
|
||||
<div class="navbar"><span class="back"><svg class="ico"><use href="#i-back"/></svg></span>申请加入名单</div>
|
||||
<div class="body">
|
||||
<div class="stepbox">
|
||||
<div class="sb on"><i>1</i>填写申请</div>
|
||||
<div class="sb"><i>2</i>等待审核</div>
|
||||
<div class="sb"><i>3</i>重新核验</div>
|
||||
</div>
|
||||
|
||||
<div class="notice info">
|
||||
<span class="ni"><svg class="ico lg"><use href="#i-shield"/></svg></span>
|
||||
<div>提交后由学校老师审核,<b>审核期间还不能填报</b>。通过后你会收到通知,届时重新核验即可进入。</div>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<div class="ptitle">申请人信息</div>
|
||||
<div class="field">
|
||||
<label>姓名<span class="req">*</span></label>
|
||||
<input class="input" value="李四">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>身份证号<span class="req">*</span></label>
|
||||
<input class="input mono" value="361004200812153318">
|
||||
</div>
|
||||
<div class="field" style="margin-bottom:0">
|
||||
<label>手机号<span class="req">*</span></label>
|
||||
<input class="input mono" value="13800008888">
|
||||
<div class="apply-hint">用于老师与你联系,不会公开展示</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<div class="ptitle">补充说明 <span class="badge lock">选填</span></div>
|
||||
<div class="field">
|
||||
<label>你所在的班级</label>
|
||||
<input class="input" value="24中技数控2班">
|
||||
</div>
|
||||
<div class="field" style="margin-bottom:0">
|
||||
<label>说明</label>
|
||||
<textarea class="input" style="height:64px;padding:10px 13px;line-height:1.6;resize:none">我是这学期转到2班的,原名单上没有我</textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="foot">
|
||||
<button class="btn primary" style="width:100%">提交申请</button>
|
||||
<div class="center-note">同一任务最多申请 2 次 · 提交后不可修改</div>
|
||||
</div>
|
||||
</div></div>
|
||||
<figcaption>
|
||||
<span class="tag">STEP 03B · 客户确认新增</span>
|
||||
<b>申请加入名单</b>
|
||||
三步进度条把"<b>提交 → 等待 → 重新核验</b>"讲清楚,避免学生误以为提交完就能填了。<br>
|
||||
<span class="note">顶部那条提示是本屏的关键:<b>「审核期间还不能填报」</b>。如果这句话不写,学生会反复回来点填报、以为系统坏了。<br>
|
||||
补充说明是<b>选填</b>——实测里"我是转班过来的"这类信息对管理员判断很有用,但不能设成必填,否则会挡住真正需要帮助的人。</span>
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
<!-- ④ 填报主页 -->
|
||||
<figure>
|
||||
<div class="device"><div class="screen">
|
||||
<div class="statusbar"><span>9:41</span><span class="icons"><svg class="ico"><use href="#i-signal"/></svg><svg class="ico"><use href="#i-wifi"/></svg><i class="bat"></i></span></div>
|
||||
<div class="navbar"><span class="back"><svg class="ico"><use href="#i-back"/></svg></span>我的填报</div>
|
||||
<div class="taskbar">
|
||||
<div class="tname">2026年9月机电专业认定材料收集</div>
|
||||
<div class="tmeta"><span>截止 10-15</span><span>江西省机电技师学院</span></div>
|
||||
</div>
|
||||
<div class="body">
|
||||
<div class="panel" style="display:flex;align-items:center;gap:14px">
|
||||
<div class="ring" style="--p:50"><span>2/4</span></div>
|
||||
<div>
|
||||
<div style="font-size:14.5px;font-weight:600">曾伊豪 · 24中技数控1班</div>
|
||||
<div style="font-size:11.5px;color:var(--ink3);margin-top:5px;line-height:1.55">还差 2 份材料,去传一下就能提交</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<div class="ptitle">报考信息 <span class="badge lock"><svg class="ico"><use href="#i-lock"/></svg>老师已确定</span></div>
|
||||
<div class="row"><span class="k">报考职业</span><span class="v">车工</span></div>
|
||||
<div class="row"><span class="k">报考工种</span><span class="v">数控车工</span></div>
|
||||
<div class="row"><span class="k">报考等级</span><span class="v">四级/中级工</span></div>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<div class="ptitle">其他信息 <span class="badge edit"><svg class="ico"><use href="#i-pencil"/></svg>请核对,可改</span></div>
|
||||
<div class="row"><span class="k">学历</span><span class="v">技工学校</span></div>
|
||||
<div class="row"><span class="k">是否在校生</span><span class="v">是</span></div>
|
||||
<div class="row"><span class="k">在读学校</span><span class="v">江西省机电技师学院</span></div>
|
||||
<div class="row"><span class="k">所学专业</span><span class="v">数控加工(数控车工)</span></div>
|
||||
<div class="row"><span class="k">入学时间</span><span class="v mono">2024-09-01</span></div>
|
||||
<div class="row"><span class="k">联系电话</span><span class="v mono">19970570823</span></div>
|
||||
</div>
|
||||
|
||||
<div class="panel flush">
|
||||
<div style="padding:15px 15px 11px"><div class="ptitle" style="margin:0">需要上传的材料 <span class="badge doc"><svg class="ico"><use href="#i-file"/></svg>你的活</span></div></div>
|
||||
<div class="mat">
|
||||
<div class="thumb ok"><svg class="ico"><use href="#i-image"/></svg></div>
|
||||
<div class="info"><div class="mname">一寸照片 <span class="state pass">已通过</span></div>
|
||||
<div class="mdesc done">曾伊豪-...-一寸照片.jpg · 96KB</div></div>
|
||||
</div>
|
||||
<div class="mat">
|
||||
<div class="thumb ok"><svg class="ico"><use href="#i-idcard"/></svg></div>
|
||||
<div class="info"><div class="mname">身份证 <span class="state pass">已通过</span></div>
|
||||
<div class="mdesc done">曾伊豪-...-身份证.jpg · 810KB</div></div>
|
||||
</div>
|
||||
<div class="mat">
|
||||
<div class="thumb"><svg class="ico"><use href="#i-file"/></svg></div>
|
||||
<div class="info"><div class="mname">学籍表 <span class="state none">未上传</span></div>
|
||||
<div class="mdesc">教务系统截图,从手机相册里选</div></div>
|
||||
</div>
|
||||
<div class="mat">
|
||||
<div class="thumb"><svg class="ico"><use href="#i-stamp"/></svg></div>
|
||||
<div class="info"><div class="mname">工作证明 <span class="state none">未上传</span></div>
|
||||
<div class="mdesc">打印店扫描后会发给你,从聊天记录里选</div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="foot"><button class="btn primary" disabled>还差 2 份材料</button></div>
|
||||
</div></div>
|
||||
<figcaption>
|
||||
<span class="tag">STEP 04 · 核心屏</span>
|
||||
<b>填报主页:一屏看完,几乎不用打字</b>
|
||||
三块分区严格对应「字段三类规则」——报考信息<span style="color:var(--seal)">锁定</span>、其他信息<span style="color:var(--amber)">可核对修改</span>、材料才是学生要干的活。<br>
|
||||
实测老师名单已填满 21 项中的 18 项,所以学生上来只是<b style="display:inline">核对着点两下 + 传文件</b>。
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
<!-- ⑤ 来源选择 -->
|
||||
<figure>
|
||||
<div class="device"><div class="screen">
|
||||
<div class="statusbar"><span>9:41</span><span class="icons"><svg class="ico"><use href="#i-signal"/></svg><svg class="ico"><use href="#i-wifi"/></svg><i class="bat"></i></span></div>
|
||||
<div class="navbar"><span class="back"><svg class="ico"><use href="#i-back"/></svg></span>上传工作证明</div>
|
||||
<div class="body">
|
||||
<div class="notice info">
|
||||
<span class="ni"><svg class="ico lg"><use href="#i-pin"/></svg></span>
|
||||
<div><b>去哪里找这份材料?</b><br>打印店扫描完会发到你的微信,通常就在<b>聊天记录</b>里。</div>
|
||||
</div>
|
||||
<div class="panel">
|
||||
<div class="ptitle">材料要求</div>
|
||||
<div class="row"><span class="k">格式</span><span class="v">JPG / PNG / PDF</span></div>
|
||||
<div class="row"><span class="k">大小</span><span class="v">不超过 1M(系统会自动压缩)</span></div>
|
||||
<div class="row"><span class="k">内容</span><span class="v">手签并加盖学校公章的扫描件</span></div>
|
||||
<div class="row"><span class="k">命名</span><span class="v mono dim">姓名-身份证号-工作证明.jpg</span></div>
|
||||
</div>
|
||||
<div class="notice ok">
|
||||
<span class="ni"><svg class="ico lg"><use href="#i-sparkle"/></svg></span>
|
||||
<div>命名不用你操心,<b>系统会自动改好</b>。</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mask"></div>
|
||||
<div class="sheet">
|
||||
<div class="grab"></div>
|
||||
<h3>选择材料来源</h3>
|
||||
<div class="stip">优先从聊天记录选——文件名里带着你的信息,系统能自动识别</div>
|
||||
<div class="scard rec">
|
||||
<div class="sicon"><svg class="ico"><use href="#i-chat"/></svg></div>
|
||||
<div class="stxt"><div class="st">从微信聊天记录选择 <span class="recommend">推荐</span></div>
|
||||
<div class="sd">照相馆、打印店、老师发来的文件都在这里</div></div>
|
||||
</div>
|
||||
<div class="scard">
|
||||
<div class="sicon"><svg class="ico"><use href="#i-image"/></svg></div>
|
||||
<div class="stxt"><div class="st">从手机相册选择</div>
|
||||
<div class="sd">系统截图、已有的翻拍件在这里</div></div>
|
||||
</div>
|
||||
<div class="scard">
|
||||
<div class="sicon"><svg class="ico"><use href="#i-folder"/></svg></div>
|
||||
<div class="stxt"><div class="st">从手机文件选择</div>
|
||||
<div class="sd">PDF 扫描件、Word 版申报表在这里</div></div>
|
||||
</div>
|
||||
<div class="center-note" style="margin-top:6px">不提供"直接拍照"——所有材料都是已有的文件</div>
|
||||
</div>
|
||||
</div></div>
|
||||
<figcaption>
|
||||
<span class="tag">STEP 05</span>
|
||||
<b>材料来源选择</b>
|
||||
<span style="color:var(--seal)">刻意没有"拍照"入口</span>。实测 4 类材料(教务截图 / 打印店扫描件 / 照相馆电子版 / 翻拍件)<b style="display:inline">没有一类来自现场拍照</b>。<br>
|
||||
「从聊天记录选」被标为推荐,因为微信会返回<b style="display:inline">原始文件名</b>——那是命名引擎最可靠的信息源。
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
<!-- ⑥ 处理中 -->
|
||||
<figure>
|
||||
<div class="device"><div class="screen">
|
||||
<div class="statusbar"><span>9:41</span><span class="icons"><svg class="ico"><use href="#i-signal"/></svg><svg class="ico"><use href="#i-wifi"/></svg><i class="bat"></i></span></div>
|
||||
<div class="navbar"><span class="back"><svg class="ico"><use href="#i-back"/></svg></span>正在处理</div>
|
||||
<div class="body" style="display:flex;flex-direction:column;justify-content:center;min-height:100%">
|
||||
<div style="text-align:center;margin-bottom:30px">
|
||||
<div class="ring" style="--p:64;width:92px;height:92px;margin:0 auto 20px"><span style="font-size:19px">64%</span></div>
|
||||
<div style="font-family:var(--font-display);font-size:19px;letter-spacing:.05em">正在处理你的材料</div>
|
||||
<div style="font-size:12px;color:var(--ink3);margin-top:8px">请稍候,不要退出</div>
|
||||
</div>
|
||||
<div class="panel">
|
||||
<div class="vitem" style="border-top:0"><span class="vi ok"><svg class="ico"><use href="#i-check-circle"/></svg></span><span class="vt">已上传 <b>16.14 MB</b></span></div>
|
||||
<div class="vitem"><span class="vi ok"><svg class="ico"><use href="#i-check-circle"/></svg></span><span class="vt">识别为 <b>工作证明</b>(A4 扫描件)</span></div>
|
||||
<div class="vitem"><span class="vi work"><svg class="ico"><use href="#i-refresh"/></svg></span><span class="vt">正在<b>规范化文件名</b>…</span></div>
|
||||
<div class="vitem"><span class="vi wait"><svg class="ico"><use href="#i-clock"/></svg></span><span class="vt" style="color:var(--ink3)">压缩到 1M 以内</span></div>
|
||||
<div class="vitem"><span class="vi wait"><svg class="ico"><use href="#i-clock"/></svg></span><span class="vt" style="color:var(--ink3)">校验材料一致性</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div></div>
|
||||
<figcaption>
|
||||
<span class="tag">STEP 06</span>
|
||||
<b>处理过程可见</b>
|
||||
把"秒级黑箱"拆成可读的步骤。<br>
|
||||
实测最大的扫描件有 <b style="display:inline">1.8 亿像素</b>、处理需几秒——这几秒如果不解释,用户会以为卡死并退出。
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
<!-- ⑦ 处理结果(核心亮点) -->
|
||||
<figure>
|
||||
<div class="device"><div class="screen">
|
||||
<div class="statusbar"><span>9:41</span><span class="icons"><svg class="ico"><use href="#i-signal"/></svg><svg class="ico"><use href="#i-wifi"/></svg><i class="bat"></i></span></div>
|
||||
<div class="navbar"><span class="back"><svg class="ico"><use href="#i-back"/></svg></span>材料已受理</div>
|
||||
<div class="body">
|
||||
<div class="filecard">
|
||||
<div class="stamp jade">已规范</div>
|
||||
<div class="fc-label">你上传的文件名</div>
|
||||
<div class="fname old">曾伊豪-361029200907295110工作证明<span class="fix">.jpg.jpg</span><i class="strike"></i></div>
|
||||
<div class="fc-arrow"><span class="line"></span>已自动规范化<span class="line"></span></div>
|
||||
<div class="fc-label">规范后的文件名</div>
|
||||
<div class="fname new">曾伊豪-361029200907295110<span class="ok">-</span>工作证明.jpg</div>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<div class="ptitle">系统为你修正了 2 处</div>
|
||||
<div class="vitem" style="border-top:0">
|
||||
<span class="vi"><svg class="ico"><use href="#i-sliders"/></svg></span>
|
||||
<span class="vt">多余的扩展名 <b>.jpg.jpg</b> → <b>.jpg</b></span></div>
|
||||
<div class="vitem">
|
||||
<span class="vi"><svg class="ico"><use href="#i-sliders"/></svg></span>
|
||||
<span class="vt">缺少连接符 → 已插入 <b>-</b></span></div>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<div class="ptitle">体积压缩</div>
|
||||
<div class="vol">
|
||||
<span class="from">16.14 MB</span>
|
||||
<span style="color:var(--ink3)">→</span>
|
||||
<span class="to">806 KB</span>
|
||||
<span class="pct">省下 95%</span>
|
||||
</div>
|
||||
<div class="hint" style="margin-top:9px">已按 A4 纸张等比缩至 300dpi 标准扫描清晰度,<b>公章与手写签名仍然清晰可辨</b>。</div>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<div class="ptitle">校验结果</div>
|
||||
<div class="vitem" style="border-top:0"><span class="vi ok"><svg class="ico"><use href="#i-check-circle"/></svg></span><span class="vt">身份证号与老师名单一致</span></div>
|
||||
<div class="vitem"><span class="vi ok"><svg class="ico"><use href="#i-check-circle"/></svg></span><span class="vt">与你的其他材料一致</span></div>
|
||||
<div class="vitem"><span class="vi ok"><svg class="ico"><use href="#i-check-circle"/></svg></span><span class="vt">体积符合要求(≤ 1M)</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="foot"><button class="btn primary">确认,继续传下一份</button></div>
|
||||
</div></div>
|
||||
<figcaption>
|
||||
<span class="tag">STEP 07 · 全原型最核心</span>
|
||||
<b>上传即规范化:让用户看见系统替他干了什么</b>
|
||||
这是整个产品的价值可视化。用户交上来一个 <span style="font-family:var(--font-mono);font-size:11px">.jpg.jpg</span> 的乱名文件,<span style="color:var(--seal)">红笔划掉</span>、<span style="color:var(--jade)">盖"已规范"章</span>,再报出压缩率。<br>
|
||||
<b style="display:inline">刻意把"原名"留得很丑、把"新名"留得很干净</b>——对比越强烈,价值感越强。
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
<!-- ⑧ 传错位置 -->
|
||||
<figure>
|
||||
<div class="device"><div class="screen">
|
||||
<div class="statusbar"><span>9:41</span><span class="icons"><svg class="ico"><use href="#i-signal"/></svg><svg class="ico"><use href="#i-wifi"/></svg><i class="bat"></i></span></div>
|
||||
<div class="navbar"><span class="back"><svg class="ico"><use href="#i-back"/></svg></span>学籍表</div>
|
||||
<div class="body">
|
||||
<div class="notice warn">
|
||||
<span class="ni"><svg class="ico lg"><use href="#i-alert"/></svg></span>
|
||||
<div><b>这张看起来是工作证明,你已经传过这一项了。</b><br>要重新选一张学籍表吗?</div>
|
||||
</div>
|
||||
<div class="filecard" style="border-color:#e8d9b5">
|
||||
<div class="fc-label">系统识别</div>
|
||||
<div class="fname new">工作证明 <span style="color:var(--jade);font-weight:600">置信度 93%</span></div>
|
||||
<div style="height:9px"></div>
|
||||
<div class="fc-label">识别依据</div>
|
||||
<div class="fname" style="font-size:11.5px;color:var(--ink2)">
|
||||
检测到关键词「申报表」「职业技能等级认定」「兹证明」<br>
|
||||
版面为 A4 纸张扫描件(约 1.6 亿像素)
|
||||
</div>
|
||||
</div>
|
||||
<div class="notice info">
|
||||
<span class="ni"><svg class="ico lg"><use href="#i-bulb"/></svg></span>
|
||||
<div>系统只做提示,<b>不会自动移动或替换你的文件</b>。</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="foot">
|
||||
<div class="btnrow"><button class="btn seal">重新选择</button><button class="btn ghost">就放这里</button></div>
|
||||
</div>
|
||||
</div></div>
|
||||
<figcaption>
|
||||
<span class="tag">STEP 08</span>
|
||||
<b>传错位置:只提示,不擅自改</b>
|
||||
识别必然有误判。<b style="display:inline;color:var(--seal)">自动挪位置比传错更危险</b>——它会把用户原本放对的文件挪走。所以规则是:给依据、给建议、<b style="display:inline">把决定权留给用户</b>。<br>
|
||||
<span style="color:var(--ink3);font-size:11.5px">本屏演示的是<b style="display:inline">目标槽位已占用</b>的情形——学生刚传完工作证明,再去传学籍表时误选了同一张图,系统识别为"重复"并只建议重新选择。<br>
|
||||
若目标槽位<b style="display:inline">为空</b>(如工作证明还没传),系统会给出「移到『工作证明』」——<b style="display:inline">能移才移,不能移就只提示</b>。</span>
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
<!-- ⑨ 提交确认 -->
|
||||
<figure>
|
||||
<div class="device"><div class="screen">
|
||||
<div class="statusbar"><span>9:41</span><span class="icons"><svg class="ico"><use href="#i-signal"/></svg><svg class="ico"><use href="#i-wifi"/></svg><i class="bat"></i></span></div>
|
||||
<div class="navbar"><span class="back"><svg class="ico"><use href="#i-back"/></svg></span>提交前确认</div>
|
||||
<div class="body">
|
||||
<div class="panel">
|
||||
<div class="ptitle">4 项材料 <span class="badge doc">3 项就绪</span></div>
|
||||
<div class="vitem" style="border-top:0"><span class="vi ok"><i class="dot doc"></i></span>
|
||||
<span class="vt">一寸照片 <b style="font-family:var(--font-mono);font-size:11.5px">96KB</b></span></div>
|
||||
<div class="vitem"><span class="vi ok"><i class="dot doc"></i></span>
|
||||
<span class="vt">身份证 <b style="font-family:var(--font-mono);font-size:11.5px">810KB</b></span></div>
|
||||
<div class="vitem"><span class="vi ok"><i class="dot doc"></i></span>
|
||||
<span class="vt">工作证明 <b style="font-family:var(--font-mono);font-size:11.5px">806KB</b></span></div>
|
||||
<div class="vitem"><span class="vi" style="color:var(--amber)"><i class="dot edit"></i></span>
|
||||
<span class="vt" style="color:var(--amber)">学籍表 分辨率偏低,放大后可能看不清</span></div>
|
||||
</div>
|
||||
<div class="notice warn">
|
||||
<span class="ni"><svg class="ico lg"><use href="#i-alert"/></svg></span>
|
||||
<div><b>1 项建议处理</b><br>学籍表这张图只有 1024×590,建议让老师重发一张更清晰的。也可以选择继续提交。</div>
|
||||
</div>
|
||||
<div style="display:flex;align-items:center;gap:9px;padding:12px 4px">
|
||||
<input type="checkbox" id="ck" checked style="width:17px;height:17px;accent-color:var(--amber)">
|
||||
<label for="ck" style="font-size:12.5px;color:var(--ink2)">我已确认,继续提交</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="foot"><button class="btn seal">提交审核</button></div>
|
||||
</div></div>
|
||||
<figcaption>
|
||||
<span class="tag">STEP 09</span>
|
||||
<b>提交前确认:红黄绿汇总</b>
|
||||
<span style="color:var(--seal)">红色</span>阻断项必须处理才能提交;<span style="color:var(--amber)">黄色</span>警告项需勾选"我已确认"后放行。<br>
|
||||
注意这里<b style="display:inline">没有阻断项</b>——因为不合格的内容在<b style="display:inline">上传那一刻</b>就已经被拦下并修正了,不会累积到最后。
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
<!-- ⑩ 提交成功 -->
|
||||
<figure>
|
||||
<div class="device"><div class="screen">
|
||||
<div class="statusbar"><span>9:41</span><span class="icons"><svg class="ico"><use href="#i-signal"/></svg><svg class="ico"><use href="#i-wifi"/></svg><i class="bat"></i></span></div>
|
||||
<div class="navbar"><span class="back"><svg class="ico"><use href="#i-back"/></svg></span>填报进度</div>
|
||||
<div class="pagehead">
|
||||
<div class="glyph ok"><svg class="ico xl"><use href="#i-check"/></svg></div>
|
||||
<h2>已提交,等待审核</h2>
|
||||
<p>班主任审核通过后,会汇总给学校管理员终审。</p>
|
||||
</div>
|
||||
<div class="body">
|
||||
<div class="panel">
|
||||
<div class="ptitle">审核进度</div>
|
||||
<div class="tl">
|
||||
<div class="node"><div class="nd done"><svg class="ico"><use href="#i-check"/></svg></div>
|
||||
<div class="nc"><div class="nt">材料提交完成</div><div class="ns">今天 09:41</div></div></div>
|
||||
<div class="node"><div class="nd now">●</div>
|
||||
<div class="nc"><div class="nt">班主任初审</div><div class="ns">李老师 · 一般 1 个工作日内处理</div></div></div>
|
||||
<div class="node"><div class="nd">3</div>
|
||||
<div class="nc"><div class="nt dim">学校管理员终审</div><div class="ns">初审通过后开始</div></div></div>
|
||||
<div class="node"><div class="nd">4</div>
|
||||
<div class="nc"><div class="nt dim">汇总上报</div><div class="ns">终审通过后由学校统一上报</div></div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="notice info">
|
||||
<span class="ni"><svg class="ico lg"><use href="#i-bell"/></svg></span>
|
||||
<div>审核结果会通过微信通知你,无需反复查看。</div>
|
||||
</div>
|
||||
<div style="text-align:center;margin-top:18px"><span class="link">需要修改?联系班主任</span></div>
|
||||
</div>
|
||||
</div></div>
|
||||
<figcaption>
|
||||
<span class="tag">STEP 10</span>
|
||||
<b>进度透明,减少追问</b>
|
||||
把"提交后一片黑"变成可见的时间轴。<br>
|
||||
实测场景里班主任最常被追问的就是"我交了没""审到哪了"——<b style="display:inline">这一屏直接消灭这类沟通成本</b>。
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
</main>
|
||||
|
||||
<section class="apron">
|
||||
<h3>设计依据 · 每一屏都对着一条真实数据</h3>
|
||||
<div class="aside-grid">
|
||||
<div class="aside-card">
|
||||
<h4><span class="n">01</span>为什么学生几乎不用打字</h4>
|
||||
实测老师交上来的名单里,21 个字段<b>已填满 18 个</b>(学历、在校生、学校、专业、入学时间、报考三项、联系电话全部有值)。所以学生端的任务是<b>核对 + 传文件</b>,不是填表。
|
||||
</div>
|
||||
<div class="aside-card">
|
||||
<h4><span class="n">02</span>为什么报考信息要锁定</h4>
|
||||
整班 28 人的报考三项<b>完全一致</b>(车工 / 数控车工 / 四级中级工)——这是老师按机构要求统一定的。<b>让学生去选只会制造错误。</b>要改,只能找老师。
|
||||
</div>
|
||||
<div class="aside-card">
|
||||
<h4><span class="n">03</span>为什么没有"拍照"</h4>
|
||||
学籍表是<b>教务系统截图</b>、工作证明是<b>打印店扫描件</b>、一寸照是<b>照相馆电子版</b>、身份证是翻拍件——<b>没有一类来自现场拍照</b>。拍照只会引入模糊、畸变、反光。
|
||||
</div>
|
||||
<div class="aside-card">
|
||||
<h4><span class="n">04</span>为什么失败提示不写原因</h4>
|
||||
如果提示"身份证号不在名单",这个接口就能被用来<b>逐个探测身份证号是否在名单里</b>。安全要求优先于用户体验。
|
||||
</div>
|
||||
<div class="aside-card">
|
||||
<h4><span class="n">05</span>为什么压缩率要显式告知</h4>
|
||||
用户最怕的是"你把我文件动了"。<b>把 16.14MB → 810KB 写出来</b>,并说明"300dpi 清晰度无损",才能建立信任。这也是认定机构最关心的问题。
|
||||
</div>
|
||||
<div class="aside-card">
|
||||
<h4><span class="n">06</span>为什么识别错误不自动纠正</h4>
|
||||
系统识别必然有误判。<b>自动挪文件比传错更危险</b>——它会把放对的文件挪走。所以只给建议与依据,决定权始终在用户手里。
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
const figs=[...document.querySelectorAll('figure')];
|
||||
figs.forEach(f=>{f.style.opacity='0';f.style.transform='translateY(22px)';
|
||||
f.style.transition='opacity .6s cubic-bezier(.2,.8,.3,1),transform .6s cubic-bezier(.2,.8,.3,1)';});
|
||||
const io=new IntersectionObserver(es=>{es.forEach(e=>{if(e.isIntersecting){
|
||||
const i=figs.indexOf(e.target);
|
||||
setTimeout(()=>{e.target.style.opacity='1';e.target.style.transform='translateY(0)';},(i%3)*110);
|
||||
io.unobserve(e.target);}});},{threshold:.12});
|
||||
figs.forEach(f=>io.observe(f));
|
||||
|
||||
document.querySelectorAll('.scard').forEach(c=>{
|
||||
c.addEventListener('click',()=>{
|
||||
document.querySelectorAll('.scard').forEach(x=>x.style.borderColor='');
|
||||
c.style.borderColor='#c0392b';
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load Diff
+271
@@ -0,0 +1,271 @@
|
||||
# 交接摘要
|
||||
|
||||
> 用途:**压缩后的唯一状态源**。新会话读这一份即可接续,不必回看历史对话。
|
||||
> 更新:2026-09-28
|
||||
|
||||
---
|
||||
|
||||
## 一、项目一句话
|
||||
|
||||
职业技能等级认定**报名材料收集 SaaS**。学生在微信小程序填报、班主任在手机初审、管理员在 PC 终审与导出。核心价值:**动态表单 + 规则引擎(命名/压缩/识别)+ 门禁核验 + 一键规范导出**。
|
||||
|
||||
工作目录 `D:\IDEA_Project\xiaotutongxue`(**空 git 仓库,无 commit,全部改动未提交**),交付物在 `docs/`。
|
||||
|
||||
---
|
||||
|
||||
## 一·二、一期范围(2026-09-28 客户确认收窄)
|
||||
|
||||
> **一期只做「学校收集 + 自主认定」一条流程。** 详见 PRD「一期范围声明」。
|
||||
|
||||
| 维度 | 字段 | 一期 | 二期后置 |
|
||||
|---|---|---|---|
|
||||
| 收集模式 | `collect_mode` | `1` 学校收集 | `2` 社会收集 |
|
||||
| 认定类型 | `cert_type` | `1` 自主认定 | `2` 社会评价 / `3` 专项职业能力 |
|
||||
|
||||
**一期材料只有 4 类,全部有实测样本**:一寸照片 / 身份证 / 学籍表 / 工作证明
|
||||
**二期才做的 2 类材料零样本**:`XJW_BA` 学信网备案表 / `OTHER_PROOF` 其它佐证材料
|
||||
|
||||
**数据结构保留不启用**(二期直接开,不需 DB 迁移)——因为这三项本来就是**配置驱动**的
|
||||
(`needClassAudit` 是开关、字段隐藏走 `visibleWhen`、材料差异是材料项配置),
|
||||
所以**省下的主要是测试成本与一个外部依赖,而不是代码量**。
|
||||
|
||||
**工期**:392 → **387 人日**(−5);**关键路径不变,仍 15 周**。
|
||||
|
||||
> ⚠️ **二期启动前必做**:索取社会评价样本(D4)→ 标定 `XJW_BA`/`OTHER_PROOF` → 补社会收集审核流与专项职业能力字段隐藏。
|
||||
|
||||
---
|
||||
|
||||
## 一·五、客户回执(2026-09-28 · 需求已冻结)
|
||||
|
||||
> **客户全部 23 项按建议方案确认。** 唯一功能增量是 **B1:名单加入申请通道**。
|
||||
|
||||
| 组 | 需开发 | 说明 |
|
||||
|---|:--:|---|
|
||||
| A 认定机构 6 项 | **0** | 与现有设计完全一致(`min_long_side=1400`、`min_quality=70`、PNG 保留等都是当初按实测设计的) |
|
||||
| B 校方业务 9 项 | **1** | **B1** 已落地:FR-509 系列 + `collect_join_request` 表 + 5 接口 + 2 屏原型 |
|
||||
| C 商务法务 5 项 | 0 | C1 小程序资质推进中,H5 保底已列入 M0-13 预研 |
|
||||
| D 内部技术 7 项 | 0 | **D4 已后置为二期**(一期不涉及零样本材料);**D7 已降级**(文件名识别准确率不影响任何交付项,残留只是上传页文案校准,班主任群里问两句即可)。**均已清除,无外部依赖** |
|
||||
|
||||
**规模变化**:FR 115 → **125** / 接口 117 → **121** / 库表 19 → **20** / VAL 20 → **21** / 错误码 50 → **57** / Golden 断言 29 → **36** / 功能验收 85 → **111** / 原型 29 → **31** 屏 / 任务 94 → **100** / 人日 380 → **392** → **387**(一期收窄 −5)/ 工期 14 → **15 周**。
|
||||
|
||||
**B1 的设计要点(三条边界,不要在后继开发中放水)**
|
||||
|
||||
| # | 边界 | 理由 |
|
||||
|:--:|---|---|
|
||||
| 1 | **只解决"进名单",不绕过门禁** | 申请期间仍不能填报;审批通过后才自动绑定放行 |
|
||||
| 2 | **证件号与手机号仅存 HMAC + 脱敏值** | 与门禁口径一致 |
|
||||
| 3 | **驳回必须逐条填原因;批量通过不提供"全选"** | 身份认定环节误操作代价极高 |
|
||||
|
||||
---
|
||||
|
||||
## 二、已锁定的铁律(不要重开讨论)
|
||||
|
||||
| # | 决策 | 关键理由 |
|
||||
|---|---|---|
|
||||
| 1 | **扫描件强制压到 ≤1M,无例外** | 认定机构硬要求 |
|
||||
| 2 | 默认参数 **300dpi / q85** | 实测 28/28 达标,平均 806KB,清晰度零损失 |
|
||||
| 3 | 材料来源只有 **聊天记录 / 相册 / 手机文件**,「直接拍照」默认关闭 | 实测 4 类材料无一来自现场拍照 |
|
||||
| 4 | **必须先导名单**,姓名+身份证匹配不通过只提示、不放行(学校与社会收集皆然) | 门禁是产品根基 |
|
||||
| 5 | 报考范围**从名单聚合**,不存在独立"计划清单"文件 | 真实名单验证 |
|
||||
| 6 | 动态表单 = **内置字段(固定列,可统计)+ 扩展字段(extras_json,仅展示导出)** | 兼顾统计能力与灵活性 |
|
||||
| 7 | 数据库 **PostgreSQL**(纯 PG);软删除字段名为 **`deleted`** | 客户已确认 |
|
||||
| 8 | 班主任端**只做右滑通过**,驳回必须点按钮 | 误触后果不对称 |
|
||||
| 9 | 批量通过**只带无问题项**,红黄一律排除 | 防"手快全选" |
|
||||
| 10 | 有不合格文件时**直接禁用导出按钮**,不允许带问题打包 | 铁律 |
|
||||
| 11 | 识别策略 **规则为主 + VLM 兜底**(触发率 2%~10%) | 成本可控 |
|
||||
| 12 | 识别失败**一律放行**,不得阻断提交 | 可用性优先 |
|
||||
| 13 | 原型**不用 emoji**(`→` `↔` `·` 等排版字符保留) | 设计约束 |
|
||||
|
||||
---
|
||||
|
||||
## 三、已完成交付物
|
||||
|
||||
| 文件 | 版本/大小 | 状态 |
|
||||
|---|---|---|
|
||||
| `docs/数据信息填报系统PRD.md` | V2.3 / 173KB / 2816 行 | ✅ 含 115 条 FR、17 条 VAL、96 接口、19 表 DDL、实测附录 |
|
||||
| `docs/数据信息填报系统PRD-功能逻辑卷.md` | V1.9 / 88KB | ✅ 纯功能逻辑,**不含库表/接口/代码** |
|
||||
| `docs/db/schema-postgresql.sql` | 34KB | ✅ **PG 16.15 实测零错误**(19 表 / 167 列注释 / 61 索引) |
|
||||
| `docs/prototype/student-app.html` | **11 屏** / 57KB | ✅ 含 STEP 03B「申请加入名单」 |
|
||||
| `docs/prototype/teacher-app.html` | 9 屏 / 63KB | ✅ |
|
||||
| `docs/prototype/admin-web.html` | **11 屏** / 114KB | ✅ 含表单设计器、材料规则配置、历史整理器、**申请审批** |
|
||||
| `docs/prototype/index.html` | 7KB | ✅ 三端入口 |
|
||||
| `docs/00-交付计划.md` | — | ✅ 7 项计划与进度记录 |
|
||||
| `docs/01-客户与机构确认函.md` | 18KB | ✅ **P1** 23 项**已全部确认**(客户按建议执行,需求冻结) |
|
||||
| `docs/02-需求追溯矩阵.md` | 10KB | ✅ **P2** 审计报告,含 10 个接口缺口 |
|
||||
| `docs/03-接口设计.md` | **102KB / 2626 行** | ✅ **P3 117 个接口**字段级定义 + 枚举全集 + 12 个 VO + 50 个错误码;**自校验通过** |
|
||||
| `docs/06-验收测试清单.md` | **29KB** | ✅ **P6 36 条 Golden 断言** + **111 条功能验收** + **27 条异常边界** |
|
||||
| `../README.md` | **10KB** | ✅ **P7 文档导航**,按角色读法 + 快速上手 |
|
||||
| `docs/04-核心模块设计说明.md` | **56KB / 1218 行** | ✅ **P4 六个模块**伪代码级说明 + 依赖图 + Golden 基线 |
|
||||
| `docs/05-开发任务拆解.md` | **28KB / 468 行** | ✅ **P5 94 个任务 / 380 人日 / 14 周**,含关键路径与核算口径 |
|
||||
|
||||
---
|
||||
|
||||
## 四、待办计划
|
||||
|
||||
| # | 任务 | 产出 | 状态 |
|
||||
|---|---|---|---|
|
||||
| ~~P1~~ | 客户与认定机构确认函 | `01-客户与机构确认函.md` | ✅ |
|
||||
| ~~P2~~ | 需求追溯矩阵 | `02-需求追溯矩阵.md` | ✅ |
|
||||
| ~~P2.5~~ | 管理端补 3 屏原型 | 并入 `admin-web.html` | ✅ |
|
||||
| **P3** | **接口设计(OpenAPI)** | `03-接口设计.md` | ✅ **已完成**(117 个接口) |
|
||||
| P4 | 核心模块设计说明(伪代码级) | `04-核心模块设计说明.md` | ✅ **已完成**(6 个模块) |
|
||||
| P5 | 开发任务拆解 WBS(M0–M7) | `05-开发任务拆解.md` | ✅ **已完成**(94 任务 / 380 人日 / 14 周) |
|
||||
| P6 | 验收测试清单(Golden Test Set) | `06-验收测试清单.md` | ✅ **已完成**(29 条 Golden 断言 + 85 条功能验收) |
|
||||
| P7 | 文档导航 README | `../README.md` | ✅ **已完成** |
|
||||
|
||||
### P3 已补齐的缺口(21 个接口,原审计发现 10 项)
|
||||
|
||||
```
|
||||
✅ I1 报考范围读取/确认 GET/PUT /api/v1/tasks/{id}/plan-scope
|
||||
✅ I2 历史数据整理器 POST /api/v1/cleanup/jobs(共 5 个)
|
||||
✅ I3 字典管理 /api/v1/dicts(4 个)
|
||||
✅ I4 操作日志 GET /api/v1/logs/operations
|
||||
✅ I5 登录日志 GET /api/v1/logs/logins
|
||||
✅ I6 消息中心 /mp/messages、/mp/messages/read、/mp/teacher/messages
|
||||
✅ I7 订阅额度登记 POST /api/v1/mp/subscribe
|
||||
✅ I8 审核撤回 POST /api/v1/audit/submissions/{id}/revoke
|
||||
✅ I9 任务暂停/恢复 POST /api/v1/tasks/{id}/pause、/resume
|
||||
✅ I10 材料识别结果读取 已并入 GET /api/v1/audit/submissions/{id} 响应
|
||||
➕ 文件规范化 process 原仅在 PRD §8.9 详述、未登记进清单,已补登
|
||||
```
|
||||
|
||||
**接口总数:96(PRD 原有)+ 21 = 117**
|
||||
|
||||
---
|
||||
|
||||
## 五、关键事实速查
|
||||
|
||||
### 真实基准数据集(Golden Test Set)
|
||||
|
||||
`D:\项目\202609机电填报系统\24中技数控1班\` —— **28 人 × 4 类材料 = 112 文件**
|
||||
|
||||
| 实测项 | 结果 |
|
||||
|---|---|
|
||||
| 原始总量 | 475 MB |
|
||||
| 命名违规 | 59 / 112(52.7%) |
|
||||
| 双扩展名 | 56 |
|
||||
| 体积超标(>1M) | 31 |
|
||||
| 证件号异常 | 5 例(3 可自动、2 待人工) |
|
||||
| 疑似串档 | 1(**唯一必须人工判断的**) |
|
||||
| 压缩后 | 300dpi/q85 → 平均 806KB、最大 831KB、**0 张超标** |
|
||||
| 材料识别 | 文件名层 112/112;形态层 99.1%;OCR 内容层 32/32 |
|
||||
|
||||
### 官方模板要点
|
||||
|
||||
- 21 列(A–U):**红=必填 11 项、绿=级联 6 项、黑=选填 4 项**
|
||||
- **「报考职业」「报考工种」在 Excel 里没有下拉,是自由填写** ← 我方产品的核心增量
|
||||
- 「职业技能等级个人申报表」的规范文件名却是 **「工作证明」**(机构既有习惯)
|
||||
- **表头行与工作表名都不固定**(实测一份在第 1 行、一份在第 6 行)
|
||||
|
||||
### 三个易错实现陷阱
|
||||
|
||||
1. **「无」是合法业务值,不是空值**
|
||||
2. **专项职业能力:无工种 / 无等级 / 无补考科目(0 科)**
|
||||
3. 补考约束:三/四/五级最多 1 科,一/二级最多 2 科,专项 0 科
|
||||
|
||||
### 压缩五档(PRD §6.3)
|
||||
|
||||
A4 扫描件 300dpi(2480×3508)/q85 · 一寸照片固定 295×413 · 学籍表为 16:9 截图**保持 PNG 不缩放**
|
||||
|
||||
### 教师端数字口径(原型必须自洽)
|
||||
|
||||
28 人 = 已通过 15 + 待审 4 + 待核对 3 + 有问题 1 + 未提交 5;**完成率 82% 是已提交率(23/28)**
|
||||
|
||||
---
|
||||
|
||||
## 六、环境与工具
|
||||
|
||||
| 项 | 内容 |
|
||||
|---|---|
|
||||
| PG 验证容器 | `docker exec -it pg-verify psql -U postgres -d filldb`;清理 `docker rm -f pg-verify` |
|
||||
| 原型校验脚本 | `python C:\Users\93292\AppData\Local\Temp\opencode\check2.py <html路径>` |
|
||||
| DDL 同步方式 | 从主 PRD 的 ` ```sql ` 代码块自动抽取生成,改文档后重跑抽取脚本 |
|
||||
| 原型约束 | 单文件自包含、零外部依赖、内联 SVG(`<symbol>`+`<use>`,1.7px 描边 currentColor)、无 emoji |
|
||||
| 原型校验基线 | 四份全通过:标签配对 ✓ / CSS 变量 ✓ / SVG 无冗余 ✓ / emoji 0 ✓ |
|
||||
|
||||
---
|
||||
|
||||
## 七、重要教训
|
||||
|
||||
### 教训一:脚本审计前必须先验证脚本本身
|
||||
P2 审计时脚本因未匹配加粗编号 ``| **FR-614** |``,误报 3 个"需求条目问题"(实为假警报);若直接采信并"修复",会引入 3 处新错误。同时 FR 总数由误算的 102 修正为 **115**。
|
||||
|
||||
### 教训二:写新文档必须回查已有编号体系,不能凭记忆
|
||||
P4 写 M5 校验引擎时,我凭记忆用了一套 VAL 编号,**与 PRD 权威清单整体错位**:
|
||||
|
||||
| P4 原写法 | 我以为的含义 | PRD 实际定义 |
|
||||
|---|---|---|
|
||||
| VAL-01 | 姓名与名册一致 | **证件号格式与校验位** |
|
||||
| VAL-03 | 证件号格式 | **姓名与名册一致** |
|
||||
| VAL-06 | 双扩展名 | **文件格式白名单** |
|
||||
| VAL-07 | 多扩展名冲突 | **文件体积上限** |
|
||||
|
||||
**后果**:若不修正,开发按 P4 实现会把规则完全接错,且两份文档互相矛盾。
|
||||
**修正**:已全部重映射到 PRD 体系(单次扫描替换避免 01↔03 碰撞),并给 PRD 补了 P4 需要的三条新规则 **VAL-18 文件名规范化 / VAL-19 疑似串档 / VAL-20 体积下限保护**。
|
||||
**规则**:新增文档引用任何已有编号(FR/VAL/错误码/接口)前,**先脚本抽取权威清单核对**,不凭记忆。
|
||||
|
||||
### 教训三:系统里的数据不会错,错的一定是「归属」
|
||||
客户指出:**"系统应该不存在名称错误的情况。是先录入了有这个姓名和身份证,然后才能填写的。"**
|
||||
|
||||
这指出一个真实的设计缺陷:
|
||||
|
||||
| 位置 | 会不会错 | 说明 |
|
||||
|---|---|---|
|
||||
| **填报单里的姓名/证件号** | **永远不会错** | 门禁已核验 + 字段锁定。客户说得对 |
|
||||
| 文件名里的姓名/证件号 | 可能不同 | **但这不是"名称错误",而是"这份文件可能不属于这个人"** |
|
||||
|
||||
**原设计的错误**:把"文件名姓证件号与名册不符"处理为「自动按名册修正」(信息级),
|
||||
这会把"A 传了 B 的材料"**静默洗白**成 A 的材料——审核时完全看不出来,事后无从追溯。
|
||||
|
||||
```
|
||||
学生 A 在微信里误选了同学 B 的材料
|
||||
↓ 文件名:李四-3610...(B 的号码,校验位通过)
|
||||
错误做法:判"与名册不符" → 改成 张三-3610... → 信息级,A 看不到
|
||||
↓ 审核:班主任看到一份名字正确的材料 → 通过
|
||||
↓ 最终:B 的工作证明被认定成 A 的 ← 灾难
|
||||
```
|
||||
|
||||
**修正(客户已确认)**:
|
||||
1. **判定优先级重排**为 `证件号反查名册 > OCR 内容 > 姓名(仅辅助)`
|
||||
2. 新增 **VAL-21 文件归属校验**:归属存疑一律**阻断提交**,且**不提供"仍然继续"**
|
||||
3. **姓名不再作为独立规则**——只在证件号佐证时按名册取值,仅留痕不打扰学生
|
||||
4. 核心判据:**文件名证件号能否在名册里反查到"另一个人"**(一次查询,走唯一索引,< 1ms)
|
||||
|
||||
**精髓在于两条的区分**:
|
||||
- **校验位失败** = 不是一个真实号码 → 只是写错了 → **可以自动改**
|
||||
- **校验位通过** = 一个真实存在的自然人 → 改了就是改了身份 → **必须阻断**
|
||||
|
||||
### 教训五:联系方式不进系统
|
||||
客户指出:**"学生本来就有班主任联系方式,不需要系统里面体现。"**
|
||||
|
||||
原设计在门禁失败页放了一个「联系老师」按钮(脱敏手机号 + 一键拨打),并为此专门开了一个接口 `GET /mp/roster/contact`。
|
||||
已移除:
|
||||
|
||||
| 移除项 | 原因 |
|
||||
|---|---|
|
||||
| `GET /mp/roster/contact` 接口 | 不必要(用户已知道怎么找人)+ 有维护成本(换人/换号要同步)+ 多一处隐私暴露面 |
|
||||
| 门禁失败页「联系老师」按钮 | 同上。改为只保留两条**系统内动作**:重新填写 / 申请加入名单 |
|
||||
| 功能逻辑卷中的联系方式展示描述 | 同步 |
|
||||
|
||||
**保留**:纯文案提示(如"如有疑问请联系老师")——**不涉及任何联系方式数据**。
|
||||
**接口总数**:122 → **121**。
|
||||
|
||||
### 教训四:「社会评价」与「社会收集」只差一个字
|
||||
`cert_type`(认定类型:自主认定 / **社会评价** / 专项职业能力)决定**填什么材料**;
|
||||
`collect_mode`(收集模式:学校收集 / **社会收集**)决定**谁审核**。二者可任意组合,已在 PRD §5.3 与接口 §2.3 分别写明。
|
||||
另外补全了 `cert_type` 的第三个取值(专项职业能力)在 DDL 注释与附录中的遗漏。
|
||||
|
||||
### 附带修正
|
||||
- **体积口径**:原型导出中心曾写"475MB → 22.4MB(95%)",与 PRD 及历史整理器屏(37MB/92%)矛盾。实为混淆——**444→22MB 是"工作证明"单项,475→37MB 是全班 112 文件合计**。已统一为 37MB / 92%。
|
||||
|
||||
---
|
||||
|
||||
## 八、外部依赖(未回)
|
||||
|
||||
`01-客户与机构确认函.md` 已发出待回,其中**最高优先级 3 项**:
|
||||
|
||||
- **A1** 认定机构是否书面接受 300dpi 扫描件
|
||||
- **B1** 名单外的人怎么处理(建议加"申请加入名单→管理员审批"通道)
|
||||
- **B3** 是否允许学生修改老师预填字段(建议:姓名/证件号/报考三项锁定,其余可改留痕)
|
||||
|
||||
未回复则按文档内建议方案推进。
|
||||
File diff suppressed because it is too large
Load Diff
+3012
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user