Jointjs工作流程图实现

JointJS 是一个强大的 ​​JavaScript 图表库​ ​,专注于创建交互式的图表、流程图、拓扑图、UML 图等。它的核心优势在于​​灵活性​ ​和​​可扩展性,支持Vue和React集成,跨浏览器兼容。

基本的效果图

安装并使用

php 复制代码
//初始化画布
import * as joint from '@joint/core';
export const initPaper: any = (
  width: number = window.screen.width,
  height: number = window.screen.height,
) => {
  const graph = new joint.dia.Graph();
  const paper = new joint.dia.Paper({
    el: document.getElementById('paper'),
    model: graph,
    width: width,
    height: height,
    gridSize: 1,
    drawGrid: true,
    background: {
      color: '#fff',
    },
    interactive: true,
    frozen: false,
    clickThreshold: 1,
    defaultLink: new joint.shapes.standard.Link({
      router: { name: 'bezierrouter' },
      attrs: {
        line: {
          stroke: 'gray',
          targetMarker: {
            d: 'M 8 -5 L 0 0 L 8 5 Z',
          },
        },
      },
    }),
    linkView: joint.dia.LinkView.extend({
      ...joint.dia.LinkView.prototype.options,
      doubleLinkTools: true,
      linkToolsOffset: 40,
      doubleLinkToolsOffset: 60,
    }),
    highlighting: {
      default: {
        name: 'stroke',
        options: {
          padding: 0,
          rx: 5,
          ry: 5,
          attrs: {
            'stroke-width': 3,
            stroke: '#24b0e1',
            opacity: '.5',
          },
        },
      },
    },
    linkPinning: false, // 禁止自动创建悬空连线
  });

  return { paper, graph };
};
  • 创建节点并加入到画布中
php 复制代码
//创建节点
const TaskNode = joint.dia.Element.define('task.Node', {
  attrs: {
    body: {
      refWidth: '100%',
      refHeight: '100%',
      fill: '#fff',
      stroke: '#000',
      strokeWidth: 1,
      rx: 5,
      ry: 5,
      cursor: 'pointer',
    },
    label: {
      textVerticalAnchor: 'middle',
      textAnchor: 'middle',
      refX: '50%',
      refY: '50%',
      fontSize: 12,
      fill: '#333',
    },
    // 状态图标组,包含图标和它的工具提示
    statusIconGroup: {
      cursor: 'pointer',
    }
  },
  ports: {
    groups: {
      right: {
        position: {
          name: 'right',
          args: { x: '90%', y: '50%' },
        },
        attrs: {
          circle: {
            magnet: true,
            stroke: 'gray',
            fill: '#fff',
            r: 6,
          },
        },
        markup: [
          {
            tagName: 'circle',
            selector: 'circle',
          },
        ],
      },
    },
  },
  markup: [
    {
      tagName: 'rect',
      selector: 'body',
    },
    {
      tagName: 'text',
      selector: 'label',
    },
    {
      tagName: 'g',
      selector: 'statusIconGroup',
      children: [
        {
          tagName: 'text',
          selector: 'statusIcon',
        },
        {
          tagName: 'title',
          selector: 'statusTooltip',
        }
      ]
    }
  ],
});
export default TaskNode;

//初始化画布时返回了画布的实例,所以这里把创建的节点加入到画布中
 graph.addCell(newNode);
  • 自定义节点连线
ini 复制代码
// 定义贝塞尔路由器
export const defineBezierRouter = () => {
  // @ts-ignore
  joint.routers.bezierrouter = (vertices: any[], args: any, linkView: any) => {
    // @ts-ignore
    vertices = joint.util.toArray(vertices).map(joint.g.Point);

    const sourcePoint: any = linkView.sourceBBox.center();
    const targetPoint: any = linkView.targetBBox.center();
    sourcePoint.y += 6;
    targetPoint.y -= 6;

    const controlPoint1 = new joint.g.Point(sourcePoint.x, sourcePoint.y + 80);
    const controlPoint2 = new joint.g.Point(targetPoint.x, targetPoint.y - 80);
    const points = new joint.g.Curve(
      sourcePoint,
      controlPoint1,
      controlPoint2,
      targetPoint,
    ).toPoints();

    points.pop();
    return vertices.concat(points);
  };
};
  • 在页面中使用,这里做参考,具体的事件处理逻辑根据需求参考官方文档
