流程组件React Flow节点拖拽添加与自定义节点


创建流程时,会涉及节点的拖拽,添加,删除 以及节点内容的修改 

效果图

官网例子: https://reactflow.dev/docs/examples/drag-and-drop/   分步实现,每一步新增的功能代码用红色标记   实现节点拖拽 主要是增加两个函数onDragOver  onDrop将选择的节点添加 index.tsx
import React, { useState, useRef, useCallback } from 'react';
import ReactFlow, {
  ReactFlowProvider,
  addEdge,
  useNodesState,
  useEdgesState,
  Controls,
} from 'react-flow-renderer';

import Sidebar from './Sidebar';

import './index.css';

const initialNodes = [
  {
    id: '1',
    type: 'input',
    data: { label: 'input node' },
    position: { x: 250, y: 5 },
  },
];

let id = 0;
const getId = () => `dndnode_${id++}`;

const DnDFlow = () => {
  const reactFlowWrapper = useRef(null);
  const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
  const [edges, setEdges, onEdgesChange] = useEdgesState([]);
  const [reactFlowInstance, setReactFlowInstance] = useState(null);

  const onConnect = useCallback((params) => setEdges((eds) => addEdge(params, eds)), []);

  // 节点拖拽时
  const onDragOver = useCallback((event) => {
    event.preventDefault();
    event.dataTransfer.dropEffect = 'move';
  }, []);

  // 添加新节点
  const onDrop = useCallback(
    (event) => {
      event.preventDefault();

      const reactFlowBounds = reactFlowWrapper.current.getBoundingClientRect();
      const type = event.dataTransfer.getData('application/reactflow');

      // check if the dropped element is valid
      if (typeof type === 'undefined' || !type) {
        return;
      }

      const position = reactFlowInstance.project({
        x: event.clientX - reactFlowBounds.left,
        y: event.clientY - reactFlowBounds.top,
      });
      const newNode = {
        id: getId(),
        type,
        position,
        data: { label: `${type} node` },
      };

      setNodes((nds) => nds.concat(newNode));
    },
    [reactFlowInstance]
  );

  return (
    
<ReactFlow nodes={nodes} edges={edges} onNodesChange={onNodesChange} onEdgesChange={onEdgesChange} onConnect={onConnect} onInit={setReactFlowInstance} onDrop={onDrop} onDragOver={onDragOver} fitView >
// 添加栏的内容
); }; export default DnDFlow;
左侧拖拽节点内容 siderBar.tsx
import React from 'react';

export default () => {
  const onDragStart = (event, nodeType) => {
    event.dataTransfer.setData('application/reactflow', nodeType);
    event.dataTransfer.effectAllowed = 'move';
  };

  return (
    
  );
};

效果:

实现自定义节点 步骤: 1.创建一个节点组件  2.增加一个定义节点类型名称,与节点组件对应  3.将节点类型传入ReactFlow的nodeTypes 4.节点拖拽新增时,将节点类型改为自定义类型名称
index.tsx
import React, { useState, useRef, useCallback } from 'react';
import ReactFlow, {
  ReactFlowProvider,
  addEdge,
  useNodesState,
  useEdgesState,
  Controls,
} from 'react-flow-renderer';
import CustomNode from '../components/customNode';

import Sidebar from './Sidebar';

import './index.css';

// 自定义节点类型
const nodeTypes = {
  custom: CustomNode,
};

const initialNodes = [
  {
    id: '1',
    type: 'input',
    data: { label: 'input node' },
    position: { x: 250, y: 5 },
  },
];

let id = 0;
const getId = () => `dndnode_${id++}`;

const DnDFlow = () => {
  const reactFlowWrapper = useRef(null);
  const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
  const [edges, setEdges, onEdgesChange] = useEdgesState([]);
  const [reactFlowInstance, setReactFlowInstance] = useState(null);

  const onConnect = useCallback((params) => setEdges((eds) => addEdge(params, eds)), []);

  // 节点拖拽时
  const onDragOver = useCallback((event) => {
    event.preventDefault();
    event.dataTransfer.dropEffect = 'move';
  }, []);

  // 添加新节点
  const onDrop = useCallback(
    (event) => {
      event.preventDefault();

      const reactFlowBounds = reactFlowWrapper.current.getBoundingClientRect();
      const type = event.dataTransfer.getData('application/reactflow');

      // check if the dropped element is valid
      if (typeof type === 'undefined' || !type) {
        return;
      }

      const position = reactFlowInstance.project({
        x: event.clientX - reactFlowBounds.left,
        y: event.clientY - reactFlowBounds.top,
      });
      const newNode = {
        id: getId(),
        type,
        position,
        data: { label: `${type} node` },
      };

      setNodes((nds) => nds.concat(newNode));
    },
    [reactFlowInstance]
  );

  return (
    
<ReactFlow nodes={nodes} edges={edges} nodeTypes={nodeTypes} onNodesChange={onNodesChange} onEdgesChange={onEdgesChange} onConnect={onConnect} onInit={setReactFlowInstance} onDrop={onDrop} onDragOver={onDragOver} fitView >
// 添加栏的内容
); }; export default DnDFlow;
Sidebar.tsx左侧添加栏内容
import React from 'react';

