Commit Graph

2 Commits

Author SHA1 Message Date
lovife e85a25a97f feat(roster): 名册导入前端 —— 任务发布现在真的能在界面上走通了
第 2 步。在此之前「配置 → 任务就绪」缺了**名册**那一环:班级能配、材料能配、
表单能配,但名册只能靠 SQL 造,于是任务在界面上**根本发布不了**(40016 名册非空)。

一、新增(frontend-pc/)
   src/apis/xt/roster.ts               导入(multipart)+ 分页 + 类型 + 状态字典
   src/views/task/TaskRosterDrawer.vue 「名单管理」抽屉:导入 → 体检报告 → 名册列表
   views/task/index.vue                 加「名单管理」入口;已截止/已归档只读仍可查看
   config/xt-routes.ts                  顶层「名单管理」菜单说明改为指向任务行

二、★ 验证了"发布真的通"(e2e-m2-roster.py 第 8 节)
   配覆盖班级 → 配必传材料 → **发布成功** → 状态变「收集中」→ stat 仍带分母。
   force=true 只跳过 P1 提醒,那 5 项阻断级校验 force 也绕不过 —— 所以是真链路。

三、界面上五个刻意的设计
   1. 先体检再导入(两步):实测表头在第 1 行或第 6 行,一步导入只会看到
      "成功 0 条",然后以为文件坏了
   2. 明说「前面那 5 行是标题与说明,已跳过,这不是导错了」—— 不解释的话
      用户会去改自己那份本来正确的表格
   3. 行号 + 列字母 + 原值 + 原因四样一起给,用户拿着就能回去改 Excel
   4. dryRun 默认 true(服务端也默认 true):不显式要求就不写库
   5. 阻断行与警告行分两张表:混在一起会让人以为整批都没进
   原因后面跟**机器码**而不是中文标签(第一版给中文,和上面那句原因重复了一遍)

四、顺带修掉一处「界面在说谎」
   名册导进来了,任务列表却还显示「待导入名册」—— TaskStatVO 恒为 null 是 M1 的
   既有缺口(注释写着"名册为空时返回 null,由界面显示待导入名册")。现在名册有了:
   · TaskService.fillStats():一次算一页(两条批量查询,不逐任务查)
   · **名册为空时仍返回 null**,口径没有放宽 —— 显示 0% 看起来像"没人填",
     而真相是"还没有分母"
   · abnormalCount / avgAuditHours 仍为 null(要 M4 的校验表 / M5 的审核记录)
   · e2e-m1-task 那条断言改成措辞更准的"名册为空时 stat 为 null"(意图不变),
     并在 e2e-m2-roster 补 4 条"名册有值后 stat 该有值"
   这条也是第 2 步**自己制造**的问题:能导名册之后,"待导入名册"从诚实变成说谎。

五、为什么顶层「名单管理」菜单还是"待建"
   名册是**按任务**的,独立页得先选任务(多一步且容易选错);它和「配置班级」
   「材料规则」「表单设计」是同一层的配置。报考范围确认(M2-04/05)会接着做进
   同一个抽屉,那时也不需要新页面。所以改的是那句已过期的"对应里程碑 M2"。

六、浏览器验证(真链路,只绕开"手指点选")
   文件框没法自动化,但可以把夹具放到 frontend-pc/public/ 再喂进去:
     fetch 字节 → new File → DataTransfer → input.files → dispatch change
   实测:3 人被正确导入、脱敏证件号、报考三项、扩展字段、状态列、搜索全对。
   夹具用完即删。

七、回归
   全量 622 条断言、0 失败(名册 E2E 62 → 67)
   frontend-pc: vue-tsc 0 错、eslint 目标文件 0 错
   verify-docs.py 19 项全通过

八、两个流程教训(已写进交接摘要)
   · `Test-Path jar` 不能当作"打包成功":这次 mvn 失败而旧 jar 还在,
     于是检查得到"打包 ✓",测试对着旧 jar 跑,四条新断言全红。
     必须看退出码 / BUILD SUCCESS。
   · `call()` 返回的是 (status, body) 元组不是字典 —— 我新写的那节忘了解包。
     与第 1 步"已存在判断写反"同类:新写的代码最容易在细节上错。

九、下一步(第 3 步)
   报考范围聚合与一致性体检(M2-04/05)—— 产品的核心增量。
   先确认:sys_eval_plan 那几张"评价计划"表还没落地(FR-311 未做),
   需要先定"计划从哪来"。
