From 354bc972712bb979797413cc81317a269064deea Mon Sep 17 00:00:00 2001 From: DigHuang <114602213+DigHuang@users.noreply.github.com> Date: Mon, 27 Apr 2026 16:27:58 +0800 Subject: [PATCH] fix(drag): mutex selection between nested parent and child nodes (#6821) * fix(drag): mutex selection between nested parent and child nodes * fix: mutex selection between nested parent and child nodes * doc --------- Co-authored-by: archer <545436317@qq.com> --- .../content/self-host/upgrading/4-15/4150.mdx | 4 +- document/data/doc-last-modified.json | 2 +- .../Flow/hooks/useWorkflow.tsx | 37 ++++++++++++++----- 3 files changed, 30 insertions(+), 13 deletions(-) diff --git a/document/content/self-host/upgrading/4-15/4150.mdx b/document/content/self-host/upgrading/4-15/4150.mdx index 7962e5f5ae..f1063a78e2 100644 --- a/document/content/self-host/upgrading/4-15/4150.mdx +++ b/document/content/self-host/upgrading/4-15/4150.mdx @@ -10,12 +10,10 @@ description: 'FastGPT V4.15.0 更新说明' ## ⚙️ 优化 -1. embedding 适配 base64 字符串返回值。 +1. 增加父子节点选中互斥功能,解决:同时选中父子节点时,移动节点会出现抖动。 ## 🐛 修复 -1. helper-bot 前缀输出 Error~ 信息 -2. 阿里云 oss copy 接口。 ## 代码优化 diff --git a/document/data/doc-last-modified.json b/document/data/doc-last-modified.json index d1a61d3c0d..a0dd6b5ab7 100644 --- a/document/data/doc-last-modified.json +++ b/document/data/doc-last-modified.json @@ -426,4 +426,4 @@ "content/use-cases/external-integration/wecom.mdx": "2026-04-26T21:08:47+08:00", "content/use-cases/index.en.mdx": "2026-04-26T21:08:47+08:00", "content/use-cases/index.mdx": "2026-04-26T21:08:47+08:00" -} \ No newline at end of file +} diff --git a/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/hooks/useWorkflow.tsx b/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/hooks/useWorkflow.tsx index 8b7872cdb5..7e518842c9 100644 --- a/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/hooks/useWorkflow.tsx +++ b/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/hooks/useWorkflow.tsx @@ -450,12 +450,6 @@ export const useRAF = () => { export const popoverWidth = 400; export const popoverHeight = 600; -// 嵌套父容器节点类型集合 -const PARENT_NODE_TYPES = new Set([ - FlowNodeTypeEnum.loop, - FlowNodeTypeEnum.parallelRun, - FlowNodeTypeEnum.loopRun -]); export const useWorkflow = () => { const { toast } = useToast(); @@ -464,8 +458,15 @@ export const useWorkflow = () => { const appDetail = useContextSelector(AppContext, (e) => e.appDetail); const { nodes, getRawNodeById } = useContextSelector(WorkflowInitContext, (state) => state); - const { onNodesChange, workflowStartNode, getNodeById, edges, setEdges, onEdgesChange } = - useContextSelector(WorkflowBufferDataContext, (state) => state); + const { + onNodesChange, + setNodes, + workflowStartNode, + getNodeById, + edges, + setEdges, + onEdgesChange + } = useContextSelector(WorkflowBufferDataContext, (state) => state); const selectedNodesMap = useContextSelector(WorkflowNodeDataContext, (v) => v.selectedNodesMap); const { setConnectingEdge, onChangeNode } = useContextSelector(WorkflowActionsContext, (v) => v); @@ -660,6 +661,24 @@ export const useWorkflow = () => { if (change.selected === false && isDowningCtrl) { change.selected = true; } + + // 父子互斥(后操作优先): 选父则取消其已选 children;选子则取消已选父。 + if (!change.selected) return; + const node = getRawNodeById(change.id); + if (!node) return; + + if (isNestedParentNodeType(node.data.flowNodeType)) { + setNodes((curr) => + curr.map((n) => + n.data.parentNodeId === node.id && n.selected ? { ...n, selected: false } : n + ) + ); + } else if (node.data.parentNodeId) { + const parent = getRawNodeById(node.data.parentNodeId); + if (parent?.selected) { + setNodes((curr) => curr.map((n) => (n.id === parent.id ? { ...n, selected: false } : n))); + } + } }); const handlePositionNode = useMemoizedFn( (change: NodePositionChange, node: Node) => { @@ -675,7 +694,7 @@ export const useWorkflow = () => { } // 场景2: Loop 父节点拖拽 - 联动子节点 - if (PARENT_NODE_TYPES.has(node.data.flowNodeType)) { + if (isNestedParentNodeType(node.data.flowNodeType)) { const parentId = node.id; const dragPos = change.position; const shouldSnap = !!change.dragging && !!dragPos;