fix(post-p95): 编辑态按钉住版渲染,自填/代填主体展示改判 submitter

- 跨版本编辑必挂:编辑态此前吃 resolve 返回的当前发布版渲染,服务端却按答卷钉住
  的旧版校验,升版后编辑永远 400。改为拉 /my/detail 取钉住版 groups,渲染与校验
  同源(renderGroups)。
- 自填行 subject 归一为本人后,"本人自填"与"他人代填"的判据从"subject 是否为空"
  改为"subject 是否等于 submitter",同步修正 My.vue / Data.vue / AnswerDetail.vue
  三处展示与 tooltip。

验证:type-check + build 双绿。
This commit is contained in:
chenxing
2026-10-16 15:04:52 +08:00
parent 48e46e367b
commit 25dc3eb2e5
4 changed files with 31 additions and 10 deletions
+27 -6
View File
@@ -14,7 +14,7 @@ import { useRoute } from "vue-router";
import { fieldWidget } from "@/components/fields"; import { fieldWidget } from "@/components/fields";
import type { DesignField } from "@/types/formSchema"; import type { DesignField } from "@/types/formSchema";
import type { ResolvedForm, MyAnswerItem } from "@/types/answer"; import type { ResolvedForm, MyAnswerItem } from "@/types/answer";
import { resolveForm, submitAnswer, listMyAnswers, deleteMyAnswer, editMyAnswer } from "@/api/module/fill"; import { resolveForm, submitAnswer, listMyAnswers, deleteMyAnswer, editMyAnswer, getMyAnswerDetail } from "@/api/module/fill";
import { startHeightReporting, reportSubmitResult, reportNeedLogin } from "@/utils/embed"; import { startHeightReporting, reportSubmitResult, reportNeedLogin } from "@/utils/embed";
import Utils from "@/utils"; import Utils from "@/utils";
import { Message, Modal } from "view-ui-plus"; import { Message, Modal } from "view-ui-plus";
@@ -107,9 +107,8 @@ function validateField(field: DesignField): string | null {
function validateAll(): boolean { function validateAll(): boolean {
const errors: Record<string, string> = {}; const errors: Record<string, string> = {};
if (!form.value?.groups) return true; // 校验与渲染同源:编辑态吃钉住版 groups(renderGroups),否则按当前发布版(Bug#1)
const groups = form.value.groups as any[]; for (const group of renderGroups.value) {
for (const group of groups) {
for (const field of group.fields || []) { for (const field of group.fields || []) {
const err = validateField(field); const err = validateField(field);
if (err) errors[field.key] = err; if (err) errors[field.key] = err;
@@ -172,6 +171,14 @@ async function handleSubmit() {
const myAnswers = ref<MyAnswerItem[]>([]); const myAnswers = ref<MyAnswerItem[]>([]);
const editingAnswer = ref<MyAnswerItem | null>(null); const editingAnswer = ref<MyAnswerItem | null>(null);
/** 编辑态渲染用的 groups = 答卷钉住的那一版(/my/detail);null/空=未进入编辑 */
const editingGroups = ref<any[] | null>(null);
/** 渲染与校验同源的 groups:编辑态吃钉住版,否则吃当前发布版 */
const renderGroups = computed<any[]>(() => {
if (editingAnswer.value) return editingGroups.value || [];
return (form.value?.groups as any[]) || [];
});
async function loadMyAnswers() { async function loadMyAnswers() {
try { try {
@@ -181,9 +188,21 @@ async function loadMyAnswers() {
} }
} }
function handleEditAnswer(answer: MyAnswerItem) { async function handleEditAnswer(answer: MyAnswerItem) {
if (submitting.value) return;
try {
// Bug#1:编辑必须按答卷**钉住的版本**渲染(/my/detail 返回该版本的 groups)。
// 用当前发布版渲染,客户端会带上/漏掉升版后新增或删除的字段,而服务端按钉住旧版校验,
// 结果必然是"含未知字段"或"缺必填"——表单升过一次版,编辑就永久 400。
const detail = await getMyAnswerDetail(answer.answerId);
editingGroups.value = (detail.groups as any[]) || [];
} catch (e: any) {
Message.error(e?.message || "加载答卷失败,无法编辑");
return;
}
editingAnswer.value = answer; editingAnswer.value = answer;
formValues.value = { ...answer.values }; formValues.value = { ...answer.values };
formErrors.value = {};
window.scrollTo(0, 0); window.scrollTo(0, 0);
} }
@@ -198,6 +217,7 @@ async function handleUpdateAnswer() {
await editMyAnswer(editingAnswer.value.answerId, formValues.value); await editMyAnswer(editingAnswer.value.answerId, formValues.value);
Message.success("更新成功"); Message.success("更新成功");
editingAnswer.value = null; editingAnswer.value = null;
editingGroups.value = null;
formValues.value = {}; formValues.value = {};
await loadMyAnswers(); await loadMyAnswers();
} catch (e: any) { } catch (e: any) {
@@ -225,6 +245,7 @@ function handleDeleteAnswer(answer: MyAnswerItem) {
function handleCancelEdit() { function handleCancelEdit() {
editingAnswer.value = null; editingAnswer.value = null;
editingGroups.value = null;
formValues.value = {}; formValues.value = {};
formErrors.value = {}; formErrors.value = {};
} }
@@ -327,7 +348,7 @@ const readonlyReason = computed(() => {
<!-- 表单字段 --> <!-- 表单字段 -->
<div v-if="!submitted && !isReadonly || editingAnswer" class="form-groups"> <div v-if="!submitted && !isReadonly || editingAnswer" class="form-groups">
<div v-for="group in (form.groups as any[])" :key="group.key" class="form-group"> <div v-for="group in renderGroups" :key="group.key" class="form-group">
<h3 v-if="group.label" class="group-label">{{ group.label }}</h3> <h3 v-if="group.label" class="group-label">{{ group.label }}</h3>
<div v-for="field in (group.fields || [])" :key="field.key" class="form-item"> <div v-for="field in (group.fields || [])" :key="field.key" class="form-item">
<label class="form-label"> <label class="form-label">
+1 -1
View File
@@ -208,7 +208,7 @@ const tableColumns = computed(() => {
render: (h2: any, p: any) => render: (h2: any, p: any) =>
h2( h2(
"span", "span",
`提交人 ${p.row.submitterId}${p.row.subjectId ? `|主体 ${p.row.subjectId}` : "(本人自填)"}` `提交人 ${p.row.submitterId}${p.row.subjectId && p.row.subjectId !== p.row.submitterId ? `|主体 ${p.row.subjectId}` : "(本人自填)"}`
) )
} }
]; ];
+1 -1
View File
@@ -151,7 +151,7 @@ onMounted(async () => {
<div class="meta-row"><span class="meta-key">报名号</span><span>{{ detail.answerNo || "—" }}</span></div> <div class="meta-row"><span class="meta-key">报名号</span><span>{{ detail.answerNo || "—" }}</span></div>
<div class="meta-row"><span class="meta-key">提交时间</span><span>{{ detail.submitTime }}</span></div> <div class="meta-row"><span class="meta-key">提交时间</span><span>{{ detail.submitTime }}</span></div>
<div class="meta-row"><span class="meta-key">提交人 / 被代填人</span> <div class="meta-row"><span class="meta-key">提交人 / 被代填人</span>
<span>{{ detail.submitterId }} / {{ detail.subjectId ?? "—(本人自填)" }}</span> <span>{{ detail.submitterId }} / {{ detail.subjectId && detail.subjectId !== detail.submitterId ? detail.subjectId : "(本人自填)" }}</span>
</div> </div>
<div class="meta-row"><span class="meta-key">表单 uuid</span><code>{{ detail.formUuid }}</code></div> <div class="meta-row"><span class="meta-key">表单 uuid</span><code>{{ detail.formUuid }}</code></div>
</div> </div>
+2 -2
View File
@@ -271,10 +271,10 @@ const tableColumns = computed(() => {
width: 170, width: 170,
render: (h2: any, p: any) => render: (h2: any, p: any) =>
h2(Tooltip, { h2(Tooltip, {
content: `submitter_id=${p.row.submitterId}${p.row.subjectId ? `,subject_id=${p.row.subjectId}(他人代填)` : ",无代填主体"}`, content: `submitter_id=${p.row.submitterId}${p.row.subjectId && p.row.subjectId !== p.row.submitterId ? `,subject_id=${p.row.subjectId}(他人代填)` : ",无代填主体"}`,
transfer: true transfer: true
}, { }, {
default: () => [h2("span", `提交人 ${p.row.submitterId}${p.row.subjectId ? `|主体 ${p.row.subjectId}` : ""}`)] default: () => [h2("span", `提交人 ${p.row.submitterId}${p.row.subjectId && p.row.subjectId !== p.row.submitterId ? `|主体 ${p.row.subjectId}` : ""}`)]
}) })
}); });
cols.push( cols.push(