feat(flowEditor): 添加节点和边的右键菜单功能

- 新增节点右键菜单和边右键菜单组件
- 实现节点和边的删除、编辑、复制等功能
- 优化画布点击事件,关闭未选中的菜单- 调整菜单显示位置,确保在画布区域内显示
master
钟良源 5 months ago
parent 10af5b0d78
commit 54235f0f8e

@ -0,0 +1,36 @@
import React from 'react';
import { Menu } from '@arco-design/web-react';
import { Edge } from '@xyflow/react';
interface EdgeContextMenuProps {
edge: Edge;
onDelete?: (edge: Edge) => void;
onEdit?: (edge: Edge) => void;
}
const EdgeContextMenu: React.FC<EdgeContextMenuProps> = ({
edge,
onDelete,
onEdit
}) => {
const handleDelete = () => {
onDelete && onDelete(edge);
};
const handleEdit = () => {
onEdit && onEdit(edge);
};
return (
<Menu>
<Menu.Item key="edit" onClick={handleEdit}>
</Menu.Item>
<Menu.Item key="delete" onClick={handleDelete}>
</Menu.Item>
</Menu>
);
};
export default EdgeContextMenu;

@ -0,0 +1,51 @@
import React from 'react';
import { Menu, Dropdown } from '@arco-design/web-react';
import { Node } from '@xyflow/react';
interface NodeContextMenuProps {
node: Node;
onRename?: (node: Node) => void;
onDelete?: (node: Node) => void;
onCopy?: (node: Node) => void;
onEdit?: (node: Node) => void;
}
const NodeContextMenu: React.FC<NodeContextMenuProps> = ({
node,
onDelete,
onCopy,
onEdit
}) => {
const handleDelete = () => {
onDelete && onDelete(node);
};
const handleCopy = () => {
onCopy && onCopy(node);
};
const handleEdit = () => {
onEdit && onEdit(node);
};
return (
<Menu>
<Menu.Item key="edit" onClick={handleEdit}>
</Menu.Item>
{(!['start', 'end'].includes(node.type)) && (
<>
<Menu.Item key="copy" onClick={handleCopy}>
</Menu.Item>
<Menu.Item key="delete" onClick={handleDelete}>
</Menu.Item>
</>
)}
</Menu>
);
};
export default NodeContextMenu;

