• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

react-flow实现dag工作流

武飞扬头像
樊庆威
帮助1

1. 官方文档

Introduction to React Flow

2.效果

学新通

学新通

学新通

3. 代码

index.jsx

  1.  
    import { useState, useCallback, useEffect } from 'react';
  2.  
    import ReactFlow, {
  3.  
    Controls,
  4.  
    Background,
  5.  
    applyNodeChanges,
  6.  
    applyEdgeChanges,
  7.  
    addEdge,
  8.  
    ReactFlowProvider,
  9.  
    useReactFlow
  10.  
    } from 'reactflow';
  11.  
    import 'reactflow/dist/style.css';
  12.  
    import { TaskNode } from './taskNode';
  13.  
    import { Button, Space, message } from 'antd';
  14.  
    import { saveMonthTaskInfo, getMonthTaskInfo } from '@/api/modules/month_task_dag';
  15.  
    import AddNode from './addNodeForm';
  16.  
    import { store } from "@/redux";
  17.  
     
  18.  
    // const initialNodes = [
  19.  
    // {
  20.  
    // id: 'qb_value_cal',
  21.  
    // data: { label: 'XXXX', task_status: 'success' },
  22.  
    // position: { x: 196.99037808813034, y: -16.15861389212995 },
  23.  
    // type: 'taskNode'
  24.  
    // },
  25.  
    // {
  26.  
    // id: 'platform_acct_price',
  27.  
    // data: { label: 'xxxx', task_status: 'failure' },
  28.  
    // position: { x: 9.004772768299915, y: 71.58667201646344 },
  29.  
    // type: 'taskNode'
  30.  
    // },
  31.  
    // {
  32.  
    // id: 't_syn_settlement',
  33.  
    // data: { label: 'xxxx', task_status: 'failure' },
  34.  
    // position: { x: 198.74746953729812, y: 150.0741352155024 },
  35.  
    // type: 'taskNode'
  36.  
    // },
  37.  
    // {
  38.  
    // id: 'midas_sett',
  39.  
    // data: { label: 'xxxx', task_status: 'running' },
  40.  
    // position: { x: -145.96731433103253, y: -13.672818469718123 },
  41.  
    // type: 'taskNode'
  42.  
    // },
  43.  
    // {
  44.  
    // id: 'midas_sett_consume',
  45.  
    // data: { label: 'xxxx', task_status: 'failure' },
  46.  
    // position: { x: -123.70028149684317, y: 184.60354995136538 },
  47.  
    // type: 'taskNode'
  48.  
    // },
  49.  
    // {
  50.  
    // id: 'dyb_value_cal',
  51.  
    // data: { label: 'xxxx', task_status: 'running' },
  52.  
    // position: { x: 16.548987140345645, y: 268.97166003745264 },
  53.  
    // type: 'taskNode'
  54.  
    // },
  55.  
    // {
  56.  
    // id: 'prepay_dyb',
  57.  
    // data: { label: 'xxxxx', task_status: 'failure' },
  58.  
    // position: { x: 114.87245269459243, y: 393.62735077521256 },
  59.  
    // type: 'taskNode'
  60.  
    // },
  61.  
    // {
  62.  
    // id: 'prepay_consume',
  63.  
    // data: { label: 'xxxxx', task_status: 'failure' },
  64.  
    // position: { x: 114.87245269459243, y: 393.62735077521256 },
  65.  
    // type: 'taskNode'
  66.  
    // },
  67.  
    // {
  68.  
    // id: 'midas_dyb_price',
  69.  
    // data: { label: 'xxxxxxx', task_status: 'failure' },
  70.  
    // position: { x: 114.87245269459243, y: 393.62735077521256 },
  71.  
    // type: 'taskNode'
  72.  
    // },
  73.  
    // {
  74.  
    // id: 'midas_split',
  75.  
    // data: { label: 'xxxxxx', task_status: 'running' },
  76.  
    // position: { x: -107.1137248521369, y: 391.20277175226386 },
  77.  
    // type: 'taskNode'
  78.  
    // },
  79.  
    // {
  80.  
    // id: 'qb_value_adjust',
  81.  
    // data: { label: 'xxxxxxx', task_status: 'failure' },
  82.  
    // position: { x: 14.93930727104339, y: 499.4122832380474 },
  83.  
    // type: 'taskNode'
  84.  
    // },
  85.  
    // {
  86.  
    // id: 'midas_bill',
  87.  
    // data: { label: 'xxxxxxx', task_status: 'failure' },
  88.  
    // position: { x: 14.93930727104339, y: 499.4122832380474 },
  89.  
    // type: 'taskNode'
  90.  
    // }
  91.  
    // ];
  92.  
    // const initialEdges = [
  93.  
    // {
  94.  
    // id: "reactflow__edge-dyb_value_calb-p",
  95.  
    // source: "dyb_value_cal",
  96.  
    // sourcehAndle: "b",
  97.  
    // target: "prepay_dyb",
  98.  
    // targethAndle: "a"
  99.  
    // },
  100.  
    // {
  101.  
    // id: "reactflow__edge-midas_dyb_priceb",
  102.  
    // source: "midas_dyb_price",
  103.  
    // sourcehAndle: "b",
  104.  
    // target: "midas_split",
  105.  
    // targethAndle: "a"
  106.  
    // },
  107.  
    // {
  108.  
    // id: "reactflow__edge-midas_settb-plat",
  109.  
    // source: "midas_sett",
  110.  
    // sourcehAndle: "b",
  111.  
    // target: "platform_acct_price",
  112.  
    // targethAndle: "a"
  113.  
    // },
  114.  
    // {
  115.  
    // id: "reactflow__edge-midas_sett_consu",
  116.  
    // source: "midas_sett_consume",
  117.  
    // sourcehAndle: "b",
  118.  
    // target: "midas_dyb_price",
  119.  
    // targethAndle: "a"
  120.  
    // },
  121.  
    // {
  122.  
    // id: "reactflow__edge-platform_acct_pr",
  123.  
    // source: "platform_acct_price",
  124.  
    // sourcehAndle: "b",
  125.  
    // target: "dyb_value_cal",
  126.  
    // targethAndle: "a"
  127.  
    // },
  128.  
    // {
  129.  
    // id: "reactflow__edge-platform_acct_priceb-midas_sett_consumea",
  130.  
    // source: "platform_acct_price",
  131.  
    // sourcehAndle: "b",
  132.  
    // target: "midas_sett_consume",
  133.  
    // targethAndle: "a"
  134.  
    // },
  135.  
    // {
  136.  
    // id: "reactflow__edge-prepay_dybb-prep",
  137.  
    // source: "prepay_dyb",
  138.  
    // sourcehAndle: "b",
  139.  
    // target: "prepay_consume",
  140.  
    // targethAndle: "a"
  141.  
    // },
  142.  
    // {
  143.  
    // id: "reactflow__edge-qb_value_calb-qb",
  144.  
    // source: "qb_value_cal",
  145.  
    // sourcehAndle: "b",
  146.  
    // target: "qb_value_adjust",
  147.  
    // targethAndle: "a"
  148.  
    // },
  149.  
    // {
  150.  
    // id: "reactflow__edge-qb_value_calb-t_",
  151.  
    // source: "qb_value_cal",
  152.  
    // sourcehAndle: "b",
  153.  
    // target: "t_syn_settlement",
  154.  
    // targethAndle: "a"
  155.  
    // },
  156.  
    // {
  157.  
    // id: "reactflow__edge-t_syn_settlement",
  158.  
    // source: "t_syn_settlement",
  159.  
    // sourcehAndle: "b",
  160.  
    // target: "dyb_value_cal",
  161.  
    // targethAndle: "a"
  162.  
    // }
  163.  
    // ];
  164.  
    const nodeTypes = { taskNode: TaskNode };
  165.  
    const username = store.getState().global.userName
  166.  
    function Flow() {
  167.  
    const [nodes, setNodes] = useState([]);
  168.  
    const [edges, setEdges] = useState([]);
  169.  
    const handleGetNodesInfo = async () => {
  170.  
    const {data} = await getMonthTaskInfo({"module_view":"toc_revenue"})
  171.  
    console.log(data);
  172.  
    setNodes(data["initialNodes"]);
  173.  
    setEdges(data["initialEdges"]);
  174.  
    };
  175.  
    // 初始化是获取节点和边信息并渲染
  176.  
    useEffect(() => {
  177.  
    handleGetNodesInfo();
  178.  
    }, []);
  179.  
    // 获取flow实例
  180.  
    const reactFlowInstance = useReactFlow();
  181.  
    // 保存节点和边信息
  182.  
    const handleSaveNodes = async () => {
  183.  
    console.log(reactFlowInstance.getNodes())
  184.  
    console.log(reactFlowInstance.getEdges())
  185.  
    const {data} = await saveMonthTaskInfo({"initialNodes":reactFlowInstance.getNodes(), "initialEdges":reactFlowInstance.getEdges(), "module_view":"toc_revenue"})
  186.  
    console.log(data)
  187.  
    if (data=="success"){
  188.  
    message.success(data);
  189.  
    }else{
  190.  
    message.error(data);
  191.  
    }
  192.  
    };
  193.  
    // 添加节点
  194.  
    const handleAddNode = (newNode) => {
  195.  
    // setNodes([...nodes, newNode]); // 受控组件的添加方式
  196.  
    reactFlowInstance.addNodes(newNode) // 非受控组件的添加方式
  197.  
    };
  198.  
    const onNodesChange = useCallback(
  199.  
    // 这段代码使用了React的useCallback hook来创建一个函数onNodesChange,
  200.  
    // 其中用到了applyNodeChanges函数和setNodes函数。onNodesChange函数接收一个名为changes的参数,
  201.  
    // 这个参数表示节点的变化。applyNodeChanges函数根据这个变化来更新节点信息,并返回新的节点信息。
  202.  
    // setNodes函数则用来更新组件的状态,将新的节点信息更新到组件中。
  203.  
    // 最后,上述代码中的useCallback hook的第二个参数是一个依赖数组,表示只有当setNodes函数发生变化时,
  204.  
    // 才需要重新创建onNodesChange函数。这样可以避免不必要的重复渲染。
  205.  
    // applyNodeChanges
  206.  
    // 描述:返回具有应用更改的节点数组
  207.  
    // 类型:(changes: NodeChange[], nodes: Node[]) => Node[]
  208.  
    (changes) => setNodes((nds) => applyNodeChanges(changes, nds)),
  209.  
    []
  210.  
    );
  211.  
    const onEdgesChange = useCallback((changes) => setEdges((eds) => applyEdgeChanges(changes, eds)),[]);
  212.  
     
  213.  
    const onConnect = useCallback((params) => setEdges((eds) => addEdge(params, eds)), []);
  214.  
    return (
  215.  
    <>
  216.  
    <Space direction="vertical">
  217.  
    <Space wrap>
  218.  
    <AddNode username={username} handleAddNode={handleAddNode}></AddNode>
  219.  
    <Button size="large" onClick={handleSaveNodes} >保存</Button>
  220.  
    </Space>
  221.  
    </Space>
  222.  
    <div style={{ height: '100%' }}>
  223.  
    <ReactFlow
  224.  
    nodes={nodes}
  225.  
    onNodesChange={onNodesChange}
  226.  
    edges={edges}
  227.  
    onEdgesChange={onEdgesChange}
  228.  
    onConnect={onConnect}
  229.  
    nodeTypes={nodeTypes}
  230.  
    >
  231.  
    <Background />
  232.  
    <Controls />
  233.  
    </ReactFlow>
  234.  
    </div>
  235.  
    </>
  236.  
    );
  237.  
    }
  238.  
    export default function () {
  239.  
    return (
  240.  
    <ReactFlowProvider>
  241.  
    <Flow />
  242.  
    </ReactFlowProvider>
  243.  
    );
  244.  
    }

