// 创建项目
npm create vite@latest my-react-flow-app -- --template react
// 安装插件
npm install reactflow
npm install antd
// 运行项目
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' }}><ReactFlownodes={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><Inputplaceholder=""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><Inputplaceholder=""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} /><divstyle={{// 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"}
}