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 时请单独留意
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
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";
|
||||
@@ -20,6 +21,12 @@ const columns = [
|
||||
{ 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",
|
||||
@@ -129,33 +136,11 @@ function onEnable(row: any) {
|
||||
});
|
||||
}
|
||||
|
||||
// ---- 表达式调试(2026-10-10 四议题):与运行时同一求值核心(/admin/router/eval) ----
|
||||
const evalVisible = ref(false);
|
||||
const evaluating = ref(false);
|
||||
const evalForm = ref<{ expression: string; variablesJson: string }>({ expression: "", variablesJson: "" });
|
||||
const evalResult = ref<{ result: boolean; value?: string; error?: string } | null>(null);
|
||||
// ---- 表达式调试(2026-10-10 四议题;2026-10-12 议题三改为三模式共享弹窗 ExprEvalModal)----
|
||||
const exprVisible = ref(false);
|
||||
|
||||
function onEval() {
|
||||
evalForm.value = { expression: "", variablesJson: "" };
|
||||
evalResult.value = null;
|
||||
evalVisible.value = true;
|
||||
}
|
||||
|
||||
async function doEval() {
|
||||
if (!evalForm.value.expression.trim()) {
|
||||
Message.warning("请填写表达式");
|
||||
return;
|
||||
}
|
||||
evaluating.value = true;
|
||||
evalResult.value = null;
|
||||
try {
|
||||
evalResult.value = (await flowApi.routerEval({
|
||||
expression: evalForm.value.expression.trim(),
|
||||
variablesJson: evalForm.value.variablesJson.trim(),
|
||||
}).send()) || null;
|
||||
} finally {
|
||||
evaluating.value = false;
|
||||
}
|
||||
exprVisible.value = true;
|
||||
}
|
||||
|
||||
onMounted(load);
|
||||
@@ -208,34 +193,7 @@ onMounted(load);
|
||||
</template>
|
||||
</Modal>
|
||||
|
||||
<Modal v-model="evalVisible" title="表达式调试" :mask-closable="false" :width="560">
|
||||
<p style="color: #808695; font-size: 12px; line-height: 1.6; margin-bottom: 10px">
|
||||
与 CONDITION 节点 EXPRESSION 模式同一套求值(AviatorScript 沙箱)。变量 = 样例 JSON 的
|
||||
键 + 线上另注入 schId/bizType/bizId/submitterId/instanceId/instanceVersion/nodeId/nodeName
|
||||
(调试时请在样例里自行提供内置变量取值)。变量未提供会报错——线上同口径(宁挂起不误放行)
|
||||
</p>
|
||||
<Input v-model="evalForm.expression" placeholder="表达式,如 amount > 1000" :maxlength="200"
|
||||
style="margin-bottom: 8px" />
|
||||
<Input v-model="evalForm.variablesJson" type="textarea" :rows="5"
|
||||
placeholder='样例变量 JSON,如 {"amount": 2000, "bizType": "leave"}' />
|
||||
<div v-if="evalResult" style="margin-top: 10px; font-size: 13px">
|
||||
<template v-if="evalResult.error">
|
||||
<span style="color: #ed4014">✗ 求值失败:{{ evalResult.error }}</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span :style="{ color: evalResult.result ? '#19be6b' : '#ff9900' }">
|
||||
{{ evalResult.result ? "✓ true(命中该出口)" : "✗ false(不命中)" }}
|
||||
</span>
|
||||
<span v-if="evalResult.value !== undefined" style="color: #808695; margin-left: 8px">
|
||||
求值值:{{ evalResult.value }}
|
||||
</span>
|
||||
</template>
|
||||
</div>
|
||||
<template #footer>
|
||||
<Button @click="evalVisible = false">关闭</Button>
|
||||
<Button type="primary" :loading="evaluating" @click="doEval">求值</Button>
|
||||
</template>
|
||||
</Modal>
|
||||
<ExprEvalModal v-model="exprVisible" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -3,9 +3,10 @@
|
||||
// 分支按出口名互斥选路(运行时调判断服务/表达式求值),分支尾隐式汇合到容器后继。
|
||||
// 分支头部的出口名 Input 即 contentJson 里 CONDITION 命名出边的 name(判断接口 outlets 契约);
|
||||
// EXPRESSION 模式(2026-10-10)下分支头部再就地编辑出边 condition,序号即求值优先级。
|
||||
import { computed, inject } from "vue";
|
||||
import { computed, inject, ref } from "vue";
|
||||
import { BRANCH_LABELS, EDITOR_CTX, MAX_BRANCHES, MIN_BRANCHES, ConditionNode } from "../model";
|
||||
import NodeList from "./NodeList.vue";
|
||||
import ExprEvalModal from "@/views/flow/components/ExprEvalModal.vue";
|
||||
|
||||
const props = defineProps<{ node: ConditionNode }>();
|
||||
|
||||
@@ -35,6 +36,15 @@ function editCondition(i: number, v: string): void {
|
||||
while (props.node.conditions.length <= i) props.node.conditions.push("");
|
||||
props.node.conditions[i] = v;
|
||||
}
|
||||
|
||||
// B5 内联试算:把该出口表达式带进共享调试弹窗(ExprEvalModal)
|
||||
const tryVisible = ref(false);
|
||||
const tryExpr = ref("");
|
||||
|
||||
function openTry(i: number): void {
|
||||
tryExpr.value = (props.node.conditions || [])[i] || "";
|
||||
tryVisible.value = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -69,14 +79,17 @@ function editCondition(i: number, v: string): void {
|
||||
<span class="cb-col-del" title="删除分支">×</span>
|
||||
</Poptip>
|
||||
</div>
|
||||
<input
|
||||
v-if="expression"
|
||||
class="cb-cond"
|
||||
:value="(node.conditions || [])[i] || ''"
|
||||
placeholder="条件表达式,如 amount > 1000"
|
||||
maxlength="200"
|
||||
@input="editCondition(i, ($event.target as HTMLInputElement).value)"
|
||||
/>
|
||||
<div v-if="expression" class="cb-cond-row">
|
||||
<input
|
||||
class="cb-cond"
|
||||
:value="(node.conditions || [])[i] || ''"
|
||||
placeholder="条件表达式,如 amount > 1000"
|
||||
maxlength="200"
|
||||
@input="editCondition(i, ($event.target as HTMLInputElement).value)"
|
||||
/>
|
||||
<!-- B5 内联试算:用真实办件变量跑该出口表达式(同「条件路由」页弹窗,预填表达式) -->
|
||||
<span class="cb-try" title="用真实办件变量试算该表达式" @click.stop="openTry(i)">试算</span>
|
||||
</div>
|
||||
<NodeList :items="branch" :container="`${node.uid}:${i}`" />
|
||||
<div v-if="!branch.length" class="cb-empty">该出口直达汇合点(直接通过)</div>
|
||||
</div>
|
||||
@@ -85,6 +98,7 @@ function editCondition(i: number, v: string): void {
|
||||
+ 添加分支
|
||||
</Button>
|
||||
<div v-for="(issue, k) in issues" :key="k" class="cb-issue" :class="issue.level">⚠ {{ issue.text }}</div>
|
||||
<ExprEvalModal v-model="tryVisible" :preset-expression="tryExpr" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -192,6 +206,31 @@ function editCondition(i: number, v: string): void {
|
||||
.cb-cond::placeholder {
|
||||
color: #c5b3e8;
|
||||
}
|
||||
.cb-cond-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
.cb-cond-row .cb-cond {
|
||||
flex: 1;
|
||||
width: auto;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.cb-try {
|
||||
flex: none;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
color: #0b7d72;
|
||||
border: 1px solid #b7e3dc;
|
||||
border-radius: 10px;
|
||||
padding: 0 8px;
|
||||
line-height: 18px;
|
||||
user-select: none;
|
||||
}
|
||||
.cb-try:hover {
|
||||
background: #f2fbf9;
|
||||
border-color: #0b7d72;
|
||||
}
|
||||
.cb-col-del {
|
||||
flex: none;
|
||||
cursor: pointer;
|
||||
|
||||
@@ -0,0 +1,410 @@
|
||||
<script setup lang="ts">
|
||||
// 表达式调试弹窗(议题三,原型 S1-S5):三模式取变量 + 与运行时同一求值口径(/admin/router/eval)。
|
||||
// 抽成共享组件供条件路由页与(B5)编辑器出口试算共用:外部只传 modelValue / presetExpression。
|
||||
import { computed, ref, watch } from "vue";
|
||||
import { Message, Tag } from "view-ui-plus";
|
||||
import { INSTANCE_STATUS, INSTANCE_STATUS_COLOR, flowApi } from "@/api/module/flow";
|
||||
|
||||
const props = defineProps<{ modelValue: boolean; presetExpression?: string }>();
|
||||
const emit = defineEmits<{ (e: "update:modelValue", v: boolean): void }>();
|
||||
|
||||
type Mode = "instance" | "routelog" | "manual";
|
||||
const mode = ref<Mode>("instance");
|
||||
const expr = ref("");
|
||||
const varsJson = ref("");
|
||||
const varsParseError = ref(false);
|
||||
|
||||
const instances = ref<any[]>([]);
|
||||
const instLoading = ref(false);
|
||||
const pickedId = ref<any>(null);
|
||||
const bizTypeFilter = ref("");
|
||||
const traceRows = ref<any[]>([]);
|
||||
const traceLoading = ref(false);
|
||||
const traceInstanceId = ref<any>(null);
|
||||
const result = ref<{ result: boolean; value?: string; error?: string } | null>(null);
|
||||
const evaluating = ref(false);
|
||||
const exprBox = ref<any>(null);
|
||||
|
||||
/** 内置变量(线上由引擎注入;schId 属管理端上下文不展示,nodeId/nodeName 需按调试目标自填) */
|
||||
const BUILTIN_KEYS = ["instanceId", "instanceVersion", "bizType", "bizId", "submitterId", "nodeId", "nodeName"];
|
||||
const picked = computed(() => instances.value.find((r) => r.id === pickedId.value) || null);
|
||||
const builtinRows = computed(() => {
|
||||
const p = picked.value;
|
||||
if (!p) return [];
|
||||
return [
|
||||
{ k: "instanceId", v: p.id },
|
||||
{ k: "instanceVersion", v: p.version },
|
||||
{ k: "bizType", v: p.bizType },
|
||||
{ k: "bizId", v: p.bizId },
|
||||
{ k: "submitterId", v: p.submitterName || p.submitterId },
|
||||
{ k: "nodeId", v: "", todo: true },
|
||||
{ k: "nodeName", v: "", todo: true },
|
||||
];
|
||||
});
|
||||
|
||||
/** 当前变量 JSON 的顶层键(解析失败为空,不阻塞手动编辑) */
|
||||
const formKeys = computed<string[]>(() => {
|
||||
try {
|
||||
const v = JSON.parse(varsJson.value || "{}");
|
||||
return v && typeof v === "object" && !Array.isArray(v) ? Object.keys(v) : [];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
});
|
||||
|
||||
/** 可用变量 chips:办件/路由日志模式=formData 键 + 内置键;手动模式=最近 50 条办件出现过的键 */
|
||||
const manualKeys = computed<string[]>(() => {
|
||||
const set = new Set<string>();
|
||||
instances.value.forEach((r) => {
|
||||
try {
|
||||
const v = JSON.parse(r.formDataJson || "{}");
|
||||
if (v && typeof v === "object") Object.keys(v).forEach((k) => set.add(k));
|
||||
} catch {
|
||||
/* 脏 formData 跳过,不影响其它办件的键聚合 */
|
||||
}
|
||||
});
|
||||
return Array.from(set).slice(0, 40);
|
||||
});
|
||||
const chips = computed(() => (mode.value === "manual" ? manualKeys.value : [...formKeys.value, ...BUILTIN_KEYS]));
|
||||
|
||||
/** bizType 筛选 chips(取当前页 distinct) */
|
||||
const bizTypes = computed(() => Array.from(new Set(instances.value.map((r) => r.bizType).filter(Boolean))));
|
||||
const shownInstances = computed(() =>
|
||||
bizTypeFilter.value ? instances.value.filter((r) => r.bizType === bizTypeFilter.value) : instances.value
|
||||
);
|
||||
|
||||
async function loadInstances(): Promise<void> {
|
||||
instLoading.value = true;
|
||||
try {
|
||||
const res: any = await flowApi.adminInstances({ page: 1, size: 50 }).send();
|
||||
instances.value = res?.content || [];
|
||||
} catch (e: any) {
|
||||
instances.value = [];
|
||||
Message.error(e?.message || "办件列表拉取失败");
|
||||
} finally {
|
||||
instLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/** 选中办件 → formData 解析后缩进填入;解析失败填原文并红字提示 */
|
||||
function fillFromInstance(id: any): void {
|
||||
const p = instances.value.find((r) => r.id === id);
|
||||
if (!p) return;
|
||||
result.value = null;
|
||||
varsParseError.value = false;
|
||||
try {
|
||||
varsJson.value = JSON.stringify(JSON.parse(p.formDataJson || "{}"), null, 2);
|
||||
} catch {
|
||||
varsJson.value = String(p.formDataJson || "");
|
||||
varsParseError.value = true;
|
||||
}
|
||||
if (mode.value === "routelog") void loadTrace(p.id);
|
||||
}
|
||||
|
||||
async function loadTrace(instanceId: any): Promise<void> {
|
||||
if (!instanceId) return;
|
||||
traceLoading.value = true;
|
||||
traceInstanceId.value = instanceId;
|
||||
try {
|
||||
const d: any = await flowApi.detail(instanceId).send();
|
||||
traceRows.value = (d?.trace || []).filter((r: any) => r.kind === "ROUTE");
|
||||
} catch (e: any) {
|
||||
traceRows.value = [];
|
||||
Message.error(e?.message || "办件详情拉取失败");
|
||||
} finally {
|
||||
traceLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/** 载入该办件变量并复现(保留当前表达式) */
|
||||
async function replayFromLog(): Promise<void> {
|
||||
if (!traceInstanceId.value) {
|
||||
Message.warning("请先选择办件");
|
||||
return;
|
||||
}
|
||||
fillFromInstance(traceInstanceId.value);
|
||||
await doEval();
|
||||
}
|
||||
|
||||
function outletText(r: any): string {
|
||||
return r.outlet ? r.outlet : "未命中";
|
||||
}
|
||||
|
||||
/** chips 点击插入表达式光标处 */
|
||||
function insertChip(key: string): void {
|
||||
const input: HTMLInputElement | null = exprBox.value?.$el?.querySelector?.("input") || null;
|
||||
const cur = expr.value;
|
||||
if (!input) {
|
||||
expr.value = cur ? `${cur} ${key}` : key;
|
||||
return;
|
||||
}
|
||||
const at = input.selectionStart ?? cur.length;
|
||||
const end = input.selectionEnd ?? at;
|
||||
expr.value = cur.slice(0, at) + key + cur.slice(end);
|
||||
requestAnimationFrame(() => {
|
||||
input.focus();
|
||||
input.setSelectionRange(at + key.length, at + key.length);
|
||||
});
|
||||
}
|
||||
|
||||
async function doEval(): Promise<void> {
|
||||
if (!expr.value.trim()) {
|
||||
Message.warning("请填写表达式");
|
||||
return;
|
||||
}
|
||||
evaluating.value = true;
|
||||
result.value = null;
|
||||
try {
|
||||
result.value = (await flowApi.routerEval({
|
||||
expression: expr.value.trim(),
|
||||
variablesJson: varsJson.value.trim(),
|
||||
}).send()) || null;
|
||||
} finally {
|
||||
evaluating.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function reset(): void {
|
||||
mode.value = "instance";
|
||||
expr.value = props.presetExpression || "";
|
||||
varsJson.value = "";
|
||||
varsParseError.value = false;
|
||||
pickedId.value = null;
|
||||
bizTypeFilter.value = "";
|
||||
traceRows.value = [];
|
||||
traceInstanceId.value = null;
|
||||
result.value = null;
|
||||
void loadInstances();
|
||||
}
|
||||
|
||||
watch(() => props.modelValue, (v) => {
|
||||
if (v) reset();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal :model-value="modelValue" title="表达式调试" :mask-closable="false" :width="700"
|
||||
@update:model-value="(v: boolean) => emit('update:modelValue', v)">
|
||||
<p class="em-note">
|
||||
与 CONDITION 节点 EXPRESSION 模式同一套求值(AviatorScript 沙箱)。变量 = 样例 JSON 的键 +
|
||||
线上另注入 schId/bizType/bizId/submitterId/instanceId/instanceVersion/nodeId/nodeName
|
||||
(内置变量条仅展示,不参与求值,需在表达式里用到时请自行写入样例 JSON)。
|
||||
变量未提供会报错——线上同口径(宁挂起不误放行,不按 nil 求值)。
|
||||
</p>
|
||||
|
||||
<RadioGroup v-model="mode" type="button" button-style="solid" class="em-seg">
|
||||
<Radio label="instance">从办件导入</Radio>
|
||||
<Radio label="routelog">从路由日志导入</Radio>
|
||||
<Radio label="manual">手动编写</Radio>
|
||||
</RadioGroup>
|
||||
|
||||
<div class="em-fld">
|
||||
<label>表达式<span class="em-sub">AviatorScript 沙箱 · 与运行时同一求值口径</span></label>
|
||||
<Input ref="exprBox" v-model="expr" class="em-mono" placeholder="表达式,如 days >= 3" :maxlength="200" />
|
||||
<div v-if="chips.length" class="em-chips">
|
||||
<span v-for="k in chips" :key="k" class="em-chip" @click="insertChip(k)">{{ k }}</span>
|
||||
</div>
|
||||
<div v-if="chips.length" class="em-sub">↑ 可用变量(点击插入表达式光标处)</div>
|
||||
</div>
|
||||
|
||||
<!-- 模式1/2 共用办件选择器;路由日志模式选中即拉该办件 trace -->
|
||||
<div v-if="mode !== 'manual'" class="em-fld">
|
||||
<label>选择办件
|
||||
<span class="em-sub">变量 = 该办件 formData 原样 + 内置变量;选完自动填充,可再手工改</span>
|
||||
</label>
|
||||
<Select v-model="pickedId" filterable clearable :loading="instLoading"
|
||||
placeholder="按标题/业务键搜索最近 50 条办件" @on-change="fillFromInstance">
|
||||
<Option v-for="r in shownInstances" :key="r.id" :value="r.id">
|
||||
#{{ r.id }} · {{ r.bizType || "-" }}/{{ r.bizId || "-" }} · {{ r.bizTitle || "(无标题)" }}
|
||||
· {{ r.submitterName || r.submitterId || "-" }} ·
|
||||
{{ (r.submitTime || "").replace("T", " ").slice(0, 16) || "-" }}
|
||||
<Tag :color="INSTANCE_STATUS_COLOR[r.status] || 'gray'" style="margin-left: 6px">
|
||||
{{ INSTANCE_STATUS[r.status] || r.status }}
|
||||
</Tag>
|
||||
</Option>
|
||||
</Select>
|
||||
<div v-if="bizTypes.length" class="em-chips">
|
||||
<span class="em-chip flt" :class="{ on: !bizTypeFilter }" @click="bizTypeFilter = ''">全部类型</span>
|
||||
<span v-for="t in bizTypes" :key="t" class="em-chip flt" :class="{ on: bizTypeFilter === t }"
|
||||
@click="bizTypeFilter = t">{{ t }}</span>
|
||||
</div>
|
||||
<div v-if="picked" class="em-bi">
|
||||
<span class="em-bi-t">内置变量(线上由引擎注入,只读展示):</span>
|
||||
<span v-for="b in builtinRows" :key="b.k" class="em-bi-i">
|
||||
{{ b.k }}={{ b.todo ? "(按调试目标自填)" : b.v }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="mode === 'routelog'" class="em-fld">
|
||||
<div v-if="traceLoading" class="em-sub">正在读取该办件的路由判断留痕…</div>
|
||||
<template v-else-if="traceRows && traceRows.length">
|
||||
<Table :columns="[
|
||||
{ title: '节点名', key: 'nodeName', width: 140 },
|
||||
{ title: '注册键', key: 'routerKey', width: 150 },
|
||||
{ title: '命中出口', width: 120, render: (h, p) => outletText(p.row) },
|
||||
{ title: 'attempt', key: 'attempt', width: 80 },
|
||||
{ title: '时间', width: 140, render: (h, p) => (p.row.time || '').replace('T', ' ').slice(0, 19) || '-' },
|
||||
]" :data="traceRows" size="small" :max-height="180" no-data-text="暂无数据" />
|
||||
<div v-for="(r, i) in traceRows" :key="`raw${i}`" class="em-raw">
|
||||
<span class="em-sub">{{ r.nodeName }} rawResponse({{ (r.rawResponse || "").length }} 字):</span>
|
||||
<pre :title="r.rawResponse || ''">{{ (r.rawResponse || "(无留痕)").slice(0, 200)
|
||||
}}{{ (r.rawResponse || "").length > 200 ? "…" : "" }}</pre>
|
||||
</div>
|
||||
<Button size="small" :loading="evaluating" @click="replayFromLog">载入该办件变量并复现</Button>
|
||||
<div class="em-sub">复现后可改表达式定位问题,再到办件台账对该办件重跑路由。</div>
|
||||
</template>
|
||||
<div v-else class="em-sub">
|
||||
{{ picked ? "该办件无条件判断记录" : "选择办件后展示其 flw_route_log 逐边留痕" }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="em-fld">
|
||||
<label>样例变量 JSON(formData)
|
||||
<span v-if="varsParseError" style="color: #ed4014">该办件 formData 不是合法 JSON,已填原文,请手工修正</span>
|
||||
<span v-else-if="mode === 'manual'" class="em-sub">粘贴应用实际传递的 formData JSON;不知道传什么?切「从办件导入」取真实变量</span>
|
||||
</label>
|
||||
<Input v-model="varsJson" type="textarea" :rows="8" :class="{ 'em-bad': varsParseError }"
|
||||
placeholder='如 {"days": 5, "bizType": "leave"}' />
|
||||
</div>
|
||||
|
||||
<hr class="em-divider">
|
||||
<div class="em-fld">
|
||||
<label>求值结果</label>
|
||||
<div v-if="!result" class="em-sub">点击「求值」查看结果(与 CONDITION 节点 EXPRESSION 模式同一求值口径)。</div>
|
||||
<template v-else-if="result.error">
|
||||
<div class="em-err">✗ 求值失败:{{ result.error }}</div>
|
||||
<div class="em-sub">宁挂起不误放行——变量缺失/类型不符都会在此暴露,线上同口径挂起而非按 nil 放行</div>
|
||||
</template>
|
||||
<template v-else-if="result.result">
|
||||
<div class="em-ok">✓ true(命中该出口)
|
||||
<span v-if="result.value !== undefined" class="em-sub">求值值:{{ result.value }}</span>
|
||||
</div>
|
||||
<div class="em-sub">线上行为:条件节点按出口顺序取首个为真者走该分支</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="em-warn">✗ false(不命中)
|
||||
<span v-if="result.value !== undefined" class="em-sub">求值值:{{ result.value }}</span>
|
||||
</div>
|
||||
<div class="em-sub">线上行为:该出口不走;全部出口不命中将挂起等管理端重跑路由</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<span class="em-hint">变量缺失即报错——线上同口径(宁挂起不误放行,不按 nil 求值)</span>
|
||||
<Button @click="emit('update:modelValue', false)">关闭</Button>
|
||||
<Button type="primary" :loading="evaluating" @click="doEval">求值</Button>
|
||||
</template>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.em-note {
|
||||
color: #808695;
|
||||
font-size: 12px;
|
||||
line-height: 1.7;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.em-seg {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.em-fld {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.em-fld > label {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
color: #515a6e;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.em-sub {
|
||||
color: #a5abb8;
|
||||
font-size: 12px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.em-fld > label .em-sub {
|
||||
margin-left: 6px;
|
||||
}
|
||||
.em-chips {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
margin: 8px 0 4px;
|
||||
}
|
||||
.em-chip {
|
||||
border: 1px solid #e3e6eb;
|
||||
border-radius: 12px;
|
||||
padding: 2px 10px;
|
||||
font-size: 12px;
|
||||
color: #0b7d72;
|
||||
background: #f2fbf9;
|
||||
cursor: pointer;
|
||||
}
|
||||
.em-chip.flt {
|
||||
color: #515a6e;
|
||||
background: #fff;
|
||||
}
|
||||
.em-chip.flt.on {
|
||||
border-color: var(--brand);
|
||||
color: var(--brand);
|
||||
}
|
||||
.em-bi {
|
||||
margin-top: 8px;
|
||||
background: #f7f9fc;
|
||||
border-radius: 6px;
|
||||
padding: 8px 10px;
|
||||
font-size: 12px;
|
||||
line-height: 1.8;
|
||||
}
|
||||
.em-bi-t {
|
||||
color: #0b7d72;
|
||||
font-weight: 600;
|
||||
}
|
||||
.em-bi-i {
|
||||
margin-right: 10px;
|
||||
font-family: Consolas, Monaco, monospace;
|
||||
color: #515a6e;
|
||||
}
|
||||
.em-mono :deep(input) {
|
||||
font-family: Consolas, Monaco, monospace;
|
||||
}
|
||||
.em-bad :deep(.ivu-input) {
|
||||
border-color: #ed4014;
|
||||
}
|
||||
.em-raw {
|
||||
margin: 6px 0;
|
||||
}
|
||||
.em-raw pre {
|
||||
margin: 2px 0 0;
|
||||
padding: 6px 8px;
|
||||
background: #fafbfd;
|
||||
border: 1px solid #eef0f3;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
}
|
||||
.em-divider {
|
||||
border: none;
|
||||
border-top: 1px solid #eef0f3;
|
||||
margin: 12px 0;
|
||||
}
|
||||
.em-ok {
|
||||
color: #19be6b;
|
||||
font-size: 13px;
|
||||
}
|
||||
.em-warn {
|
||||
color: #ff9900;
|
||||
font-size: 13px;
|
||||
}
|
||||
.em-err {
|
||||
color: #ed4014;
|
||||
font-size: 13px;
|
||||
}
|
||||
.em-hint {
|
||||
float: left;
|
||||
color: #b0b6c1;
|
||||
font-size: 12px;
|
||||
line-height: 32px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user