Files
flow-center-front/src/views/flow/admin/Routers.vue
T
chenxing b96d3d6451 B4+B5 表达式调试三模式弹窗(共享组件)+ 编辑器出口内联试算
- 新增 views/flow/components/ExprEvalModal.vue(原型 S1-S5,宽 560→700):
  1) 从办件导入:adminInstances(page1,size50) 的 filterable Select(#id·bizType/bizId·标题·提交人·时间+状态Tag)、
     bizType distinct chips 筛选、选中即 formData 解析缩进填充(解析失败填原文+红字)、内置变量只读条
     (instanceId/instanceVersion/bizType/bizId/submitterId;nodeId/nodeName 提示按调试目标自填;schId 不展示)
  2) 从路由日志导入:detail(instanceId) 取 kind=ROUTE 行,表格 节点名/注册键/命中出口(空=未命中)/attempt/时间
     + 每行 rawResponse <pre>(>200 字截断、title 全量、无留痕显示「(无留痕)」);
     「载入该办件变量并复现」= 填 formData + 保留表达式再求值;无 ROUTE 行给空态文案
  3) 手动编写:原 textarea 语义保留 + 最近 50 条办件 formData 键聚合 chips(点击插光标处)+ 引导文案
  可用变量 chips 点击插入表达式光标(selectionStart/End,无焦点退化追加);求值仍走 /admin/router/eval,
  结果区四态文案(true/false/error/未求值)含线上行为说明与「宁挂起不误放行」口径
- Routers.vue:删旧内联求值弹窗(evalForm/evalResult/doEval),改挂 <ExprEvalModal v-model="exprVisible">
- ConditionBox.vue(B5/S6):EXPRESSION 模式出口表达式行右侧加「试算」小钮(@click.stop 不触发容器交互),
  带该出口表达式打开同一弹窗

注意:本提交连带纳入了并发会话在 Routers.vue 的 6 行「来源」列改动(regAppKey 自注册标识,
属 SERVICE 收口工作),非本批次内容,review 时请单独留意
2026-10-12 16:35:26 +08:00

208 lines
8.4 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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, Tag } from "view-ui-plus";
import MyConfirm from "@/components/MyConfirm";
import ExprEvalModal from "@/views/flow/components/ExprEvalModal.vue";
import { flowApi } from "@/api/module/flow";
import { actionColumn } from "@/utils/tableActions";
import { useElementHeight } from "@/utils/hook/useElementHeight";
const loading = ref(false);
const rows = ref<any[]>([]);
// 无搜索分页脚:表格高度实测 .table-region(flex:1)
const routerRegionRef = ref<HTMLElement | null>(null);
const routersH = useElementHeight(routerRegionRef);
const columns = [
{ title: "序号", type: "index", width: 70, align: "center" },
{ title: "注册键", key: "routerKey", width: 220 },
{ title: "名称", key: "name", width: 160 },
{ title: "服务目录键", key: "serviceKey", width: 110 },
{
// 2026-10-12 SERVICE 收口:reg_app_key 非空=yh-flow-route starter 自注册行(归属应用)
title: "来源",
width: 160,
render: (h: any, p: any) => h("span", p.row.regAppKey ? `自注册 · ${p.row.regAppKey}` : "手工"),
},
{ 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 ? "启用" : "停用")),
},
actionColumn(
[
{ key: "edit", label: "编辑", icon: "icon-edit", on: (r: any) => onEdit(r) },
{ key: "disable", label: "停用", icon: "icon-stop", on: (r: any) => onDisable(r), disabledWhen: (r: any) => r.status !== 1, danger: true },
{ key: "enable", label: "启用", icon: "icon-play", on: (r: any) => onEnable(r), disabledWhen: (r: any) => r.status === 1 },
],
{ mode: "text", width: 150, fixed: "right" },
),
];
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();
},
});
}
// ---- 表达式调试(2026-10-10 四议题;2026-10-12 议题三改为三模式共享弹窗 ExprEvalModal)----
const exprVisible = ref(false);
function onEval() {
exprVisible.value = true;
}
onMounted(load);
</script>
<template>
<div class="page-wrapper yh-fill">
<div class="main-container">
<div class="fix mb-12">
<Button class="fl" type="primary" size="large" @click="onAdd">新增注册</Button>
<Button class="fl" size="large" style="margin-left: 8px" @click="onEval">表达式调试</Button>
</div>
<div class="search-wrap fix">
<span class="fr" style="color: #808695; font-size: 12px; line-height: 32px">
判断逻辑由业务方实现:引擎运行到 CONDITION 节点时按注册键同步调用(3s×3 重试),
失败挂起、管理端办件记录里重跑路由
</span>
</div>
<div class="table-region" ref="routerRegionRef">
<Table :columns="columns" :data="rows" :loading="loading" class="link-table table-bottom-box"
:height="routersH || undefined" no-data-text="暂无数据" />
</div>
<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>
<ExprEvalModal v-model="exprVisible" />
</div>
</div>
</template>
<style scoped>
/* 无分页脚:通道只对含 .page-panel 的卡片自动转纵向分栏,本页自声明保证 table-region 撑满 */
.main-container {
display: flex;
flex-direction: column;
}
</style>