diff --git a/src/views/flow/admin/def-editor/components/ConditionBox.vue b/src/views/flow/admin/def-editor/components/ConditionBox.vue index 24c025a..0aa7820 100644 --- a/src/views/flow/admin/def-editor/components/ConditionBox.vue +++ b/src/views/flow/admin/def-editor/components/ConditionBox.vue @@ -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>({}); +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([...availableVars.value, ...BUILTIN_VARS]); + const out = new Set(); + (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); +}