feat(condition): 条件表达式内联变量 chips(议题一 P0)
编辑器出口表达式下展示可用变量(近期 50 条办件 formData 顶层键聚合去重截 40 + 内置,口径同调试弹窗),点击插入光标处;未知标识符内联提示疑似拼错(仅提示不拦截,去字符串/函数名/属性访问后比对)。EditorCtx 增 availableVars,index.vue 载入一次(拉取失败退化为仅内置,不阻塞编辑)。vite build:base 通过。
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
// 分支按出口名互斥选路(运行时调判断服务/表达式求值),分支尾隐式汇合到容器后继。
|
||||
// 分支头部的出口名 Input 即 contentJson 里 CONDITION 命名出边的 name(判断接口 outlets 契约);
|
||||
// EXPRESSION 模式(2026-10-10)下分支头部再就地编辑出边 condition,序号即求值优先级。
|
||||
import { computed, inject, ref } from "vue";
|
||||
import { computed, inject, nextTick, 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";
|
||||
@@ -45,6 +45,57 @@ function openTry(i: number): void {
|
||||
tryExpr.value = (props.node.conditions || [])[i] || "";
|
||||
tryVisible.value = true;
|
||||
}
|
||||
|
||||
// ==================== 可用变量 chips(2026-10-14 议题一 P0):内联提示 + 点击插入 ====================
|
||||
/** 可用变量(表单字段聚合键 + 内置),来自编辑器上下文;未拉到为空数组则不展示 chips */
|
||||
const availableVars = computed(() => ctx.availableVars || []);
|
||||
const BUILTIN_VARS = new Set([
|
||||
"schId", "instanceId", "instanceVersion", "bizType", "bizId", "nodeId", "nodeName", "submitterId",
|
||||
]);
|
||||
const isBuiltin = (k: string): boolean => BUILTIN_VARS.has(k);
|
||||
|
||||
/** Aviator 关键字/字面量:不参与"未知变量"提示 */
|
||||
const EXPR_KEYWORDS = new Set([
|
||||
"true", "false", "nil", "null", "and", "or", "not", "if", "else", "elsif", "let", "while",
|
||||
"for", "return", "in", "like", "new", "end", "break", "continue", "seq", "lambda",
|
||||
]);
|
||||
|
||||
const condInputs = ref<Record<number, HTMLInputElement | null>>({});
|
||||
function setCondInput(el: any, i: number): void {
|
||||
condInputs.value[i] = (el as HTMLInputElement) || null;
|
||||
}
|
||||
|
||||
/** chips 点击:插入到该出口表达式光标处(无输入框时追加到末尾) */
|
||||
function insertChip(i: number, key: string): void {
|
||||
const cur = (props.node.conditions || [])[i] || "";
|
||||
const el = condInputs.value[i];
|
||||
const s = el?.selectionStart ?? cur.length;
|
||||
const e = el?.selectionEnd ?? cur.length;
|
||||
editCondition(i, cur.slice(0, s) + key + cur.slice(e));
|
||||
nextTick(() => {
|
||||
const input = condInputs.value[i];
|
||||
if (input) {
|
||||
input.focus();
|
||||
input.setSelectionRange(s + key.length, s + key.length);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** 该出口表达式里不在可用列表中的标识符(疑似拼错提示,仅提示不拦截) */
|
||||
function unknownVars(i: number): string[] {
|
||||
const cond = (props.node.conditions || [])[i] || "";
|
||||
if (!cond.trim()) return [];
|
||||
const stripped = cond
|
||||
.replace(/'[^']*'|"[^"]*"/g, " ") // 字符串字面量
|
||||
.replace(/[A-Za-z_][A-Za-z0-9_]*\s*\(/g, " ") // 函数名
|
||||
.replace(/\.\s*[A-Za-z_][A-Za-z0-9_]*/g, " "); // 命名空间/属性访问
|
||||
const known = new Set<string>([...availableVars.value, ...BUILTIN_VARS]);
|
||||
const out = new Set<string>();
|
||||
(stripped.match(/[A-Za-z_][A-Za-z0-9_]*/g) || []).forEach((t) => {
|
||||
if (!EXPR_KEYWORDS.has(t) && !known.has(t)) out.add(t);
|
||||
});
|
||||
return Array.from(out);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -81,6 +132,7 @@ function openTry(i: number): void {
|
||||
</div>
|
||||
<div v-if="expression" class="cb-cond-row">
|
||||
<input
|
||||
:ref="(el) => setCondInput(el, i)"
|
||||
class="cb-cond"
|
||||
:value="(node.conditions || [])[i] || ''"
|
||||
placeholder="条件表达式,如 amount > 1000"
|
||||
@@ -90,6 +142,23 @@ function openTry(i: number): void {
|
||||
<!-- B5 内联试算:用真实办件变量跑该出口表达式(同「条件路由」页弹窗,预填表达式) -->
|
||||
<span class="cb-try" title="用真实办件变量试算该表达式" @click.stop="openTry(i)">试算</span>
|
||||
</div>
|
||||
<!-- 议题一 P0:可用变量 chips(表单键 + 内置),点击插入到表达式光标处 -->
|
||||
<div v-if="expression && availableVars.length" class="cb-vars">
|
||||
<span
|
||||
v-for="k in availableVars.slice(0, 14)"
|
||||
:key="k"
|
||||
class="cb-var"
|
||||
:class="{ bt: isBuiltin(k) }"
|
||||
:title="isBuiltin(k) ? '内置变量(引擎运行时注入)' : '表单变量(来自近期办件 formData)'"
|
||||
@click.stop="insertChip(i, k)"
|
||||
>{{ k }}</span>
|
||||
<span v-if="availableVars.length > 14" class="cb-vars-more" :title="availableVars.join('、')">
|
||||
+{{ availableVars.length - 14 }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-for="(v, vi) in unknownVars(i)" :key="'u' + vi" class="cb-unknown">
|
||||
⚠ 「{{ v }}」不在可用变量列表,若为动态变量可忽略
|
||||
</div>
|
||||
<NodeList :items="branch" :container="`${node.uid}:${i}`" />
|
||||
<div v-if="!branch.length" class="cb-empty">该出口直达汇合点(直接通过)</div>
|
||||
</div>
|
||||
@@ -231,6 +300,52 @@ function openTry(i: number): void {
|
||||
background: #f2fbf9;
|
||||
border-color: #0b7d72;
|
||||
}
|
||||
.cb-vars {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 5px;
|
||||
margin: -2px 0 8px;
|
||||
}
|
||||
.cb-var {
|
||||
font-size: 11px;
|
||||
line-height: 16px;
|
||||
padding: 0 8px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid #b7e3dc;
|
||||
background: #f2fbf9;
|
||||
color: #0b7d72;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
font-family: Consolas, Monaco, monospace;
|
||||
}
|
||||
.cb-var:hover {
|
||||
background: #0b7d72;
|
||||
color: #fff;
|
||||
border-color: #0b7d72;
|
||||
}
|
||||
.cb-var.bt {
|
||||
border-color: #dcdee2;
|
||||
background: #f5f7fa;
|
||||
color: #4e5969;
|
||||
}
|
||||
.cb-var.bt:hover {
|
||||
background: #4e5969;
|
||||
color: #fff;
|
||||
border-color: #4e5969;
|
||||
}
|
||||
.cb-vars-more {
|
||||
font-size: 11px;
|
||||
line-height: 16px;
|
||||
padding: 0 8px;
|
||||
color: #86909c;
|
||||
cursor: default;
|
||||
}
|
||||
.cb-unknown {
|
||||
text-align: center;
|
||||
font-size: 11px;
|
||||
color: #ff9900;
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
.cb-col-del {
|
||||
flex: none;
|
||||
cursor: pointer;
|
||||
|
||||
@@ -69,6 +69,8 @@ const depts = reactive<DeptOption[]>([]);
|
||||
const userNames = reactive<Record<number, string>>({});
|
||||
/** 条件分支的判断服务注册(flw_router;拉取失败为空数组,校验降级提示) */
|
||||
const routers = reactive<RouterOption[]>([]);
|
||||
/** 可用变量 chips:近期办件 formData 顶层键聚合(去重截 40)+ 内置变量;拉取失败仅内置 */
|
||||
const availableVars = reactive<string[]>([]);
|
||||
|
||||
const issues = computed<Issue[]>(() => validateModel(model, depts, routers));
|
||||
const defJson = computed(() => toContentJson(model));
|
||||
@@ -201,6 +203,27 @@ async function loadRouters(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
/** 表达式可用变量:近期 50 条办件 formData 顶层键聚合(去重截 40)+ 内置,口径同调试弹窗;失败仅内置 */
|
||||
const BUILTIN_VARS = ["schId", "instanceId", "instanceVersion", "bizType", "bizId", "nodeId", "nodeName", "submitterId"];
|
||||
async function loadVars(): Promise<void> {
|
||||
const keys = new Set<string>();
|
||||
try {
|
||||
const rows: any[] = (await flowApi.adminInstances({ page: 1, size: 50 }).send())?.content || [];
|
||||
rows.forEach((r) => {
|
||||
try {
|
||||
const v = JSON.parse(r.formDataJson || "{}");
|
||||
if (v && typeof v === "object" && !Array.isArray(v)) Object.keys(v).forEach((k) => keys.add(k));
|
||||
} catch {
|
||||
/* 脏 formData 跳过,不影响其它办件的键聚合 */
|
||||
}
|
||||
});
|
||||
} catch {
|
||||
/* 办件列表拉取失败:退化为仅内置变量,不阻塞编辑 */
|
||||
}
|
||||
const merged = Array.from(new Set([...Array.from(keys).slice(0, 40), ...BUILTIN_VARS]));
|
||||
availableVars.splice(0, availableVars.length, ...merged);
|
||||
}
|
||||
|
||||
function applyParsed(parsed: ParsedDef): void {
|
||||
model.code = parsed.code || model.code;
|
||||
model.name = parsed.name || model.name;
|
||||
@@ -217,6 +240,7 @@ async function load(): Promise<void> {
|
||||
try {
|
||||
await loadDepts();
|
||||
await loadRouters();
|
||||
void loadVars();
|
||||
try {
|
||||
const rows: any[] = (await flowApi.appList().send()) || [];
|
||||
appOptions.value = rows
|
||||
@@ -385,6 +409,7 @@ provide<EditorCtx>(EDITOR_CTX, {
|
||||
depts,
|
||||
userNames,
|
||||
routers,
|
||||
availableVars,
|
||||
issueOf,
|
||||
openNode,
|
||||
removeNode,
|
||||
|
||||
@@ -363,6 +363,8 @@ export interface EditorCtx {
|
||||
userNames: Record<number, string>;
|
||||
/** 条件分支的判断服务注册下拉(flw_router 启用项;拉取失败为空数组,校验降级提示) */
|
||||
routers: RouterOption[];
|
||||
/** 可用变量 chips:近期办件 formData 顶层键聚合 + 内置变量(供条件表达式内联提示/插入) */
|
||||
availableVars: string[];
|
||||
issueOf: (uid: string) => { level: "error" | "warn"; text: string }[];
|
||||
openNode: (uid: string) => void;
|
||||
removeNode: (uid: string) => void;
|
||||
|
||||
Reference in New Issue
Block a user