feat(flowEditor): 增加初版流程编辑器功能

- 增加开始结束节点,增加编辑器侧边栏
master
钟良源 5 months ago
parent 88a43e8e02
commit afa5de1030

@ -1,4 +1,4 @@
import React, { useState, useCallback } from 'react';
import React, { useState, useCallback, useRef } from 'react';
import {
ReactFlow,
applyNodeChanges,
@ -7,58 +7,78 @@ import {
Background,
Controls,
Node,
Edge
Edge,
ReactFlowProvider,
useReactFlow,
NodeTypes,
Panel
} from '@xyflow/react';
import '@xyflow/react/dist/style.css';
import TextUpdaterNode from './node/textUpdateNode/TextUpdaterNode';
import StartNode from './node/startNode/StartNode';
import EndNode from './node/endNode/EndNode';
import DraggableNode from './node/draggableNode/DraggableNode';
import SideBar from './sideBar/sideBar';
const nodeTypes = {
textUpdater: TextUpdaterNode
const nodeTypes: NodeTypes = {
textUpdater: TextUpdaterNode,
start: StartNode,
end: EndNode,
draggable: DraggableNode
};
const initialNodes: Node[] = [
{
id: 'n1',
id: 'start-node',
position: { x: 0, y: 0 },
data: { label: 'Node 1' },
type: 'input'
data: { label: '开始' },
type: 'start'
},
{
id: 'end-node',
position: { x: 300, y: 200 },
data: { label: '结束' },
type: 'end'
},
{
id: 'node-1',
type: 'textUpdater',
position: { x: 150, y: 0 },
data: { value: 123 }
},
{
id: 'n2',
position: { x: 100, y: 100 },
data: { label: 'Custom Node' }
}
];
const initialEdges: Edge[] = [
{
id: 'n1-n2',
source: 'n1',
target: 'n2'
},
{
id: 'n1-node-1',
source: 'n1',
id: 'start-node-1',
source: 'start-node',
target: 'node-1',
targetHandle: 'a'
sourceHandle: 'start-source'
},
{
id: 'n2-node-1',
source: 'n2',
target: 'node-1',
targetHandle: 'a1'
id: 'node-1-end',
source: 'node-1',
target: 'end-node',
targetHandle: 'end-target'
}
];
const FlowEditorWithProvider: React.FC = () => {
return (
<div style={{ width: '100%', height: '91vh', display: 'flex' }}>
<ReactFlowProvider>
<SideBar />
<FlowEditor />
</ReactFlowProvider>
</div>
);
};
const FlowEditor: React.FC = () => {
const [nodes, setNodes] = useState<Node[]>(initialNodes);
const [edges, setEdges] = useState<Edge[]>(initialEdges);
const reactFlowInstance = useReactFlow();
const reactFlowWrapper = useRef<HTMLDivElement>(null);
const onNodesChange = useCallback(
(changes: any) => setNodes((nodesSnapshot) => applyNodeChanges(changes, nodesSnapshot)),
@ -73,8 +93,41 @@ const FlowEditor: React.FC = () => {
[]
);
const onDragOver = useCallback((event: React.DragEvent) => {
event.preventDefault();
event.dataTransfer.dropEffect = 'move';
}, []);
const onDrop = useCallback(
(event: React.DragEvent) => {
event.preventDefault();
if (!reactFlowInstance) return;
const type = event.dataTransfer.getData('application/reactflow');
if (typeof type === 'undefined' || !type) {
return;
}
const position = reactFlowInstance.screenToFlowPosition({
x: event.clientX,
y: event.clientY,
});
const newNode = {
id: `${type}-${Date.now()}`,
type,
position,
data: { label: `${type} node` },
};
setNodes((nds) => nds.concat(newNode));
},
[reactFlowInstance]
);
return (
<div style={{ width: '90vw', height: '91vh' }}>
<div ref={reactFlowWrapper} style={{ width: '100%', height: '100%' }}>
<ReactFlow
nodes={nodes}
edges={edges}
@ -82,13 +135,18 @@ const FlowEditor: React.FC = () => {
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
onDrop={onDrop}
onDragOver={onDragOver}
fitView
>
<Background />
<Controls />
{/*<Panel position="top-right">*/}
{/* <div>从左侧拖拽节点到画布中</div>*/}
{/*</Panel>*/}
</ReactFlow>
</div>
);
};
export default React.memo(FlowEditor);
export default FlowEditorWithProvider;

@ -0,0 +1,30 @@
import React from 'react';
import { Handle, Position } from '@xyflow/react';
const DraggableNode = ({ data }: { data: any }) => {
return (
<div style={{
padding: '10px 20px',
backgroundColor: '#1890ff',
borderRadius: '5px',
color: 'white',
fontWeight: 'bold'
}}>
<div>{data.label || '任务节点'}</div>
<Handle
type="target"
position={Position.Left}
id="task-target"
style={{ background: '#555' }}
/>
<Handle
type="source"
position={Position.Right}
id="task-source"
style={{ background: '#555' }}
/>
</div>
);
};
export default DraggableNode;

@ -0,0 +1,24 @@
import React from 'react';
import { Handle, Position } from '@xyflow/react';
const EndNode = ({ data }: { data: any }) => {
return (
<div style={{
padding: '10px 20px',
backgroundColor: '#ff4d4f',
borderRadius: '5px',
color: 'white',
fontWeight: 'bold'
}}>
<div></div>
<Handle
type="target"
position={Position.Left}
id="end-target"
style={{ background: '#555' }}
/>
</div>
);
};
export default EndNode;

@ -0,0 +1,24 @@
import React from 'react';
import { Handle, Position } from '@xyflow/react';
const StartNode = ({ data }: { data: any }) => {
return (
<div style={{
padding: '10px 20px',
backgroundColor: '#52c41a',
borderRadius: '5px',
color: 'white',
fontWeight: 'bold'
}}>
<div></div>
<Handle
type="source"
position={Position.Right}
id="start-source"
style={{ background: '#555' }}
/>
</div>
);
};
export default StartNode;

@ -0,0 +1,41 @@
import React from 'react';
import { Card } from '@arco-design/web-react';
const onDragStart = (event: React.DragEvent, nodeType: string) => {
event.dataTransfer.setData('application/reactflow', nodeType);
event.dataTransfer.effectAllowed = 'move';
};
const SideBar: React.FC = () => {
return (
<div style={{
width: 200,
padding: 10,
borderRight: '1px solid #eee',
backgroundColor: '#fafafa'
}}>
<div style={{ marginBottom: 20 }}>
<h3></h3>
<p></p>
</div>
<Card
style={{ marginBottom: 10, cursor: 'grab' }}
draggable
onDragStart={(event) => onDragStart(event, 'draggable')}
>
<div> 1</div>
</Card>
<Card
style={{ marginBottom: 10, cursor: 'grab' }}
draggable
onDragStart={(event) => onDragStart(event, 'textUpdater')}
>
<div></div>
</Card>
</div>
);
};
export default SideBar;
Loading…
Cancel
Save