diff --git a/src/views/flow/config.js b/src/views/flow/config.js index 062bc1c..6100346 100644 --- a/src/views/flow/config.js +++ b/src/views/flow/config.js @@ -399,7 +399,7 @@ function handler(params) { name: "通用指令", type: "deviceUniversal", desc: "执行设备通用指令", - show: __PROJECT_NAME__ === 'aima', + // show: __PROJECT_NAME__ === 'aima', action: 'DEVICE_EXECUTE_JSON_COMMAND', nodeType: 'EDGE', nodeParams: [ @@ -415,7 +415,7 @@ function handler(params) { name: "动作队列", type: "actionQueue", desc: "在边缘端按顺序连续执行多个动作", - show: __PROJECT_NAME__ === 'aima', + // show: __PROJECT_NAME__ === 'aima', action: 'SYSTEM_EXECUTE_ACTION_QUEUE', nodeType: 'EDGE', nodeParams: [ diff --git a/src/views/flow/index.vue b/src/views/flow/index.vue index b2faa8e..dba7874 100644 --- a/src/views/flow/index.vue +++ b/src/views/flow/index.vue @@ -1511,6 +1511,45 @@ const findMainFlowEndLog = (logs) => { }); }; +/** + * 等待运行结果引起的 Vue 渲染、ResizeObserver 和锚点同步全部完成。 + * + * 实现思路:运行日志更新节点后,部分布局同步会延迟到下一动画帧。连续等待两个动画帧, + * 再跨过一个任务周期,确保同帧内后注册的布局回调也已执行;随后才建立新的图数据基线 + * 并开放编辑和发布。 + * + * @returns {Promise} 两轮浏览器布局帧完成后结束 + */ +const waitForRunResultLayout = async () => { + await nextTick(); + await new Promise((resolve) => { + window.requestAnimationFrame(() => { + window.requestAnimationFrame(() => window.setTimeout(resolve, 0)); + }); + }); +}; + +/** + * 将当前试运行实例稳定地切换为成功状态。 + * + * 实现思路:先等待所有运行态布局副作用结束,再以最终画布数据刷新脏数据比较基线; + * 最后设置成功状态并解除表单锁,确保延迟的内部布局事件不会被识别为用户编辑。 + * + * @param {string | number | null} currentInstId 当前流程实例 ID + * @param {Object} mainEnd 主流程结束节点成功日志 + * @param {Object} first 当前实例第一条执行日志 + * @returns {Promise} 当前实例成功完成状态同步时返回 true + */ +const completeTrialRun = async (currentInstId, mainEnd, first) => { + await waitForRunResultLayout(); + if (!isCurrentFlowPoll(currentInstId)) return false; + oldFlowData = JSON.parse(JSON.stringify(lf.getGraphData())); + totalRunningTime.value = mainEnd.endTime - first.startTime; + flowState.value = "testRunFinish"; + flowStore.updateDisableForm(false); + return true; +}; + /** * 查询并同步试运行结果,直到当前实例进入成功、失败、暂停或终止状态。 * @@ -1585,9 +1624,7 @@ const loopFlowView = async (currentInstId, taskId = null) => { } } else if (mainEndStatus === "SUCCESS") { if (!flowInfoData.value.isLog) { - flowStore.updateDisableForm(false); - flowState.value = "testRunFinish"; - totalRunningTime.value = mainEnd.endTime - first.startTime; + await completeTrialRun(currentInstId, mainEnd, first); } } else if (["STOPPED", "PAUSED"].includes(lastStatus)) { if (!flowInfoData.value.isLog) { diff --git a/src/views/flow/vue-flow/FlowNode.vue b/src/views/flow/vue-flow/FlowNode.vue index f867850..0f85169 100644 --- a/src/views/flow/vue-flow/FlowNode.vue +++ b/src/views/flow/vue-flow/FlowNode.vue @@ -214,11 +214,20 @@ const removeAnchor = (anchorId) => syncAnchors(branchAnchors.value.filter((ancho */ const changeAnchor = (height, anchorId) => syncAnchors(branchAnchors.value.map((anchor) => anchor.id === anchorId ? { ...anchor, height } : anchor)); /** - * 同步与 branch anchors 相关的状态或数据,该影响范围仅限当前组件。 + * 同步分支节点的锚点位置。 + * + * 实现思路:编辑状态下把测量结果写回节点属性,保证保存和重载后的连线位置一致; + * 试运行期间节点状态变化也会触发 ResizeObserver,但这不是流程定义修改,因此只刷新 + * Vue Flow 内部布局,不持久化锚点,避免延迟回调取消“试运行成功”状态。 + * * @param {*} positions 分支锚点位置集合 */ const syncBranchAnchors = (positions = []) => { if (!positions.length) return; + if (flowStore.disableForm) { + vueFlow.updateNodeInternals([props.id]); + return; + } const positionMap = new Map(positions.map((item) => [item.id, Number(item.height || 0)])); const current = [...(properties.value.anchor || [])]; let changed = false; @@ -238,11 +247,14 @@ const syncBranchAnchors = (positions = []) => { else vueFlow.updateNodeInternals([props.id]); }; /** - * 处理与 content changed 相关的状态或数据,该影响范围仅限当前组件。 + * 响应节点内容变化并刷新节点尺寸。 + * + * 实现思路:用户编辑时记录历史和草稿脏状态;试运行期间的状态图标、耗时和输出结果 + * 只是临时展示数据,不应写入编辑历史,否则异步布局事件会错误撤销发布资格。 */ const contentChanged = () => { vueFlow.updateNodeInternals([props.id]); - window.lf?.markChanged(); + if (!flowStore.disableForm) window.lf?.markChanged(); }; /** * 绑定与 component 相关的状态或数据,该影响范围仅限当前组件。