feat: 表单使用场景可见闭环——绑定明细/发布引导/schema 预览
build / build (push) Successful in 3m7s

- ServiceFormManage:被绑定列显示"已绑定 N 个事项",弹窗列明细(站点/编码/名称/状态)可直达事项编辑(跨站点先切站点);发布未绑定时 confirm 引导前往办事事项;保存文案带下一步;编辑弹窗"预览效果"复用 DynamicForm 渲染 schemaJson
- ServiceItemEdit:选中草稿/停用表单时红字提示"发布后才能在发起页实际填写",下拉选项标注状态
- types/svc.ts:SvcFormVo +refItems、新增 SvcFormItemBrief
This commit is contained in:
chenxing
2026-09-16 16:34:13 +08:00
parent 7277251feb
commit 21d19eceda
3 changed files with 109 additions and 9 deletions
+10
View File
@@ -152,6 +152,16 @@ export interface SvcFormVo {
schemaJson: string; schemaJson: string;
publishTime?: string; publishTime?: string;
referenced?: boolean; referenced?: boolean;
/** 绑定本表单的事项明细(表单页反查使用位置;sch 级共享,可能跨站点) */
refItems?: SvcFormItemBrief[];
}
export interface SvcFormItemBrief {
id: number;
itemCode: string;
title: string;
siteCode: string;
status: number;
} }
export interface SvcFormSaveRequest { export interface SvcFormSaveRequest {
+85 -8
View File
@@ -1,11 +1,14 @@
<script setup lang="ts"> <script setup lang="ts">
/** 表单管理(modId 25):列表/JSON 编辑(发布版保存=自动升版本)/发布/删除/版本轨迹。 */ /** 表单管理(modId 25):列表/JSON 编辑(发布版保存=自动升版本)/发布/删除/版本轨迹/绑定明细/schema 预览。 */
import { onMounted, ref } from "vue"; import { onMounted, ref } from "vue";
import { useRouter } from "vue-router";
import { Message, Modal } from "view-ui-plus"; import { Message, Modal } from "view-ui-plus";
import { svcFormApi } from "@/api/module/svc"; import { svcFormApi } from "@/api/module/svc";
import type { SvcFormVo } from "@/types/svc"; import type { SvcFormItemBrief, SvcFormVo } from "@/types/svc";
import { useSite } from "@/utils/site"; import { useSite } from "@/utils/site";
import DynamicForm from "@/components/DynamicForm.vue";
const router = useRouter();
const { currentSiteCode, sites, loadSites, setSite } = useSite(); const { currentSiteCode, sites, loadSites, setSite } = useSite();
const loading = ref(false); const loading = ref(false);
const list = ref<SvcFormVo[]>([]); const list = ref<SvcFormVo[]>([]);
@@ -14,9 +17,15 @@ const versionsModal = ref(false);
const saving = ref(false); const saving = ref(false);
const form = ref<any>({ schemaJson: '{\n "groups": [\n { "title": "基本信息", "fields": [\n { "key": "reason", "label": "申请事由", "type": "textarea", "required": true }\n ] }\n ]\n}' }); const form = ref<any>({ schemaJson: '{\n "groups": [\n { "title": "基本信息", "fields": [\n { "key": "reason", "label": "申请事由", "type": "textarea", "required": true }\n ] }\n ]\n}' });
const versionRows = ref<SvcFormVo[]>([]); const versionRows = ref<SvcFormVo[]>([]);
const refsModal = ref(false);
const refRows = ref<SvcFormItemBrief[]>([]);
const refFormName = ref("");
const previewModal = ref(false);
const previewData = ref<Record<string, any>>({});
const statusName = (s: number) => (s === 1 ? "已发布" : s === 2 ? "停用" : "草稿"); const statusName = (s: number) => (s === 1 ? "已发布" : s === 2 ? "停用" : "草稿");
const statusColor = (s: number) => (s === 1 ? "success" : "default"); const statusColor = (s: number) => (s === 1 ? "success" : "default");
const itemStatusName = (s: number) => (s === 1 ? "已发布" : s === 2 ? "已下线" : "草稿");
async function load() { async function load() {
if (!currentSiteCode.value) return; if (!currentSiteCode.value) return;
@@ -48,7 +57,7 @@ async function save() {
saving.value = true; saving.value = true;
try { try {
await svcFormApi.save(currentSiteCode.value, form.value).send(); await svcFormApi.save(currentSiteCode.value, form.value).send();
Message.success("已保存(已发布版本会自动生成新草稿版本)"); Message.success("已保存为草稿。下一步:发布表单,并在「办事事项」中绑定使用");
modal.value = false; modal.value = false;
load(); load();
} finally { } finally {
@@ -58,8 +67,18 @@ async function save() {
async function publish(row: SvcFormVo) { async function publish(row: SvcFormVo) {
await svcFormApi.publish(currentSiteCode.value, row.id).send(); await svcFormApi.publish(currentSiteCode.value, row.id).send();
Message.success("已发布");
load(); load();
if (!row.referenced) {
// 使用场景引导:发布后唯一消费入口是事项绑定,未绑定的直接带过去
Modal.confirm({
title: "发布成功,下一步去绑定事项",
content: `表单「${row.name}」尚未绑定任何事项。绑定后用户在事项发起页才能填写此表单,是否前往办事事项?`,
okText: "前往办事事项",
onOk: () => router.push("/svc/item"),
});
} else {
Message.success("已发布");
}
} }
function remove(row: SvcFormVo) { function remove(row: SvcFormVo) {
@@ -79,6 +98,31 @@ async function showVersions(row: SvcFormVo) {
versionsModal.value = true; versionsModal.value = true;
} }
/** 绑定明细:这张表单用在了哪些事项上(可跨站点),并可直达事项编辑 */
function showRefs(row: SvcFormVo) {
refFormName.value = row.name;
refRows.value = row.refItems || [];
refsModal.value = true;
}
/** 跨站点明细先切站点再跳编辑,保证事项编辑页站点上下文一致 */
function editRefItem(item: SvcFormItemBrief) {
setSite(item.siteCode);
router.push(`/svc/item/edit?id=${item.id}`);
}
/** schema 预览:发布前即可看到表单长相(复用发起页 DynamicForm 渲染器) */
function openPreview() {
try {
JSON.parse(form.value.schemaJson);
} catch {
Message.error("表单结构不是合法 JSON,无法预览");
return;
}
previewData.value = {};
previewModal.value = true;
}
onMounted(async () => { onMounted(async () => {
await loadSites(); await loadSites();
load(); load();
@@ -89,9 +133,9 @@ const cols = [
{ title: "名称", key: "name", minWidth: 140 }, { title: "名称", key: "name", minWidth: 140 },
{ title: "最新版本", key: "version", width: 90 }, { title: "最新版本", key: "version", width: 90 },
{ title: "状态", slot: "status", width: 90 }, { title: "状态", slot: "status", width: 90 },
{ title: "被绑定", slot: "referenced", width: 90 }, { title: "被绑定", slot: "referenced", width: 110 },
{ title: "发布时间", key: "publishTime", width: 160 }, { title: "发布时间", key: "publishTime", width: 160 },
{ title: "操作", slot: "action", width: 220 }, { title: "操作", slot: "action", width: 260 },
]; ];
</script> </script>
@@ -109,12 +153,13 @@ const cols = [
<Tag :color="statusColor(row.status)">{{ statusName(row.status) }}</Tag> <Tag :color="statusColor(row.status)">{{ statusName(row.status) }}</Tag>
</template> </template>
<template #referenced="{ row }"> <template #referenced="{ row }">
<Tag v-if="row.referenced" color="blue">已绑定事项</Tag> <a v-if="row.referenced" @click="showRefs(row)">已绑定 {{ (row.refItems || []).length }} 个事项</a>
<span v-else>—</span> <span v-else style="color: #999">未绑定</span>
</template> </template>
<template #action="{ row }"> <template #action="{ row }">
<Button size="small" type="text" @click="open(row)">编辑</Button> <Button size="small" type="text" @click="open(row)">编辑</Button>
<Button v-if="row.status === 0" size="small" type="text" @click="publish(row)">发布</Button> <Button v-if="row.status === 0" size="small" type="text" @click="publish(row)">发布</Button>
<Button v-if="!row.referenced" size="small" type="text" @click="router.push('/svc/item')">去绑定</Button>
<Button size="small" type="text" @click="showVersions(row)">版本</Button> <Button size="small" type="text" @click="showVersions(row)">版本</Button>
<Button size="small" type="text" style="color: #ed4014" @click="remove(row)">删除</Button> <Button size="small" type="text" style="color: #ed4014" @click="remove(row)">删除</Button>
</template> </template>
@@ -135,6 +180,7 @@ const cols = [
契约:{"groups":[{title, fields:[{key,label,type,required,pattern,min,max,maxLength,options,accept,maxSize,maxFiles,…}]}]} 契约:{"groups":[{title, fields:[{key,label,type,required,pattern,min,max,maxLength,options,accept,maxSize,maxFiles,…}]}]}
;一期控件:text/textarea/number/date/select/radio/upload ;一期控件:text/textarea/number/date/select/radio/upload
</div> </div>
<Button size="small" type="text" @click="openPreview">预览效果</Button>
</FormItem> </FormItem>
</Form> </Form>
<template #footer> <template #footer>
@@ -143,6 +189,37 @@ const cols = [
</template> </template>
</Modal> </Modal>
<Modal v-model="previewModal" width="640" title="表单预览(发起页渲染效果)">
<DynamicForm :schema-json="form.schemaJson" v-model:form-data="previewData" />
<template #footer>
<Button @click="previewModal = false">关闭</Button>
</template>
</Modal>
<Modal v-model="refsModal" width="720" :title="`「${refFormName}」被以下事项使用`">
<Table size="small" :columns="[
{ title: '站点', key: 'siteCode', width: 100 },
{ title: '事项编码', key: 'itemCode', width: 140 },
{ title: '事项名称', key: 'title', minWidth: 140 },
{ title: '状态', slot: 'refStatus', width: 90 },
{ title: '操作', slot: 'refAction', width: 80 },
]" :data="refRows">
<template #refStatus="{ row }">
<Tag :color="row.status === 1 ? 'success' : 'default'">{{ itemStatusName(row.status) }}</Tag>
</template>
<template #refAction="{ row }">
<Button size="small" type="text" @click="editRefItem(row)">编辑</Button>
</template>
</Table>
<div class="tip" style="margin-top: 8px">
用户在绑定此表单的事项发起页填写该表单;草稿表单发布后才会实际下发给发起页。
</div>
<template #footer>
<Button @click="refsModal = false">关闭</Button>
<Button type="primary" @click="refsModal = false; router.push('/svc/item')">去办事事项</Button>
</template>
</Modal>
<Modal v-model="versionsModal" title="版本轨迹"> <Modal v-model="versionsModal" title="版本轨迹">
<Table size="small" :columns="[ <Table size="small" :columns="[
{ title: '版本', key: 'version', width: 80 }, { title: '版本', key: 'version', width: 80 },
+14 -1
View File
@@ -40,6 +40,13 @@ const uploadingCover = ref(false);
const editing = computed(() => !!route.query.id); const editing = computed(() => !!route.query.id);
const pageTitle = computed(() => (editing.value ? "编辑事项" : "新建事项")); const pageTitle = computed(() => (editing.value ? "编辑事项" : "新建事项"));
/** 选中表单的最新版本是否为草稿/停用(草稿可先绑,但发起页要等发布才生效) */
const selectedFormDraft = computed(() => {
if (!form.value.formCode) return false;
const f = forms.value.find((x) => x.code === form.value.formCode);
return !!f && f.status !== 1;
});
/** 部门树扁平化(缩进展示,承袭 CMS flattenDepts) */ /** 部门树扁平化(缩进展示,承袭 CMS flattenDepts) */
const deptOptions = computed(() => { const deptOptions = computed(() => {
const out: { id: number; label: string }[] = []; const out: { id: number; label: string }[] = [];
@@ -233,8 +240,14 @@ onMounted(loadAll);
<Col span="12"> <Col span="12">
<FormItem label="办理表单"> <FormItem label="办理表单">
<Select v-model="form.formCode" clearable placeholder="该事项发起时填写的自定义表单"> <Select v-model="form.formCode" clearable placeholder="该事项发起时填写的自定义表单">
<Option v-for="f in forms" :key="f.code" :value="f.code">{{ f.name }}</Option> <Option v-for="f in forms" :key="f.code" :value="f.code">
{{ f.name }}{{ f.status !== 1 ? `(${f.status === 2 ? "已停用" : "草稿"})` : "" }}
</Option>
</Select> </Select>
<div v-if="selectedFormDraft" class="tip" style="color: #ed4014">
该表单尚未发布,发布后才能在发起页实际填写;请到「表单管理」发布。
</div>
<div v-else class="tip">发起页按表单编码取最新已发布版本渲染</div>
</FormItem> </FormItem>
</Col> </Col>
<Col span="24"> <Col span="24">