React Flow

// 创建项目
npm create vite@latest my-react-flow-app -- --template react
// 安装插件
npm install reactflow
npm install antd
// 运行项目
npm run dev

https://reactflow.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"
  }
}

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是React Flow的一个基本示例,该示例使用自定义节点和连线样式,以及可拖动的节点和连线。你可以根据自己的需要进行修改和扩展。 ```jsx import React, { useState } from 'react'; import ReactFlow, { ReactFlowProvider, addEdge, removeElements, Controls, Background, } from 'react-flow-renderer'; const initialElements = [ { id: '1', type: 'input', data: { label: 'Input Node' }, position: { x: 250, y: 5 } }, { id: '2', type: 'default', data: { label: 'Node 2' }, position: { x: 100, y: 100 } }, { id: '3', type: 'default', data: { label: 'Node 3' }, position: { x: 400, y: 100 } }, { id: '4', type: 'output', data: { label: 'Output Node' }, position: { x: 250, y: 200 } }, { id: 'e1-2', source: '1', target: '2', animated: true }, { id: 'e2-3', source: '2', target: '3', animated: true }, { id: 'e3-4', source: '3', target: '4', animated: true }, ]; const nodeTypes = { input: ({ data }) => ( <div className="node-input"> {data.label} </div> ), default: ({ data }) => ( <div className="node-default"> {data.label} </div> ), output: ({ data }) => ( <div className="node-output"> {data.label} </div> ), }; const App = () => { const [elements, setElements] = useState(initialElements); const onElementsRemove = (elementsToRemove) => setElements((els) => removeElements(elementsToRemove, els)); const onConnect = (params) => setElements((els) => addEdge(params, els)); return ( <ReactFlowProvider> <div className="react-flow-wrapper"> <ReactFlow elements={elements} onElementsRemove={onElementsRemove} onConnect={onConnect} nodeTypes={nodeTypes} snapToGrid={true} snapGrid={[15, 15]} > <Controls /> <Background color="#888" gap={16} /> </ReactFlow> </div> </ReactFlowProvider> ); }; export default App; ``` 在上面的示例中,我们定义了一个最初包含四个节点和三个连接的元素数组。我们还定义了三种节点类型,分别是输入节点、默认节点和输出节点。 在ReactFlow组件中,我们将元素和节点类型传递给它,并指定了一些其他属性,如onElementsRemove和onConnect,用于删除和添加元素,以及snapToGrid和snapGrid,用于将节点对齐到网格上。 最后,我们使用ReactFlowProvider包装ReactFlow组件,以便在整个应用程序中共享ReactFlow的状态和功能。 如果你想要一个更复杂的示例,可以访问React Flow的官方网站,那里有许多其他示例和文档。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值