feat(condition): 编辑器支持表达式判断——判断方式切换 + 出口条件就地编辑 + 表达式调试

- ConditionNode 增 judgeMode/conditions(SERVICE 缺省,旧定义天然兼容);DefEdgeJson/DefNodeJson 透传 condition/judgeMode
- convert.ts 双向透传:CONDITION 分支首边带 condition,序列化顺序=分支顺序(求值优先级保序)
- ConditionDrawer 判断方式 Radio(服务/表达式);ConditionBox 表达式模式分支头部条件输入(序号即优先级)
- validate.ts 镜像校验:表达式模式每出口条件必填(语法由后端发布期拦)
- Routers 页加「表达式调试」Modal(/admin/router/eval,与线上同一求值核心)
- addBranch/removeBranch 同步维护 conditions 占位
This commit is contained in:
chenxing
2026-10-10 18:52:54 +08:00
parent a4cc8ea2fa
commit acbf601e7a
8 changed files with 205 additions and 20 deletions
+21 -5
View File
@@ -43,15 +43,19 @@ export function toContentJson(model: EditorModel): DefJson {
* 条件分支(2026-09-24)无 JOIN 语义:分支尾不互连,隐式汇合到容器后继——cursor 置 null 后
* 下一个节点(或 END)只接收各分支尾的连线;firstEdgeName 只作用于分支首边(出口名)。
* walk 返回"尾集合"(普通序列=末节点,末尾含条件分支=各分支尾)。
* firstEdgeCondition(2026-10-10):EXPRESSION 模式分支首边的条件表达式,与出口名同位写入。
*/
const walk = (items: EditorItem[], prev: string, firstEdgeName?: string): string[] => {
const walk = (items: EditorItem[], prev: string, firstEdgeName?: string,
firstEdgeCondition?: string): string[] => {
let cursor: string | null = prev;
let pending: string[] = [];
let first = true;
const linkTo = (toId: string): void => {
if (cursor !== null) {
if (first && firstEdgeName) {
edges.push({ from: cursor, to: toId, name: firstEdgeName });
const e: DefEdgeJson = { from: cursor, to: toId, name: firstEdgeName };
if (firstEdgeCondition !== undefined) e.condition = firstEdgeCondition;
edges.push(e);
} else {
edges.push({ from: cursor, to: toId });
}
@@ -102,16 +106,21 @@ export function toContentJson(model: EditorModel): DefJson {
}
if (item.kind === "condition") {
const condId = `${item.uid}c`;
nodes.push({
const node: DefNodeJson = {
id: condId,
type: "CONDITION",
name: item.name || "条件判断",
routerKey: item.routerKey || "",
});
};
// SERVICE 是后端缺省,不写字段保持最小 JSON;EXPRESSION 显式携带
if (item.judgeMode === "EXPRESSION") node.judgeMode = "EXPRESSION";
nodes.push(node);
linkTo(condId);
const tails: string[] = [];
const expression = item.judgeMode === "EXPRESSION";
item.branches.forEach((branch, bi) => {
tails.push(...walk(branch, condId, item.outlets[bi]));
const cond = expression ? (item.conditions || [])[bi] || "" : undefined;
tails.push(...walk(branch, condId, item.outlets[bi], cond));
});
cursor = null;
pending = tails;
@@ -311,12 +320,19 @@ export function fromContentJson(def: any): ParsedDef {
// uid 复用 JSON id 并剥掉生成态的 "c" 尾缀:uid+"c" 是本编辑器的 JSON id 生成约定,
// 剥尾保证"图→树→图→树→图"的 id 收敛(否则每轮追加一个 c,往返不幂等)
const uidRaw = condId.endsWith("c") ? condId.slice(0, -1) : condId;
// 出口条件反解(2026-10-10):EXPRESSION 模式出边 condition 与 outlets 同位
const conditions = heads.map((head) => {
const e = edges.find((x) => x.from === condId && x.to === head);
return e && e.condition ? String(e.condition) : "";
});
return {
item: {
uid: UsedIds.take(uidRaw),
kind: "condition",
name: n.name || "条件判断",
routerKey: n.routerKey || "",
judgeMode: n.judgeMode === "EXPRESSION" ? "EXPRESSION" : "SERVICE",
conditions,
outlets,
branches: branches.map((b) => (b.length ? b : [createApproval()])),
},