export default () => {
  const onDragStart = (event: any, nodeType: any) => {
    event.dataTransfer.setData('application/reactflow', nodeType);
    event.dataTransfer.effectAllowed = 'move';
  };

  return (
    
<div className="dndnode" onDragStart={(event) => { event.stopPropagation(); onDragStart(event, 'custom'); // 类型为自定义节点类型名称 }} draggable > 中间节点
<div className="dndnode output" onDragStart={(event) => { event.stopPropagation(); onDragStart(event, 'output'); }} draggable > 结束节点
); }; customNode.tsx自定义节点内容
import React, { memo } from 'react';
import { CloseOutlined } from '@ant-design/icons';

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

export default memo(({ data, id, isConnectable }: any) => {
  // console.log(1, data)

  return (
    <>
      <Handle
        type="target"
        position="top"
        className="my_handle"
        onConnect={(params) => console.log('handle onConnect', params)}
        isConnectable={isConnectable}
      /> // 上面的连接点

      
{data.label}
<CloseOutlined onClick={(e) => { e.stopPropagation(); data.onChange(id); }} className="icon-close" />
// 节点内容,可以自定义内容 <Handle type="source" position="bottom" id="a" className="my_handle" isConnectable={isConnectable} /> // 下面的连接点 ); });

效果:

修改节点内容 步骤: 1.创建一个修改内容的表单组件  2.点击节点将当前节点数据传入  3.将节点初始配置回显  4.修改表单,触发修改事件,回传修改后的数据  5.在修改函数中,修改数据,重新设置nodes
index.tsx
import React, { useState, useRef, useCallback } from 'react';
import ReactFlow, {
  ReactFlowProvider,
  addEdge,
  useNodesState,
  useEdgesState,
  Controls,
} from 'react-flow-renderer';
import CustomNode from '../components/customNode';
import UpdateNode from '../components/nodeContent';

import Sidebar from './Sidebar';

import './index.css';

// 自定义节点类型
const nodeTypes = {
  custom: CustomNode,
};

const initialNodes = [
  {
    id: '1',
    type: 'input',
    data: { label: 'input node' },
    position: { x: 250, y: 5 },
  },
];

let id = 0;
const getId = () => `dndnode_${id++}`;

const DnDFlow = () => {
  const reactFlowWrapper = useRef(null);
  const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
  const [edges, setEdges, onEdgesChange] = useEdgesState([]);
  const [reactFlowInstance, setReactFlowInstance] = useState(null);
const [nodeInfo, setNodeInfo] = useState({});
  const onConnect = useCallback((params) => setEdges((eds) => addEdge(params, eds)), []);

  // 节点拖拽时
  const onDragOver = useCallback((event) => {
    event.preventDefault();
    event.dataTransfer.dropEffect = 'move';
  }, []);

  // 添加新节点
  const onDrop = useCallback(
    (event) => {
      event.preventDefault();

      const reactFlowBounds = reactFlowWrapper.current.getBoundingClientRect();
      const type = event.dataTransfer.getData('application/reactflow');

      // check if the dropped element is valid
      if (typeof type === 'undefined' || !type) {
        return;
      }

      const position = reactFlowInstance.project({
        x: event.clientX - reactFlowBounds.left,
        y: event.clientY - reactFlowBounds.top,
      });
      const newNode = {
        id: getId(),
        type,
        position,
        data: { label: `${type} node` },
      };

      setNodes((nds) => nds.concat(newNode));
    },
    [reactFlowInstance]
  );

  // 点击节点,将节点初始配置传入
  const onNodeClick = (e: any, node: any) => {
    console.log(node);
    setNodeInfo({
      ...node.data,
      id: node.id,
      nodeBg: node.style && node.style.background ? node.style.background : '#ffffff',
    });
    setNodeShow(true);
  };

  // 修改完成,setNodes改变节点内容
  const changeNode = (val: any) => {
    setNodes((nds) =>
      nds.map((item) => {
        if (item.id === val.id) {
          item.data = val;
          item.hidden = val.isHidden;
          item.style = { background: val.nodeBg };
        }
        return item;
      }),
    );
    // console.log(val, nodes)
  };

  return (
    
<ReactFlow nodes={nodes} edges={edges} nodeTypes={nodeTypes}
       onNodeClick={onNodeClick} onNodesChange
={onNodesChange} onEdgesChange={onEdgesChange} onConnect={onConnect} onInit={setReactFlowInstance} onDrop={onDrop} onDragOver={onDragOver} fitView > // 修改节点内容组件
// 添加栏的内容
); }; export default DnDFlow;
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({});

  useEffect(() => {
    if (info.id) {
      // console.log(info)
      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) => {
    // console.log(value);
    setNodeInfo({
      ...nodeInfo,
      nodeBg: value,
    });
    onChange({
      ...nodeInfo,
      nodeBg: value,
    });
  };

  // 是否隐藏
  const setNodeHidden = (value: boolean) => {
    setNodeInfo({
      ...nodeInfo,
      isHidden: value,
    });
    onChange({
      ...nodeInfo,
      isHidden: value,
    });
  };

  return nodeInfo.id ? (
    
{/* setNodeName(evt.target.value)} /> */} <Input placeholder="" value={nodeInfo.label} onChange={(evt) => setNodeName(evt.target.value)} /> setNodeBg(evt.target.value)} />
) : ( <> ); };
  连接线自定义与内容修改也是一样的操作步骤, 连接线效果图: githup上传了完整的代码,有节点内容修改. githup地址: https://github.com/shengbid/antdpro-demo/tree/main/src/pages/flowManage



相关