react實(shí)現(xiàn)繪制簡(jiǎn)單流程圖(AntV/X6的平替)

一個(gè)輕量級(jí)的庫(kù)react-flow-renderer,領(lǐng)導(dǎo)讓做一個(gè)拖拉拽的流程圖,實(shí)現(xiàn)一個(gè)產(chǎn)品的制作流程。
樣子大致如下:

image.png

一開(kāi)始想用antv/x6,但是之前沒(méi)用過(guò),看了半天發(fā)現(xiàn)展示數(shù)據(jù)容易,想要實(shí)現(xiàn)節(jié)點(diǎn)的增刪,拖拉拽,節(jié)點(diǎn)的先后順序更改交互實(shí)在是無(wú)力。
然后發(fā)現(xiàn)了react-flow-renderer,詳情可以查看:
[https://github.com/wbkd/react-flow]
[https://reactflow.dev/docs/introduction/]

實(shí)現(xiàn)的效果如下:


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([])                                                  //節(jié)點(diǎn)數(shù)組

    const [edgesData, setEdgesData] = useState([])                                                  //邊數(shù)組

    const [choosedData, setChoosedData] = useState<objectStatment>({})   //選中的節(jié)點(diǎn)信息

    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) {
            //如果是單個(gè)添加
            let index = nodes?.length
            //更新節(jié)點(diǎn)數(shù)組
            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單個(gè)", temp1)
            setNodes(temp1)
            //重新渲染flow
            setIsRefresh(true);
        } else {
            let temp1 = nodes
            //多個(gè)一次性添加
            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多個(gè)", temp1)
            setNodes(temp1)
            //重新渲染flow
            setIsRefresh(true);
        }
    }

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

    useEffect(() => {
        //回傳節(jié)點(diǎn),路徑信息信息
        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]
    );
    //連接節(jié)點(diǎn)時(shí)觸發(fā)
    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)
    }

    //刪除節(jié)點(diǎn)
    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>被選中的節(jié)點(diǎn)為{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('請(qǐng)先選中工序')
                    }
                }}>編輯工序參數(shù)</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
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容