@ -11,7 +11,8 @@ import {
ReactFlowProvider,
useReactFlow,
EdgeTypes,
SelectionMode
SelectionMode,
useStoreApi
} from '@xyflow/react';
import '@xyflow/react/dist/style.css';
import { nodeTypeMap, nodeTypes, registerNodeType } from './node';
@ -20,6 +21,8 @@ import { convertFlowData } from '@/utils/convertFlowData';
import { exampleFlowData } from '@/pages/flowEditor/test/exampleFlowData';
import LocalNode from '@/pages/flowEditor/node/localNode/LocalNode';
import CustomEdge from './components/customEdge';
import NodeContextMenu from './components/nodeContextMenu';
import EdgeContextMenu from './components/edgeContextMenu';
const edgeTypes: EdgeTypes = {
custom: CustomEdge
@ -41,6 +44,13 @@ const FlowEditor: React.FC = () => {
const [edges, setEdges] = useState<Edge[]>([]);
const reactFlowInstance = useReactFlow();
const reactFlowWrapper = useRef<HTMLDivElement>(null);
const [menu, setMenu] = useState<{
id: string;
type: 'node' | 'edge';
top: number;
left: number;
} | null>(null);
const store = useStoreApi();
const onNodesChange = useCallback(
(changes: any) => setNodes((nodesSnapshot) => applyNodeChanges(changes, nodesSnapshot)),
@ -50,6 +60,7 @@ const FlowEditor: React.FC = () => {
(changes: any) => setEdges((edgesSnapshot) => applyEdgeChanges(changes, edgesSnapshot)),
[]
);
const onConnect = useCallback(
(params: any) => setEdges((edgesSnapshot) => addEdge({ ...params, type: 'custom' }, edgesSnapshot)),
[]
@ -105,8 +116,81 @@ const FlowEditor: React.FC = () => {
setEdges(initialEdges);
}, []);
// 节点右键菜单处理
const onNodeContextMenu = useCallback(
(event: React.MouseEvent, node: Node) => {
event.preventDefault();
const pane = reactFlowWrapper.current?.getBoundingClientRect();
if (!pane) return;
setMenu({
id: node.id,
type: 'node',
top: event.clientY - pane.top,
left: event.clientX - pane.left
});
},
[setMenu]
);
// 边右键菜单处理
const onEdgeContextMenu = useCallback(
(event: React.MouseEvent, edge: Edge) => {
event.preventDefault();
const pane = reactFlowWrapper.current?.getBoundingClientRect();
if (!pane) return;
setMenu({
id: edge.id,
type: 'edge',
top: event.clientY - pane.top,
left: event.clientX - pane.left
});
},
[setMenu]
);
// 点击画布其他区域关闭菜单
const onPaneClick = useCallback(() => setMenu(null), [setMenu]);
// 删除节点
const deleteNode = useCallback((node: Node) => {
setNodes((nds) => nds.filter((n) => n.id !== node.id));
setEdges((eds) => eds.filter((e) => e.source !== node.id && e.target !== node.id));
setMenu(null);
}, []);
// 删除边
const deleteEdge = useCallback((edge: Edge) => {
setEdges((eds) => eds.filter((e) => e.id !== edge.id));
setMenu(null);
}, []);
// 编辑节点
const editNode = useCallback((node: Node) => {
// 这里可以实现节点编辑逻辑
console.log('编辑节点:', node);
setMenu(null);
}, []);
// 编辑边
const editEdge = useCallback((edge: Edge) => {
// 这里可以实现边编辑逻辑
console.log('编辑边:', edge);
setMenu(null);
}, []);
// 复制节点
const copyNode = useCallback((node: Node) => {
// 这里可以实现节点复制逻辑
console.log('复制节点:', node);
setMenu(null);
}, []);
return (
<div ref={reactFlowWrapper} style={{ width: '100%', height: '100%' }}>
<div ref={reactFlowWrapper} style={{ width: '100%', height: '100%', position: 'relative' }}>
<ReactFlow
nodes={nodes}
edges={edges}
@ -117,6 +201,10 @@ const FlowEditor: React.FC = () => {
onConnect={onConnect}
onDrop={onDrop}
onDragOver={onDragOver}
onNodeContextMenu={onNodeContextMenu}
onEdgeContextMenu={onEdgeContextMenu}
onPaneClick={onPaneClick}
onPaneContextMenu={onPaneClick}
fitView
selectionKeyCode={['Meta', 'Control']}
selectionMode={SelectionMode.Partial}
@ -131,8 +219,44 @@ const FlowEditor: React.FC = () => {
{/* <div>从左侧拖拽节点到画布中</div>*/}
{/*</Panel>*/}
</ReactFlow>
{menu && menu.type === 'node' && (
<div
style={{
position: 'absolute',
top: menu.top,
left: menu.left,
zIndex: 1000
}}
>
<NodeContextMenu
node={nodes.find(n => n.id === menu.id)!}
onDelete={deleteNode}
onEdit={editNode}
onCopy={copyNode}
/>
</div>
)}
{menu && menu.type === 'edge' && (
<div
style={{
position: 'absolute',
top: menu.top,
left: menu.left,
zIndex: 1000
}}
>
<EdgeContextMenu
edge={edges.find(e => e.id === menu.id)!}
onDelete={deleteEdge}
onEdit={editEdge}
/>
</div>
)}
</div>
);
};
export default FlowEditorWithProvider;
Loading…
Cancel
Save