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

- 新增 utils/formDataGroups.ts:groups 顺序展示、schema 外字段归「其他信息」、无 schema 回落按 key
- ApplyManage 详情抽屉改按分组渲染;ApplyVo 类型补 formSchemaJson
This commit is contained in:
chenxing
2026-09-17 14:51:03 +08:00
parent ac36b31a32
commit c2023755ac
3 changed files with 73 additions and 4 deletions
+2
View File
@@ -182,6 +182,8 @@ export interface ApplyVo {
itemSnapshotJson?: string;
formVersion?: number;
formDataJson?: string;
/** 办理表单 schema(仅详情出参;「申请内容」按 groups/fields 显示中文 label 与分组的唯一事实源) */
formSchemaJson?: string;
attachmentsJson?: string;
submitterId: number;
submitterName: string;
+53
View File
@@ -0,0 +1,53 @@
/**
* 「申请内容」展示整形(2026-09-17):办件的 formDataJson 以英文 key 存储,
* 展示层以办理表单 schemaJson(详情出参 formSchemaJson,版本=提交时 formVersion)为
* key→中文 label 与分组的唯一事实源:
* groups[{title, fields[{key,label}]}] 顺序即展示顺序,只保留有填报值的字段;
* schema 里没有而数据里有的 key(历史数据/字段下架)归入「其他信息」兜底组;
* 无 schema 时整体回落按 key 展示(老数据不报错)。
*/
export interface FormDataRow {
key: string;
label: string;
value: any;
}
export interface FormDataGroup {
title: string;
rows: FormDataRow[];
}
function safeParse(json?: string): any {
try {
return JSON.parse(json || "{}") ?? {};
} catch {
return {};
}
}
export function buildFormDataGroups(schemaJson?: string, formDataJson?: string): FormDataGroup[] {
const data = safeParse(formDataJson);
if (typeof data !== "object" || Array.isArray(data) || data === null) return [];
const schema = safeParse(schemaJson);
const groups: any[] = Array.isArray(schema.groups) ? schema.groups : [];
const used = new Set<string>();
const out: FormDataGroup[] = [];
groups.forEach((g, i) => {
const rows: FormDataRow[] = [];
for (const f of Array.isArray(g.fields) ? g.fields : []) {
const key = f?.key;
if (!key || !(key in data)) continue;
used.add(key);
rows.push({ key, label: f.label || key, value: data[key] });
}
if (rows.length) {
out.push({ title: g.title || (groups.length > 1 ? `第 ${i + 1} 部分` : "申请内容"), rows });
}
});
const rest = Object.keys(data)
.filter((k) => !used.has(k))
.map((key) => ({ key, label: key, value: data[key] }));
if (rest.length) out.push({ title: "其他信息", rows: rest });
if (!out.length) out.push({ title: "申请内容", rows: Object.keys(data).map((key) => ({ key, label: key, value: data[key] })) });
return out;
}
+18 -4
View File
@@ -12,6 +12,7 @@ import { Message } from "view-ui-plus";
import { applyApi } from "@/api/module/svc";
import type { ApplyVo, FlowProgressVo } from "@/types/svc";
import { useSite } from "@/utils/site";
import { buildFormDataGroups, FormDataGroup } from "@/utils/formDataGroups";
const { currentSiteCode, sites, loadSites, setSite } = useSite();
const loading = ref(false);
@@ -44,6 +45,9 @@ const SNAPSHOT_LABEL: Record<string, string> = {
contactPhone: "咨询电话",
};
/** 申请内容:按办理表单 schema 显示中文 label 与分组(详情出参 formSchemaJson) */
const contentGroups = ref<FormDataGroup[]>([]);
const cols = [
{ title: "办件号", key: "id", width: 90 },
{ title: "事项", key: "itemTitle", minWidth: 180 },
@@ -99,6 +103,7 @@ async function openDetail(row: ApplyVo) {
progress.value = null;
try {
detail.value = (await applyApi.detail(row.id).send()) as any;
contentGroups.value = buildFormDataGroups(detail.value?.formSchemaJson, detail.value?.formDataJson);
} catch (e: any) {
Message.error(e?.message || "加载办件详情失败");
} finally {
@@ -179,10 +184,13 @@ onMounted(async () => {
<template v-if="detail.formDataJson">
<Divider orientation="left">申请内容</Divider>
<div class="kv" v-for="(v, k) in parse(detail.formDataJson)" :key="String(k)">
<span class="k">{{ k }}</span>
<span class="v">{{ displayValue(v) }}</span>
</div>
<template v-for="g in contentGroups" :key="g.title">
<p v-if="g.title !== '申请内容'" class="group-title">{{ g.title }}</p>
<div class="kv" v-for="row in g.rows" :key="row.key">
<span class="k">{{ row.label }}</span>
<span class="v">{{ displayValue(row.value) }}</span>
</div>
</template>
</template>
<Divider orientation="left">审批轨迹</Divider>
@@ -233,6 +241,12 @@ onMounted(async () => {
font-size: 13px;
border-bottom: 1px dashed #eee;
}
.group-title {
font-weight: 600;
font-size: 13px;
color: #515a6e;
margin: 10px 0 2px;
}
.k {
width: 110px;
color: #999;