diff --git a/src/hooks/useFlowCallbacks.ts b/src/hooks/useFlowCallbacks.ts index 7699684..0288eaf 100644 --- a/src/hooks/useFlowCallbacks.ts +++ b/src/hooks/useFlowCallbacks.ts @@ -520,92 +520,222 @@ export const useFlowCallbacks = ( // 这里可以实现边编辑逻辑 console.log('编辑边:', edge); }, []); - // 复制节点 + // 复制节点(支持多节点和多边) const copyNode = useCallback((node: Node) => { - // 将节点数据存储到localStorage中,以便粘贴时使用 - const nodeData = { - ...node, - // 清除可能存在的运行时状态 + // 获取所有选中的节点(包括当前节点) + const selectedNodes = nodes.filter(n => n.selected || n.id === node.id); + + // 过滤掉开始和结束节点 + const nodesToCopy = selectedNodes.filter(n => n.type !== 'start' && n.type !== 'end'); + + if (nodesToCopy.length === 0) { + console.warn('没有可复制的节点(开始和结束节点不能复制)'); + return; + } + + // 获取这些节点之间的边 + const nodeIds = new Set(nodesToCopy.map(n => n.id)); + const edgesToCopy = edges.filter(e => + nodeIds.has(e.source) && nodeIds.has(e.target) + ); + + // 清除运行时状态 + const cleanedNodes = nodesToCopy.map(n => ({ + ...n, selected: false, - dragging: false, - // 添加应用标识信息 + dragging: false + })); + + const cleanedEdges = edgesToCopy.map(e => ({ + ...e, + selected: false + })); + + // 存储复制的数据 + const copiedData = { + nodes: cleanedNodes, + edges: cleanedEdges, appId: initialData?.appId }; - localStorage.setItem('copiedNode', JSON.stringify(nodeData)); - }, [initialData?.appId]); + localStorage.setItem('copiedFlowData', JSON.stringify(copiedData)); + console.log(`已复制 ${nodesToCopy.length} 个节点和 ${edgesToCopy.length} 条边`); + }, [nodes, edges, initialData?.appId]); - // 粘贴节点 + // 粘贴节点(支持多节点和多边) const pasteNode = useCallback((position: { x: number; y: number }) => { + // 尝试读取新格式的复制数据(多节点+边) + const copiedFlowDataStr = localStorage.getItem('copiedFlowData'); + + // 兼容旧格式的单节点复制 const copiedNodeStr = localStorage.getItem('copiedNode'); - if (!copiedNodeStr) { + + if (!copiedFlowDataStr && !copiedNodeStr) { console.warn('没有找到复制的节点数据'); return; } try { - const copiedNode = JSON.parse(copiedNodeStr); + // 处理新格式(多节点+边) + if (copiedFlowDataStr) { + const copiedData = JSON.parse(copiedFlowDataStr); - // 检查是否为同一应用,如果不是则不允许粘贴 - if (copiedNode.appId && initialData?.appId && copiedNode.appId !== initialData.appId) { - console.warn('不能在不同应用之间粘贴节点'); - // Message.warning('不能在不同应用之间粘贴节点'); - return; - } + // 检查是否为同一应用 + if (copiedData.appId && initialData?.appId && copiedData.appId !== initialData.appId) { + console.warn('不能在不同应用之间粘贴节点'); + return; + } - // 创建新节点,更新ID和位置 - const newNode = { - ...copiedNode, - id: `${copiedNode.type}-${Date.now()}`, // 生成新的唯一ID - position, // 使用传入的位置 - selected: false, - dragging: false, - // 移除应用标识信息,避免存储在节点数据中 - appId: undefined - }; + const { nodes: copiedNodes, edges: copiedEdges } = copiedData; + + if (!copiedNodes || copiedNodes.length === 0) { + console.warn('没有可粘贴的节点'); + return; + } - // 特殊处理循环节点 - if (copiedNode.type === 'LOOP') { - // 对于循环节点,我们需要特殊处理 + // 计算所有节点的边界框中心点 + const minX = Math.min(...copiedNodes.map((n: Node) => n.position.x)); + const minY = Math.min(...copiedNodes.map((n: Node) => n.position.y)); + const maxX = Math.max(...copiedNodes.map((n: Node) => n.position.x)); + const maxY = Math.max(...copiedNodes.map((n: Node) => n.position.y)); + const centerX = (minX + maxX) / 2; + const centerY = (minY + maxY) / 2; + + // 计算偏移量,使粘贴的节点组以鼠标位置为中心 + const offsetX = position.x - centerX; + const offsetY = position.y - centerY; + + // 创建旧ID到新ID的映射 + const idMap = new Map(); + const timestamp = Date.now(); + + // 创建新节点 + const newNodes = copiedNodes.map((node: Node, index: number) => { + const newId = `${node.type}-${timestamp}-${index}`; + idMap.set(node.id, newId); + + const newNode = { + ...node, + id: newId, + position: { + x: node.position.x + offsetX, + y: node.position.y + offsetY + }, + selected: false, + dragging: false, + appId: undefined + }; + + // 注册节点类型 + const nodeMap = Array.from(Object.values(nodeTypeMap).map(key => key)); + if (!nodeMap.includes(newNode.type)) { + registerNodeType(newNode.type, getNodeComponent(newNode.type), newNode.data?.title as string || newNode.type); + } + + return newNode; + }); + + // 创建新边,使用新的节点ID + const newEdges = copiedEdges.map((edge: Edge, index: number) => { + const newSourceId = idMap.get(edge.source); + const newTargetId = idMap.get(edge.target); + + if (!newSourceId || !newTargetId) { + console.warn('边的源节点或目标节点未找到:', edge); + return null; + } + + return { + ...edge, + id: `e${newSourceId}-${newTargetId}-${timestamp}-${index}`, + source: newSourceId, + target: newTargetId, + selected: false + }; + }).filter(Boolean); // 过滤掉null值 + + // 更新节点和边 setNodes((nds: Node[]) => { - const newNodes = [...nds, newNode]; + const updatedNodes = [...nds, ...newNodes]; // 添加节点后记录历史 setTimeout(() => { const event = new CustomEvent('takeSnapshot', { - detail: { nodes: [...newNodes], edges: [...edges] } + detail: { nodes: [...updatedNodes], edges: [...edges, ...newEdges] } }); document.dispatchEvent(event); }, 0); - return newNodes; + return updatedNodes; }); - return; - } - // 将未定义的节点动态追加进nodeTypes - const nodeMap = Array.from(Object.values(nodeTypeMap).map(key => key)); - if (!nodeMap.includes(newNode.type)) { - registerNodeType(newNode.type, getNodeComponent(newNode.type), newNode.data?.title || newNode.type); + setEdges((eds: Edge[]) => { + const updatedEdges = [...eds, ...newEdges]; + return updatedEdges; + }); + + console.log(`已粘贴 ${newNodes.length} 个节点和 ${newEdges.length} 条边`); } + // 处理旧格式(单节点)- 保持向后兼容 + else if (copiedNodeStr) { + const copiedNode = JSON.parse(copiedNodeStr); - setNodes((nds: Node[]) => { - const newNodes = [...nds, newNode]; + // 检查是否为同一应用 + if (copiedNode.appId && initialData?.appId && copiedNode.appId !== initialData.appId) { + console.warn('不能在不同应用之间粘贴节点'); + return; + } - // 添加节点后记录历史 - setTimeout(() => { - const event = new CustomEvent('takeSnapshot', { - detail: { nodes: [...newNodes], edges: [...edges] } + // 创建新节点,更新ID和位置 + const newNode = { + ...copiedNode, + id: `${copiedNode.type}-${Date.now()}`, + position, + selected: false, + dragging: false, + appId: undefined + }; + + // 特殊处理循环节点 + if (copiedNode.type === 'LOOP') { + setNodes((nds: Node[]) => { + const newNodes = [...nds, newNode]; + + setTimeout(() => { + const event = new CustomEvent('takeSnapshot', { + detail: { nodes: [...newNodes], edges: [...edges] } + }); + document.dispatchEvent(event); + }, 0); + + return newNodes; }); - document.dispatchEvent(event); - }, 0); + return; + } - return newNodes; - }); + // 注册节点类型 + const nodeMap = Array.from(Object.values(nodeTypeMap).map(key => key)); + if (!nodeMap.includes(newNode.type)) { + registerNodeType(newNode.type, getNodeComponent(newNode.type), newNode.data?.title || newNode.type); + } + + setNodes((nds: Node[]) => { + const newNodes = [...nds, newNode]; + + setTimeout(() => { + const event = new CustomEvent('takeSnapshot', { + detail: { nodes: [...newNodes], edges: [...edges] } + }); + document.dispatchEvent(event); + }, 0); + + return newNodes; + }); + } } catch (error) { console.error('粘贴节点时出错:', error); } - }, [edges, initialData?.appId]); + }, [edges, initialData?.appId, nodes]); // endregion // region 节点/边操作 @@ -1195,7 +1325,7 @@ export const useFlowCallbacks = ( appEventDefinition, sceneId: info.id }; - + const res: any = await setMainFlowNew(params, initialData.appId); if (res.code === 200) { Message.success('保存成功'); @@ -1450,17 +1580,20 @@ export const useFlowCallbacks = ( Message.success('应用已恢复'); // 更新暂停状态为 false dispatch(updateIsPaused(false)); - } else { + } + else { Message.error(res.msg || '恢复失败'); } - } else { + } + else { // 当前正在运行,执行暂停操作 const res: any = await pauseApp({ id: runId }); if (res.code === 200) { Message.success('应用已暂停'); // 更新暂停状态为 true dispatch(updateIsPaused(true)); - } else { + } + else { Message.error(res.msg || '暂停失败'); } } @@ -1505,7 +1638,8 @@ export const useFlowCallbacks = ( // 重置节点状态 dispatch(resetNodeStatus()); - } else { + } + else { Message.error(res.msg || '重跑失败'); } } catch (error) {