使用react-flow制作流程图

发布时间 2023-05-25 11:30:24作者: Qing`ing
1.react-flow

 react-flow是一个用于构建基于节点的应用程序的库。这些可以是简单的静态图或复杂的基于节点的编辑器。同时react-flow支持自定义节点类型和边线类型,并且它附带一些组件,可以查看缩略图的Mini Map和悬浮控制器Controls.

2.react-flow安装  

  npm install react-flow-renderer    # npm
  yarn add react-flow-renderer       # Yarn
3.react-flow基本使用  

  1、每个节点固定格式 里面添加内容

  代码

      index.tsx

import React from 'react';
import ReactFlow, {
  addEdge,
  MiniMap,
  Controls,
  Background,
  useNodesState,
  useEdgesState,
} from 'react-flow-renderer';

import { nodes as initialNodes, edges as initialEdges } from './initial-elements';

const OverviewFlow = () => {
  const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
  const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
  const onConnect = (params) => setEdges((eds) => addEdge(params, eds));

  return (
    <ReactFlow
      nodes={nodes} // 节点
      edges={edges} // 连接线
      onNodesChange={onNodesChange} // 节点拖拽等改变
      onEdgesChange={onEdgesChange} // 连接线拖拽等改变
      onConnect={onConnect} // 节点直接连接
      fitView // 渲染节点数据
      attributionPosition="top-right" // react-flow的位置,类似水印,可以通过css隐藏
    >
      // 背景图 可以配置颜色 方格宽度
      <Background color="#aaa" gap={16} />
    </ReactFlow>
  );
};

export default OverviewFlow;

initial-elements.ts节点与连接线数据

import { MarkerType } from 'react-flow-renderer';

export const nodes = [
  {
    id: '1', // id必须
    type: 'input', // 类型: input开始  default默认  output结束 区别在于连接点不一样
    data: { // 额外的数据
      label: ( // 节点名称
        <>
          Welcome to <strong>React Flow!</strong>
        </>
      ),
      // value: 5, .... // 可以将其他数据放入
    },
    position: { x: 250, y: 0 }, // 节点位置
  },
  {
    id: '2',
    data: {
      label: (
        <>
          This is a <strong>default node</strong>
        </>
      ),
    },
    position: { x: 100, y: 100 },
  },
  {
    id: '3',
    data: {
      label: (
        <>
          This one has a <strong>custom style</strong>
        </>
      ),
    },
    position: { x: 400, y: 100 },
    style: {
      background: '#D6D5E6',
      color: '#333',
      border: '1px solid #222138',
      width: 180,
    },
  },
  {
    id: '4',
    position: { x: 250, y: 200 },
    data: {
      label: 'Another default node',
    },
  },
  {
    id: '5',
    data: {
      label: 'Node id: 5',
    },
    position: { x: 250, y: 325 },
  },
  {
    id: '6',
    type: 'output',
    data: {
      label: (
        <>
          An <strong>output node</strong>
        </>
      ),
    },
    position: { x: 100, y: 480 },
  },
  {
    id: '7',
    type: 'output',
    data: { label: 'Another output node' },
    position: { x: 400, y: 450 },
  },
];

export const edges = [
  { id: 'e1-2', source: '1', target: '2', label: 'this is an edge label' },
  { id: 'e1-3', source: '1', target: '3' },
  {
    id: 'e3-4', // id必须
    source: '3', // 连接线起始节点id
    target: '4', // 连接线结束节点id
    animated: true, // 连接线是否有动画
    label: 'animated edge', // 连接线名称
  },
  {
    id: 'e4-5',
    source: '4',
    target: '5',
    label: 'edge with arrow head',
    markerEnd: { // 连接线尾部的箭头
      type: MarkerType.ArrowClosed,
    },
  },
  {
    id: 'e5-6',
    source: '5',
    target: '6',
    type: 'smoothstep', // 连接线类型 default straight step smoothstep
    label: 'smooth step edge',
  },
  {
    id: 'e5-7',
    source: '5',
    target: '7',
    type: 'step',
    style: { stroke: '#f6ab6c' }, // 连接线颜色
    label: 'a step edge',
    animated: true,
    labelStyle: { fill: '#f6ab6c', fontWeight: 700 }, // 连接线名称样式
  },
];

  效果图

 

2、自定义每个节点中的内容和样式 以及连接点

  这个是静态的 展示流程图 想拖动节点 加上1里面的 onNodesChange... 的参数即可

index.tsx

import React, {useEffect} from 'react';

import ReactFlow, {
    useNodesState,
    useEdgesState,
} from 'react-flow-renderer';

import {nodes as initialNodes, edges as initialEdges} from './initial-elements';
import CustomNode from './ResizableNode'; //自定义的渲染每个节点的代码

const nodeTypes = {
    custom: CustomNode, //自定义的内容
};
const OverviewFlow = ({resizeFlag}: any) => {
    const [nodes, setNodes] = useNodesState(initialNodes);
    const [edges] = useEdgesState(initialEdges);

    useEffect(() => {
        setNodes([]);
        setTimeout(() => {
            setNodes(initialNodes);
        }, 50);
    }, [resizeFlag]);

    if (!nodes?.length) {
        return null;
    }

    return (
        <ReactFlow
            nodes={nodes} // 节点
            edges={edges} // 连接线
            panOnDrag={false}
            zoomOnDoubleClick={false}
            zoomOnPinch={false}
            zoomOnScroll={false}
            panOnScroll={false}
            fitView // 渲染节点数据
            nodeTypes={nodeTypes}
            attributionPosition="top-left" // react-flow的位置,类似水印,可以通过css隐藏 =》 .react-flow__attribution.left {display: none};
        >
            {/* <Background color="#aaa" gap={16} /> */}
        </ReactFlow>
    );
};

export default OverviewFlow;

 initial-elements.ts

import {MarkerType, Position} from 'react-flow-renderer';

const styles = {
    color: '#333',
    border: '1px solid #4E8FF0',
    borderRadius: '5px',
    background: 'white',

};
//因为数据太多删除了几个 不过格式都是这样写 export const nodes
= [ { id: '0', type: 'custom',//有input,output,default三种,input只有一个输出,output只有一个输入,default输入输出各有一个 或者自定义的 data: { label: '', }, position: {x: -20, y: 40}, // 节点位置 style: { width: 1550, height: 500, border: '1px solid #91caff', borderRadius: '15px', color: '#4585F2', background: '#E2E6F3', zIndex: -2, }, }, { id: '1', // id必须 type: 'custom', // 类型: input开始 default默认 output结束 区别在于连接点不一样 data: { // 额外的数据 label: '任务1', // value: 1 // .... // 可以将其他数据放入 }, position: {x: 200, y: 70}, // 节点位置 style: { width: 200, height: 150, ...styles, }, }, { id: '2', type: 'custom', data: { label: '任务2', }, position: {x: 450, y: 70}, style: { width: 200, height: 150, }, }, { id: '3', type: 'custom', data: { label: ( '任务3' ), }, position: {x: 700, y: 70}, style: { width: 200, height: 150, ...styles, }, } ]; export const edges = [ { id: '1-2', source: '1', target: '2', markerEnd: { // 连接线尾部的箭头 type: MarkerType.ArrowClosed, color: '#4E8FF0', }, style: {stroke: '#4E8FF0'}, // 连接线颜色 labelStyle: {fill: '#4E8FF0', fontWeight: 700}, // 连接线名称样式 }, { id: '2-3', // id必须 source: '2', // 连接线起始节点id target: '3', // 连接线结束节点id markerEnd: { // 连接线尾部的箭头 type: MarkerType.ArrowClosed, color: '#4E8FF0', }, style: {stroke: '#4E8FF0'}, // 连接线颜色 labelStyle: {fill: '#4E8FF0', fontWeight: 700}, // 连接线名称样式 }, { id: '3-4', source: '3', target: '4', style: {stroke: '#4E8FF0'}, // 连接线颜色 labelStyle: {fill: '#4E8FF0', fontWeight: 700}, // 连接线名称样式 markerEnd: { // 连接线尾部的箭头 type: MarkerType.ArrowClosed, color: '#4E8FF0', }, } ];

 

ResizableNode.tsx
import React, {memo} from 'react';
import {Handle, Position} from 'react-flow-renderer';
import className from './home.module.scss';
import newTask from '@/static/newTask.png';
import Operator from '@/static/OperatorConfig.png';
import DeployTask from '@/static/DeploymentTask.png';
import TaskReview from '@/static/TaskReview.png';
import TaskLaunch from '@/static/TaskLaunch.png';
import {Button} from 'antd';

const datadevelopment = [
    {
        id: '0',
    },
    {
        src: newTask,
        id: '1',
        button: '快速开始',
        url: '',
        width: '50px',
        height: '50px',
    },
    {
        src: Operator,
        id: '11',
        button: '配置说明',
        link: '',
        width: '45px',
        height: '45px',
    },
    {
        src: DeployTask,
        id: '3',
        title: '任务基本信息',
        width: '55px',
        height: '55px',
    },

];

export default memo(({data, id, isConnectable}: any) => {

    // console.log(1, data);
    const position = (sum: any) => {
        switch (sum) {
            case '6':
                return Position.Right;
            case '9':
                return Position.Top;
            default:
                return Position.Left;
        }
    };

    const pageButton = (item: any) => {
        if (item.link) {
            return (
                <Button
                    target='_blank'
                    type='link'
                    htmlType='button'
                    href={item.link}
                > {item.button}
                </Button>
            );
        } else {
            return (
                <Button
                    style={{
                        background: 'linear-gradient(90deg,#2468E8,#2C61E4,#4148D0,#5127B8)',
                        border: 'none',
                    }}
                    onClick={e => {
                        e.stopPropagation();
                        window.location.hash = item.url;
                    }}
                    type='primary'
                > {item.button}
                </Button>
            );
        }
    };

    return (
        <div className={className.ResicabelNode}>
            <Handle
                style={{visibility: 'hidden'}}
                type="target"
                position={position(id)}
                isConnectable={isConnectable}
            />
            {
                datadevelopment.filter((item: any) => {
                    return item.id === id;
                }).map((item: any) => {
                    return (
                        +item.id < 12
                            ? <div
                                key={item.id}
                                className={className.nodeContent}
                                style={data.style}
                            >
                                {item.src ? <img style={{width: item.width, height: item.height}} src={item.src} /> : null}
                                <div className={className.nodeRightbox}>
                                    <p className={className.nodelabel}>{data.label}</p>
                                    {item.button
                                        ? pageButton(item)
                                        : <span className={className.nodelabelTitle}>{item.title}</span>}
                                </div>
                            </div>
                            : <div
                                key={item.id}
                                className={className.dataDistribution}
                                style={data.style}
                            >
                                {
                                    item.order
                                        ? <p className={className.circle}>
                                            {item.order}
                                        </p> : null
                                }
                                <p className={className.nodelabel}>{data.label}</p>
                                {
                                    item.button ? pageButton(item) : null
                                }
                            </div>
                    );
                })
            }
            <Handle
                style={{visibility: 'hidden'}}
                type='source'
                position={id === '11'
                || id === '5' ? Position.Bottom : Position.Right}
                id='a'
                className='my_handle'
                isConnectable={isConnectable}
            />
        </div>
    );
});

 

 效果图

 

 收集的一些关于 react-flow 参数讲解链接以及官网地址

  https://www.5axxw.com/wiki/content/obkffc

   https://reactflow.dev/