t1: 事项编辑接表单中心绑定(表单目录选择/设计器对齐svc模型)
build / build (push) Successful in 1m38s

This commit is contained in:
chenxing
2026-10-22 10:03:15 +08:00
parent 54813134ae
commit 509fc6d20c
4 changed files with 193 additions and 352 deletions
+15
View File
@@ -3,6 +3,7 @@ import { alovaInstance } from "@/utils/alova/alova-iview";
import config from "@/assets/config/host.config"; import config from "@/assets/config/host.config";
import type { import type {
ApplyVo, ApplyVo,
FcFormCatalogPage,
FlowProgressVo, FlowProgressVo,
FlowSummary, FlowSummary,
PageResult, PageResult,
@@ -135,6 +136,20 @@ class SvcDictApi {
} }
class SvcFormApi { class SvcFormApi {
/**
* 表单中心目录(v3/TASK_1):事项编辑页的表单选择器数据源。
* 走本应用同源代理,浏览器不持 appKey/secret、也不出现 form-center 地址(同 flow 能力的纪律);
* 校域由后端从 CAS 会话派生,前端没有 schId 入参——这是有意的,别"顺手加一个"。
*/
centerForms(siteCode: string, filter: { keyword?: string; page?: number; pageSize?: number } = {}) {
return get<FcFormCatalogPage>(`${SVC}/formCenter/forms`, {
siteCode,
keyword: filter.keyword || undefined,
page: filter.page,
pageSize: filter.pageSize
});
}
/** 本地表单清单(存量回显用;v3 起不再新增定义,save/publish 已在后端明确退出) */
list(siteCode: string) { list(siteCode: string) {
return get<SvcFormVo[]>(`${SVC}/form/list`, { siteCode }); return get<SvcFormVo[]>(`${SVC}/form/list`, { siteCode });
} }
+32
View File
@@ -97,6 +97,9 @@ export interface SvcItemEditVo {
ruleCategoryId?: number; ruleCategoryId?: number;
coverImage?: string; coverImage?: string;
formCode?: string; formCode?: string;
/** 表单中心绑定坐标(v3/TASK_1,成对;保存时后端会再向中心核验一次才落库) */
fcFormUuid?: string;
fcFormVersion?: number;
flowGuid?: string; flowGuid?: string;
applyUrl?: string; applyUrl?: string;
sortNo?: number; sortNo?: number;
@@ -127,7 +130,13 @@ export interface SvcItemSaveRequest {
noticeCategoryId?: number; noticeCategoryId?: number;
ruleCategoryId?: number; ruleCategoryId?: number;
coverImage?: string; coverImage?: string;
/**
* 本地表单 code:v3/TASK_1 起**只读**——编辑存量事项时原样回传即保留历史绑定,
* 写一个新值会被后端拒(保存链路的 form_code 已停写)。新表单一律走 fcFormUuid + fcFormVersion。
*/
formCode?: string; formCode?: string;
fcFormUuid?: string;
fcFormVersion?: number;
flowGuid?: string; flowGuid?: string;
applyUrl?: string; applyUrl?: string;
// ===== 服务承诺(P1-1):与后端 SvcModels.SvcItemSaveRequest 同名,勿改 ===== // ===== 服务承诺(P1-1):与后端 SvcModels.SvcItemSaveRequest 同名,勿改 =====
@@ -149,6 +158,29 @@ export interface SvcItemAttachmentVo {
fileSize?: number; fileSize?: number;
} }
// ===== 表单中心目录(v3/TASK_1)=====
/**
* 目录行 = 后端 `SvcFormCatalogVo`(形状对齐 form-center `GET /oapi/form/forms` 的 items)。
* 浏览器不直连表单中心:这条走本应用同源代理 `/api/svc/formCenter/forms`,
* 校域由后端从 CAS 会话派生,应用签名也不在前端出现。
*/
export interface FcFormCatalogItem {
formUuid: string;
code: string;
name: string;
appCode: string;
/** 中心当前发布版本:选中即成对存下 formUuid + version */
version: number;
audience: string[];
}
export interface FcFormCatalogPage {
items: FcFormCatalogItem[];
/** 中心侧"已发布 ∩ 本应用可读"过滤后的总条数(先过滤再分页) */
total: number;
}
// ===== 表单 ===== // ===== 表单 =====
export interface SvcFormVo { export interface SvcFormVo {
+98 -338
View File
@@ -1,364 +1,124 @@
<script setup lang="ts"> <script setup lang="ts">
/** /**
* 表单设计器(独立全屏页,/svc/form/designer):三栏布局迁移自旧 FormManage, * 表单设计器(v3/TASK_1 退场):表单定义的<b>正主是表单中心</b>,办事大厅不再维护第二套设计器,
* 左组件库 / 中多分组画布 / 右字段属性;预览复用 DynamicForm 渲染器。 * 也不再复制一套渲染器——两套房子的 schema 方言迟早分叉,而分叉的代价是线上事项填不了表。
* 产出 schema 严格对齐后端 FormValidator 契约(groups/fields + 白名单属性)。 *
* 路由刻意保留(导航与既有入口都指着 `/svc/form/designer`),直接 404 会让管理员以为系统坏了;
* 这里只回答两件事:**去哪儿做**(表单中心管理页)与**做完怎么接上**(回事项编辑页从目录里选)。
*
* 「表单管理」列表仍在:存量本地表单的历史绑定还要看得到、能清理,只是新建/发布不再在这里发生
* (那两个写入口已在后端明确退出,会原样返回业务错)。
*/ */
import { computed, onMounted, ref } from "vue"; import { computed } from "vue";
import { useRoute, useRouter } from "vue-router"; import { useRoute } from "vue-router";
import { cloneDeep } from "lodash";
import { Message, Modal } from "view-ui-plus";
import { svcFlowApi, svcFormApi } from "@/api/module/svc";
import { useSite } from "@/utils/site";
import ComponentLibrary from "@/components/designer/ComponentLibrary.vue";
import DesignerCanvas from "@/components/designer/DesignerCanvas.vue";
import FieldPropPanel from "@/components/designer/FieldPropPanel.vue";
import DynamicForm from "@/components/DynamicForm.vue";
import type { DesignField, DesignGroup } from "@/types/designer";
import { KEY_PATTERN } from "@/types/designer";
import {
collectUsedKeys,
countFields,
defaultField,
genUid,
makeNextKey,
pickAllowed,
restoreGroups,
} from "@/utils/designer/schema";
const route = useRoute(); const route = useRoute();
const router = useRouter();
const { currentSiteCode, loadSites } = useSite();
const formId = ref<number | null>(null); /** 旧设计器带过来的表单 id:只做提示,不再驱动任何编辑 */
const formStatus = ref(0); const legacyId = computed(() => String(route.query.id ?? ""));
const formVersion = ref(1);
const code = ref("");
const name = ref("");
/** 表单级流程绑定(2026-09-20):用本表单的事项未自身绑流时回退用它 */
const flowGuid = ref("");
const flows = ref<{ flowGuid: string; name: string; version: number }[]>([]);
const flowDisabled = ref(false);
const flowDisabledReason = ref("");
const groups = ref<DesignGroup[]>([]);
const selected = ref<{ gi: number; fi: number } | null>(null);
const saving = ref(false);
const previewModal = ref(false);
const previewData = ref<Record<string, any>>({});
/** /**
* 装载的是已发布/停用版本:行不可变,保存必须不传 id,由后端按 code 克隆出 version+1 草稿 * 中心表单管理页。生产各子应用同网关、按 context-path 分发(表单中心是 `/form-center/front/`),
* (传 id 会被 ServiceFormService.save 直接拒:「该版本已发布不可修改」)。 * 所以用当前 origin 拼得出可用链接;本地各应用分端口跑,这条会落到本应用自己的 dev server,
* 需要在本地另开 form-center front(vite dev)后按那边的地址访问——不做多源硬编码配置,
* 因为这一份"跳去哪"的权威本来就是部署侧的服务目录。
*/ */
const loadedPublished = computed(() => formId.value !== null && formStatus.value !== 0); const centerUrl = computed(() => `${window.location.origin}/form-center/front/#/admin/forms`);
/** key 自动生成器(基于当前占用集合每次重建,保证新增/复制去重) */ function openCenter() {
const nextKey = computed(() => makeNextKey(collectUsedKeys(groups.value))); window.open(centerUrl.value, "_blank", "noopener");
/** 全表单重复的 key */
const duplicateKeys = computed(() => {
const counts = new Map<string, number>();
for (const g of groups.value) {
for (const f of g.fields) counts.set(f.key, (counts.get(f.key) || 0) + 1);
}
return new Set([...counts.entries()].filter(([, c]) => c > 1).map(([k]) => k));
});
const selectedField = computed<DesignField | null>(() => {
const s = selected.value;
if (!s) return null;
const g = groups.value[s.gi];
return g?.fields[s.fi] ?? null;
});
const keyFormatOk = computed(() => {
for (const g of groups.value) {
for (const f of g.fields) {
if (!f.key || !KEY_PATTERN.test(f.key)) return false;
}
}
return true;
});
function buildSchemaJson(): string {
return JSON.stringify({
groups: groups.value.map((g) => ({
title: g.title || undefined,
fields: g.fields.map((f) => pickAllowed(f)),
})),
});
} }
/** 预览用的 schema(与保存一致) */
const previewSchemaJson = computed(() => buildSchemaJson());
function select(gi: number, fi: number) {
const g = groups.value[gi];
if (!g || !g.fields[fi]) {
selected.value = null;
return;
}
selected.value = { gi, fi };
}
function addField(seed: { type: string; name: string }) {
if (!groups.value.length) {
groups.value.push({ _uid: genUid(), title: "基本信息", fields: [] });
}
const gi = selected.value?.gi ?? groups.value.length - 1;
const f = defaultField(seed.type, nextKey.value, seed.name);
groups.value[gi].fields.push(f);
select(gi, groups.value[gi].fields.length - 1);
}
function copyField(gi: number, fi: number) {
const src = groups.value[gi]?.fields[fi];
if (!src) return;
const copy: DesignField = cloneDeep(src);
copy._uid = genUid();
copy.key = nextKey.value(src.type);
groups.value[gi].fields.splice(fi + 1, 0, copy);
select(gi, fi + 1);
}
function removeField(gi: number, fi: number) {
groups.value[gi].fields.splice(fi, 1);
const s = selected.value;
if (s && s.gi === gi) {
if (groups.value[gi].fields.length) select(gi, Math.min(s.fi, groups.value[gi].fields.length - 1));
else selected.value = null;
}
}
function addGroup() {
groups.value.push({ _uid: genUid(), title: `分组${groups.value.length + 1}`, fields: [] });
}
function removeGroup(gi: number) {
const g = groups.value[gi];
if (!g) return;
const content = (g.fields || []).length
? `分组「${g.title || "未命名"}」内还有 ${g.fields.length} 个字段,删除后字段将一并移除。`
: "确定删除该分组吗?";
Modal.confirm({
title: "删除分组确认",
content,
onOk: () => {
groups.value.splice(gi, 1);
const s = selected.value;
if (!s) return;
if (s.gi === gi) selected.value = null;
else if (s.gi > gi) selected.value = { ...s, gi: s.gi - 1 };
},
});
}
function openPreview() {
if (!countFields(groups.value)) {
Message.info("请先添加字段");
return;
}
if (!keyFormatOk.value || duplicateKeys.value.size) {
Message.error("字段 key 非法或重复,请检查右侧属性面板");
return;
}
previewData.value = {};
previewModal.value = true;
}
function validateBeforeSave(): string | null {
const c = code.value.trim();
if (!c) return "请输入表单编码";
if (!/^[A-Za-z0-9_-]{2,64}$/.test(c)) return "表单编码仅限字母/数字/下划线/中划线,2~64 位";
const n = name.value.trim();
if (!n) return "请输入表单名称";
if (n.length > 128) return "表单名称过长(≤128 字)";
if (!countFields(groups.value)) return "请至少添加一个字段";
const noLabel = groups.value.flatMap((g) => g.fields).find((f) => !f.label || !f.label.trim());
if (noLabel) return `字段「${noLabel.key}」缺少标题(服务端同样必填)`;
for (const g of groups.value) {
if ((g.title || "").length > 64) return `分组标题过长(≤64):${g.title}`;
}
if (duplicateKeys.value.size) return "存在重复的字段 key,请修改";
if (!keyFormatOk.value) return "存在非法字段 key(仅限字母/数字/下划线,1~64 位)";
return null;
}
/** 流程图预览(与事项编辑同款:本应用后端换免签 token 出图) */
function openFlowDiagram() {
if (!currentSiteCode.value || !flowGuid.value) {
Message.warning("请先选择流程并确认当前站点");
return;
}
window.open(svcFlowApi.diagramUrl(currentSiteCode.value, flowGuid.value, "dept"), "_blank");
}
async function loadFlows() {
if (!currentSiteCode.value) return;
try {
flows.value = ((await svcFlowApi.list(currentSiteCode.value).send()) as any[]) || [];
} catch (e: any) {
flowDisabled.value = true;
const msg = String(e?.message || e || "");
flowDisabledReason.value = msg.includes("流程中心未接入")
? "流程中心未接入,表单暂不能绑定流程"
: `流程列表获取失败:${msg}`;
}
}
async function save() {
if (!currentSiteCode.value) {
Message.error("站点上下文缺失,请从表单列表进入设计器");
return;
}
const err = validateBeforeSave();
if (err) {
Message.error(err);
return;
}
saving.value = true;
try {
await svcFormApi
.save(currentSiteCode.value, {
id: loadedPublished.value ? undefined : formId.value ?? undefined,
code: code.value.trim(),
name: name.value.trim(),
schemaJson: buildSchemaJson(),
flowGuid: flowGuid.value || undefined,
})
.send();
Message.success(
loadedPublished.value
? `已保存为草稿 v${formVersion.value + 1}。新结构需发布后才对发起页生效`
: "已保存为草稿。下一步:在表单列表发布,并在「办事事项」中绑定使用"
);
router.push("/svc/form");
} catch (e: any) {
Message.error(e?.message || e || "保存失败");
} finally {
saving.value = false;
}
}
async function load() {
if (!formId.value) return;
try {
const vo: any = await svcFormApi.get(currentSiteCode.value, formId.value).send();
code.value = vo.code;
name.value = vo.name;
formStatus.value = vo.status ?? 0;
formVersion.value = vo.version ?? 1;
flowGuid.value = vo.flowGuid || "";
let parsed: any = {};
try {
parsed = JSON.parse(vo.schemaJson);
} catch {
parsed = {};
}
groups.value = restoreGroups(parsed.groups || []);
if (!groups.value.length) {
groups.value.push({ _uid: genUid(), title: "基本信息", fields: [] });
}
} catch (e: any) {
Message.error(e?.message || e || "表单加载失败");
router.push("/svc/form");
}
}
onMounted(async () => {
await loadSites();
loadFlows();
const idRaw = route.query.id;
formId.value = idRaw !== undefined && idRaw !== null ? Number(idRaw) : null;
if (formId.value && !Number.isNaN(formId.value)) {
await load();
} else {
formId.value = null;
groups.value = [{ _uid: genUid(), title: "基本信息", fields: [] }];
}
});
</script> </script>
<template> <template>
<div class="designer-page"> <div class="page-wrapper">
<div class="designer-head"> <div class="main-container">
<div class="head-left"> <Alert type="warning" show-icon banner class="notice">
<Button icon="ios-arrow-back" @click="router.push('/svc/form')">返回列表</Button> 表单设计已迁到<b>表单中心</b>。本页不再新建或修改表单定义,事项只需绑定中心里<b>已发布的某个版本</b>。
<Input v-model="name" placeholder="请输入表单名称" style="width: 240px" /> </Alert>
<Input
v-model="code" <div class="steps">
:disabled="!!formId" <div class="step">
placeholder="表单编码(字母/数字/下划线/中划线)" <span class="no">1</span>
style="width: 220px" <div>
/> <div class="title">在表单中心设计并发布</div>
<Select v-model="flowGuid" clearable filterable :disabled="flowDisabled" <div class="desc">
placeholder="绑定流程(可空,事项未绑时回退)" style="width: 240px"> 进入中心的管理页建表、设计字段、发布;如果这张表要给办事大厅用,
<Option v-for="f in flows" :key="f.flowGuid" :value="f.flowGuid"> 还需要在中心的「应用设置 · 表单授权」里把读权授给本应用,否则事项里选不到它。
{{ f.name }}(v{{ f.version }}) </div>
</Option> </div>
</Select> </div>
<Button v-if="flowGuid" size="small" @click="openFlowDiagram">流程图</Button> <div class="step">
<span v-if="flowDisabled" style="color: #999; font-size: 12px"> <span class="no">2</span>
{{ flowDisabledReason }} <div>
</span> <div class="title">回到事项编辑页选择表单</div>
<div class="desc">
「服务事项 · 编辑」的<b>办理表单</b>已从表单中心目录里选,选中即成对记下
表单 uuid 与当时发布版本号,保存时由服务端向中心核验;本地表单编码不再接受新绑定。
</div>
</div>
</div>
</div> </div>
<div class="head-right">
<Tag v-if="loadedPublished" color="success"> <div v-if="legacyId" class="tip">
已发布 v{{ formVersion }}(保存将生成新草稿版本) 你是从「表单管理」里点“编辑”进来的(本地表单 id={{ legacyId }})。这张历史表仍可在列表里查看与清理,
</Tag> 但要改结构请去表单中心重新建一张,再回事项里换绑。
<Button @click="openPreview">预览</Button> </div>
<Button type="primary" :loading="saving" @click="save">保存</Button>
<div class="actions">
<Button type="primary" @click="openCenter">打开表单中心管理页</Button>
<span class="tip">链接:{{ centerUrl }}</span>
</div> </div>
</div> </div>
<div class="designer-body">
<ComponentLibrary :next-key="nextKey" @add="addField" />
<DesignerCanvas
:groups="groups"
:selected="selected"
:next-key="nextKey"
@select="select"
@copy="copyField"
@remove="removeField"
@add="addField"
@add-group="addGroup"
@remove-group="removeGroup"
/>
<FieldPropPanel :field="selectedField" :duplicate-keys="duplicateKeys" />
</div>
<Modal v-model="previewModal" width="720" title="表单预览(发起页渲染效果)">
<DynamicForm v-if="previewModal" :schema-json="previewSchemaJson" v-model:form-data="previewData" />
<template #footer>
<Button @click="previewModal = false">关闭</Button>
</template>
</Modal>
</div> </div>
</template> </template>
<style scoped> <style scoped>
.designer-page { .notice {
display: flex; margin-bottom: 16px;
flex-direction: column;
height: 100%;
background: #f2f4f8;
} }
.designer-head {
.steps .step {
display: flex;
gap: 12px;
padding: 12px 0;
border-bottom: 1px dashed #e8eaec;
}
.steps .no {
flex: none;
width: 22px;
height: 22px;
line-height: 22px;
text-align: center;
border-radius: 50%;
background: #2d8cf0;
color: #fff;
font-size: 12px;
}
.steps .title {
font-weight: 600;
margin-bottom: 4px;
}
.steps .desc {
color: #515a6e;
font-size: 13px;
line-height: 1.7;
}
.actions {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; gap: 12px;
padding: 10px 16px; margin-top: 16px;
background: #fff; flex-wrap: wrap;
border-bottom: 1px solid #e8eaec;
flex-shrink: 0;
} }
.head-left,
.head-right { .tip {
display: flex; color: #808695;
align-items: center; font-size: 12px;
gap: 8px; margin-top: 12px;
} }
.designer-body { </style>
flex: 1;
display: flex;
min-height: 0;
overflow: hidden;
}
</style>
+48 -14
View File
@@ -10,7 +10,7 @@ import {
svcFormApi, svcFormApi,
svcItemApi, svcItemApi,
} from "@/api/module/svc"; } from "@/api/module/svc";
import type { SvcDeptVo, SvcFormVo, SvcItemAttachmentVo, SvcItemSaveRequest } from "@/types/svc"; import type { FcFormCatalogItem, SvcDeptVo, SvcItemAttachmentVo, SvcItemSaveRequest } from "@/types/svc";
import { useSite } from "@/utils/site"; import { useSite } from "@/utils/site";
import RichTextEditor from "@/components/RichTextEditor.vue"; import RichTextEditor from "@/components/RichTextEditor.vue";
@@ -43,7 +43,10 @@ const form = ref<SvcItemSaveRequest>({
const objects = ref<{ dictKey: string; dictName: string }[]>([]); const objects = ref<{ dictKey: string; dictName: string }[]>([]);
const categories = ref<{ dictKey: string; dictName: string }[]>([]); const categories = ref<{ dictKey: string; dictName: string }[]>([]);
const depts = ref<SvcDeptVo[]>([]); const depts = ref<SvcDeptVo[]>([]);
const forms = ref<SvcFormVo[]>([]); /** 表单中心目录(v3/TASK_1:办理表单只能从这里选;本地表单定义已停写) */
const fcForms = ref<FcFormCatalogItem[]>([]);
const fcFormsDisabled = ref(false);
const fcFormsReason = ref("");
const flows = ref<{ flowGuid: string; name: string; version: number }[]>([]); const flows = ref<{ flowGuid: string; name: string; version: number }[]>([]);
const flowDisabled = ref(false); const flowDisabled = ref(false);
const flowDisabledReason = ref(""); const flowDisabledReason = ref("");
@@ -53,11 +56,22 @@ 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(() => { * 选中即成对存 uuid + 中心当时的发布版(v3 §4:只保存服务器验证过的坐标,不接受手填版本)。
if (!form.value.formCode) return false; * 清空=两列一起清——后端要求成对,留一个没人认领的版本号没有意义。
const f = forms.value.find((x) => x.code === form.value.formCode); */
return !!f && f.status !== 1; function onFcFormChange(uuid?: string) {
const hit = uuid ? fcForms.value.find((f) => f.formUuid === uuid) : undefined;
form.value.fcFormUuid = hit ? hit.formUuid : undefined;
form.value.fcFormVersion = hit ? hit.version : undefined;
}
/** 绑定回显:存的是当时核验过的版本,中心又发新版时页面要点出来(不自动改绑) */
const fcBinding = computed(() => {
if (!form.value.fcFormUuid) return "";
const newest = fcForms.value.find((f) => f.formUuid === form.value.fcFormUuid)?.version;
const base = `已绑定 ${form.value.fcFormUuid} v${form.value.fcFormVersion}`;
return newest && newest !== form.value.fcFormVersion ? `${base}(中心最新已发布 v${newest},需换版请重新选择)` : base;
}); });
/** 部门树扁平化(缩进展示,承袭 CMS flattenDepts) */ /** 部门树扁平化(缩进展示,承袭 CMS flattenDepts) */
@@ -84,7 +98,20 @@ async function loadAll() {
objects.value = ((await svcDictApi.list(siteCode, "object").send()) as any[]) || []; objects.value = ((await svcDictApi.list(siteCode, "object").send()) as any[]) || [];
categories.value = ((await svcDictApi.list(siteCode, "category").send()) as any[]) || []; categories.value = ((await svcDictApi.list(siteCode, "category").send()) as any[]) || [];
depts.value = (await svcDictApi.deptTree(siteCode).send()) || []; depts.value = (await svcDictApi.deptTree(siteCode).send()) || [];
forms.value = ((await svcFormApi.list(siteCode).send()) as SvcFormVo[]) || []; // 表单中心目录(同源代理):授权没配/中心不可达时降级为"选不了",不阻塞事项其余字段的编辑
try {
const page = await svcFormApi.centerForms(siteCode, { page: 1, pageSize: 100 }).send();
fcForms.value = page?.items || [];
fcFormsDisabled.value = false;
fcFormsReason.value = "";
} catch (e: any) {
fcForms.value = [];
fcFormsDisabled.value = true;
const msg = String(e?.message || e || "");
fcFormsReason.value = msg.includes("不可达") || msg.includes("返回空")
? msg
: `表单中心目录获取失败:${msg}`;
}
try { try {
flows.value = ((await svcFlowApi.list(siteCode).send()) as any[]) || []; flows.value = ((await svcFlowApi.list(siteCode).send()) as any[]) || [];
flowDisabled.value = false; flowDisabled.value = false;
@@ -312,15 +339,22 @@ onMounted(loadAll);
</Col> </Col>
<Col span="12"> <Col span="12">
<FormItem label="办理表单"> <FormItem label="办理表单">
<Select v-model="form.formCode" clearable placeholder="该事项发起时填写的自定义表单"> <Select :value="form.fcFormUuid" clearable placeholder="从表单中心选择已发布表单"
<Option v-for="f in forms" :key="f.code" :value="f.code"> :disabled="fcFormsDisabled" @on-change="onFcFormChange">
{{ f.name }}{{ f.status !== 1 ? `(${f.status === 2 ? "已停用" : "草稿"})` : "" }} <Option v-for="f in fcForms" :key="f.formUuid" :value="f.formUuid">
{{ f.name }}({{ f.appCode }}/{{ f.code }} · v{{ f.version }})
</Option> </Option>
</Select> </Select>
<div v-if="selectedFormDraft" class="tip" style="color: #ed4014"> <div v-if="fcFormsDisabled" class="tip" style="color: #ed4014">
该表单尚未发布,发布后才能在发起页实际填写;请到「表单管理」发布。 {{ fcFormsReason || "表单中心目录不可达" }}
</div>
<div v-else-if="fcBinding" class="tip">{{ fcBinding }}</div>
<div v-else class="tip">
目录只列中心已发布、且已授权给本应用的表单;保存时服务端会再向中心核验一次坐标
</div>
<div v-if="form.formCode" class="tip" style="color: #ed4014">
历史本地表单「{{ form.formCode }}」只读保留(本地表单定义已停用,改绑请选表单中心的表)
</div> </div>
<div v-else class="tip">发起页按表单编码取最新已发布版本渲染</div>
</FormItem> </FormItem>
</Col> </Col>
<Col span="24"> <Col span="24">