- 修复办理页「查看流程图」双 hash URL:改 router.resolve + toAppHashUrl 单 hash
- 修复管理端办件抽屉「查看大图」域名尾点(origin + BASE_URL("./") 拼接)
- 新增 flowError 口径层与办理页内联失败条,审批失败提示可读、不泄漏堆栈
- 审批节点抽屉新增「预览候选人」真入口 + AssigneePreviewModal(只读,不写回定义)
- 线级停发/恢复失败改为单条错误面,避免同错双 toast
- 审批抽屉核心区/高级设置分层;插入菜单常用/高级分组
- 新增 FlowAssignEmbed 选人 iframe 父侧宿主(origin/source/信封/context 四校验)
- 修复 /diagram、/assign-preview 面包屑误显示「流程编辑器」
This commit is contained in:
@@ -412,8 +412,8 @@ async function applyLineAction(row: any, action: "offline" | "enable") {
|
||||
lineOffline: Number(row.lineOffline) || 0,
|
||||
action,
|
||||
request: async (guid) => Number(await (action === "offline"
|
||||
? flowApi.defOfflineLine(guid).send()
|
||||
: flowApi.defEnableLine(guid).send())) || 0,
|
||||
? flowApi.defOfflineLine(guid, { silentBizToast: true }).send()
|
||||
: flowApi.defEnableLine(guid, { silentBizToast: true }).send())) || 0,
|
||||
reload: () => load(),
|
||||
});
|
||||
if (out.kind === "skipped") {
|
||||
|
||||
@@ -0,0 +1,226 @@
|
||||
/**
|
||||
* 审批节点「预览候选人」(FL上线独立验收报告 P2-1:/assign-preview 页存在但编辑器内无入口)。
|
||||
*
|
||||
* 口径:
|
||||
* - 只读。解析当前抽屉里的**草稿配置**,不写回节点、不改流程定义、不建实例。
|
||||
* - 数据全部来自既有接口(科室成员 / UserBase 用户检索 / 发起预览),前端不重实现人员解析:
|
||||
* DEPT_KEY 读的是后端 AssigneeResolver 同一张 flw_dept_member,USERS 读的是 UserBase byId。
|
||||
* - 最终办理资格仍由 FL 服务端在正式发起/办理时复核(跨校、禁用、非本人等),这里只是设计期核对。
|
||||
*/
|
||||
import { describeFlowError } from "@/utils/flowError";
|
||||
|
||||
export type PreviewAssigneeType = "DEPT_KEY" | "USERS";
|
||||
|
||||
export interface PreviewAssignPolicy {
|
||||
mode?: string;
|
||||
min?: number | null;
|
||||
max?: number | null;
|
||||
required?: boolean;
|
||||
}
|
||||
|
||||
/** 抽屉里的当前(可能未保存)节点配置 */
|
||||
export interface PreviewNodeConfig {
|
||||
uid: string;
|
||||
name: string;
|
||||
type: PreviewAssigneeType;
|
||||
deptKey: string;
|
||||
userIds: number[];
|
||||
countersign: "ALL" | "ANY";
|
||||
assignPolicy: PreviewAssignPolicy | null | undefined;
|
||||
}
|
||||
|
||||
export interface PreviewDept {
|
||||
deptKey: string;
|
||||
deptName: string;
|
||||
count: number;
|
||||
}
|
||||
|
||||
export interface CandidateRow {
|
||||
userId: number;
|
||||
userName: string;
|
||||
/** 该候选人不计入/存在风险的说明;空=正常 */
|
||||
warn?: string;
|
||||
}
|
||||
|
||||
/** 已发布版本的发起预览对账结果 */
|
||||
export interface PublishedView {
|
||||
state: "ok" | "missing" | "unavailable";
|
||||
defVersion?: number;
|
||||
rows: CandidateRow[];
|
||||
text: string;
|
||||
}
|
||||
|
||||
export interface NodePreviewResult {
|
||||
/** 是否解析出了可用候选人(false 时一定有 blocked 或 published.unavailable 解释) */
|
||||
ok: boolean;
|
||||
headline: string;
|
||||
rows: CandidateRow[];
|
||||
blocked?: string;
|
||||
published?: PublishedView;
|
||||
notes: string[];
|
||||
}
|
||||
|
||||
export interface PreviewApi {
|
||||
deptMembers: (deptId: string) => Promise<any[]>;
|
||||
lookupUser: (userId: number) => Promise<any>;
|
||||
applyPreview: (flowGuid: string) => Promise<any>;
|
||||
}
|
||||
|
||||
/** flw_dept_member.status / flw_dept.status:0=启用(与后端 resolveEnabled 同口径) */
|
||||
const DEPT_MEMBER_ENABLED = 0;
|
||||
|
||||
function countersignText(c: "ALL" | "ANY"): string {
|
||||
return c === "ALL" ? "会签(全部同意才通过)" : "或签(任一人同意即通过)";
|
||||
}
|
||||
|
||||
function assignPolicyText(p: PreviewAssignPolicy | null | undefined): string {
|
||||
if (!p) return "";
|
||||
const mode = p.mode === "SINGLE" ? "单选 1 人" : `多选 ${p.min ?? 1}~${p.max ?? "不限"} 人`;
|
||||
return `;发起时指派:${mode}${p.required ? " · 必选" : ""}`;
|
||||
}
|
||||
|
||||
/** UserBase BoaUser.status:1=启用(后端 D11 终审按此拒绝禁用/未知状态账号) */
|
||||
function userRow(u: any, id: number): CandidateRow {
|
||||
if (!u) return { userId: id, userName: `#${id}`, warn: "UserBase 查不到该用户,正式发起时会被拒绝" };
|
||||
const status = typeof u.status === "number" ? u.status : null;
|
||||
return {
|
||||
userId: id,
|
||||
userName: u.name || `#${id}`,
|
||||
warn: status === 1 ? undefined : `账号状态${status == null ? "未知" : status}(非启用),正式办理时会被服务端拒绝`,
|
||||
};
|
||||
}
|
||||
|
||||
async function deptRows(deptKey: string, api: PreviewApi): Promise<CandidateRow[]> {
|
||||
const members = (await api.deptMembers(deptKey)) || [];
|
||||
return members.map((m: any) => ({
|
||||
userId: Number(m.userId),
|
||||
userName: m.userName || `#${m.userId}`,
|
||||
warn: Number(m.status) === DEPT_MEMBER_ENABLED ? undefined : "该成员已停用,不计入候选",
|
||||
}));
|
||||
}
|
||||
|
||||
/** 已发布版本对账:只有本线已保存(有 flowGuid)时才有可比对象 */
|
||||
export async function loadPublishedView(
|
||||
cfg: PreviewNodeConfig,
|
||||
flowGuid: string,
|
||||
api: PreviewApi,
|
||||
): Promise<PublishedView> {
|
||||
if (!flowGuid) {
|
||||
return { state: "unavailable", rows: [], text: "本流程线尚未保存,没有可比对的已发布版本(预览按当前草稿配置解析)。" };
|
||||
}
|
||||
let res: any;
|
||||
try {
|
||||
res = await api.applyPreview(flowGuid);
|
||||
} catch (e: any) {
|
||||
return { state: "unavailable", rows: [], text: `已发布版本预览不可用:${describeFlowError(e)}` };
|
||||
}
|
||||
const version = Number(res?.defVersion) || undefined;
|
||||
const hit = (res?.nodes || []).find((n: any) => String(n.nodeId) === String(cfg.uid));
|
||||
if (!hit) {
|
||||
return {
|
||||
state: "missing",
|
||||
defVersion: version,
|
||||
rows: [],
|
||||
text: `已发布版本 v${version ?? "?"} 的发起预览里没有本节点:要么它未开启发起时指派,要么是尚未发布的草稿节点。`,
|
||||
};
|
||||
}
|
||||
const rows: CandidateRow[] = (hit.candidates || []).map((c: any) => ({
|
||||
userId: Number(c.userId),
|
||||
userName: c.userName || `#${c.userId}`,
|
||||
}));
|
||||
return {
|
||||
state: "ok",
|
||||
defVersion: version,
|
||||
rows,
|
||||
text: `已发布版本 v${version ?? "?"} 解析出的可指派候选人(发起人实际可选范围以此为准,本节点${rows.length ? "" : "当前无候选人"})。`,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析当前节点配置的候选人。
|
||||
* depts 传编辑器的科室上下文(含启用人数),用于把「科室不存在/0 人」讲清楚而不靠猜。
|
||||
*/
|
||||
export async function resolveNodePreview(
|
||||
cfg: PreviewNodeConfig,
|
||||
depts: PreviewDept[],
|
||||
api: PreviewApi,
|
||||
flowGuid = "",
|
||||
): Promise<NodePreviewResult> {
|
||||
const notes = [
|
||||
`多人审批方式:${countersignText(cfg.countersign)}${assignPolicyText(cfg.assignPolicy)}`,
|
||||
"预览只读:不写回节点、不改动流程定义;最终办理资格由 FL 服务端在正式发起时复核。",
|
||||
];
|
||||
const published = await loadPublishedView(cfg, flowGuid, api);
|
||||
|
||||
if (cfg.type === "DEPT_KEY") {
|
||||
if (!cfg.deptKey) {
|
||||
return { ok: false, headline: "按科室映射解析", rows: [], blocked: "尚未选择科室映射,请先在「科室映射」里选一个科室。", published, notes };
|
||||
}
|
||||
const dept = depts.find((d) => d.deptKey === cfg.deptKey);
|
||||
if (!dept) {
|
||||
return {
|
||||
ok: false,
|
||||
headline: "按科室映射解析",
|
||||
rows: [],
|
||||
blocked: `科室映射「${cfg.deptKey}」不在本校科室列表中(可能已删除),发布会被拦截。`,
|
||||
published,
|
||||
notes,
|
||||
};
|
||||
}
|
||||
let rows: CandidateRow[];
|
||||
try {
|
||||
rows = await deptRows(cfg.deptKey, api);
|
||||
} catch (e: any) {
|
||||
return {
|
||||
ok: false,
|
||||
headline: `科室「${dept.deptName || dept.deptKey}」`,
|
||||
rows: [],
|
||||
blocked: `读取科室成员失败:${describeFlowError(e)}`,
|
||||
published,
|
||||
notes,
|
||||
};
|
||||
}
|
||||
const enabled = rows.filter((r) => !r.warn);
|
||||
if (!enabled.length) {
|
||||
return {
|
||||
ok: false,
|
||||
headline: `科室「${dept.deptName || dept.deptKey}」(${dept.deptKey})`,
|
||||
rows,
|
||||
blocked: `科室「${dept.deptName || dept.deptKey}」当前 0 名启用办理人,发布会被拦截;请到「办理人映射」补人。`,
|
||||
published,
|
||||
notes,
|
||||
};
|
||||
}
|
||||
if (rows.length !== enabled.length) {
|
||||
notes.unshift(`共 ${rows.length} 名成员,其中 ${enabled.length} 名启用(停用成员已在下表标注)。`);
|
||||
}
|
||||
return {
|
||||
ok: true,
|
||||
headline: `科室「${dept.deptName || dept.deptKey}」(${dept.deptKey})· ${enabled.length} 名启用办理人`,
|
||||
rows,
|
||||
published,
|
||||
notes,
|
||||
};
|
||||
}
|
||||
|
||||
if (!cfg.userIds.length) {
|
||||
return { ok: false, headline: "按指定用户解析", rows: [], blocked: "尚未选择用户,请先点「选择用户」添加审批人。", published, notes };
|
||||
}
|
||||
const rows: CandidateRow[] = [];
|
||||
for (const id of cfg.userIds) {
|
||||
try {
|
||||
rows.push(userRow(await api.lookupUser(Number(id)), Number(id)));
|
||||
} catch (e: any) {
|
||||
rows.push({ userId: Number(id), userName: `#${id}`, warn: `用户检索失败:${describeFlowError(e)}` });
|
||||
}
|
||||
}
|
||||
const usable = rows.filter((r) => !r.warn);
|
||||
return {
|
||||
ok: usable.length > 0,
|
||||
headline: `指定用户 · ${rows.length} 人(${usable.length} 人可检索到且状态启用)`,
|
||||
rows,
|
||||
blocked: usable.length ? undefined : "指定的用户全部查不到或状态非启用,发布会被拦截。",
|
||||
published,
|
||||
notes,
|
||||
};
|
||||
}
|
||||
@@ -6,6 +6,8 @@ import { computed, inject, reactive, ref, watch } from "vue";
|
||||
import { Message } from "view-ui-plus";
|
||||
import UserCheckModal from "@/components/UserCheckModal.vue";
|
||||
import { flowApi } from "@/api/module/flow";
|
||||
import AssigneePreviewModal from "./AssigneePreviewModal.vue";
|
||||
import type { PreviewNodeConfig } from "../assignPreview";
|
||||
import { ApprovalNode, EDITOR_CTX, createApproval } from "../model";
|
||||
|
||||
const props = defineProps<{
|
||||
@@ -114,6 +116,49 @@ function removeUser(id: number): void {
|
||||
if (i >= 0) form.userIds.splice(i, 1);
|
||||
}
|
||||
|
||||
// ===== 高级设置分层(P2 核心/高级):只挪位置,不改字段、不改数据语义 =====
|
||||
/** 高级区是否展开;打开抽屉时若已有高级配置则默认展开,避免"配了但看不见" */
|
||||
const advOpen = ref(false);
|
||||
const activeAdvanced = computed(() => {
|
||||
const hits: string[] = [];
|
||||
if (form.countersign === "ALL") hits.push("会签");
|
||||
if (form.assignEnabled) hits.push("发起时指派");
|
||||
if (form.deadlineHours) hits.push("办理时限");
|
||||
if (form.multiOutlet) hits.push("多出口");
|
||||
return hits;
|
||||
});
|
||||
|
||||
watch(() => [props.modelValue, props.node?.uid], () => {
|
||||
if (props.modelValue) advOpen.value = activeAdvanced.value.length > 0;
|
||||
}, { immediate: true });
|
||||
|
||||
// ===== 指派预览(验收 P2-1:编辑器内要有可发现入口) =====
|
||||
const previewModal = ref<any>();
|
||||
|
||||
/** 用抽屉里的实时表单(含未保存改动)作为预览输入;预览全程只读,不回写节点 */
|
||||
function previewConfig(): PreviewNodeConfig {
|
||||
return {
|
||||
uid: props.node?.uid || "",
|
||||
name: form.name,
|
||||
type: form.type,
|
||||
deptKey: form.deptKey,
|
||||
userIds: [...form.userIds],
|
||||
countersign: form.countersign,
|
||||
assignPolicy: form.assignEnabled
|
||||
? {
|
||||
mode: form.assignMode,
|
||||
min: form.assignMin,
|
||||
max: form.assignMax,
|
||||
required: form.assignRequired,
|
||||
}
|
||||
: null,
|
||||
};
|
||||
}
|
||||
|
||||
function onPreviewCandidates(): void {
|
||||
previewModal.value?.show(previewConfig(), ctx.depts, ctx.flowGuid);
|
||||
}
|
||||
|
||||
function onOk(): void {
|
||||
const n = props.node;
|
||||
if (!n) {
|
||||
@@ -206,6 +251,20 @@ function onRemove(): void {
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 候选人预览:核心区入口(读当前配置,不保存、不改动定义) -->
|
||||
<div class="fld">
|
||||
<Button size="small" type="primary" ghost @click="onPreviewCandidates">预览候选人</Button>
|
||||
<div class="hint">按当前配置(含未保存改动)解析实际会拿到待办的人,并与已发布版本对账;最终资格由服务端在正式发起时复核</div>
|
||||
</div>
|
||||
|
||||
<!-- 高级能力:默认收起,已配置时自动展开并在收起态标出条目(不删除、不改语义) -->
|
||||
<div class="adv-head" @click="advOpen = !advOpen">
|
||||
<span class="adv-caret">{{ advOpen ? "▾" : "▸" }}</span>
|
||||
<span class="adv-title">高级设置</span>
|
||||
<span v-if="!advOpen && activeAdvanced.length" class="adv-badge">已配置 {{ activeAdvanced.join("、") }}</span>
|
||||
<span v-else-if="!advOpen" class="adv-sub">会签/或签 · 发起时指派 · 办理时限 · 多出口路由</span>
|
||||
</div>
|
||||
<div v-show="advOpen" class="adv-body">
|
||||
<div class="fld">
|
||||
<label>多人审批方式</label>
|
||||
<RadioGroup v-model="form.countersign" type="button" button-style="solid">
|
||||
@@ -260,6 +319,7 @@ function onRemove(): void {
|
||||
关闭请用泳道头部的"恢复单出口"(需处理泳道内容去向)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
|
||||
<div class="dw-foot">
|
||||
@@ -278,6 +338,8 @@ function onRemove(): void {
|
||||
:userids="form.userIds"
|
||||
@on-submit="onUsersPicked"
|
||||
/>
|
||||
|
||||
<AssigneePreviewModal ref="previewModal" />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
@@ -302,6 +364,32 @@ function onRemove(): void {
|
||||
.hint.warn {
|
||||
color: #ff9900;
|
||||
}
|
||||
.adv-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 0;
|
||||
margin: 4px 0 12px;
|
||||
border-top: 1px dashed #e8eaec;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
.adv-title {
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
}
|
||||
.adv-sub,
|
||||
.adv-badge {
|
||||
font-size: 12px;
|
||||
color: #808695;
|
||||
}
|
||||
.adv-badge {
|
||||
color: #ff9900;
|
||||
}
|
||||
.adv-body {
|
||||
padding-left: 10px;
|
||||
border-left: 2px solid #f0f0f0;
|
||||
}
|
||||
.user-add {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* 审批节点候选人预览弹窗(编辑器内入口,验收报告 P2-1)。
|
||||
* 只读展示 resolveNodePreview 的结果:草稿解析 + 已发布版本对账 + 明确的不可预览原因。
|
||||
* 关闭不做任何写回——它只接收抽屉里的本地表单快照。
|
||||
*/
|
||||
import { ref } from "vue";
|
||||
import { Modal, Alert, Tag, Button } from "view-ui-plus";
|
||||
import { resolveNodePreview, type PreviewApi, type PreviewDept, type PreviewNodeConfig } from "../assignPreview";
|
||||
import { flowApi } from "@/api/module/flow";
|
||||
|
||||
const api: PreviewApi = {
|
||||
deptMembers: (deptId) => flowApi.deptMembers(deptId).send() as Promise<any[]>,
|
||||
lookupUser: (userId) => flowApi.mapLookupUser(userId).send() as Promise<any>,
|
||||
applyPreview: (flowGuid) => flowApi.applyPreview(flowGuid).send() as Promise<any>,
|
||||
};
|
||||
|
||||
const visible = ref(false);
|
||||
const loading = ref(false);
|
||||
const title = ref("预览候选人");
|
||||
const result = ref<Awaited<ReturnType<typeof resolveNodePreview>> | null>(null);
|
||||
|
||||
async function show(cfg: PreviewNodeConfig, depts: PreviewDept[], flowGuid = ""): Promise<void> {
|
||||
visible.value = true;
|
||||
loading.value = true;
|
||||
result.value = null;
|
||||
title.value = `预览候选人 · ${cfg.name || "未命名节点"}`;
|
||||
try {
|
||||
result.value = await resolveNodePreview(cfg, depts, api, flowGuid);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({ show });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal v-model="visible" :title="title" :width="620" :footer-hide="true">
|
||||
<div v-if="loading" class="muted" style="padding: 24px 0; text-align: center">正在解析候选人…</div>
|
||||
<template v-else-if="result">
|
||||
<Alert v-if="result.blocked" type="warning" show-icon class="pv-block">{{ result.blocked }}</Alert>
|
||||
<div class="pv-headline">
|
||||
<b>{{ result.headline }}</b>
|
||||
<span class="pv-source">数据源:{{ result.rows.length ? "FL 科室映射 / UserBase 既有接口" : "未取到候选" }}</span>
|
||||
</div>
|
||||
<div v-if="result.rows.length" class="pv-rows">
|
||||
<div v-for="r in result.rows" :key="r.userId" class="pv-row">
|
||||
<Tag :color="r.warn ? 'warning' : 'blue'">{{ r.userName }}</Tag>
|
||||
<span class="pv-id">#{{ r.userId }}</span>
|
||||
<span v-if="r.warn" class="pv-warn">{{ r.warn }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="muted">(本节点当前没有可展示的候选人)</div>
|
||||
|
||||
<div class="pv-pub" v-if="result.published">
|
||||
<div class="pv-pub-title">已发布版本对账</div>
|
||||
<div class="pv-pub-body">
|
||||
<Tag :color="result.published.state === 'ok' ? 'success' : result.published.state === 'missing' ? 'default' : 'warning'">
|
||||
{{ result.published.state === "ok" ? "可比对" : result.published.state === "missing" ? "草稿节点未发布" : "不可比对" }}
|
||||
</Tag>
|
||||
<span>{{ result.published.text }}</span>
|
||||
</div>
|
||||
<div v-if="result.published.state === 'ok' && result.published.rows.length" class="pv-rows">
|
||||
<Tag v-for="p in result.published.rows" :key="p.userId" color="green">{{ p.userName }}</Tag>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul class="pv-notes">
|
||||
<li v-for="(n, i) in result.notes" :key="i">{{ n }}</li>
|
||||
</ul>
|
||||
</template>
|
||||
<div style="margin-top: 12px; text-align: right">
|
||||
<Button @click="visible = false">关闭</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.pv-block { margin-bottom: 10px; }
|
||||
.pv-headline { display: flex; align-items: baseline; gap: 10px; margin-bottom: 10px; }
|
||||
.pv-source { color: #808695; font-size: 12px; }
|
||||
.pv-rows { display: flex; flex-wrap: wrap; gap: 6px 0; margin-bottom: 12px; }
|
||||
.pv-row { display: flex; align-items: center; gap: 6px; width: 100%; }
|
||||
.pv-id { color: #c5c8ce; font-size: 12px; }
|
||||
.pv-warn { color: #ff9900; font-size: 12px; }
|
||||
.pv-pub { border-top: 1px dashed #e8eaec; padding-top: 10px; margin-bottom: 10px; }
|
||||
.pv-pub-title { font-weight: 600; font-size: 13px; margin-bottom: 6px; }
|
||||
.pv-pub-body { display: flex; align-items: flex-start; gap: 8px; font-size: 13px; }
|
||||
.pv-notes { margin: 0; padding-left: 18px; color: #808695; font-size: 12px; line-height: 1.7; }
|
||||
</style>
|
||||
@@ -23,6 +23,7 @@ function pick(kind: "approval" | "parallel" | "notify" | "condition"): void {
|
||||
<Poptip v-model="visible" trigger="click" placement="right-start" transfer :width="200" popper-class="def-plus-pop">
|
||||
<div class="plus-btn" title="在此插入节点">+</div>
|
||||
<template #content>
|
||||
<div class="menu-group">常用节点</div>
|
||||
<div class="menu-item" @click="pick('approval')">
|
||||
<span class="mi-icon approval">审</span>
|
||||
<span class="mi-text">
|
||||
@@ -37,6 +38,8 @@ function pick(kind: "approval" | "parallel" | "notify" | "condition"): void {
|
||||
<i>知会指定人,不产生待办、不阻塞流程</i>
|
||||
</span>
|
||||
</div>
|
||||
<!-- 高级结构:只是分组展示,四类插入能力一个都没少,插入后的 JSON 语义完全不变 -->
|
||||
<div class="menu-group adv">高级结构</div>
|
||||
<div class="menu-item" @click="pick('parallel')">
|
||||
<span class="mi-icon parallel">⇉</span>
|
||||
<span class="mi-text">
|
||||
@@ -94,6 +97,17 @@ function pick(kind: "approval" | "parallel" | "notify" | "condition"): void {
|
||||
color: #fff;
|
||||
box-shadow: 0 0 0 4px rgba(45, 140, 240, 0.15);
|
||||
}
|
||||
.menu-group {
|
||||
padding: 4px 10px 2px;
|
||||
font-size: 11px;
|
||||
color: #808695;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
.menu-group.adv {
|
||||
margin-top: 6px;
|
||||
border-top: 1px dashed #e8eaec;
|
||||
padding-top: 8px;
|
||||
}
|
||||
.menu-item {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
|
||||
@@ -428,6 +428,10 @@ provide<EditorCtx>(EDITOR_CTX, {
|
||||
model,
|
||||
depts,
|
||||
userNames,
|
||||
// 升版本/编辑草稿时由 defGet 带回;新建态为空串(预览据此说明"没有可比对的已发布版本")
|
||||
get flowGuid() {
|
||||
return sourceFlowGuid.value || "";
|
||||
},
|
||||
routers,
|
||||
availableVars,
|
||||
issueOf,
|
||||
@@ -647,13 +651,13 @@ onUnmounted(() => window.removeEventListener("beforeunload", onBeforeUnload));
|
||||
:disabled="!!editId || !!baseGuid" :maxlength="40" />
|
||||
</div>
|
||||
<div class="ip-fld">
|
||||
<label>归属应用 <span style="color: #ed4014">*</span></label>
|
||||
<Select v-model="appKey" placeholder="归属应用(必选)" filterable>
|
||||
<label>归属应用<span class="ip-mini">可选 · 仅作创建方筛选/审计,不是发起资格</span></label>
|
||||
<Select v-model="appKey" placeholder="归属应用(可留空)" filterable clearable>
|
||||
<Option v-for="a in appOptions" :key="a.appKey" :value="a.appKey">{{ a.appName }}</Option>
|
||||
</Select>
|
||||
</div>
|
||||
<div class="ip-fld">
|
||||
<label>关联表单<span class="ip-mini">表单中心 · 成对存版本</span></label>
|
||||
<label>关联表单<span class="ip-mini">可选 · 表单中心 · 成对存版本</span></label>
|
||||
<Select :value="formUuid" clearable filterable placeholder="不关联(原生办理)"
|
||||
:disabled="!!formCenterError" @on-change="onFormChange">
|
||||
<Option v-for="f in formOptions" :key="f.formUuid" :value="f.formUuid">
|
||||
|
||||
@@ -415,6 +415,8 @@ export interface EditorCtx {
|
||||
model: EditorModel;
|
||||
depts: DeptOption[];
|
||||
userNames: Record<number, string>;
|
||||
/** 当前流程线 flowGuid(新建/尚未保存时为空):指派预览与已发布版本对账要用 */
|
||||
flowGuid: string;
|
||||
/** 条件分支的判断服务注册下拉(flw_router 启用项;拉取失败为空数组,校验降级提示) */
|
||||
routers: RouterOption[];
|
||||
/** 可用变量 chips:近期办件 formData 顶层键聚合 + 内置变量(供条件表达式内联提示/插入) */
|
||||
|
||||
Reference in New Issue
Block a user