From eff5a761bfa8e4fe7509ae0f75881d259ada3510 Mon Sep 17 00:00:00 2001
From: lovife <474239041@qq.com>
Date: Tue, 29 Sep 2026 11:28:53 +0800
Subject: [PATCH] =?UTF-8?q?feat(fill):=20=E8=A1=A8=E5=8D=95=E7=BB=93?=
=?UTF-8?q?=E6=9E=84=E6=8E=A5=E5=8F=A3=EF=BC=88=E5=AD=A6=E7=94=9F=E7=AB=AF?=
=?UTF-8?q?=E5=A1=AB=E6=8A=A5=E7=9A=84=E8=B5=B7=E7=82=B9=EF=BC=89?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
GET /api/v1/mp/tasks/{id}/form —— 按 §9.2 装配表单结构 + 当前值。
两道前置门槛(缺一不可):
1) 任务在收集中(草稿/暂停/截止/归档都不给填);
2) **这个人已过门禁**(有绑定关系)。没过门禁就给表单 = "没核对身份也能填",
门禁就白设了。
字段三态(§9.2)实测正确:
real_name/id_card_no/occupation/worktype/job_level → 只读 + 老师已填
education/phone/is_student → 可改 + 「老师已填,请核对」
其余 → 空
其中 occupation/worktype/job_level 只读是刻意的:它们由老师按机构报考策略确定,
学生不懂也不该改 —— 而报错工种在机构那边是直接不受理,等发现时报名已结束。
为什么 locked 必须是真的只读、而不是"前端灰一下":让它看起来可编辑(哪怕服务端
会拒绝)只会让人白改一次。所以前端渲染成只读,服务端再拒绝一次(两层)。
身份证号与手机号只回**脱敏值**:表单上回显完整证件号完全没有必要(学生自己知道),
而页面上多一处明文就多一处泄露面(截图、录屏、分享)。
两处实现说明:
· prefilled 与 locked 独立:prefilled=true,locked=false → 可改但标注(如联系电话,
名册里常是家长的老号码);prefilled=true,locked=true → 只读灰底。
· FormFieldVO 补了 value / prefilled 两个字段(契约 §9.2 的字段对象里有)。
放在同一个类而不是另建 VO:两者结构完全一致,拆开只会让前端写两套解析。
本轮范围:只做"看得到、读得对"。**草稿保存(POST/PUT /mp/submissions)与
前端表单页是下一段** —— 已在 FillService 里留了 TODO。
验收:用真实会话实测 GET /mp/tasks/17/form,21 个字段分两组返回,三态与脱敏均正确。
---
.../fill/controller/FillController.java | 34 +++
.../fill/module/fill/dto/StudentFormVO.java | 84 +++++++
.../fill/module/fill/service/FillService.java | 212 ++++++++++++++++++
.../fill/module/form/dto/FormFieldVO.java | 17 ++
4 files changed, 347 insertions(+)
create mode 100644 backend/xt-server/src/main/java/cn/tianbaotong/fill/module/fill/controller/FillController.java
create mode 100644 backend/xt-server/src/main/java/cn/tianbaotong/fill/module/fill/dto/StudentFormVO.java
create mode 100644 backend/xt-server/src/main/java/cn/tianbaotong/fill/module/fill/service/FillService.java
diff --git a/backend/xt-server/src/main/java/cn/tianbaotong/fill/module/fill/controller/FillController.java b/backend/xt-server/src/main/java/cn/tianbaotong/fill/module/fill/controller/FillController.java
new file mode 100644
index 0000000..b96e7e5
--- /dev/null
+++ b/backend/xt-server/src/main/java/cn/tianbaotong/fill/module/fill/controller/FillController.java
@@ -0,0 +1,34 @@
+package cn.tianbaotong.fill.module.fill.controller;
+
+import cn.tianbaotong.fill.common.api.ApiResult;
+import cn.tianbaotong.fill.module.fill.dto.StudentFormVO;
+import cn.tianbaotong.fill.module.fill.service.FillService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * 学生端填报(《03-接口设计》§9.x)。
+ *
+ *
要求已登录(学生),且必须已过门禁 —— 见 {@code FillService}。
+ */
+@Tag(name = "小程序/H5 · 填报")
+@RestController
+@RequestMapping("/api/v1/mp")
+@RequiredArgsConstructor
+public class FillController {
+
+ private final FillService fillService;
+
+ @GetMapping("/tasks/{id}/form")
+ @Operation(summary = "表单结构 + 当前值",
+ description = "字段三态见 §9.2:locked=true 只读、prefilled=true 标注「老师已填,请核对」。"
+ + "身份证号只回脱敏值。未过门禁时返回 40007(提示先核对身份)")
+ public ApiResult form(@PathVariable Long id) {
+ return ApiResult.ok(fillService.getForm(id));
+ }
+}
diff --git a/backend/xt-server/src/main/java/cn/tianbaotong/fill/module/fill/dto/StudentFormVO.java b/backend/xt-server/src/main/java/cn/tianbaotong/fill/module/fill/dto/StudentFormVO.java
new file mode 100644
index 0000000..a639ed9
--- /dev/null
+++ b/backend/xt-server/src/main/java/cn/tianbaotong/fill/module/fill/dto/StudentFormVO.java
@@ -0,0 +1,84 @@
+package cn.tianbaotong.fill.module.fill.dto;
+
+import cn.tianbaotong.fill.module.form.dto.FormFieldVO;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 学生端表单(《03-接口设计》§9.2)。
+ *
+ * 字段三态是这一屏的核心(§9.2 的表):
+ *
+ *
+ * | {@code locked} | {@code prefilled} | 学生端表现 |
+ * | true | — | 只读、灰底、右侧提示"如有误请联系老师" |
+ * | false | true | 可编辑,标注「老师已填,请核对」,改后留痕 |
+ * | false | false | 可编辑,正常样式 |
+ *
+ *
+ * ⚠️ 为什么 locked 必须是真的只读、而不是"前端灰一下":
+ * 「报考职业/工种/等级」由老师按机构的报考策略确定,学生不懂也不该改。
+ * 让它看起来可编辑(哪怕服务端会拒绝),只会让人白改一次 ——
+ * 而报错工种在机构那边是直接不受理,等发现时报名已经结束。
+ * 所以前端渲染成只读,服务端再拒绝一次(两层)。
+ */
+@Getter
+@Setter
+@Schema(description = "学生端表单")
+public class StudentFormVO {
+
+ private Long taskId;
+
+ private String taskName;
+
+ /** 表单版本(学生是按某一版填的,留痕用)。 */
+ private Integer formVersion;
+
+ /** 草稿单 id;没有草稿时为 null。 */
+ private Long submissionId;
+
+ /** 提交状态({@code 0} 未开始 / {@code 1} 草稿 / {@code 2} 待审 …)。 */
+ private Integer submissionStatus;
+
+ private List sections = new ArrayList<>();
+
+ /** 材料项(结构见 §3.4)。材料上传属下一段,这里先给出清单。 */
+ private List