feat(admin): 抄送(NOTIFY)与条件分支(CONDITION)全栈接入编辑器/轨迹/管理面
build / build (push) Successful in 1m55s

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:
chenxing
2026-09-24 01:19:15 +08:00
parent cdf04f0930
commit e30e7ab665
20 changed files with 1614 additions and 84 deletions
+182
View File
@@ -0,0 +1,182 @@
<script setup lang="ts">
// 条件路由维护(2026-09-24 二期第二批 §4.2):flw_router 判断服务注册表(全局表)。
// CONDITION 节点的 routerKey 必须命中启用中的注册(发布校验口径);basePath 是对端 context-path,
// 漏填必 401(规范 §4.2 同款坑)——生产 notice 为 /notice,本地为空。
import { ref, onMounted } from "vue";
import { Message } from "view-ui-plus";
import MyConfirm from "@/components/MyConfirm";
import { flowApi } from "@/api/module/flow";
import Utils from "@/utils";
const loading = ref(false);
const rows = ref<any[]>([]);
const tableHeight = ref(Utils.useTableHeight({ isSearch: true, isOperat: true }));
const columns = [
{ title: "序号", type: "index", width: 70, align: "center" },
{ title: "注册键", key: "routerKey", width: 220 },
{ title: "名称", key: "name", width: 160 },
{ title: "服务目录键", key: "serviceKey", width: 110 },
{ title: "判断端点", key: "path", minWidth: 300 },
{
title: "basePath",
width: 110,
render: (h: any, p: any) => h("span", p.row.basePath || "(空=沿用目录)"),
},
{
title: "状态",
width: 90,
align: "center",
render: (h: any, p: any) =>
h("Tag", { color: p.row.status === 1 ? "green" : "gray" }, () => (p.row.status === 1 ? "启用" : "停用")),
},
{
title: "操作",
width: 150,
align: "center",
fixed: "right",
render: (h: any, p: any) => [
Utils.operatingHandle(h, "编辑", "icon-edit", () => onEdit(p.row)),
h("span", { style: "display:inline-block;width:10px" }),
p.row.status === 1
? Utils.operatingHandle(h, "停用", "icon-stop", () => onDisable(p.row))
: Utils.operatingHandle(h, "启用", "icon-play", () => onEnable(p.row)),
],
},
];
async function load() {
loading.value = true;
try {
rows.value = (await flowApi.routerList().send()) || [];
} finally {
loading.value = false;
}
}
// ---- 新增/编辑弹窗 ----
const saveVisible = ref(false);
const saving = ref(false);
const formRef = ref<any>(null);
const form = ref<any>({ id: null, routerKey: "", name: "", serviceKey: "", path: "", basePath: "" });
const saveRules = {
routerKey: [{ required: true, message: "请填写红色星号内容!", trigger: "blur" }],
name: [{ required: true, message: "请填写红色星号内容!", trigger: "blur" }],
serviceKey: [{ required: true, message: "请填写红色星号内容!", trigger: "blur" }],
path: [{ required: true, message: "请填写红色星号内容!", trigger: "blur" }],
};
function onAdd() {
form.value = { id: null, routerKey: "", name: "", serviceKey: "notice", path: "", basePath: "" };
saveVisible.value = true;
}
function onEdit(row: any) {
form.value = {
id: row.id,
routerKey: row.routerKey,
name: row.name,
serviceKey: row.serviceKey,
path: row.path,
basePath: row.basePath || "",
};
saveVisible.value = true;
}
function onSave() {
formRef.value?.validate(async (valid: boolean) => {
if (!valid) return;
saving.value = true;
try {
await flowApi.routerSave({
id: form.value.id || undefined,
routerKey: form.value.routerKey.trim(),
name: form.value.name.trim(),
serviceKey: form.value.serviceKey.trim(),
path: form.value.path.trim(),
basePath: form.value.basePath.trim(),
}).send();
Message.success("已保存");
saveVisible.value = false;
load();
} finally {
saving.value = false;
}
});
}
function onDisable(row: any) {
MyConfirm({
title: "停用",
msg: `确定停用判断服务「${row.name || row.routerKey}」吗?`,
info: "停用后引用它的流程定义不能发布;在途挂起办件的重跑会失败",
async onOk() {
await flowApi.routerStatus(row.id, false).send();
Message.success("已停用");
load();
},
});
}
function onEnable(row: any) {
MyConfirm({
title: "启用",
msg: `确定启用判断服务「${row.name || row.routerKey}」吗?`,
info: "启用后流程编辑器的判断服务下拉可选它,发布校验放行引用",
async onOk() {
await flowApi.routerStatus(row.id, true).send();
Message.success("已启用");
load();
},
});
}
onMounted(load);
</script>
<template>
<div class="page-wrapper">
<div class="main-container">
<div class="fix mb-12">
<Button class="fl" type="primary" size="large" @click="onAdd">新增注册</Button>
</div>
<div class="search-wrap fix">
<span class="fr" style="color: #808695; font-size: 12px; line-height: 32px">
判断逻辑由业务方实现:引擎运行到 CONDITION 节点时按注册键同步调用(3s×3 重试),
失败挂起、管理端办件记录里重跑路由
</span>
</div>
<Table :columns="columns" :data="rows" :loading="loading" class="link-table table-bottom-box"
:height="tableHeight" no-data-text="暂无数据" />
<Modal v-model="saveVisible" title="新增/编辑判断服务" :mask-closable="false">
<Form ref="formRef" :model="form" :rules="saveRules" :label-width="100">
<FormItem label="注册键" prop="routerKey">
<Input v-model="form.routerKey" placeholder="CONDITION 节点 routerKey 引用它,如 notice.dept-judge" :maxlength="64" />
</FormItem>
<FormItem label="名称" prop="name">
<Input v-model="form.name" placeholder="展示名(编辑器下拉),如 发文部门判断" :maxlength="128" />
</FormItem>
<FormItem label="服务目录键" prop="serviceKey">
<Input v-model="form.serviceKey" placeholder="服务目录三级解析的 key,如 notice" :maxlength="64" />
</FormItem>
<FormItem label="判断端点" prop="path">
<Input v-model="form.path" placeholder="以 / 开头,如 /oapi/notice/flow/route/dept-judge" :maxlength="255" />
</FormItem>
<FormItem label="basePath">
<Input v-model="form.basePath" placeholder="对端 context-path(生产 /notice;本地无则留空)" :maxlength="64" />
<p style="color: #808695; font-size: 12px; line-height: 1.6; margin-top: 4px">
签名拼串要剥它,漏填必 401;留空=沿用服务目录条目自带的前缀
</p>
</FormItem>
</Form>
<template #footer>
<Button @click="saveVisible = false">取消</Button>
<Button type="primary" :loading="saving" @click="onSave">提交</Button>
</template>
</Modal>
</div>
</div>
</template>