From ce1cd00c91938954bd864697b758b7822aae0940 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=98=94=E6=A2=A6?= Date: Wed, 6 Aug 2025 15:08:34 +0800 Subject: [PATCH] =?UTF-8?q?fix:=E4=BF=AE=E5=A4=8D=E9=BC=A0=E6=A0=87?= =?UTF-8?q?=E7=9B=91=E5=90=AC=E4=BA=8B=E4=BB=B6=E9=80=A0=E6=88=90=E7=9A=84?= =?UTF-8?q?=E6=80=A7=E8=83=BD=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/x-flow/src/XFlow.tsx | 46 ++++++++++++++++++- .../src/components/CandidateNode/index.tsx | 1 + .../src/components/NodeContainer/index.tsx | 1 - packages/x-flow/src/hooks/useFlow.ts | 34 ++++++++++++++ packages/x-flow/src/models/store.ts | 6 +++ 5 files changed, 85 insertions(+), 3 deletions(-) diff --git a/packages/x-flow/src/XFlow.tsx b/packages/x-flow/src/XFlow.tsx index 8b76622d7..11fcc9b05 100644 --- a/packages/x-flow/src/XFlow.tsx +++ b/packages/x-flow/src/XFlow.tsx @@ -66,6 +66,7 @@ const XFlow: FC = memo(props => { setCandidateNode, isAddingNode, setMousePosition, + copyNodes, } = useStore( s => ({ nodes: s.nodes, @@ -80,6 +81,7 @@ const XFlow: FC = memo(props => { onNodesChange: s.onNodesChange, onEdgesChange: s.onEdgesChange, onConnect: s.onConnect, + copyNodes: s.copyNodes, }), shallow ); @@ -98,6 +100,10 @@ const XFlow: FC = memo(props => { setAutoFreeze(false); return () => { setAutoFreeze(true); + const { copyTimeoutId } = storeApi.getState(); + if (copyTimeoutId) { + clearTimeout(copyTimeoutId); + } }; }, []); @@ -125,6 +131,17 @@ const XFlow: FC = memo(props => { pasteNodeSimple(); e.preventDefault(); } + else if (copyNodes.length > 0) { + // 只在有复制节点时才检查其他操作 + const { copyTimeoutId } = storeApi.getState(); + if (copyTimeoutId) { + clearTimeout(copyTimeoutId); + storeApi.setState({ + copyTimeoutId: null, + isAddingNode: false, + }); + } + } }); useEventListener( @@ -143,8 +160,33 @@ const XFlow: FC = memo(props => { }, { target: workflowContainerRef.current, - // enable: isAddingNode, - enable: true, // 复制粘贴的时候需要监听鼠标位置 + enable: isAddingNode, + // enable: true, // 复制粘贴的时候需要监听鼠标位置 + } + ); + + // 监听点击事件,当用户点击其他地方时清除复制状态 + useEventListener( + 'click', + e => { + // 如果点击的不是节点或候选节点,清除复制状态 + const target = e.target as HTMLElement; + const isClickingNode = target.closest('.xflow-node-container') || target.closest('.candidate-node'); + + if (!isClickingNode) { + const { copyTimeoutId, copyNodes } = storeApi.getState(); + if (copyTimeoutId && copyNodes?.length > 0) { + clearTimeout(copyTimeoutId); + storeApi.setState({ + copyTimeoutId: null, + isAddingNode: false, + }); + } + } + }, + { + target: workflowContainerRef.current, + enable: copyNodes?.length > 0, } ); diff --git a/packages/x-flow/src/components/CandidateNode/index.tsx b/packages/x-flow/src/components/CandidateNode/index.tsx index 9f33ee8c4..5d8af42e5 100644 --- a/packages/x-flow/src/components/CandidateNode/index.tsx +++ b/packages/x-flow/src/components/CandidateNode/index.tsx @@ -54,6 +54,7 @@ const CandidateNode = () => { return (
{ const hasBody = !!children; const hasDesc = !!desc && !hideDesc; const gradientHeight = _gradientHeight || (hasBody || hasDesc || NodeWidget ? '20%' : '100%'); - debugger; return (
{ const copyNodes = generateCopyNodes( storeApi.getState().nodes.find((node) => node.id === nodeId), ); + + // 清除之前的超时定时器 + if (storeApi.getState().copyTimeoutId) { + clearTimeout(storeApi.getState().copyTimeoutId); + } + + // 设置isAddingNode为true,开启mousemove监听 + storeApi.getState().setIsAddingNode(true); + + // 设置30秒超时,自动关闭mousemove监听 + const timeoutId = setTimeout(() => { + storeApi.getState().setIsAddingNode(false); + storeApi.setState({ copyTimeoutId: null }); + }, 30000); + storeApi.setState({ copyNodes, + copyTimeoutId: timeoutId, }); }); const pasteNode = useMemoizedFn((nodeId: string, data: any) => { if (storeApi.getState().copyNodes.length > 0) { + // 清除超时定时器 + if (storeApi.getState().copyTimeoutId) { + clearTimeout(storeApi.getState().copyTimeoutId); + } + + // 关闭mousemove监听 + storeApi.getState().setIsAddingNode(false); + const newEdges = { id: uuid(), source: nodeId, @@ -117,6 +141,7 @@ export const useFlow = () => { storeApi.getState().addEdges(newEdges); storeApi.setState({ copyNodes: [], + copyTimeoutId: null, }); }else{ message.warning('请先复制节点!') @@ -126,6 +151,14 @@ export const useFlow = () => { const pasteNodeSimple = useMemoizedFn(() => { const mousePosition = storeApi.getState().mousePosition; if (storeApi.getState().copyNodes.length > 0) { + // 清除超时定时器 + if (storeApi.getState().copyTimeoutId) { + clearTimeout(storeApi.getState().copyTimeoutId); + } + + // 关闭mousemove监听 + storeApi.getState().setIsAddingNode(false); + const flowPos = screenToFlowPosition({ x: mousePosition.elementX, y: mousePosition.elementY, @@ -142,6 +175,7 @@ export const useFlow = () => { // 将清空剪贴板的操作也加入记录,使其可撤销 storeApi.setState({ copyNodes: [], + copyTimeoutId: null, }); }); } else { diff --git a/packages/x-flow/src/models/store.ts b/packages/x-flow/src/models/store.ts index b8243b472..ab10678bb 100644 --- a/packages/x-flow/src/models/store.ts +++ b/packages/x-flow/src/models/store.ts @@ -34,6 +34,7 @@ export type FlowState = { isAddingNode?: boolean; candidateNode: any; mousePosition: any; + copyTimeoutId: NodeJS.Timeout | null; // 添加超时定时器ID onNodesChange: OnNodesChange; onEdgesChange: OnEdgesChange; onConnect: OnConnect; @@ -45,6 +46,7 @@ export type FlowState = { setIsAddingNode: (payload: boolean) => void; setCandidateNode: (candidateNode: any) => void; setMousePosition: (mousePosition: any) => void; + setCopyTimeoutId: (timeoutId: NodeJS.Timeout | null) => void; // 添加设置超时定时器的方法 }; const createStore = (initProps?: Partial) => { @@ -64,6 +66,7 @@ const createStore = (initProps?: Partial) => { copyEdges: [], isAddingNode: false, candidateNode: null, + copyTimeoutId: null, // 添加超时定时器ID初始值 // nodeMenus: [], mousePosition: { pageX: 0, pageY: 0, elementX: 0, elementY: 0 }, onNodesChange: changes => { @@ -106,6 +109,9 @@ const createStore = (initProps?: Partial) => { setMousePosition: (mousePosition: any) => { set({ mousePosition }); }, + setCopyTimeoutId: (timeoutId: NodeJS.Timeout | null) => { + set({ copyTimeoutId: timeoutId }); + }, setLayout: (layout: 'LR' | 'TB') => { if (!layout) { return;