scss 复制代码
  useEffect(() => {
    // 4. 初始化画布 - 设置画布的基本属性和交互选项
    const { paper, graph } = initPaper();
    setPaper(paper);
    setGraph(graph);

    // 自定义的贝塞尔曲线
    defineBezierRouter();
    // 监听节点移动
    paper.on('cell:pointermove', cellPointerMove);
    // 监听节点点击
    paper.on('cell:pointerclick', CellPointerClick);
    // 监听节点双击
    paper.on('cell:pointerdblclick', CellPointerClick);
    // 监听连接点击事件
    paper.on('link:connect', handleLinkConnect);
    // 监听连接松开事件
    paper.on('link:pointerup', handleLinkPointerUp);
    // 监听画布空白处的点击事件
    paper.on('blank:pointerclick', handleBlankClick);

    // 监听画布空白处的点击事件
    paper.on('blank:pointerdown', (evt: any, x: number, y: number) => {
      handleBlankPointerDown(evt, x, y, graph);
    });

    // 添加右键菜单事件监听
    paper.on('cell:contextmenu', handleBlankContextMenu);
    paper.on('element:magnet:contextmenu', portClick);

    return () => {
      // 移除事件
      paper.off('element:magnet:contextmenu', portClick);
      paper.off('link:connect', handleLinkConnect);
      paper.off('link:pointerup', handleLinkPointerUp);
      paper.off('blank:pointerdown', handleBlankPointerDown);
      paper.off('blank:pointerclick', handleBlankClick);
      paper.off('cell:pointerclick', CellPointerClick);
      paper.off('cell:pointerdblclick', CellPointerClick);
      paper.off('cell:contextmenu', handleBlankContextMenu);
      paper.off('cell:pointermove', cellPointerMove);
      // 清除画布
      graph.clear();
      paper.remove();
    };

结合业务做增删改查的思路

基本的效果图:

1.首先页面分为三个部分,左侧节点的拖拽基于react-dnd和react-dnd-html5-backend来实现的,中间是画布部分,右侧是表单,当选中节点时可以配置这个节点的参数。
2. 从左侧拖拽到画布中生成一个节点,当选中一个节点的时候,使用一个react状态来存储这个节点的信息,节点信息如下:
3. 每次创建一个节点到画布上时,会生成节点的一个唯一id,那么可以根据这个id去创建一个Map数据结构,形成一个key和value的映射。
arduino 复制代码
//创建一个Map数据
  const nodeMap = new Map();
  //创建节点
   const node = new TaskNode({
   //这里是节点相关配置数据
    position,
    size: { width, height },
    attrs: attrs,
  });
 // 将完整的节点数据存储在nodeMap中
  nodeMap.set(node.id, {
   //节点相关数据 如右侧表单的数据
  });
4.由于把节点的数据都存入了nodeMap中去了,如图:
5.每次需要去更新或者获取某个节点的数据时,就可以根据nodeMap去获取
csharp 复制代码
  // 获取当前节点在nodeMap中的数据
   const currentNodeData = nodeMap.get(id) || {};
   
  // 获设置当前节点在nodeMap中的数据
  const currentNodeData = nodeMap.set(id) || {};
6. 为什么不使用对象去存储节点数据呢?,个人觉得因为object描述的是一个整体,这里节点的数据只是id对应value数据,而且在业务中我们会时长去做节点的数据更改,连线,新增节点,删除节点,配置节点数据等,Map更适合且性能更快点。
相关推荐
水银嘻嘻29 分钟前
12 web 自动化之基于关键字+数据驱动-反射自动化框架搭建
运维·前端·自动化
小嘟嚷ovo1 小时前
h5,原生html,echarts关系网实现
前端·html·echarts
十一吖i1 小时前
Vue3项目使用ElDrawer后select方法不生效
前端
只可远观1 小时前
Flutter目录结构介绍、入口、Widget、Center组件、Text组件、MaterialApp组件、Scaffold组件
前端·flutter
周胡杰1 小时前
组件导航 (HMRouter)+flutter项目搭建-混合开发+分栏效果
前端·flutter·华为·harmonyos·鸿蒙·鸿蒙系统
敲代码的小吉米2 小时前
前端上传el-upload、原生input本地文件pdf格式(纯前端预览本地文件不走后端接口)
前端·javascript·pdf·状态模式
是千千千熠啊2 小时前
vue使用Fabric和pdfjs完成合同签章及批注
前端·vue.js
九月TTS2 小时前
TTS-Web-Vue系列:组件逻辑分离与模块化重构
前端·vue.js·重构
我是大头鸟3 小时前
SpringMVC 内容协商处理
前端
Humbunklung3 小时前
Visual Studio 2022 中添加“高级保存选项”及解决编码问题
前端·c++·webview·visual studio