流程图

87 阅读4分钟

1.安装@datafe/graph-react包

yarn add @datafe/graph-react
npm install @datafe/graph-react

2.代码demo

同级文件目录index.tsx,dragPanel.tsx,data.ts,style.scss

1.index.tsx 画布页面,左侧拖拽怒路和画布操作

import { Button, Icon } from '@ss/mtd-react'
import React, { useState, useEffect } from 'react'
import {} from '@/pages/lark/api'
import PageTitle from '@/pages/lark/views/components/page-title'
import './style.scss'
import {
  GraphReact,
  ToolBox,
  Menu,
  Graph,
  INode,
  IEdge,
  // IPort,
  IEdgeModel,
  IAction,
} from '@datafe/graph-react'
import dataMock from './data'
import DragPanel from './dragPanel'

const RecallProgramGraph = (props: any) => {
  console.log('props==', props)
  // 定义画布的交互,可以根据需求删除不需要的
  const action = [
    'drag-blank',
    'drag-node',
    'click-select',
    'connect-edge',
    'wheel-move',
    'wheel-zoom',
    'brush-select',
  ] as IAction | any
  const dragComponent = {
    offsetX: 0,
    offsetY: 0,
    label: '',
  }
  const [graph, setGraph] = useState<Graph>((null as unknown) as Graph)
  const [showMenu, setShowMenu] = useState(false)
  const [activeNodeId, setActiveNodeId] = useState('')
  const handleNodeContextMenu = ({ data }: { data: { id: string } }) => {
    setShowMenu(true)
    setActiveNodeId(data.id)
  }
  const handleConnent = (data: IEdgeModel) => {
    graph.addEdge(data)
  }
  const handleDrop = (e: any) => {
    e.preventDefault()
    const x = e.x - dragComponent.offsetX * graph.getZoom()
    const y = e.y - dragComponent.offsetY * graph.getZoom()
    const point = graph.getPointByClient(x, y)
    graph.addNode({
      label: dragComponent.label,
      x: point.x,
      y: point.y,
      width: 160,
      height: 40,
    })
  }
  useEffect(() => {
    if (!graph) return
    // 在这里绑定画布所需的事件
    graph.on('node:contextmenu', handleNodeContextMenu)
    graph.on('edge:connected', handleConnent)

    graph.on('drop', handleDrop) //监听拖拽
    // 模拟接口返回数据
    setTimeout(() => {
      graph.data(dataMock)
    }, 100)
  }, [graph])
  const handleDragStart = (data: any) => {
    console.log('data88', data)
    dragComponent.offsetX = data.offsetX
    dragComponent.offsetY = data.offsetY
    dragComponent.label = data.itemLabel
  }
  const nodeComponent = (node: INode) => {
    return (
      <div className={`component-item ${node.hasState('selected') && 'selected'}`}>
        {node.model.label}
      </div>
    )
  }
  const edgeComponent = (edge: IEdge) => {
    const path = `M ${edge.source.x},${edge.source.y} L ${edge.target.x},${edge.target.y}`
    return <path d={path} markerEnd="url(#arrow)" className="graph-custom-edge" />
  }
  const init = (graph: Graph) => {
    setGraph(graph)
  }
  const deleteNode = () => {
    graph.deleteNode(activeNodeId)
  }
  const save = () => {
    console.log('graph', graph)
    console.log('graph.getDataModel', graph.getDataModel()) //获取节点和边的数据
  }
  return (
    <div style={{ background: 'white', padding: '0px 15px 15px' }}>
      <PageTitle title="召排运维" />
      <div className="graphBox">
        <DragPanel dragStart={handleDragStart} />
        <GraphReact
          node={nodeComponent}
          edge={edgeComponent}
          // port={portComponent}
          init={init}
          action={action}
          defaultNode={{ width: 160, height: 40 }}
          // onDrop={handleDrop}
        >
          <ToolBox />
          {showMenu ? (
            <Menu>
              <div className="menu-item" onClick={deleteNode}>
                删除
              </div>
            </Menu>
          ) : (
            ''
          )}
        </GraphReact>
      </div>
      <Button type="primary" onClick={save}>
        保存
      </Button>
      <div>
        <Icon type="save-o" />
      </div>
    </div>
  )
}

export default RecallProgramGraph

2.dragPanel.tsx 左侧拖拽目录

import React from 'react'
import './style.scss'

const sideMenu = [
  { label: 't1', value: 't1' },
  { label: 't2', value: 't2' },
  { label: 't3', value: 't3' },
  { label: 't4', value: 't4' },
  { label: 't5', value: 't5' },
  { label: 't6', value: 't6' },
  { label: 't7', value: 't7' },
  { label: 't8', value: 't8' },
  { label: 't9', value: 't9' },
  { label: 't10', value: 't10' },
  { label: 't11', value: 't11' },
  { label: 't12', value: 't12' },
  { label: 't13', value: 't13' },
  { label: 't14', value: 't14' },
  { label: 't15', value: 't15' },
  { label: 't16', value: 't16' },
]

