feat: 审批节点发起时指派配置(开关/单选多选/必选)与转换无损
build / build (push) Successful in 5m34s

This commit is contained in:
chenxing
2026-10-13 12:05:22 +08:00
parent ec810afd14
commit 0a060ab22a
3 changed files with 58 additions and 0 deletions
@@ -29,6 +29,11 @@ const form = reactive({
countersign: "ANY" as "ALL" | "ANY", countersign: "ANY" as "ALL" | "ANY",
deadlineHours: null as number | null, deadlineHours: null as number | null,
multiOutlet: false, multiOutlet: false,
assignEnabled: false,
assignMode: "MULTI" as "SINGLE" | "MULTI",
assignMin: 1,
assignMax: null as number | null,
assignRequired: false,
}); });
const userCheckModal = ref<any>(); const userCheckModal = ref<any>();
@@ -49,6 +54,11 @@ function syncFromNode(): void {
form.countersign = n.countersign; form.countersign = n.countersign;
form.deadlineHours = n.deadlineHours; form.deadlineHours = n.deadlineHours;
form.multiOutlet = !!(n.outlets && n.outlets.length >= 2); form.multiOutlet = !!(n.outlets && n.outlets.length >= 2);
form.assignEnabled = !!n.assignPolicy;
form.assignMode = n.assignPolicy?.mode === "SINGLE" ? "SINGLE" : "MULTI";
form.assignMin = n.assignPolicy?.min ?? 1;
form.assignMax = n.assignPolicy?.max ?? null;
form.assignRequired = !!n.assignPolicy?.required;
} }
watch( watch(
@@ -127,6 +137,13 @@ function onOk(): void {
n.outlets = ["同意", "退回"]; n.outlets = ["同意", "退回"];
n.branches = [[createApproval()], [createApproval()]]; n.branches = [[createApproval()], [createApproval()]];
} }
n.assignPolicy = form.assignEnabled
? {
mode: form.assignMode,
...(form.assignMode === "MULTI" ? { min: form.assignMin || 1, max: form.assignMax ?? undefined } : {}),
required: form.assignRequired,
}
: undefined;
emit("update:modelValue", false); emit("update:modelValue", false);
} }
@@ -200,6 +217,32 @@ function onRemove(): void {
</div> </div>
</div> </div>
<div class="fld">
<label>发起时指派</label>
<div class="mo-row">
<i-switch v-model="form.assignEnabled" />
<span class="mo-label">允许发起人在提交时指定本节点审批人</span>
</div>
<template v-if="form.assignEnabled">
<div class="mo-row" style="margin-top: 8px">
<RadioGroup v-model="form.assignMode" type="button" button-style="solid" size="small">
<Radio label="SINGLE">单选</Radio>
<Radio label="MULTI">可多选</Radio>
</RadioGroup>
</div>
<div v-if="form.assignMode === 'MULTI'" class="mo-row" style="margin-top: 8px">
<span>最少</span>
<InputNumber v-model="form.assignMin" :min="1" size="small" style="width: 90px" />
<span>最多</span>
<InputNumber v-model="form.assignMax" :min="1" size="small" style="width: 90px" placeholder="不限" />
</div>
<div class="mo-row" style="margin-top: 8px">
<Checkbox v-model="form.assignRequired">提交时必选(子应用提交页提示)</Checkbox>
</div>
</template>
<div class="hint">开启后,发起人在子应用提交时可从本节点候选审批人中指定实际审核人;未指定则全体候选按上方方式审批。或签/会签语义不变,仅收窄人选</div>
</div>
<div class="fld"> <div class="fld">
<label>办理时限(小时)</label> <label>办理时限(小时)</label>
<InputNumber v-model="form.deadlineHours" :min="1" placeholder="可空" style="width: 100%" /> <InputNumber v-model="form.deadlineHours" :min="1" placeholder="可空" style="width: 100%" />
@@ -92,6 +92,7 @@ export function toContentJson(model: EditorModel): DefJson {
}; };
if (item.countersign === "ANY") node.countersign = "ANY"; if (item.countersign === "ANY") node.countersign = "ANY";
if (item.deadlineHours) node.deadlineHours = item.deadlineHours; if (item.deadlineHours) node.deadlineHours = item.deadlineHours;
if (item.assignPolicy) node.assignPolicy = { ...item.assignPolicy };
nodes.push(node); nodes.push(node);
linkTo(id); linkTo(id);
if (item.outlets && item.outlets.length >= 2 && item.branches) { if (item.outlets && item.outlets.length >= 2 && item.branches) {
@@ -487,6 +488,7 @@ function toApprovalNode(n: any): ApprovalNode {
assignee: n.assignee ? cloneAssignee(n.assignee as Assignee) : null, assignee: n.assignee ? cloneAssignee(n.assignee as Assignee) : null,
countersign, countersign,
deadlineHours: n.deadlineHours ? Number(n.deadlineHours) : null, deadlineHours: n.deadlineHours ? Number(n.deadlineHours) : null,
assignPolicy: n.assignPolicy ? { ...n.assignPolicy } : undefined,
}; };
} }
+13
View File
@@ -19,6 +19,14 @@ export interface AssigneeUsers {
} }
export type Assignee = AssigneeDept | AssigneeUsers; export type Assignee = AssigneeDept | AssigneeUsers;
/** 发起时指派:mode SINGLE=恰 1 人|MULTI=min~max;required 仅子应用 UI 必选提示 */
export interface AssignPolicy {
mode: "SINGLE" | "MULTI";
min?: number | null;
max?: number | null;
required?: boolean;
}
export interface ApprovalNode { export interface ApprovalNode {
uid: string; uid: string;
kind: "approval"; kind: "approval";
@@ -28,6 +36,8 @@ export interface ApprovalNode {
countersign: Countersign; countersign: Countersign;
/** 办理时限(小时):null=不限,生成 JSON 时省略 */ /** 办理时限(小时):null=不限,生成 JSON 时省略 */
deadlineHours: number | null; deadlineHours: number | null;
/** 发起时指派策略:存在即允许指派;生成 JSON 时关闭则不写字段(与 countersign 省略风格一致) */
assignPolicy?: AssignPolicy | null;
/** 多出口(≥2 才有多出口语义):出口按钮文案,办理人按下哪个按钮走哪条泳道;单出口不写字段 */ /** 多出口(≥2 才有多出口语义):出口按钮文案,办理人按下哪个按钮走哪条泳道;单出口不写字段 */
outlets?: string[]; outlets?: string[];
/** 各出口泳道(与 outlets 一一对应);分支尾隐式汇合到后继节点(同条件分支,无 JOIN) */ /** 各出口泳道(与 outlets 一一对应);分支尾隐式汇合到后继节点(同条件分支,无 JOIN) */
@@ -117,6 +127,8 @@ export interface DefNodeJson {
countersign?: Countersign; countersign?: Countersign;
waitFor?: string; waitFor?: string;
deadlineHours?: number; deadlineHours?: number;
/** 发起时指派策略(审批节点可选,2026-10-13;关闭态不写字段) */
assignPolicy?: AssignPolicy;
} }
export interface DefEdgeJson { export interface DefEdgeJson {
@@ -324,6 +336,7 @@ export function assigneeText(a: Assignee | null, depts: DeptOption[], userNames:
/** 卡片标签行(会签/或签 + 时限) */ /** 卡片标签行(会签/或签 + 时限) */
export function nodeTags(n: ApprovalNode): string[] { export function nodeTags(n: ApprovalNode): string[] {
const tags = [n.countersign === "ANY" ? "或签" : "会签"]; const tags = [n.countersign === "ANY" ? "或签" : "会签"];
if (n.assignPolicy) tags.push("可指派");
if (n.deadlineHours) tags.push(`${n.deadlineHours} 小时时限`); if (n.deadlineHours) tags.push(`${n.deadlineHours} 小时时限`);
return tags; return tags;
} }