taskNode.jsx

  1.  
    /* eslint-disable react/prop-types */
  2.  
    import React, { useState } from 'react'
  3.  
    import { Button, Modal, Form, Input, Popover, Table, message } from 'antd'
  4.  
    import { CheckCircleOutlined, CloseCircleOutlined, LoadingOutlined, PlayCircleOutlined } from '@ant-design/icons';
  5.  
    import { getMonthTaskInfo, getHistoryTaskInfo, runMonthTask } from '@/api/modules/month_task_dag';
  6.  
    import { store } from "@/redux";
  7.  
    import { getFifteenAgoData } from '@/utils/date_utils';
  8.  
    import { Handle, Position, useReactFlow } from 'reactflow';
  9.  
    const username = store.getState().global.userName
  10.  
     
  11.  
    const columns = [
  12.  
    {
  13.  
    title: '任务ID',
  14.  
    dataIndex: 'task_type',
  15.  
    key: 'task_type',
  16.  
    },
  17.  
    {
  18.  
    title: '任务名称',
  19.  
    dataIndex: 'task_type_name',
  20.  
    key: 'task_type_name',
  21.  
    },
  22.  
    {
  23.  
    title: '任务状态',
  24.  
    dataIndex: 'task_status',
  25.  
    key: 'task_status',
  26.  
    },
  27.  
    {
  28.  
    title: '运行者',
  29.  
    dataIndex: 'user',
  30.  
    key: 'user',
  31.  
    },
  32.  
    {
  33.  
    title: '数据月份',
  34.  
    dataIndex: 'data_month',
  35.  
    key: 'data_month',
  36.  
    },
  37.  
    {
  38.  
    title: '创建时间',
  39.  
    dataIndex: 'create_time',
  40.  
    key: 'create_time',
  41.  
    },
  42.  
    {
  43.  
    title: '完成时间',
  44.  
    dataIndex: 'finish_time',
  45.  
    key: 'finish_time',
  46.  
    },
  47.  
    {
  48.  
    title: '修改时间',
  49.  
    dataIndex: 'modify_time',
  50.  
    key: 'modify_time',
  51.  
    }
  52.  
    ];
  53.  
     
  54.  
    // const historydata = [
  55.  
    // {
  56.  
    // task_id: '任务ID',
  57.  
    // task_name: '任务名称',
  58.  
    // task_status: '任务状态',
  59.  
    // user: '运行者',
  60.  
    // create_time:'创建时间',
  61.  
    // finish_time:'完成时间',
  62.  
    // modify_time:'修改时间'
  63.  
    // }
  64.  
    // ];
  65.  
    // eslint-disable-next-line react/prop-types
  66.  
    export function TaskNode({ data, isConnectable }) {
  67.  
    const [isModalVisible, setIsModalVisible] = useState(false); // 表单
  68.  
    const [isPopoverVisible, setPopoverVisible] = useState(false); //菜单
  69.  
    const [isTableVisible, setTableVisible] = useState(false); // 表格
  70.  
    const [historyRunData, setHistoryRunData] = useState(false); // 表格
  71.  
     
  72.  
    const reactFlowInstance = useReactFlow();
  73.  
     
  74.  
    const handleVisibleChange = (isPopoverVisible) => {
  75.  
    setPopoverVisible(isPopoverVisible);
  76.  
    };
  77.  
    const getHistoryInfo = async () => {
  78.  
    console.log(data)
  79.  
    const {data: historydata} = await getHistoryTaskInfo({"task_type":data.task_type})
  80.  
    setHistoryRunData(historydata)
  81.  
    };
  82.  
    const handleMenuClick = () => {
  83.  
    getHistoryInfo();
  84.  
    setPopoverVisible(false);
  85.  
    setTableVisible(true);
  86.  
    };
  87.  
    const menu = (
  88.  
    <a key="record" onClick={handleMenuClick}>
  89.  
    运行记录
  90.  
    </a>
  91.  
    );
  92.  
     
  93.  
    const fifteenAgo = getFifteenAgoData()
  94.  
     
  95.  
    const handleFormSubmit = (values) => {
  96.  
    const runTask = async () => {
  97.  
    const {data: runResult} = await runMonthTask({"task_type":data.task_type,"task_type_name":data.task_type_name, "data_month":values.data_month, "username":values.username})
  98.  
    console.log(runResult);
  99.  
    message.success(runResult);
  100.  
    };
  101.  
    // 提交任务后刷新任务状态
  102.  
    console.log(values, data.task_type, data.task_type_name);
  103.  
    runTask();
  104.  
    setIsModalVisible(false);
  105.  
    const handleGetNodesInfo = async () => {
  106.  
    const {data} = await getMonthTaskInfo({"module_view":"toc_revenue"})
  107.  
    console.log(data);
  108.  
    };
  109.  
    handleGetNodesInfo();
  110.  
    reactFlowInstance.addNodes[data["initialNodes"]];
  111.  
    };
  112.  
    const handleClick = () => {
  113.  
    setIsModalVisible(true);
  114.  
    };
  115.  
     
  116.  
    const getStatusIcon = () => {
  117.  
    switch (data.task_status) {
  118.  
    case 'no_run':
  119.  
    return <PlayCircleOutlined />
  120.  
    case 'success':
  121.  
    return <CheckCircleOutlined />;
  122.  
    case 'failed':
  123.  
    return <CloseCircleOutlined />;
  124.  
    case 'running':
  125.  
    return <LoadingOutlined />;
  126.  
    default:
  127.  
    return null;
  128.  
    }
  129.  
    };
  130.  
    return (
  131.  
    <>
  132.  
    <Popover
  133.  
    content={menu} // 弹出一个菜单项
  134.  
    trigger="hover"
  135.  
    visible={isPopoverVisible}
  136.  
    onVisibleChange={handleVisibleChange}
  137.  
    placement="rightTop"
  138.  
    title="编辑选项"
  139.  
    >
  140.  
    <div style={{ display: 'flex', alignItems: 'center', padding: '10px', borderRadius: '10px', backgroundColor: '#f5f5f5' }}
  141.  
    onClick={handleClick}
  142.  
    >
  143.  
    <div style={{ marginRight: '10px', borderRadius: '50%', overflow: 'hidden' }}>
  144.  
    {getStatusIcon(data.task_status)}
  145.  
    </div>
  146.  
    <div>{data.task_type_name}</div>
  147.  
    <Handle type="target" position={Position.Top} id="a" isConnectable={isConnectable} />
  148.  
    <Handle type="source" position={Position.Bottom} id="b" isConnectable={isConnectable} />
  149.  
    </div>
  150.  
    </Popover>
  151.  
    {/* ===========================================运行记录=========================================== */}
  152.  
    <Modal
  153.  
    title="运行记录"
  154.  
    visible={isTableVisible}
  155.  
    onCancel={() => setTableVisible(false)}
  156.  
    footer={null}
  157.  
    width={'100vh'} // 设置Modal的宽度
  158.  
    >
  159.  
    <div style={{ overflowY: 'auto', maxHeight: '70vh' }}> {/* 设置带有垂直滚动条的容器 */}
  160.  
    <Table columns={columns} dataSource={historyRunData} rowKey="create_time" pagination={{ pageSize: 5 }}/>
  161.  
    </div>
  162.  
    </Modal>
  163.  
    {/* ===========================================表单=========================================== */}
  164.  
    <Modal
  165.  
    title={data.task_type_name}
  166.  
    visible={isModalVisible}
  167.  
    onCancel={() => setIsModalVisible(false)}
  168.  
    footer={null}
  169.  
    >
  170.  
    <Form onFinish={handleFormSubmit} initialValues={{ data_month:fifteenAgo.substring(0,7), username: username}}>
  171.  
    <Form.Item
  172.  
    label="数据月份"
  173.  
    name="data_month"
  174.  
    rules={[{ required: true, message: 'YYYY-MM' }]}
  175.  
    >
  176.  
    <Input />
  177.  
    </Form.Item>
  178.  
    <Form.Item
  179.  
    label="执行人"
  180.  
    name="username"
  181.  
    rules={[{ required: true }]}
  182.  
    >
  183.  
    <Input disabled/>
  184.  
    </Form.Item>
  185.  
    <Form.Item>
  186.  
    <Button htmlType="submit">运行</Button>
  187.  
    </Form.Item>
  188.  
    </Form>
  189.  
    </Modal>
  190.  
    </>
  191.  
    );
  192.  
    }

