react实现绘制简单流程图(AntV/X6的平替)

一个轻量级的库react-flow-renderer,领导让做一个拖拉拽的流程图,实现一个产品的制作流程。
样子大致如下:

image.png

一开始想用antv/x6,但是之前没用过,看了半天发现展示数据容易,想要实现节点的增删,拖拉拽,节点的先后顺序更改交互实在是无力。
然后发现了react-flow-renderer,详情可以查看:
[https://github.com/wbkd/react-flow]
[https://reactflow.dev/docs/introduction/]

实现的效果如下:


image.png

下面上代码:(reacthook+ts)

/**
 * @description react-flow-renderer
 * @author njj
 */
import ProForm, {
    ProFormInstance, ProFormRadio, ProFormSelect, ProFormText, ProFormTextArea,
    ProFormSwitch, ProFormUploadButton
} from "@ant-design/pro-form";
import { Button, Card, Divider, message, Tag, Input, Tooltip, Upload, Modal } from "antd";
import { useRef, useState, useEffect, useCallback } from "react";
import { ProductionPlanList } from '@/defind/plan';
import { getUrlParmas } from '@/common/utils/util';
import '../index.less';
import ReactFlow, {
    MiniMap, Controls, applyNodeChanges,
    addEdge, applyEdgeChanges,
} from 'react-flow-renderer';
import Item from "antd/lib/list/Item";

import WorkProcedureSelectModal from '@/pages/Plan/ProductionPlan/WorkProcedureSelectModal/index'
import { nodeObject } from '@/defind/plan'
import { history } from "umi";


export type ProductionFlowChartProps = {
    onNodesCallback: (a: any, b: any) => void;
    onNodeClickCallback:(object:objectStatment)=>void;
}

type objectStatment = {
    id?: string|number;
    data?: {
        label: string | undefined;
    };
    position?: {
        x: number;
        y: number;
    };
    sourcePosition?: string;
    targetPosition?: string;
    connectable?: boolean;
}

const ProductionFlowChart: React.FC<ProductionFlowChartProps> = (props) => {

    const [isRefresh, setIsRefresh] = useState(false);

    const [nodesData, setNodesData] = useState([])                                                  //节点数组

    const [edgesData, setEdgesData] = useState([])                                                  //边数组

    const [choosedData, setChoosedData] = useState<objectStatment>({})   //选中的节点信息

    const [inputInfo, setInputInfo] = useState()   //Modal里输入的值
    const [modalVisible, setModalVisible] = useState(false)  //Modal是否显示

    //////
    const [nodes, setNodes] = useState<any[]|undefined>([]);
    const [edges, setEdges] = useState([]);

    const addNewFlow = (dataSource: any|undefined) => {
        if (dataSource.length == 1) {
            //如果是单个添加
            let index = nodes?.length
            //更新节点数组
            let object1:objectStatment = {
                id: index + '',
                data: {
                    label: dataSource[0].name,
                    // id: dataSource[0].id,
                    ...dataSource[0]            //非必要好像,是否只要保存id和name就行?
                },
                // data: dataSource[0],
                position: { x: 40 + index! * 250, y: 50 },
                sourcePosition: 'right',
                targetPosition: 'left',
                connectable: true
            }
            let temp1 = nodes
            temp1!.push(object1)
            // console.log("======最终的nodes单个", temp1)
            setNodes(temp1)
            //重新渲染flow
            setIsRefresh(true);
        } else {
            let temp1 = nodes
            //多个一次性添加
            dataSource?.map((item:object, index:number) => {
                let index0 = temp1?.length
                let object1 = {
                    id: index0 + '',
                    data: {
                        label: dataSource[index].name,
                        // id:dataSource[index].id
                        ...dataSource[index]            //非必要好像,是否只要保存id和name就行?
                    },
                    position: { x: 40 + index0! * 250, y: 50 },
                    sourcePosition: 'right',
                    targetPosition: 'left',
                    connectable: true
                }
                temp1!.push(object1)
            })
            // console.log("======最终的nodes多个", temp1)
            setNodes(temp1)
            //重新渲染flow
            setIsRefresh(true);
        }
    }

    useEffect(() => {
        isRefresh && setTimeout(() => setIsRefresh(false));
    }, [isRefresh]);

    useEffect(() => {
        //回传节点,路径信息信息
        props.onNodesCallback(nodes, edges)
    }, [nodes, edges]);


    const onNodesChange = useCallback(
        (changes) => {
            //@ts-ignore
            setNodes((nds) => applyNodeChanges(changes, nds))
        },
        [setNodes]
    );
    const onEdgesChange = useCallback(
        (changes) => {
            //@ts-ignore
            setEdges((eds) => applyEdgeChanges(changes, eds))
        },
        [setEdges]
    );
    //连接节点时触发
    const onConnect = useCallback(
        (connection) => { 
            //@ts-ignore    
            setEdges((eds) => addEdge(connection, eds)) 
        },
        [setEdges]
    );

    const onNodeClick=(event: React.MouseEvent, node: objectStatment)=>{
        console.log("=====onNodeClick",event,node)
        setChoosedData(node)
        props.onNodeClickCallback(node)
    }

    //删除节点
    const deleteNode=()=>{
        console.log("========nodes",nodes)
        console.log("========choosedData",choosedData)
        let temp = nodes
        temp?.map((item,index)=>{
            if(item.id == choosedData.id){
                temp?.splice(index,1)
                return
            }
        })
        console.log("========temp",temp)
        setNodes(temp)
        //重新渲染flow
        setIsRefresh(true);

    }

    return (
        <div style={{ display: 'flex' }}>

            <div style={{ width: 1200, height: 300 }}>
                {!isRefresh &&
                    <ReactFlow
                        nodes={nodes}
                        edges={edges}
                        onNodesChange={onNodesChange}
                        onEdgesChange={onEdgesChange}
                        onConnect={onConnect}
                        fitView
                        className="react-flow__edge"
                        //@ts-ignore
                        onNodeClick={onNodeClick}
                        
                    />
                }
            </div>


            <div className='buttonBlocks'>
                {/* <div>被选中的节点为{choosedData?.data?.label}</div> */}
                <Button type="primary" onClick={() => {
                    setModalVisible(true)
                    setInputInfo(undefined)
                }}>添加工序</Button>
                <Button onClick={() => { 
                    deleteNode()
                 }}>删除工序</Button>
                <Button onClick={()=>{
                    if(JSON.stringify(choosedData) !== "{}"){
                        //@ts-ignore
                        history.push('/plan/productionPlan/add?id='+ choosedData?.data?.id)
                    }else{
                        message.info('请先选中工序')
                    }
                }}>编辑工序参数</Button>
            </div>



            <div>
                <WorkProcedureSelectModal
                    width={800}
                    visible={modalVisible}
                    onSubmit={(selectedRowKeys, dataSource:nodeObject[]|undefined) => {
                        // console.log("====selectedRowKeys, dataSource", selectedRowKeys, dataSource)
                        setModalVisible(false)
                        addNewFlow(dataSource)
                    }}
                    onCancel={() => { setModalVisible(false) }}
                />
            </div>

        </div>
    )
}
export default ProductionFlowChart
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容