feat(flowEditor): 实现节点复制粘贴功能

- 新增 pasteNode 方法支持在指定位置粘贴节点
- 在 FlowEditorMain 中添加对 Ctrl/Cmd+C 和 Ctrl/Cmd+V 快捷键的支持
- 限制仅允许复制非开始和结束类型的节点
- 粘贴时自动计算视口中心位置作为新节点坐标
- 更新依赖数组以确保键盘事件监听器正确响应状态变化
master
钟良源 2 months ago
parent 27b0dd3e9d
commit dde9083fd5

@ -78,6 +78,7 @@ interface FlowEditorMainProps {
editNode: (node: Node) => void; editNode: (node: Node) => void;
editEdge: (edge: Edge) => void; editEdge: (edge: Edge) => void;
copyNode: (node: Node) => void; copyNode: (node: Node) => void;
pasteNode: (position: { x: number; y: number }) => void;
addNodeOnEdge: (nodeType: string, node: any) => void; addNodeOnEdge: (nodeType: string, node: any) => void;
addNodeOnPane: (nodeType: string, position: { x: number, y: number }, node?: any) => void; addNodeOnPane: (nodeType: string, position: { x: number, y: number }, node?: any) => void;
handleAddNode: (nodeType: string, node: any) => void; handleAddNode: (nodeType: string, node: any) => void;
@ -130,6 +131,7 @@ const FlowEditorMain: React.FC<FlowEditorMainProps> = (props) => {
editNode, editNode,
editEdge, editEdge,
copyNode, copyNode,
pasteNode,
addNodeOnEdge, addNodeOnEdge,
addNodeOnPane, addNodeOnPane,
handleAddNode, handleAddNode,
@ -224,28 +226,67 @@ const FlowEditorMain: React.FC<FlowEditorMainProps> = (props) => {
// 监听键盘事件实现快捷键 // 监听键盘事件实现快捷键
useEffect(() => { useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => { const handleKeyDown = (e: KeyboardEvent) => {
// Ctrl+Z 撤销 // 如果当前正在运行或不是默认模式,不处理复制粘贴快捷键
if (e.ctrlKey && e.key === 'z' && !e.shiftKey && canUndo) { const canEdit = !currentAppIsRunning && useDefault;
// 兼容 Mac (metaKey) 和 Windows/Linux (ctrlKey)
const isModifierKey = e.ctrlKey || e.metaKey;
// Ctrl/Cmd+Z 撤销
if (isModifierKey && e.key === 'z' && !e.shiftKey && canUndo) {
e.preventDefault(); e.preventDefault();
undo(); undo();
} }
// Ctrl+Shift+Z 重做 // Ctrl/Cmd+Shift+Z 重做
if (e.ctrlKey && e.shiftKey && e.key === 'Z' && canRedo) { if (isModifierKey && e.shiftKey && e.key === 'Z' && canRedo) {
e.preventDefault(); e.preventDefault();
redo(); redo();
} }
// Ctrl+Y 重做 // Ctrl/Cmd+Y 重做
if (e.ctrlKey && e.key === 'y' && canRedo) { if (isModifierKey && e.key === 'y' && canRedo) {
e.preventDefault(); e.preventDefault();
redo(); redo();
} }
// Ctrl/Cmd+C 复制选中的节点
if (isModifierKey && e.key === 'c' && canEdit) {
// 获取当前选中的节点
const selectedNode = nodes.find(node => node.selected);
if (selectedNode) {
// 不允许复制开始和结束节点
if (selectedNode.type === 'start' || selectedNode.type === 'end') {
return;
}
e.preventDefault();
copyNode(selectedNode);
}
}
// Ctrl/Cmd+V 粘贴节点
if (isModifierKey && e.key === 'v' && canEdit) {
const copiedNodeStr = localStorage.getItem('copiedNode');
if (copiedNodeStr) {
e.preventDefault();
// 获取当前视口中心位置
if (reactFlowInstance) {
const viewport = reactFlowInstance.getViewport();
const { x, y, zoom } = viewport;
// 获取画布容器的尺寸
const wrapper = reactFlowWrapper.current;
if (wrapper) {
const rect = wrapper.getBoundingClientRect();
// 计算视口中心点在流程图坐标系中的位置
const centerX = (-x + rect.width / 2) / zoom;
const centerY = (-y + rect.height / 2) / zoom;
pasteNode({ x: centerX, y: centerY });
}
}
}
}
}; };
document.addEventListener('keydown', handleKeyDown); document.addEventListener('keydown', handleKeyDown);
return () => { return () => {
document.removeEventListener('keydown', handleKeyDown); document.removeEventListener('keydown', handleKeyDown);
}; };
}, [undo, redo, canUndo, canRedo]); }, [undo, redo, canUndo, canRedo, currentAppIsRunning, useDefault, nodes, copyNode, pasteNode, reactFlowInstance]);
// 处理流程发布 // 处理流程发布
const handlePublish = () => { const handlePublish = () => {

@ -341,6 +341,7 @@ const FlowEditor: React.FC<{ initialData?: any, useDefault?: boolean }> = ({ ini
editNode={editNode} editNode={editNode}
editEdge={editEdge} editEdge={editEdge}
copyNode={copyNode} copyNode={copyNode}
pasteNode={pasteNode}
// Node operations // Node operations
addNodeOnEdge={addNodeOnEdge} addNodeOnEdge={addNodeOnEdge}

Loading…
Cancel
Save