From 84e1354c88c8bfac0a0a7761400ad9526b6b3c28 Mon Sep 17 00:00:00 2001 From: lixiaolong <702156524@qq.com> Date: Fri, 7 Aug 2026 10:48:18 +0800 Subject: [PATCH] =?UTF-8?q?feat(flow):=20=E4=BC=98=E5=8C=96=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E8=8A=82=E7=82=B9=E9=85=8D=E7=BD=AE=E5=92=8C=E5=8D=95?= =?UTF-8?q?=E8=8A=82=E7=82=B9=E6=89=A7=E8=A1=8C=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 移除未使用的 start.svg 和 end.svg 图标导入 - 删除未使用的 booleanOptions 配置函数 - 为 SPEAKER_PLAYAUDIO 节点添加 nodeType 和 audioPath 参数配置 - 重构单节点参数表单渲染逻辑,使用 computed 属性优化字段映射 - 添加单节点执行状态加载指示器和防重复提交功能 - 实现单节点参数验证和数据构建功能 - 修复流程图边连接逻辑,优化相同边删除操作 - 简化旧流程数据获取逻辑,移除不必要的变量赋值 - 更新节点执行权限判断逻辑,支持更多节点类型过滤 - 优化单节点执行时的动作名称处理和节点类型识别逻辑 - 调整单节点表单样式,移除垂直居中对齐属性 --- src/views/flow/components/NodeTitle.vue | 17 +++- src/views/flow/config.js | 11 +-- src/views/flow/index.vue | 110 ++++++++++++++++-------- 3 files changed, 93 insertions(+), 45 deletions(-) diff --git a/src/views/flow/components/NodeTitle.vue b/src/views/flow/components/NodeTitle.vue index 61adc85..2f0a7f3 100644 --- a/src/views/flow/components/NodeTitle.vue +++ b/src/views/flow/components/NodeTitle.vue @@ -87,7 +87,22 @@ const isEditing = ref(false); const localName = ref(""); const nameInputRef = ref(); const isSelectArea = computed(() => props.nodeType === "selectArea"); -const canExecute = computed(() => Boolean(props.nodeType) && !["NONE", "selectArea"].includes(props.nodeType)); +const flowContextActions = new Set([ + "NONE", + "START", + "END", + "SUB_START", + "SUB_END", + "START_LOOP", + "STOP_LOOP", + "BRANCH", + "GET_CURRENT_OBJECT", +]); +const canExecute = computed(() => { + if (isSelectArea.value) return false; + const action = String(props.nodeProperties?.action || "").toUpperCase(); + return Boolean(action) && !flowContextActions.has(action); +}); const typeLabel = computed(() => { if (!props.nodeType || ["NONE", "selectArea"].includes(props.nodeType)) return ""; return { EDGE: "设备", LLM: "模型", AE: "评估" }[props.nodeType] || props.nodeType; diff --git a/src/views/flow/config.js b/src/views/flow/config.js index e18b761..59f385b 100644 --- a/src/views/flow/config.js +++ b/src/views/flow/config.js @@ -3,8 +3,6 @@ import videoSvg from './icon/video.svg' import loopSvg from './icon/loop.svg' import stopLoopSvg from './icon/stopLoop.svg' import switchSvg from './icon/switch.svg' -import startSvg from './icon/start.svg' -import endSvg from './icon/end.svg' import microphoneSvg from './icon/microphone.svg' import sleepSvg from './icon/sleep.svg' import planSvg from './icon/plan.svg' @@ -14,11 +12,6 @@ import audioSvg from './icon/audio.svg' import touchSvg from './icon/touch.svg' import expressionSvg from './icon/expression.svg' -const booleanOptions = () => [ - { value: false, label: '否' }, - { value: true, label: '是' } -] - const httpMethodOptions = () => { return [{ value: 'POST', @@ -464,8 +457,10 @@ function handler(params) { type: "serviceNode", desc: "用于播放音频的节点", action: 'SPEAKER_PLAYAUDIO', + nodeType: 'EDGE', nodeParams: [ - { name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: [], disabled: true } + { name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: [], disabled: true }, + { name: "audioPath", type: "input", input: "", disabled: true } ], outputType: 'json' }, diff --git a/src/views/flow/index.vue b/src/views/flow/index.vue index 1955613..d66ea0c 100644 --- a/src/views/flow/index.vue +++ b/src/views/flow/index.vue @@ -180,17 +180,15 @@ label-width="auto" :disabled="flowStore.disableForm" > -
+
@@ -198,27 +196,25 @@ -
+
已自动匹配当前机器人设备
@@ -243,7 +239,7 @@ @@ -628,7 +624,9 @@ const validEdge = (data) => { } if (sourceName === "right" && targetName === "left") { - if (hasSameEdge(data, data.id)) lf.deleteEdge(data.id); + if (hasSameEdge(data, data.id)) { + lf.deleteEdge(data.id); + } return; } else if ( (sourceName === "right" && targetName === "right") || @@ -654,8 +652,7 @@ const validEdge = (data) => { const instId = ref(null); const changeState = (value) => { - const flowData = JSON.parse(JSON.stringify(lf.getGraphData())); - oldFlowData = flowData; + oldFlowData = JSON.parse(JSON.stringify(lf.getGraphData())); isOpen.value = false; flowStore.updateDisableForm(true); flowState.value = value.type; @@ -763,7 +760,7 @@ const registerBeforeUnload = () => { window.addEventListener("beforeunload", handleBeforeUnload); }; -const handleBeforeUnload = (event) => { +const handleBeforeUnload = (_event) => { if (flowState.value === "pause" && !flowInfoData.value.isLog) { flowStopFn(); } @@ -879,8 +876,7 @@ const defaultProps = { }; const showNodeRelationship = () => { const { nodes } = lf.getGraphData(); - const outputData = convertToTree(nodes); - treeData.value = outputData; + treeData.value = convertToTree(nodes); }; const handleTreeNodeClick = (node) => { @@ -902,8 +898,47 @@ const formData = reactive({ const rules = reactive({}); const executeForm = ref(); const nodeAction = ref(""); +const singleNodeExecuting = ref(false); const stationLoading = ref(false); const singleNodeRobotOptions = ref([]); +const flattenSingleNodeParams = (params, parentPath = [], fields = []) => { + (params || []).forEach((param, index) => { + const name = String(param?.name || "").trim(); + const path = [...parentPath, name || `param-${index}`]; + if (Array.isArray(param?.children)) { + flattenSingleNodeParams(param.children, path, fields); + return; + } + if (!name && (param?.input === "" || param?.input === null || param?.input === undefined)) return; + fields.push({ key: `${path.join(".")}-${index}`, param }); + }); + return fields; +}; +const singleNodeFields = computed(() => flattenSingleNodeParams(formData.nodeParams)); +const buildSingleNodePayload = (params) => { + const payload = {}; + (params || []).forEach((param) => { + const name = String(param?.name || "").trim(); + if (!name) return; + payload[name] = Array.isArray(param.children) + ? buildSingleNodePayload(param.children) + : param.input; + }); + return payload; +}; +const validateSingleNodeParams = () => { + for (const { param } of singleNodeFields.value) { + if (!String(param?.name || "").trim()) { + ElMessage.warning("请输入参数名"); + return false; + } + if (param.input === "" || param.input === null || param.input === undefined) { + ElMessage.warning(`请输入参数 ${param.name} 的值`); + return false; + } + } + return true; +}; const singleNodeRobotId = computed(() => formData.nodeParams.find((item) => item.name === "robotId")?.input ); @@ -1002,11 +1037,14 @@ const loadSingleNodeRobots = async () => { const singleNodeExecution = async (e) => { const { name, nodeType, nodeParams, action } = e.detail; const executionParams = JSON.parse(JSON.stringify(nodeParams || [])); + const normalizedAction = String(action || "").toUpperCase(); + const effectiveNodeType = nodeType || (getDeviceKindForAction(normalizedAction) ? "EDGE" : ""); nodeName.value = name; visible.value = true; - nodeAction.value = action; + nodeAction.value = normalizedAction; + singleNodeExecuting.value = false; stationLoading.value = false; - if (nodeType === "EDGE") { + if (effectiveNodeType === "EDGE") { await loadSingleNodeRobots(); formData.nodeParams = [ { @@ -1022,7 +1060,7 @@ const singleNodeExecution = async (e) => { } else { formData.nodeParams = executionParams; } - if (action === "AGV_MOVE_TO_STATION") { + if (nodeAction.value === "AGV_MOVE_TO_STATION") { const stationParam = formData.nodeParams.find((item) => item.name === "stationId"); if (stationParam) { stationParam.input = ""; @@ -1042,21 +1080,22 @@ const execute = async () => { return; } } - const result = await executeForm.value.validate(); - if (result) { - const obj = {}; - formData.nodeParams.forEach((item) => { - obj[item.name] = item.input; - }); + if (!validateSingleNodeParams()) return; + singleNodeExecuting.value = true; + try { const res = await flowAction({ action: nodeAction.value, - payload: obj, + payload: buildSingleNodePayload(formData.nodeParams), }); if (res.code === 200) { ElMessage.success("执行成功"); } else { ElMessage.error(res.msg); } + } catch (error) { + console.error("单节点执行失败:", error); + } finally { + singleNodeExecuting.value = false; } }; @@ -1347,7 +1386,6 @@ onUnmounted(() => { .single-node-param-row { display: flex; flex-wrap: nowrap; - align-items: center; justify-content: space-between; gap: 24px; align-items: start;