feat(apply): 办件详情「申请内容」按表单 schema 渲染——中文 label + 分组
build / build (push) Successful in 2m11s

- 新增 utils/formDataGroups.ts:groups 顺序展示、schema 外字段归「其他信息」、无 schema 回落按 key
- ApplyDetail 改用详情出参 formSchemaJson(删除再调 /api/apply/item 取 schema 的旁路);ApplyVo 类型补 formSchemaJson
- 顺带入库 hall 动态表单修复(就地写 reactive + 内联 validate,2026-09-17 会话)
This commit is contained in:
chenxing
2026-09-17 14:51:04 +08:00
parent f87523c524
commit 05b6c78a93
6 changed files with 273 additions and 36 deletions
+31 -2
View File
@@ -14,9 +14,9 @@ import { computed, onMounted, reactive, ref } from "vue";
import { useRoute, useRouter } from "vue-router";
import { Message } from "view-ui-plus";
import { applyApi } from "@/api/module/svc";
import type { ApplyAttachmentItem } from "@/types/svc";
import { useSite } from "@/utils/site";
import DynamicForm from "@/components/DynamicForm.vue";
const route = useRoute();
const router = useRouter();
const { resolveSite, rememberItem } = useSite();
@@ -31,6 +31,7 @@ const item = ref<Record<string, any> | null>(null);
const loadError = ref("");
const siteCode = ref("");
const formData = reactive<Record<string, any>>({});
const dynFormRef = ref<InstanceType<typeof DynamicForm> | null>(null); // DynamicForm.validate()
const schemaJson = computed(() => (item.value?.schemaJson as string) || "");
@@ -56,6 +57,30 @@ async function load() {
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
/**
* 从 formData 提取附件行(upload/image 字段值为 /api/file/upload 返回体数组,
* 每项含附件行 id):后端 submit 依据 attachments 做办件材料绑定(bindApply),
* 不传则附件行永远未落 topic,表现为「附件传了但提交后无效」。
*/
function collectAttachments(): ApplyAttachmentItem[] {
const out: ApplyAttachmentItem[] = [];
try {
const groups = JSON.parse(schemaJson.value || "{}").groups || [];
for (const g of groups) {
for (const f of g.fields || []) {
if (f.type !== "upload" && f.type !== "image") continue;
const list = formData[f.key];
for (const it of Array.isArray(list) ? list : []) {
if (it && it.id != null) out.push({ fieldKey: f.key, attachmentId: it.id });
}
}
}
} catch {
// schema 解析失败时只丢附件绑定,不阻断提交
}
return out;
}
/** 轮询办件状态直到离开 PENDING;超时返回最后一次看到的办件(可能仍是待提交) */
async function awaitFlowStart(applyId: number, first: any) {
let latest = first;
@@ -75,6 +100,9 @@ async function awaitFlowStart(applyId: number, first: any) {
async function submit() {
if (!item.value) return;
// 前端先按 schema 校验(字段下方内联红字 + 滚到第一个错误),不再把"必填项未填写"
// 这类校验留给后端弹 Message——后端强校验仍保留作兜底
if (dynFormRef.value && !dynFormRef.value.validate()) return;
submitting.value = true;
try {
const apply: any = await applyApi
@@ -82,6 +110,7 @@ async function submit() {
siteCode: siteCode.value,
itemCode: String(route.query.itemCode),
formData: Object.keys(formData).length ? JSON.stringify(formData) : undefined,
attachments: collectAttachments(),
})
.send();
@@ -121,7 +150,7 @@ onMounted(load);
</div>
</div>
<Divider v-if="schemaJson" orientation="left">申请表单</Divider>
<DynamicForm v-if="schemaJson" :schema-json="schemaJson" v-model:form-data="formData" />
<DynamicForm v-if="schemaJson" ref="dynFormRef" :schema-json="schemaJson" :form-data="formData" />
<div v-else class="no-form">该事项无需填写表单,提交后由办理部门联系确认。</div>
<div class="actions">
<Button class="sh-btn-primary" type="primary" :loading="submitting" @click="submit">