// 创建项目
npm create vite@latest my-react-flow-app -- --template react
// 安装插件
npm install reactflow
// 运行项目
npm run dev
1、App.jsx
import { useCallback, useState } from 'react';
import ReactFlow,
{
addEdge,
ReactFlowProvider,
MiniMap,
Controls,
useNodesState,
useEdgesState,
useReactFlow,
MarkerType,
Panel,
ConnectionMode
} from 'reactflow';
import 'reactflow/dist/style.css';
import './index.css';
import UpdateNode from './components/nodeContent';
import UpdateEdge from './components/edgeContent';
import ResizableNodeSelected from './components/ResizableNodeSelected';
import {nodes as initialNodes1,edges as initialEdges1} from './components/data';
const nodeTypes = {
ResizableNodeSelected,
};
const rfStyle = {
backgroundColor: '#B8CEFF',
};
const initialNodes = [
{
id: '1',
type: 'ResizableNodeSelected',
position: { x: 100, y: 100 },
data: { label: '1' },
style: {
background: "#F3A011",
color: "white",
border: '1px solid orange',
borderRadius: '100%',
width: 80,
height: 80,
},
},
{
id: '2',
type: 'ResizableNodeSelected',
position: { x: 200, y: 300 },
data: { label: '2' },
style: {
background: "#F3A011",
color: "white",
border: '1px solid orange',
borderRadius: '100%',
width: 80,
height: 80,
},
},
{
id: '3',
type: 'ResizableNodeSelected',
position: { x: 100, y: 500 },
data: { label: '3' },
style: {
background: "#F3A011",
color: "white",
border: '1px solid orange',
borderRadius: '100%',
width: 80,
height: 80,
},
},
];
const initialEdges = [
{
id: 'e1-2',
source: '1',
target: '2',
style: { stroke: "#116F97" },
label: "连接1-2",
sourceHandle: 'c',
targetHandle: 'a',
},
{
id: "e2-3",
source: "2",
target: "3",
// labelStyle: { fill: "#116F97", fontWeight: 100 }, // 连接线名称样式
style: { stroke: "#116F97" }, // 连接线颜色
label: "连接2-3",
sourceHandle: 'c',
targetHandle: 'a',
},
];
const flowKey = 'flow_test';
const localNodes = JSON.parse(localStorage.getItem(flowKey)).nodes;
const localEdges = JSON.parse(localStorage.getItem(flowKey)).edges;
let nodeId = 1;
function App1() {
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes1);
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges1);
const [nodeInfo, setNodeInfo] = useState({});
const [edgeInfo, setEdgeInfo] = useState({});
const [nodeShow, setNodeShow] = useState(true);
const onConnect = useCallback(
(connection) => setEdges((eds) => addEdge(connection, eds)),
[setEdges]
);
// 保存
const [rfInstance, setRfInstance] = useState({});
const onSave = useCallback(() => {
if (rfInstance) {
const flow = rfInstance.toObject();
localStorage.setItem(flowKey, JSON.stringify(flow));
console.log(JSON.stringify(flow));
}
}, [rfInstance]
);
// 恢复
const { setViewport } = useReactFlow();
const onRestore = useCallback(() => {
const restoreFlow = async () => {
const flow = JSON.parse(localStorage.getItem(flowKey));
if (flow) {
const { x = 0, y = 0, zoom = 0 } = flow.viewport;
setNodes(flow.nodes || []);
setEdges(flow.edges || []);
setViewport({ x, y, zoom });
}
};
restoreFlow();
}, [setNodes, setViewport]
);
// 清空
const onDelete = useCallback(() => {
const restoreFlow = async () => {
setNodes([] || []);
setEdges([] || []);
};
restoreFlow();
}, [setNodes]
);
// 点击节点
const onNodeClick = (e, node) => {
setNodeInfo({
...node.data,
id: node.id,
nodeBg: node.style && node.style.background ? node.style.background : '#ffffff',
});
setNodeShow(true);
};
// 点击节点连接线
const onEdgeClick = (e, edge) => {
setEdgeInfo(edges.find((item) => edge.id === item.id));
setNodeShow(false);
};
// 新增节点
const reactFlowInstance = useReactFlow();
const onAdd = useCallback(() => {
const id = `${++nodeId}`;
const newNode = {
id,
type: 'ResizableNodeSelected',
position: {
x: 100,
y: 300,
// x: Math.random() * 200,
// y: Math.random() * 200,
},
data: {
label: `Node ${id}`,
},
style: {
background: "#F3A011",
color: "white",
border: '1px solid orange',
borderRadius: '100%',
width: 80,
height: 80,
},
};
reactFlowInstance.addNodes(newNode);
}, []);
// 改变节点内容
const changeNode = (val) => {
setNodes((nds) =>
nds.map((item) => {
if (item.id === val.id) {
item.data = val;
item.hidden = val.isHidden;
item.style = { background: val.nodeBg, width: 80, height: 80, borderRadius: '100%', color: "white", fontSize: 2 };
}
return item;
}),
);
};
// 改变连接线内容
const changeEdge = (val) => {
setEdges((nds) =>
nds.map((item) => {
if (item.id === val.id) {
item.label = val.label;
item.type = val.type;
item.hidden = val.isHidden;
item.style = { stroke: val.color };
}
return item;
}),
);
};
// 默认edge样式
const defaultEdgeOptions = {
style: {
strokeWidth: 1,
stroke: '#116F97'
},
type: 'default',
markerEnd: {
type: MarkerType.ArrowClosed,
color: '#116F97'
} // 连接线尾部的箭头
}
return (
<div style={{ width: '100vw', height: '100vh' }}>
<ReactFlow
nodes={nodes} // 节点
edges={edges} // 连接线
onNodesChange={onNodesChange} // 节点拖拽等改变
onEdgesChange={onEdgesChange} // 连接线拖拽等改变
onNodeClick={onNodeClick} // 点击节点
onEdgeClick={onEdgeClick} // 点击连接线
onConnect={onConnect} // 节点直接连接
nodeTypes={nodeTypes} // 节点类型
// edgeTypes={edgeTypes}
fitView // 渲染节点数据
style={rfStyle} // 背景色
defaultEdgeOptions={defaultEdgeOptions} // 默认连接线样式
onInit={setRfInstance} // 初始化保存的数据
connectionMode={ConnectionMode.Loose}
/>
{nodeShow ? (
<UpdateNode info={nodeInfo} onChange={changeNode} />
) : (
<UpdateEdge info={edgeInfo} onChange={changeEdge} />
)}
<Panel position='top-left'>
<button onClick={onAdd}>add node</button>
<button onClick={onSave}>save</button>
<button onClick={onRestore}>restore</button>
<button onClick={onDelete}>delete</button>
</Panel>
<MiniMap />
<Controls />
</div>
);
}
export default function () {
return (
<ReactFlowProvider>
<App1 />
</ReactFlowProvider>
);
}
2、index.css
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
/* line-height: 2; */
font-weight: 400;
/* color-scheme: light dark; */
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
/* -webkit-text-size-adjust: 100%; */
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #116F97;
}
button {
background-color: #f9f9f9;
}
}
/* edge颜色 */
.react-flow__handle{
color: #116F97;
background-color: #116F97;
border:0;
border-radius: 100%;
min-width: 1px;
min-height: 1px;
}
.react-flow__edge-textbg{
fill:#3a94BB;
}
.react-flow__handle.connectionindicator{
width: 1;
height: 1;
}
.react-flow__node{
width: 50;
height: 50;
}
/* 4个连接点样式 */
.simple-floatingedges {
flex-direction: column;
display: flex;
flex-grow: 1;
height: 100%;
}
.simple-floatingedges .react-flow__handle {
width: 8px;
height: 8px;
background-color: #bbb;
}
.simple-floatingedges .react-flow__handle-top {
top: -5px;
}
.simple-floatingedges .react-flow__handle-bottom {
bottom: -5px;
}
.simple-floatingedges .react-flow__handle-left {
left: -5px;
}
.simple-floatingedges .react-flow__handle-right {
right: -5px;
}
.simple-floatingedges .react-flow__node-custom {
background: #fff;
border: 1px solid #1a192b;
border-radius: 3px;
color: #222;
font-size: 12px;
padding: 10px;
text-align: center;
width: 150px;
}
/* node与wdge编辑样式 */
.dndflow {
display: flex;
flex-direction: column;
flex-grow: 1;
height: 70vh;
}
.react-flow__attribution {
display: none;
}
.dndflow aside {
padding: 15px 10px;
font-size: 12px;
background: #fcfcfc;
border-right: 1px solid #eee;
}
.dndflow aside .description {
margin-bottom: 10px;
}
.dndflow .dndnode {
display: flex;
align-items: center;
justify-content: center;
height: 20px;
margin-bottom: 10px;
padding: 4px;
border: 1px solid #1a192b;
border-radius: 2px;
cursor: grab;
}
.dndflow .dndnode.input {
border-color: #0041d0;
}
.dndflow .dndnode.output {
border-color: #ff0072;
}
.dndflow .reactflow-wrapper {
flex-grow: 1;
height: 100%;
}
.dndflow .selectall {
margin-top: 10px;
}
@media screen and (min-width: 768px) {
.dndflow {
flex-direction: row;
}
.dndflow aside {
width: 20%;
max-width: 250px;
}
}
.my_handle {
z-index: 99;
}
.nodeContent {
position: relative;
color: #222;
font-size: 12px;
line-height: 10px;
text-align: center;
background-color: #fff;
border: 1px solid #1a192b;
border-radius: 3px;
}
.nodeStyle {
width: 110px;
height: 30px;
line-height: 10px;
}
.updatenode__controls {
position: absolute;
top: 10px;
right: 10px;
z-index: 4;
padding: 16px;
font-size: 12px;
background-color: #fff;
}
.updatenode__controls label {
display: block;
}
.updatenode__bglabel {
margin-top: 10px;
}
.updatenode__checkboxwrapper {
display: flex;
align-items: center;
margin-top: 10px;
}
3、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) {
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) => {
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
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} /> */}
<input type='checkbox' checked={nodeInfo.isHidden} onChange={(evt) => setNodeHidden(evt.target.checked)} />
</div>
</div>
) : (
<></>
);
};
4、edgeContent.tsx
import React, { useState, useEffect } from 'react';
import { Input, Select, Switch } from 'antd';
const { Option } = Select;
export type edgeProps = {
info: any;
onChange: (val: any) => void;
};
export default ({ info, onChange }: edgeProps) => {
const [edgeInfo, setEdgeInfo] = useState<any>({});
const edgeTypes = [
{ label: '曲线', value: 'default' },
{ label: '直线', value: 'straight' },
{ label: '直角线', value: 'step' },
{ label: '圆滑直角线', value: 'smoothstep' },
];
useEffect(() => {
if (info.id) {
if (info.style) {
info.color = info.style.stroke;
}
if (!info.isHidden) {
info.isHidden = false;
}
setEdgeInfo(info);
}
}, [info.id]);
// 改变名称
const setNodeName = (value: string) => {
setEdgeInfo({
...edgeInfo,
label: value,
});
onChange({
...edgeInfo,
label: value,
});
};
// 改变颜色
const setNodeBg = (value: string) => {
setEdgeInfo({
...edgeInfo,
color: value,
});
onChange({
...edgeInfo,
color: value,
});
};
// 改变类型
const changeEdgeType = (value: string) => {
setEdgeInfo({
...edgeInfo,
type: value,
});
onChange({
...edgeInfo,
type: value,
});
};
// 是否隐藏
const setEdgeHidden = (value: boolean) => {
setEdgeInfo({
...edgeInfo,
isHidden: value,
});
onChange({
...edgeInfo,
isHidden: value,
});
};
return edgeInfo.id ? (
<div className="updatenode__controls">
<label>连接线名称:</label>
<Input
placeholder=""
value={edgeInfo.label}
onChange={(evt) => setNodeName(evt.target.value)}
/>
<label className="updatenode__bglabel">连接线颜色:</label>
<Input type="color" value={edgeInfo.color} onChange={(evt) => setNodeBg(evt.target.value)} />
<div className="updatenode__checkboxwrapper">
<label>连接线类型:</label>
<Select defaultValue="曲线 " value={edgeInfo.type} onChange={changeEdgeType}>
{edgeTypes.map((item) => (
<Option value={item.value} key={item.value}>
{item.label}
</Option>
))}
</Select>
</div>
<div className="updatenode__checkboxwrapper">
<label>是否隐藏:</label>
<Switch checked={edgeInfo.isHidden} onChange={setEdgeHidden} />
</div>
</div>
) : (
<></>
);
};
5、ResizableNodeSelected.tsx
import { memo } from 'react';
import { Handle, Position, NodeResizer } from 'reactflow';
const ResizableNodeSelected = ({ data, selected }) => {
return (
<>
<NodeResizer color="#F3A011" isVisible={selected} minWidth={80} minHeight={80} />
<div
style={{
// width: 60,
// height: 60,
padding: 10,
// display: "flex",
// justifyContent: "center",
// alignItems: "center",
// fontSize: 2
}}
>
{data.label}
</div>
<Handle style={{ opacity: 0 }} type="source" position={Position.Top} id='a' />
<Handle style={{ opacity: 0 }} type="source" position={Position.Right} id='b' />
<Handle style={{ opacity: 0 }} type="source" position={Position.Bottom} id='c' />
<Handle style={{ opacity: 0 }} type="source" position={Position.Left} id='d' />
</>
);
};
export default memo(ResizableNodeSelected);
6、data.js
export const nodes = [
{ "width": 80, "height": 80, "id": "13", "type": "ResizableNodeSelected", "position": { "x": 181.99158953145331, "y": 472.7199877834713 }, "data": { "label": "服务实例JVM堆大小", "id": "13", "nodeBg": "#F3A011", "isHidden": false }, "style": { "background": "#F3A011", "width": 80, "height": 80, "borderRadius": "100%", "color": "white", "fontSize": 2 }, "selected": false, "positionAbsolute": { "x": 181.99158953145331, "y": 472.7199877834713 }, "dragging": false, "hidden": false },
{ "width": 80, "height": 80, "id": "12", "type": "ResizableNodeSelected", "position": { "x": 458.51664737488375, "y": 497.7400344424826 }, "data": { "label": "服务实例JVM线程数", "id": "12", "nodeBg": "#F3A011", "isHidden": false }, "style": { "background": "#F3A011", "width": 80, "height": 80, "borderRadius": "100%", "color": "white", "fontSize": 2 }, "selected": false, "positionAbsolute": { "x": 458.51664737488375, "y": 497.7400344424826 }, "dragging": false, "hidden": false },
{ "width": 80, "height": 80, "id": "11", "type": "ResizableNodeSelected", "position": { "x": 456.86503312460417, "y": 278.4940093032253 }, "data": { "label": "应用服务平均响应时长", "id": "11", "nodeBg": "#F3A011", "isHidden": false }, "style": { "background": "#F3A011", "width": 80, "height": 80, "borderRadius": "100%", "color": "white", "fontSize": 2 }, "selected": false, "positionAbsolute": { "x": 456.86503312460417, "y": 278.4940093032253 }, "dragging": false, "hidden": false },
{ "width": 80, "height": 80, "id": "10", "type": "ResizableNodeSelected", "position": { "x": 188.70901987307826, "y": 316.5335073980088 }, "data": { "label": "存储I/O负载", "id": "10", "nodeBg": "#F3A011", "isHidden": false }, "style": { "background": "#F3A011", "width": 80, "height": 80, "borderRadius": "100%", "color": "white", "fontSize": 2 }, "selected": false, "positionAbsolute": { "x": 188.70901987307826, "y": 316.5335073980088 }, "dragging": false, "hidden": false },
{ "width": 80, "height": 80, "id": "9", "type": "ResizableNodeSelected", "position": { "x": 86.9908194969212, "y": 56.769326529302944 }, "data": { "label": "服务端点平均响应时长", "id": "9", "nodeBg": "#F3A011", "isHidden": false }, "style": { "background": "#F3A011", "width": 80, "height": 80, "borderRadius": "100%", "color": "white", "fontSize": 2 }, "selected": false, "positionAbsolute": { "x": 86.9908194969212, "y": 56.769326529302944 }, "dragging": false, "hidden": false },
{ "width": 80, "height": 80, "id": "8", "type": "ResizableNodeSelected", "position": { "x": 461.40008223448365, "y": 92.49854876752454 }, "data": { "label": "服务实例", "id": "8", "nodeBg": "#F3A011", "isHidden": false }, "style": { "background": "#F3A011", "width": 80, "height": 80, "borderRadius": "100%", "color": "white", "fontSize": 2 }, "selected": false, "positionAbsolute": { "x": 461.40008223448365, "y": 92.49854876752454 }, "dragging": false, "hidden": false },
{ "width": 80, "height": 80, "id": "7", "type": "ResizableNodeSelected", "position": { "x": -87.30759005365732, "y": 133.76253323256137 }, "data": { "label": "端点链路(动态模型)平均响应时长", "id": "7", "nodeBg": "#F3A011", "isHidden": false }, "style": { "background": "#F3A011", "width": 80, "height": 80, "borderRadius": "100%", "color": "white", "fontSize": 4 }, "selected": false, "dragging": false, "hidden": false, "positionAbsolute": { "x": -87.30759005365732, "y": 133.76253323256137 } },
{ "width": 80, "height": 80, "id": "6", "type": "ResizableNodeSelected", "position": { "x": -11.910201399135396, "y": 485.8445794117532 }, "data": { "label": "主机I/O负载", "id": "6", "nodeBg": "#F3A011", "isHidden": false }, "style": { "background": "#F3A011", "width": 80, "height": 80, "borderRadius": "100%", "color": "white", "fontSize": 4 }, "selected": false, "dragging": false, "hidden": false, "positionAbsolute": { "x": -11.910201399135396, "y": 485.8445794117532 }, "resizing": false },
{ "width": 80, "height": 80, "id": "5", "type": "ResizableNodeSelected", "position": { "x": -10, "y": 280.5 }, "data": { "label": "Mysql实例慢查询", "id": "5", "nodeBg": "#F3A011", "isHidden": false }, "style": { "background": "#F3A011", "width": 80, "height": 80, "borderRadius": "100%", "color": "white", "fontSize": 4 }, "selected": false, "dragging": false, "hidden": false, "positionAbsolute": { "x": -10, "y": 280.5 } },
{ "width": 80, "height": 80, "id": "4", "type": "ResizableNodeSelected", "position": { "x": -282.5, "y": 453 }, "data": { "label": "主机内存使用率", "id": "4", "nodeBg": "#F3A011", "isHidden": false }, "style": { "background": "#F3A011", "width": 80, "height": 80, "borderRadius": "100%", "color": "white", "fontSize": 4 }, "selected": false, "dragging": false, "hidden": false, "positionAbsolute": { "x": -282.5, "y": 453 } },
{ "width": 80, "height": 80, "id": "3", "type": "ResizableNodeSelected", "position": { "x": -275, "y": 283 }, "data": { "label": "主机CPU使用率", "id": "3", "nodeBg": "#F3A011", "isHidden": false }, "style": { "background": "#F3A011", "width": 80, "height": 80, "borderRadius": "100%", "color": "white", "fontSize": 4 }, "selected": false, "positionAbsolute": { "x": -275, "y": 283 }, "dragging": false, "hidden": false },
{ "width": 80, "height": 80, "id": "2", "type": "ResizableNodeSelected", "position": { "x": -271, "y": 133.5 }, "data": { "label": "消息中间件堆积数", "id": "2", "nodeBg": "#F3A011", "isHidden": false }, "style": { "background": "#F3A011", "width": 80, "height": 80, "borderRadius": "100%", "color": "white", "fontSize": 4 }, "selected": false, "dragging": false, "hidden": false, "positionAbsolute": { "x": -271, "y": 133.5 } }
];
export const edges = [
{ "style": { "stroke": "#116F97" }, "type": "smoothstep", "markerEnd": { "type": "arrowclosed", "color": "#116F97" }, "source": "4", "sourceHandle": "a", "target": "5", "targetHandle": "d", "id": "reactflow__edge-4a-5d", "selected": false, "hidden": true },
{ "style": { "stroke": "#116F97" }, "type": "straight", "markerEnd": { "type": "arrowclosed", "color": "#116F97" }, "source": "4", "sourceHandle": "b", "target": "5", "targetHandle": "c", "id": "reactflow__edge-4b-5c", "selected": false, "hidden": true },
{ "style": { "stroke": "#116F97" }, "type": "default", "markerEnd": { "type": "arrowclosed", "color": "#116F97" }, "source": "5", "sourceHandle": "d", "target": "4", "targetHandle": "a", "id": "reactflow__edge-5d-4a", "selected": false, "hidden": true },
{ "style": { "stroke": "#116F97" }, "type": "default", "markerEnd": { "type": "arrowclosed", "color": "#116F97" }, "source": "2", "sourceHandle": "b", "target": "7", "targetHandle": "d", "id": "reactflow__edge-2b-7d", "selected": false, "label": "模型间接关系", "hidden": false },
{ "style": { "stroke": "#116F97" }, "type": "default", "markerEnd": { "type": "arrowclosed", "color": "#116F97" }, "source": "5", "sourceHandle": "d", "target": "3", "targetHandle": "b", "id": "reactflow__edge-5d-3b", "selected": false, "label": "模型直接关系", "hidden": false },
{ "style": { "stroke": "#116F97" }, "type": "default", "markerEnd": { "type": "arrowclosed", "color": "#116F97" }, "source": "6", "sourceHandle": "a", "target": "5", "targetHandle": "c", "id": "reactflow__edge-6a-5c", "selected": false, "label": "模型直接关系", "hidden": false },
{ "style": { "stroke": "#116F97" }, "type": "smoothstep", "markerEnd": { "type": "arrowclosed", "color": "#116F97" }, "source": "5", "sourceHandle": "a", "target": "7", "targetHandle": "c", "id": "reactflow__edge-5a-7c", "selected": false, "label": "模型直接关系", "hidden": false },
{ "style": { "stroke": "#116F97" }, "type": "smoothstep", "markerEnd": { "type": "arrowclosed", "color": "#116F97" }, "source": "4", "sourceHandle": "a", "target": "5", "targetHandle": "c", "id": "reactflow__edge-4a-5c", "selected": false, "label": "模型直接关系", "hidden": false },
{ "style": { "stroke": "#116F97" }, "type": "step", "markerEnd": { "type": "arrowclosed", "color": "#116F97" }, "source": "5", "sourceHandle": "b", "target": "7", "targetHandle": "b", "id": "reactflow__edge-5b-7b", "selected": false, "label": "模型直接关系", "hidden": false },
{ "style": { "stroke": "#116F97" }, "type": "smoothstep", "markerEnd": { "type": "arrowclosed", "color": "#116F97" }, "source": "7", "sourceHandle": "b", "target": "9", "targetHandle": "d", "id": "reactflow__edge-7b-9d", "selected": false, "hidden": false },
{ "style": { "stroke": "#116F97" }, "type": "smoothstep", "markerEnd": { "type": "arrowclosed", "color": "#116F97" }, "source": "10", "sourceHandle": "d", "target": "5", "targetHandle": "b", "id": "reactflow__edge-10d-5b", "selected": false, "label": "模型直接关系", "hidden": false },
{ "style": { "stroke": "#116F97" }, "type": "smoothstep", "markerEnd": { "type": "arrowclosed", "color": "#116F97" }, "source": "8", "sourceHandle": "c", "target": "11", "targetHandle": "a", "id": "reactflow__edge-8c-11a", "selected": false, "label": "模型直接关系", "hidden": false },
{ "style": { "stroke": "#116F97" }, "type": "straight", "markerEnd": { "type": "arrowclosed", "color": "#116F97" }, "source": "12", "sourceHandle": "a", "target": "11", "targetHandle": "c", "id": "reactflow__edge-12a-11c", "selected": false, "label": "模型直接关系", "hidden": false },
{ "style": { "stroke": "#116F97" }, "type": "smoothstep", "markerEnd": { "type": "arrowclosed", "color": "#116F97" }, "source": "13", "sourceHandle": "b", "target": "11", "targetHandle": "d", "id": "reactflow__edge-13b-11d", "selected": false, "label": "模型直接关系", "hidden": false },
{ "style": { "stroke": "#116F97" }, "type": "smoothstep", "markerEnd": { "type": "arrowclosed", "color": "#116F97" }, "source": "9", "sourceHandle": "b", "target": "11", "targetHandle": "d", "id": "reactflow__edge-9b-11d", "selected": false, "label": "模型直接关系", "hidden": false }
]
// "viewport": { "x": 654.5507940552135, "y": -54.945769269730704, "zoom": 1.6908994642667994 } }
7、package.json
{
"name": "my-react-flow-app",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"lint": "eslint src --ext js,jsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
},
"dependencies": {
"antd": "^5.7.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"reactflow": "^11.7.4"
},
"devDependencies": {
"@types/react": "^18.2.14",
"@types/react-dom": "^18.2.6",
"@vitejs/plugin-react": "^4.0.1",
"eslint": "^8.44.0",
"eslint-plugin-react": "^7.32.2",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.1",
"vite": "^4.4.0"
}
}