創建流程時,會涉及節點的拖拽,添加,刪除 以及節點內容的修改
效果圖

分步實現,每一步新增的功能代碼用紅色標記
實現節點拖拽
主要是增加兩個函數onDragOver onDrop將選擇的節點添加
index.tsx
import React, { useState, useRef, useCallback } from 'react';
import ReactFlow, {
ReactFlowProvider,
addEdge,
useNodesState,
useEdgesState,
Controls,
} from 'react-flow-renderer';
import Sidebar from './Sidebar';
import './index.css';
const initialNodes = [
{
id: '1',
type: 'input',
data: { label: 'input node' },
position: { x: 250, y: 5 },
},
];
let id = 0;
const getId = () => `dndnode_${id++}`;
const DnDFlow = () => {
const reactFlowWrapper = useRef(null);
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
const [reactFlowInstance, setReactFlowInstance] = useState(null);
const onConnect = useCallback((params) => setEdges((eds) => addEdge(params, eds)), []);
// 節點拖拽時
const onDragOver = useCallback((event) => {
event.preventDefault();
event.dataTransfer.dropEffect = 'move';
}, []);
// 添加新節點
const onDrop = useCallback(
(event) => {
event.preventDefault();
const reactFlowBounds = reactFlowWrapper.current.getBoundingClientRect();
const type = event.dataTransfer.getData('application/reactflow');
// check if the dropped element is valid
if (typeof type === 'undefined' || !type) {
return;
}
const position = reactFlowInstance.project({
x: event.clientX - reactFlowBounds.left,
y: event.clientY - reactFlowBounds.top,
});
const newNode = {
id: getId(),
type,
position,
data: { label: `${type} node` },
};
setNodes((nds) => nds.concat(newNode));
},
[reactFlowInstance]
);
return (
<div className="dndflow">
<ReactFlowProvider>
<div className="reactflow-wrapper" ref={reactFlowWrapper}>
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
onInit={setReactFlowInstance}
onDrop={onDrop}
onDragOver={onDragOver}
fitView
>
<Controls />
</ReactFlow>
</div>
<Sidebar /> // 添加欄的內容
</ReactFlowProvider>
</div>
);
};
export default DnDFlow;
左側拖拽節點內容
siderBar.tsx
import React from 'react'; export default () => { const onDragStart = (event, nodeType) => { event.dataTransfer.setData('application/reactflow', nodeType); event.dataTransfer.effectAllowed = 'move'; }; return ( <aside> <div className="description">You can drag these nodes to the pane on the right.</div> <div className="dndnode input" onDragStart={(event) => onDragStart(event, 'input')} draggable> Input Node </div> // 傳回節點的類型,這里的類型可以使用自定義的類型 <div className="dndnode" onDragStart={(event) => onDragStart(event, 'default')} draggable> Default Node </div> <div className="dndnode output" onDragStart={(event) => onDragStart(event, 'output')} draggable> Output Node </div> </aside> ); };
效果:

實現自定義節點
步驟: 1.創建一個節點組件 2.增加一個定義節點類型名稱,與節點組件對應 3.將節點類型傳入ReactFlow的nodeTypes 4.節點拖拽新增時,將節點類型改為自定義類型名稱
index.tsx
import React, { useState, useRef, useCallback } from 'react';
import ReactFlow, {
ReactFlowProvider,
addEdge,
useNodesState,
useEdgesState,
Controls,
} from 'react-flow-renderer';
import CustomNode from '../components/customNode';
import Sidebar from './Sidebar';
import './index.css';
// 自定義節點類型
const nodeTypes = { custom: CustomNode, };
const initialNodes = [
{
id: '1',
type: 'input',
data: { label: 'input node' },
position: { x: 250, y: 5 },
},
];
let id = 0;
const getId = () => `dndnode_${id++}`;
const DnDFlow = () => {
const reactFlowWrapper = useRef(null);
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
const [reactFlowInstance, setReactFlowInstance] = useState(null);
const onConnect = useCallback((params) => setEdges((eds) => addEdge(params, eds)), []);
// 節點拖拽時
const onDragOver = useCallback((event) => {
event.preventDefault();
event.dataTransfer.dropEffect = 'move';
}, []);
// 添加新節點
const onDrop = useCallback(
(event) => {
event.preventDefault();
const reactFlowBounds = reactFlowWrapper.current.getBoundingClientRect();
const type = event.dataTransfer.getData('application/reactflow');
// check if the dropped element is valid
if (typeof type === 'undefined' || !type) {
return;
}
const position = reactFlowInstance.project({
x: event.clientX - reactFlowBounds.left,
y: event.clientY - reactFlowBounds.top,
});
const newNode = {
id: getId(),
type,
position,
data: { label: `${type} node` },
};
setNodes((nds) => nds.concat(newNode));
},
[reactFlowInstance]
);
return (
<div className="dndflow">
<ReactFlowProvider>
<div className="reactflow-wrapper" ref={reactFlowWrapper}>
<ReactFlow
nodes={nodes}
edges={edges}
nodeTypes={nodeTypes}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
onInit={setReactFlowInstance}
onDrop={onDrop}
onDragOver={onDragOver}
fitView
>
<Controls />
</ReactFlow>
</div>
<Sidebar /> // 添加欄的內容
</ReactFlowProvider>
</div>
);
};
export default DnDFlow;
Sidebar.tsx左側添加欄內容
import React from 'react'; export default () => { const onDragStart = (event: any, nodeType: any) => { event.dataTransfer.setData('application/reactflow', nodeType); event.dataTransfer.effectAllowed = 'move'; }; return ( <aside> <div className="description">拖拽節點到畫布添加</div> <div className="dndnode input" onDragStart={(event) => { event.stopPropagation(); onDragStart(event, 'input'); }} draggable > 開始節點 </div> <div className="dndnode" onDragStart={(event) => { event.stopPropagation(); onDragStart(event, 'custom'); // 類型為自定義節點類型名稱 }} draggable > 中間節點 </div> <div className="dndnode output" onDragStart={(event) => { event.stopPropagation(); onDragStart(event, 'output'); }} draggable > 結束節點 </div> </aside> ); };
customNode.tsx自定義節點內容
import React, { memo } from 'react';
import { CloseOutlined } from '@ant-design/icons';
import { Handle } from 'react-flow-renderer';
export default memo(({ data, id, isConnectable }: any) => {
// console.log(1, data)
return (
<>
<Handle
type="target"
position="top"
className="my_handle"
onConnect={(params) => console.log('handle onConnect', params)}
isConnectable={isConnectable}
/> // 上面的連接點
<div className="nodeContent" style={data.style}>
<div className="nodelabel">{data.label}</div>
<div className="close">
<CloseOutlined
onClick={(e) => {
e.stopPropagation();
data.onChange(id);
}}
className="icon-close"
/>
</div>
</div> // 節點內容,可以自定義內容
<Handle
type="source"
position="bottom"
id="a"
className="my_handle"
isConnectable={isConnectable}
/> // 下面的連接點
</>
);
});
效果:

修改節點內容
步驟: 1.創建一個修改內容的表單組件 2.點擊節點將當前節點數據傳入 3.將節點初始配置回顯 4.修改表單,觸發修改事件,回傳修改后的數據 5.在修改函數中,修改數據,重新設置nodes
index.tsx
import React, { useState, useRef, useCallback } from 'react';
import ReactFlow, {
ReactFlowProvider,
addEdge,
useNodesState,
useEdgesState,
Controls,
} from 'react-flow-renderer';
import CustomNode from '../components/customNode';
import UpdateNode from '../components/nodeContent';
import Sidebar from './Sidebar';
import './index.css';
// 自定義節點類型
const nodeTypes = {
custom: CustomNode,
};
const initialNodes = [
{
id: '1',
type: 'input',
data: { label: 'input node' },
position: { x: 250, y: 5 },
},
];
let id = 0;
const getId = () => `dndnode_${id++}`;
const DnDFlow = () => {
const reactFlowWrapper = useRef(null);
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
const [reactFlowInstance, setReactFlowInstance] = useState(null);
const [nodeInfo, setNodeInfo] = useState<any>({});
const onConnect = useCallback((params) => setEdges((eds) => addEdge(params, eds)), []); // 節點拖拽時 const onDragOver = useCallback((event) => { event.preventDefault(); event.dataTransfer.dropEffect = 'move'; }, []); // 添加新節點 const onDrop = useCallback( (event) => { event.preventDefault(); const reactFlowBounds = reactFlowWrapper.current.getBoundingClientRect(); const type = event.dataTransfer.getData('application/reactflow'); // check if the dropped element is valid if (typeof type === 'undefined' || !type) { return; } const position = reactFlowInstance.project({ x: event.clientX - reactFlowBounds.left, y: event.clientY - reactFlowBounds.top, }); const newNode = { id: getId(), type, position, data: { label: `${type} node` }, }; setNodes((nds) => nds.concat(newNode)); }, [reactFlowInstance] ); // 點擊節點,將節點初始配置傳入 const onNodeClick = (e: any, node: any) => { console.log(node); setNodeInfo({ ...node.data, id: node.id, nodeBg: node.style && node.style.background ? node.style.background : '#ffffff', }); setNodeShow(true); }; // 修改完成,setNodes改變節點內容 const changeNode = (val: any) => { setNodes((nds) => nds.map((item) => { if (item.id === val.id) { item.data = val; item.hidden = val.isHidden; item.style = { background: val.nodeBg }; } return item; }), ); // console.log(val, nodes) }; return ( <div className="dndflow"> <ReactFlowProvider> <div className="reactflow-wrapper" ref={reactFlowWrapper}> <ReactFlow nodes={nodes} edges={edges} nodeTypes={nodeTypes}
onNodeClick={onNodeClick} onNodesChange={onNodesChange} onEdgesChange={onEdgesChange} onConnect={onConnect} onInit={setReactFlowInstance} onDrop={onDrop} onDragOver={onDragOver} fitView > <UpdateNode info={nodeInfo} onChange={changeNode} /> // 修改節點內容組件 <Controls /> </ReactFlow> </div> <Sidebar /> // 添加欄的內容 </ReactFlowProvider> </div> ); }; export default DnDFlow;
nodeContent.tsx節點修改組件
import React, { useState, useEffect } from 'react';
import { Input, Switch } from 'antd';
export type nodeProps = {
info: any;
onChange: (val: any) => void;
};
export default ({ info, onChange }: nodeProps) => {
const [nodeInfo, setNodeInfo] = useState<any>({});
useEffect(() => {
if (info.id) {
// console.log(info)
if (!info.isHidden) {
info.isHidden = false;
}
setNodeInfo(info);
}
}, [info.id]);
// 改變名稱
const setNodeName = (value: string) => {
setNodeInfo({
...nodeInfo,
label: value,
});
onChange({
...nodeInfo,
label: value,
});
};
// 改變背景色
const setNodeBg = (value: string) => {
// console.log(value);
setNodeInfo({
...nodeInfo,
nodeBg: value,
});
onChange({
...nodeInfo,
nodeBg: value,
});
};
// 是否隱藏
const setNodeHidden = (value: boolean) => {
setNodeInfo({
...nodeInfo,
isHidden: value,
});
onChange({
...nodeInfo,
isHidden: value,
});
};
return nodeInfo.id ? (
<div className="updatenode__controls">
<label>名稱:</label>
{/* <input value={nodeInfo.label} onChange={(evt) => setNodeName(evt.target.value)} /> */}
<Input
placeholder=""
value={nodeInfo.label}
onChange={(evt) => setNodeName(evt.target.value)}
/>
<label className="updatenode__bglabel">背景色:</label>
<Input type="color" value={nodeInfo.nodeBg} onChange={(evt) => setNodeBg(evt.target.value)} />
<div className="updatenode__checkboxwrapper">
<label>是否隱藏:</label>
<Switch checked={nodeInfo.isHidden} onChange={setNodeHidden} />
</div>
</div>
) : (
<></>
);
};
連接線自定義與內容修改也是一樣的操作步驟,
連接線效果圖:
githup上傳了完整的代碼,有節點內容修改.
