NOTIFY(2026-09-23):编辑器抄送卡片+抽屉+画布 bpmn:ServiceTask 映射、工作台「抄送我的」页签、轨迹 kind=CC 行、示意图青框卡片 CONDITION(2026-09-24):ConditionBox 互斥分支容器(分支列出口名就地编辑、无 JOIN 语义分支尾隐式汇合)、ConditionDrawer 判断服务下拉(flw_router)、convert 双向序列化(CONDITION+命名出边、uid+c 尾缀往返收敛)、validate 镜像后端口径、画布 bpmn:ExclusiveGateway 映射、轨迹「系统判断→出口」行(失败挂起提示)、示意图紫菱形(SVG 与服务端 PNG 同配色)、管理面 flw_router 维护页(/admin/routers)与办件记录「重跑路由」入口 convert 往返 54 项全过、npm run build 通过
This commit is contained in:
@@ -5,7 +5,15 @@
|
||||
* 结构类规则(id 唯一/度约束/孤儿/环/START·END 唯一)由结构化生成保证,前端无需校验。
|
||||
* 后端 DefValidator 仍是发布期权威,此处仅作体验前置。
|
||||
*/
|
||||
import { BRANCH_LABELS, DeptOption, EditorItem, EditorModel, MAX_BRANCHES, MIN_BRANCHES } from "./model";
|
||||
import {
|
||||
BRANCH_LABELS,
|
||||
DeptOption,
|
||||
EditorItem,
|
||||
EditorModel,
|
||||
MAX_BRANCHES,
|
||||
MIN_BRANCHES,
|
||||
RouterOption,
|
||||
} from "./model";
|
||||
|
||||
export type IssueLevel = "error" | "warn";
|
||||
|
||||
@@ -20,7 +28,7 @@ export interface Issue {
|
||||
text: string;
|
||||
}
|
||||
|
||||
export function validateModel(model: EditorModel, depts: DeptOption[]): Issue[] {
|
||||
export function validateModel(model: EditorModel, depts: DeptOption[], routers: RouterOption[] = []): Issue[] {
|
||||
const issues: Issue[] = [];
|
||||
const push = (uid: string, level: IssueLevel, path: string, msg: string): void => {
|
||||
issues.push({ uid, level, path, msg, text: `${path}:${msg}` });
|
||||
@@ -50,6 +58,62 @@ export function validateModel(model: EditorModel, depts: DeptOption[]): Issue[]
|
||||
return;
|
||||
}
|
||||
|
||||
if (item.kind === "notify") {
|
||||
// 抄送节点(运行时宽容:解析不出人只跳过不阻塞,但发布期后端仍拦配置缺失)
|
||||
if (!item.name.trim()) push(item.uid, "error", path, "节点名称不能为空");
|
||||
const r = item.receivers;
|
||||
if (!r) {
|
||||
push(item.uid, "error", path, "未配置抄送对象");
|
||||
} else if (r.type === "DEPT_KEY") {
|
||||
if (!r.deptKey) {
|
||||
push(item.uid, "error", path, "未选择抄送科室");
|
||||
} else {
|
||||
const d = depts.find((x) => x.deptKey === r.deptKey);
|
||||
if (!d) {
|
||||
push(item.uid, "warn", path, `科室映射「${r.deptKey}」不存在,发布会被拦截`);
|
||||
} else if (!d.count) {
|
||||
push(item.uid, "warn", path, `科室「${d.deptName || r.deptKey}」无启用人员,抄送会落空`);
|
||||
}
|
||||
}
|
||||
} else if (!r.userIds.length) {
|
||||
push(item.uid, "error", path, "抄送用户不能为空");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (item.kind === "condition") {
|
||||
// 条件分支(2026-09-24):判断服务必选(未注册仅 warn,发布期后端权威拦截);
|
||||
// 出口名即判断接口 outlets 契约,非空唯一(镜像后端 CONDITION 校验口径)
|
||||
if (!item.name.trim()) push(item.uid, "error", path, "节点名称不能为空");
|
||||
if (!item.routerKey) {
|
||||
push(item.uid, "error", path, "未选择判断服务(发布后运行到该节点会挂起)");
|
||||
} else if (routers.length && !routers.some((r) => r.routerKey === item.routerKey && r.status === 1)) {
|
||||
push(item.uid, "warn", path, `判断服务「${item.routerKey}」未注册或已停用,发布会被拦截`);
|
||||
}
|
||||
if (item.branches.length < MIN_BRANCHES) {
|
||||
push(item.uid, "error", path, `条件分支至少保留 ${MIN_BRANCHES} 个`);
|
||||
}
|
||||
if (item.branches.length > MAX_BRANCHES) {
|
||||
push(item.uid, "error", path, `条件分支最多 ${MAX_BRANCHES} 个`);
|
||||
}
|
||||
item.outlets.forEach((o, i) => {
|
||||
if (!o.trim()) {
|
||||
push(item.uid, "error", path, `第 ${i + 1} 条分支的出口名不能为空`);
|
||||
} else if (item.outlets.indexOf(o) !== i) {
|
||||
push(item.uid, "error", path, `出口名重复:「${o}」(出口名即判断依据,必须唯一)`);
|
||||
}
|
||||
});
|
||||
item.branches.forEach((branch, i) => {
|
||||
const branchPath = `${path}·出口「${item.outlets[i] || BRANCH_LABELS[i] || i + 1}」`;
|
||||
if (!branch.length) {
|
||||
push(item.uid, "error", branchPath, "分支为空,请添加节点");
|
||||
return;
|
||||
}
|
||||
rec(branch, branchPath);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (item.branches.length < MIN_BRANCHES) {
|
||||
push(item.uid, "error", path, `并行分支至少保留 ${MIN_BRANCHES} 个`);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user