fix(def-editor): 空出口分支(直接通过)往返支持——解析不再造无审批人占位卡(曾致打开即报"未选择科室映射"且保存凭空加节点改语义);序列化空分支=起点→汇合点命名+条件直连边(Tail 携带出口属性);校验空分支降为 warn(并行分支保持 error:后端禁 SPLIT 直连 JOIN);ConditionBox 空分支渲染"直达汇合点"提示。convert 往返 64/64(新增空分支/空泳道 6 例+修正 1 处过期负例文案)
build / build (push) Successful in 1m53s
build / build (push) Successful in 1m53s
This commit is contained in:
@@ -78,6 +78,7 @@ function editCondition(i: number, v: string): void {
|
||||
@input="editCondition(i, ($event.target as HTMLInputElement).value)"
|
||||
/>
|
||||
<NodeList :items="branch" :container="`${node.uid}:${i}`" />
|
||||
<div v-if="!branch.length" class="cb-empty">该出口直达汇合点(直接通过)</div>
|
||||
</div>
|
||||
</div>
|
||||
<Button v-if="canAddBranch" class="cb-add" size="small" long @click="ctx.addBranch(node.uid)">
|
||||
@@ -118,6 +119,16 @@ function editCondition(i: number, v: string): void {
|
||||
color: #9254de;
|
||||
margin: 6px 0 10px;
|
||||
}
|
||||
.cb-empty {
|
||||
margin: 4px 0 8px;
|
||||
padding: 8px 6px;
|
||||
border: 1px dashed #d3c6e8;
|
||||
border-radius: 6px;
|
||||
text-align: center;
|
||||
font-size: 11px;
|
||||
color: #9254de;
|
||||
background: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
.cb-cols {
|
||||
display: flex;
|
||||
gap: 14px;
|
||||
|
||||
@@ -42,25 +42,42 @@ export function toContentJson(model: EditorModel): DefJson {
|
||||
/**
|
||||
* 条件分支(2026-09-24)无 JOIN 语义:分支尾不互连,隐式汇合到容器后继——cursor 置 null 后
|
||||
* 下一个节点(或 END)只接收各分支尾的连线;firstEdgeName 只作用于分支首边(出口名)。
|
||||
* walk 返回"尾集合"(普通序列=末节点,末尾含条件分支=各分支尾)。
|
||||
* firstEdgeCondition(2026-10-10):EXPRESSION 模式分支首边的条件表达式,与出口名同位写入。
|
||||
* 空出口分支(2026-10-12,"直接通过"):items 为空时 walk 原样返回起点作为尾并携带出口名/条件,
|
||||
* 汇合连线即"起点→汇合点"的命名直连边——与引擎 cond→汇合点直连边双向保真。
|
||||
*/
|
||||
interface Tail {
|
||||
id: string;
|
||||
name?: string;
|
||||
condition?: string;
|
||||
}
|
||||
const tailEdge = (from: string, to: string, t: Tail): DefEdgeJson => {
|
||||
const e: DefEdgeJson = { from, to };
|
||||
if (t.name) e.name = t.name;
|
||||
if (t.condition !== undefined && t.condition !== null && t.condition !== "") {
|
||||
e.condition = t.condition;
|
||||
}
|
||||
return e;
|
||||
};
|
||||
const walk = (items: EditorItem[], prev: string, firstEdgeName?: string,
|
||||
firstEdgeCondition?: string): string[] => {
|
||||
firstEdgeCondition?: string): Tail[] => {
|
||||
let cursor: string | null = prev;
|
||||
let pending: string[] = [];
|
||||
let pending: Tail[] = [];
|
||||
let first = true;
|
||||
const linkTo = (toId: string): void => {
|
||||
if (cursor !== null) {
|
||||
if (first && firstEdgeName) {
|
||||
const e: DefEdgeJson = { from: cursor, to: toId, name: firstEdgeName };
|
||||
if (firstEdgeCondition !== undefined) e.condition = firstEdgeCondition;
|
||||
if (firstEdgeCondition !== undefined && firstEdgeCondition !== null
|
||||
&& firstEdgeCondition !== "") {
|
||||
e.condition = firstEdgeCondition;
|
||||
}
|
||||
edges.push(e);
|
||||
} else {
|
||||
edges.push({ from: cursor, to: toId });
|
||||
}
|
||||
}
|
||||
pending.forEach((t) => edges.push({ from: t, to: toId }));
|
||||
pending.forEach((t) => edges.push(tailEdge(t.id, toId, t)));
|
||||
pending = [];
|
||||
first = false;
|
||||
};
|
||||
@@ -80,7 +97,7 @@ export function toContentJson(model: EditorModel): DefJson {
|
||||
if (item.outlets && item.outlets.length >= 2 && item.branches) {
|
||||
// 多出口(2026-10-03 P2):APPROVAL 的多条命名出边即按钮选路,
|
||||
// 分支尾隐式汇合到后继(与条件分支容器完全同构)
|
||||
const tails: string[] = [];
|
||||
const tails: Tail[] = [];
|
||||
item.branches.forEach((branch, bi) => {
|
||||
tails.push(...walk(branch, id, item.outlets![bi]));
|
||||
});
|
||||
@@ -116,7 +133,7 @@ export function toContentJson(model: EditorModel): DefJson {
|
||||
if (item.judgeMode === "EXPRESSION") node.judgeMode = "EXPRESSION";
|
||||
nodes.push(node);
|
||||
linkTo(condId);
|
||||
const tails: string[] = [];
|
||||
const tails: Tail[] = [];
|
||||
const expression = item.judgeMode === "EXPRESSION";
|
||||
item.branches.forEach((branch, bi) => {
|
||||
const cond = expression ? (item.conditions || [])[bi] || "" : undefined;
|
||||
@@ -132,17 +149,27 @@ export function toContentJson(model: EditorModel): DefJson {
|
||||
nodes.push({ id: join, type: "PARALLEL_JOIN", waitFor: "ALL" });
|
||||
linkTo(split);
|
||||
item.branches.forEach((branch) => {
|
||||
walk(branch, split).forEach((tail) => edges.push({ from: tail, to: join }));
|
||||
walk(branch, split).forEach((tail) => edges.push(tailEdge(tail.id, join, tail)));
|
||||
});
|
||||
cursor = join;
|
||||
});
|
||||
const tails: string[] = [];
|
||||
if (cursor !== null) tails.push(cursor);
|
||||
const tails: Tail[] = [];
|
||||
// 空出口分支:无任何节点消费(first 未翻转、cursor 停在起点)——出口名/条件随尾携带,
|
||||
// 由汇合点连线落成命名直连边(非空分支的 first 边已由 linkTo 写过,此处 first=false 不受影响)
|
||||
if (cursor !== null) {
|
||||
const t: Tail = { id: cursor };
|
||||
if (first && firstEdgeName) t.name = firstEdgeName;
|
||||
if (first && firstEdgeCondition !== undefined && firstEdgeCondition !== null
|
||||
&& firstEdgeCondition !== "") {
|
||||
t.condition = firstEdgeCondition;
|
||||
}
|
||||
tails.push(t);
|
||||
}
|
||||
tails.push(...pending);
|
||||
return tails;
|
||||
};
|
||||
|
||||
walk(model.list, "start").forEach((tail) => edges.push({ from: tail, to: "end" }));
|
||||
walk(model.list, "start").forEach((tail) => edges.push(tailEdge(tail.id, "end", tail)));
|
||||
const endNode: DefNodeJson = { id: "end", type: "END" };
|
||||
if (model.endName) endNode.name = model.endName;
|
||||
nodes.push(endNode);
|
||||
@@ -334,7 +361,9 @@ export function fromContentJson(def: any): ParsedDef {
|
||||
judgeMode: n.judgeMode === "EXPRESSION" ? "EXPRESSION" : "SERVICE",
|
||||
conditions,
|
||||
outlets,
|
||||
branches: branches.map((b) => (b.length ? b : [createApproval()])),
|
||||
// 空出口分支(2026-10-12):保留空数组("直接通过"直达汇合点),
|
||||
// 不再造无审批人占位卡(曾导致打开即报"未选择科室映射"、保存还会凭空改语义)
|
||||
branches,
|
||||
},
|
||||
mergeId,
|
||||
};
|
||||
@@ -377,7 +406,7 @@ export function fromContentJson(def: any): ParsedDef {
|
||||
}
|
||||
const node = toApprovalNode(n);
|
||||
node.outlets = outlets;
|
||||
node.branches = branches.map((b) => (b.length ? b : [createApproval()]));
|
||||
node.branches = branches; // 空出口泳道保留(直达汇合点),不造占位卡
|
||||
list.push(node);
|
||||
cur = mergeId;
|
||||
} else {
|
||||
|
||||
@@ -69,7 +69,8 @@ export function validateModel(model: EditorModel, depts: DeptOption[], routers:
|
||||
(item.branches || []).forEach((branch, i) => {
|
||||
const p = `${path}·出口「${item.outlets![i] || i + 1}」`;
|
||||
if (!branch.length) {
|
||||
push(item.uid, "error", p, "出口泳道为空,请添加节点");
|
||||
// 空出口泳道(2026-10-12):合法语义=该出口直达汇合点(直接通过),仅提示不阻断
|
||||
push(item.uid, "warn", p, "出口泳道为空:该出口直达汇合点(直接通过)");
|
||||
return;
|
||||
}
|
||||
rec(branch, p);
|
||||
@@ -136,7 +137,8 @@ export function validateModel(model: EditorModel, depts: DeptOption[], routers:
|
||||
item.branches.forEach((branch, i) => {
|
||||
const branchPath = `${path}·出口「${item.outlets[i] || BRANCH_LABELS[i] || i + 1}」`;
|
||||
if (!branch.length) {
|
||||
push(item.uid, "error", branchPath, "分支为空,请添加节点");
|
||||
// 空出口分支(2026-10-12):合法语义=该出口直达汇合点(直接通过),仅提示不阻断
|
||||
push(item.uid, "warn", branchPath, "分支为空:该出口直达汇合点(直接通过)");
|
||||
return;
|
||||
}
|
||||
rec(branch, branchPath);
|
||||
|
||||
Reference in New Issue
Block a user