2026-09-29 17:17:55 +08:00
lovife daf42608d9 feat(task): M1-01 收集任务 CRUD 与状态机
后端:/tasks 建改删查 + publish/pause/resume/close/archive 六个动作,
按角色过滤可见范围(班主任只看自己带的班级的任务)。
前端:收集任务列表页 + 建/改任务弹窗(命名模板带实时预览与变量插入)。

验收:e2e-m1-task.py 71/71;e2e-m0.py 36/36、e2e-m0-user.py 55/55 回归通过;
前端 typecheck 0 错 / build 通过 / 浏览器肉眼确认。

三条硬约束(改动时当成规则):

1) 「暂停」与「截止」不是一回事。暂停:学生不能交、已提交的照常审、
   且可恢复;截止不可逆。做成一个会让"临时冻一下"变成不可逆操作。
2) 发布前置 5 项全是阻断级,force 绕不过:名册非空(40016)、名册含姓名与
   证件号(40017)、至少 1 项必传材料(40018)、已设置覆盖班级(40019)、
   命名模板可用(40020)。只有 P1 级提醒(某班未指定初审人)可 force 跳过。
3) collectMode / certType 发布后不可改 —— 它们决定审核流与材料清单,
   改了会让已填过的学生面对另一份表单。

本轮修掉的 3 个真问题:

1) collect_task_org 没有 deleted 列,而我让它继承了带 @TableLogic 的基类
   —— MyBatis-Plus 会给每条查询自动加 deleted = 0,运行时报"列不存在",
   编译期完全看不出来。改为不继承基类、显式声明字段;
   "移除"语义走 status = 0,不要对它调 deleteById(会变成物理删除)。

2) 任务编码查重被逻辑删除遮蔽:用 MP 的 selectCount 会自动过滤 deleted = 0,
   但数据库的唯一约束不看软删标记 —— 软删掉的任务仍占着那个编码,
   于是"查重说没有、插进去说重复",报 duplicate key(500)。
   另外原用 COUNT+1 也错:删掉中间一个后计数会退回去,算出已占用的号。
   改为 MAX(序号)+1,且查重语句刻意不加 deleted = 0(与唯一约束同口径);
   编码一次用过就不复用(它同时是文件目录名,复用会把两批文件混进同一目录)。

3) Boot 4 把 Jackson 自动配置挪到了 spring-boot-starter-jackson,项目没引,
   导致容器里没有 ObjectMapper bean(@Autowired 直接启动失败),
   且 application.yml 的 spring.jackson.* 静默失效 —— 其中
   time-zone: Asia/Shanghai 不生效最危险:本机时区恰好是上海时看不出来,
   部署到 UTC 服务器后所有时间会整体偏 8 小时。
   注意 spring-boot-starter-json 在 Boot 4 已无 4.x 版本(仓库里最高 3.5.6)。
   补依赖,但任务配置的 JSON 读写仍不用 Jackson —— Boot 4 用 Jackson 3
   而 jjwt-jackson 带来 Jackson 2,打包后两个 jackson-databind 并存
   (2.21.5 + 3.1.5),import 哪个都是赌;改用项目已直接声明的 hutool-all。

已知缺口(都写进了代码注释与交接摘要):

- 发布在前置数据齐备前不可达(名册属 M2、材料属 M1-04、覆盖班级属 M1-02),
  E2E 用 SQL 夹具造数据才走到发布之后的状态 —— M1-02/M1-04/M2 落地后
  要回来把夹具换成真实接口。
- 命名规则试跑目前只做静态校验(变量名合法 + 必含姓名/证件号/材料名),
  完整形态(拿真实名册跑一遍查非空与不重名)属 M1-09 / M3-01。
- stat 恒为 null(依赖名册与提交单)。故意不返回全 0 的对象 ——
  那会让界面显示"完成率 0%",而真相是"还没有分母";界面显示「待导入名册」。
- 任务编码并发撞号由唯一约束兜底 + 可读提示(不再 500),未做分布式锁。

顺带:

- 两个列表页的列宽按实测可用宽度重算(1366 屏:收集任务 824px、
  账号管理因左侧组织树只有 574px),并把"操作列必须 fixed + 列宽口径"
  写进注释 —— 之前把「状态」列整个遮住、把操作链接挤成竖排。
- 状态与最后登录并成一列,给窄屏省出 150px。
2026-09-28 23:48:21 +08:00