export default (props: any) => {
  const handleDragStart = (e: any, itemLabel: any) => {
    const dom = e.target
    dom.classList.add('active')
    const rect = dom.getBoundingClientRect()
    const offsetX = e.clientX - rect.x
    const offsetY = e.clientY - rect.y

    props.dragStart && props.dragStart({ offsetX, offsetY, itemLabel })
  }

  const handleDragEnd = (e: any) => {
    e.target.classList.remove('active')
  }

  return (
    <div className="component-panel">
      <div className="title">拖动组件到画布</div>
      <div className="item-box">
        {sideMenu.map(item => (
          <li
            key={item.value}
            style={{ marginTop: '10px' }}
            className="component-item"
            onDragStart={e => handleDragStart(e, item.label)}
            onDragEnd={handleDragEnd}
            draggable
          >
            <span> {item.label} </span>
          </li>
        ))}
      </div>
    </div>
  )
}

3.data.ts画布初始mock数据

export default {
  nodes: [
    {
      id: '1',
      // label: '工具栏悬浮有说明',
      label: 'test1',
      nodeId: 232,
    },
    {
      id: '2',
      // label: '拖动添加组件',
      label: 'test2',
      ports: [
        {
          type: 'in',
          id: 'port3',
        },
        {
          type: 'in',
          id: 'port4',
        },
        {
          type: 'out',
          id: 'port5',
        },
      ],
    },
    {
      id: '3',
      // label: '拖动插槽连线',
      label: '测试3',
    },
    {
      id: '4',
      // label: '右键可删除',
      label: '测试4',
    },
    {
      id: '5',
      // label: '交互可配置',
      label: '测试5',
    },
    {
      id: '6',
      // label: '交互可配置',
      label: '测试6',
    },
    {
      id: '7',
      label: '交互可配置',
      ports: [
        {
          type: 'in',
          id: 'port14',
        },
        {
          type: 'in',
          id: 'port15',
        },
        {
          type: 'out',
          id: 'port16',
        },
      ],
    },
  ],
  edges: [
    {
      fromNodeId: '1',
      toNodeId: '5',
      id: 'edge1',
    },
    {
      fromNodeId: '3',
      toNodeId: '2',
      toPortId: 'port3',
      id: 'edge2',
    },
    {
      fromNodeId: '1',
      toNodeId: '3',
      id: 'edge3',
    },
    {
      fromNodeId: '7',
      toNodeId: '4',
      fromPortId: 'port16',
      id: 'edge4',
    },
    {
      fromNodeId: '4',
      toNodeId: '6',
      id: 'edge5',
    },
    {
      fromNodeId: '2',
      toNodeId: '6',
      fromPortId: 'port5',
      id: 'edge6',
    },
    {
      fromNodeId: '5',
      toNodeId: '7',
      toPortId: 'port14',
      id: 'edge7',
    },
  ],
}

4.style.scss 画布和左侧目录样式

/* dragPanel 样式 */
.component-panel {
  position: absolute;
  top: 10px;
  left: 10px;
  width: 180px;
  // height: 150px;
  height: 680px;
  user-select: none;
  box-sizing: border-box;
  background: #fcfcfc;
  z-index: 1;
  // padding: 10px;
  box-shadow: 0 0 4px 1px #eee;
  border: 1px solid red;
}

.title {
  font-size: 12px;
  color: #222;
  text-align: center;
  // margin: 10px 10px;
  padding: 10px 0px;
  margin: 0px 0px 10px;
  box-shadow: 0 0px 4px 1px rgba(0, 0, 0, 0.01), 0 3px 6px 3px rgba(0, 0, 0, 0.01),
    0 2px 6px 0 rgba(0, 0, 0, 0.03);
}
.item-box {
  width: 180px;
  // height: 150px;
  height: 620px;
  padding: 0px 10px;
  overflow-y: auto;
}
.component-item {
  display: flex;
  height: 40px;
  box-sizing: border-box;
  color: #222;
  font-size: 12px;
  padding-left: 40px;
  align-items: center;
  cursor: pointer;
  user-select: none;
  border: 1px solid #222;
  border-radius: 4px;
  background: #fff;
}
.selected {
  border-width: 1.5px !important;
  background: #eea !important;
}
.active {
  border: 1px dashed;
  cursor: grabbing;
}
.graph-custom-edge {
  stroke: #aaa;
  fill: transparent;
  stroke-width: 1.5;
}
.graph-react-wrapper {
  width: 100%;
  height: 700px !important;
  // border: 1px solid green;
}
/* 如果想修改自带样式,可以使用直接样式覆盖 */
.graph-react-wrapper .graph-react-tool-box {
  background: #fcfcfc;
  border: none;
  color: #222;
  box-shadow: 0 0 4px 1px #eee;
  border-radius: 4px;
}
.graph-react-wrapper .graph-react-tool-box-item:hover {
  background: #e9e9e9;
}
.graph-react-wrapper .graph-react-tool-box-disable {
  color: #eee;
}
.menu-item {
  height: 30px;
  line-height: 30px;
  color: #555;
  font-size: 12px;
  cursor: pointer;
  background: #fff;
  text-align: center;
}
.menu-item:hover {
  color: #222;
  background: #eee;
}

.graphBox {
  position: relative;
  width: 100%;
  min-height: 700px;
  border: 1px solid red;
}