addNodeForm.jsx

  1.  
    import { PlusOutlined } from '@ant-design/icons';
  2.  
    import {
  3.  
    ModalForm,
  4.  
    ProFormText
  5.  
    } from '@ant-design/pro-components';
  6.  
    import { Button, message } from 'antd';
  7.  
    const AddNodeForm = (props:any) => {
  8.  
    const initialValues = {
  9.  
    mail_to: ''
  10.  
    };
  11.  
    return (
  12.  
    <ModalForm<{
  13.  
    task_type: string;
  14.  
    task_type_name: string;
  15.  
    system_module: string;
  16.  
    settlement_mode: string;
  17.  
    mail_to: string;
  18.  
    creater: string;
  19.  
    }
  20.  
    >
  21.  
    title="新增节点"
  22.  
    initialValues={initialValues}
  23.  
    trigger={
  24.  
    <Button type="primary" size="large">
  25.  
    <PlusOutlined />
  26.  
    新增节点
  27.  
    </Button>
  28.  
    }
  29.  
    autoFocusFirstInput
  30.  
    modalProps={{
  31.  
    onCancel: () => message.info("取消成功"),
  32.  
    }}
  33.  
    submitTimeout={2000}
  34.  
    onFinish={
  35.  
    async (values) => {
  36.  
    const newNode = {
  37.  
    id: values.task_type,
  38.  
    data: { task_type:values.task_type, task_type_name: values.task_type_name,
  39.  
    system_module:values.system_module, settlement_mode:values.settlement_mode,
  40.  
    mail_to: values.mail_to, creater:values.creater,
  41.  
    task_status: 'no_run'},
  42.  
    position: { x: Math.random() * 500, y: Math.random() * 500 },
  43.  
    type: 'taskNode'
  44.  
    }
  45.  
    props.handleAddNode(newNode)
  46.  
    message.success('提交成功');
  47.  
    return true;
  48.  
    }}
  49.  
    >
  50.  
    <ProFormText width="md" name="task_type" label="任务ID" placeholder="任务用于唯一标识的英文名" />
  51.  
    <ProFormText width="md" name="task_type_name" label="任务描述" placeholder="用于节点名称显示" />
  52.  
    <ProFormText width="md" name="system_module" label="归属的系统模块" placeholder="归属的系统模块" />
  53.  
    <ProFormText width="md" name="settlement_mode" label="归属的结算模式" placeholder="mobile/non_mobile/operator等" />
  54.  
    <ProFormText width="md" name="mail_to" label="邮件人" placeholder="邮件人" />
  55.  
    <ProFormText width="md" name="creater" label="创建人" disabled initialValue={props.username} />
  56.  
    </ModalForm>
  57.  
    );
  58.  
    };
  59.  
     
  60.  
    export default AddNodeForm;

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhgfgahi
系列文章
更多 icon
同类精品
更多 icon
继续加载