「AntV」使用AntV X6实现流程编排设计器

news2024/12/27 13:12:06

通过对BPMN的深入学习,以及对业界成熟的流程编排设计器的调研,了解到要研发一个流程编排设计器,需要实现如下几个主要的功能:

  1. 支持创建各种流程图元素,包括任务(活动)、网关、事件等。
  2. 支持绘制各种连接线,包括普通连接线、条件连接线、消息连接线等。
  3. 支持流程图的编辑,包括元素的选择、移动、删除、复制、粘贴等。
  4. 支持流程图的保存,包括保存流程图数据、导入/导出流程图数据等。
  5. 具备良好的可扩展性,可以通过插件等方式进行功能扩展和定制化。
  6. 具备良好的用户体验,包括界面美观、交互友好等。

除了设计器本身的能力,还需要物料区域,工具栏操作区域,属性设置区域等额外的功能。

技术方案

基于阿里低代码引擎(Low-Code Engine)强大的定制扩展能力,自定义开发了缩放、组件面板、AntV X6画布面板、设置器面板等功能,构建出遵循BPMN规范的流程编排设计器。

  • 组件面板基于标准的物料协议完成物料元素渲染;
  • 画布面板基于 AntV X6 实现,借助自身Addon拖拽能力完成物料到画布的编排功能;
  • 设置器面板基于 Formily 表单协议完成各类配置表单渲染;
  • 协议转换基于fast-xml-parser,完成Graph JSON 和 XML 之间的协议转换。

在这里插入图片描述

实现步骤

以下内容将重点介绍一下基于AntV X6实现流程编排画布的过程,以及介绍在开发过程中重点用到的配置项和使用方法。当然设计器的内容除了画布,还有物料区、设置区、协议转换等内容,在这里不做过多阐述。

初始化画布

在页面中创建一个画布容器x6-container,用于绘制BPMN流程图。期间需要构造画布配置参数GraphOptions,初始化画布对象。

import React from "react";

export default props => {
  const containerRef = useRef(null);

  useLayoutEffect(() => {
    // 初始化图形
    registerShape();

    // 初始化画布
    let options = getDefaultGraphOptions(containerRef.current);
    const _graph = new Graph(options);

    // 初始化撤销重做、快捷键、图形变换等插件
    initPlugins(_graph);

    // 初始化删除,双击等事件
    initEvents(_graph);
  }, []);
  
  return (
    <div className="lc-designer lowcode-plugin-designer">
      <div className="lc-project">
        <div className="lc-simulator-canvas lc-simulator-device-default">
          <div id="x6-container" ref={containerRef} />
        </div>
      </div>
    </div>
  );
};

连线(connecting)

通过配置 connecting 可以实现丰富的连线交互。

router

采用了曼哈顿算法,注意需要使用excludeShapes排除对于Group节点的计算

{
  router: {
    name: "manhattan",
      args: {
        excludeShapes: [ElementType.Group],
        padding: 25
    }
  },
}
createEdge

自定义新建边的样式,在动态拖拽生成线条时使用。

{
  createEdge() {
    return new Shape.Edge({
      shape: ElementType.SequenceFlow,
      router: {
        name: "manhattan",
        args: {
          excludeShapes: [ElementType.Group],
          padding: 25
        }
      },
    });
  },
}

组合(embedding)

通过 embedding 可以将一个节点拖动到另一个节点中,使其成为另一节点的子节点,默认禁用。本项目中用于支持向Group节点中拖动子节点达到分组展示的效果。

{
  embedding: {
    enabled: true,  // 是否允许节点之间嵌套
    findParent({ node }) {
      const bbox = node.getBBox();
      return this.getNodes().filter(item => {
        const data = item.getData<any>();
        if (data && data.parent) {
          item.toBack(); // 修改Group节点zIndex,解决拖拽覆盖问题
          const targetBBox = item.getBBox();
          return bbox.isIntersectWithRect(targetBBox);
        }
        return false;
      });
    },
	},
}

限制(interacting)

限制节点和边的交互行为,实际案例中用到了edgeLabelMovable,支持边的标签可移动。

  • nodeMovable 节点是否可以被移动。
  • edgeMovable 边是否可以被移动。
  • edgeLabelMovable 边的标签是否可以被移动。

初始化元素

BPMN规范中,存在事件、网关、活动等基础元素,因此定义了如下几种元素类型,并分别注册了节点和边。每种图形都有自身的属性配置。

export enum ElementType {
  /** 开始事件 */
  StartEvent = "startEvent",
  /** 结束事件 */
  EndEvent = "endEvent",
  /** 错误结束事件 */
  ErrorEndEvent = "errorEndEvent",
  /** 连接线 */
  SequenceFlow = "sequenceFlow",
  /** 排他网关 */
  ExclusiveGateway = "exclusiveGateway",
  /** 并行网关 */
  ParallelGateway = "parallelGateway",
  /** 功能节点 */
  FunctionTask = "functionTask",
  /** 分组 */
  Group = "group",
}

配置节点各类属性信息,并注册节点。这里以Group元素为例,代码如下:

// 分组元素配置信息
const defaultGroupConfig = {
  inherit: "rect",
  width: 240,
  height: 160,
  zIndex: 0,
  markup: [
    {
      tagName: 'rect',
      selector: 'body',
    },
    {
      tagName: 'text',
      selector: 'text',
    },
  ],
  attrs: {
    text: {
      refX: 0.5,
      refY: 10,
      textAnchor: 'middle',
      textVerticalAnchor: 'top',
      // 文本换行:https://antv-x6.gitee.io/zh/docs/api/registry/attr/#textwrap
      textWrap: {
        width: 200,
        height: 30,
        ellipsis: true,
      },
    },
    body: {
      rx: 6,
      ry: 6,
      strokeWidth: 1,
      strokeDasharray: "3,3",
      stroke: "#bfbfbf",
      fill: "rgba(238,238,238,.3)",
    },
  },
  data: {
    parent: true,
  },
}

Graph.registerNode(EElementType.Group, defaultGroupConfig, true);

markup

markup 指定了渲染节点时使用的 SVG片段,使用 JSON 格式描述。如上代码则表示节点内部包含<rect><text>两个SVG元素,渲染到页面之后,节点对应的元素如下:

<g data-cell-id="d873ae84-1655-4973-9ba8-bd65b6613b80" data-shape="group" class="x6-cell x6-node" transform="translate(-590,20)">
  <rect fill="rgba(238,238,238,.3)" stroke="#bfbfbf" stroke-width="1" rx="6" ry="6" stroke-dasharray="3,3" width="240" height="160"></rect>
  <text font-size="14" xml:space="preserve" fill="#000000" text-anchor="middle" font-family="Arial, helvetica, sans-serif" text="分组" transform="matrix(1,0,0,1,120,10)">
    <tspan dy="0.8em" class="v-line">分组</tspan>
  </text>
</g>
tagName

指定需要创建哪种 SVG/HTML 元素

selector

该元素的唯一选择器,通过选择器为该元素指定属性样式

attrs

属性选项 attrs 是一个复杂对象,该对象的 Key 是节点 Markup 定义中元素的选择器(selector),对应的值是应用到该 SVG 元素的 SVG 属性值(如 fill 和 stroke),如果你对 SVG 属性还不熟悉,可以参考 MDN 提供的填充和边框入门教程。

效果图

在这里插入图片描述

初始化连接线

Graph.registerEdge(
    ElementType.SequenceFlow,
    {
      inherit: "edge",
      attrs: {
        line: {
          strokeWidth: Size.LineStrokeWith,
          stroke: Colors.Line
        }
      },
      router: {
        name: "manhattan",
        args: {
          excludeShapes: [ElementType.Group], // 解决Group节点下manhattan算法失效问题,导致线条显示异常
          padding: 25
        }
      }
    },
    true,
);

初始化插件

快捷键

使用@antv/x6-plugin-keyboard,为画布绑定快捷键,例如复制,粘贴,删除等

import { Graph } from "@antv/x6";
import { Keyboard } from "@antv/x6-plugin-keyboard";

export const initPlugins = (graph: Graph) => {
  graph.use(
    new Keyboard({
      enabled: true,
    })
  );
};

对齐线

使用@antv/x6-plugin-snapline,在移动节点时辅助排版

import { Graph } from "@antv/x6";
import { Snapline } from "@antv/x6-plugin-snapline";

export const initPlugins = (graph: Graph) => {
  graph.use(
    new Snapline({
      enabled: true,
    }),
  );

撤销重做

使用@antv/x6-plugin-history,实现元素操作的撤销和重做

import { Graph } from "@antv/x6";
import { History } from "@antv/x6-plugin-history";

export const initPlugins = (graph: Graph) => {
  graph.use(
    new History({
      enabled: true,
      beforeAddCommand(event, args: any) {
        // console.log(event, args);
        if (args.key === "tools") {
          return false;
        }
        return true;
      },
    }),
  );

框选插件

使用@antv/x6-plugin-selection,可以实现点击元素选中,启用多选能力,按住Ctrl/Command后点击元素可以多选

import { Graph } from "@antv/x6";
import { Selection } from "@antv/x6-plugin-selection";

export const initPlugins = (graph: Graph) => {
  graph.use(
    // 支持节点选中样式
    new Selection({
      enabled: true,
    }),
  );
};

图形变换

使用@antv/x6-plugin-transform,实现节点大小的调整,节点渲染角度的调整,例如本项目中的Group节点就需要进行大小调整。

import { Graph } from "@antv/x6";
import { Transform } from "@antv/x6-plugin-transform";

export const initPlugins = (graph: Graph) => {
  graph.use(
    new Transform({
      resizing: {
        enabled: node => {
          // 支持分组节点调整大小
          if (node.shape === "group") {
            return true;
          }
          return false;
        },
      },
    }),
  );
};

拖拽

使用@antv/x6-plugin-dnd,通过拖拽交互往画布中添加节点,本项目中需要从流程图组件库中拖拽组件到画布中。

import { Dnd } from "@antv/x6-plugin-dnd";

const ComponentPanel = props => {
    const dndRef = useRef<any>();
    const dndContainerRef = useRef(null);
    useLayoutEffect(() => {
      const _dnd = new Dnd({
        target: graph, //来源全局Graph实例,此处省略
        scaled: false,
        dndContainer: dndContainerRef.current as any,
      });
      dndRef.current = _dnd; 
    }

    const startDrag = useCallback((e: React.MouseEvent<HTMLDivElement, MouseEvent>, data: any) => {
        const target = e.currentTarget;
        const type = target.getAttribute("data-type");
        const nodeConfig: any = {
          shape: type,
          label: data.title,
          data: {
            ...data
          }
        };

        // 此处省略Graph实例获取
        const node = graph.createNode(nodeConfig);

        dndRef.current.start(node, e.nativeEvent as any);
    }, []);

    return (
        <div className="x6-component-panel" ref={dndContainerRef}>
          <CustomComponent startDrag={startDrag} />
        </div>
    );

}

初始化事件

选中/取消选中

监听边的选中和取消选中事件,对应修改线条样式内容。

export const initEvents = (graph: Graph) => {
  graph.on("edge:selected", ({ edge }) => {
    edge.toFront();
    edge.attr({
      line: {
        stroke: Colors.LineActived,
        strokeWidth: 2
      },
    })
  });
  graph.on("edge:unselected", ({ edge }) => {
    edge.attr({
      line: {
        stroke: Colors.Line,
        strokeWidth: Size.LineStrokeWith
      },
    })
  });
}

鼠标移入移出

监听节点的鼠标移入、移出事件,控制链接桩的显示和隐藏

export const initEvents = (graph: Graph) => {

    const showPorts = (ports: NodeListOf<SVGElement>, show: boolean) => {
      for (let i = 0, len = ports.length; i < len; i += 1) {
        ports[i].style.visibility = show ? 'visible' : 'hidden'
      }
    }
    graph.on('node:mouseenter', () => {
      const container = document.getElementById('graph-container')!
      const ports = container.querySelectorAll(
        '.x6-port-body',
      ) as NodeListOf<SVGElement>
      showPorts(ports, true)
    })
    graph.on('node:mouseleave', () => {
      const container = document.getElementById('graph-container')!
      const ports = container.querySelectorAll(
        '.x6-port-body',
      ) as NodeListOf<SVGElement>
      showPorts(ports, false)
    })
}

节点双击

监听节点的双击事件,添加小工具编辑节点名称

export const initEvents = (graph: Graph) => {
  graph.on('node:dblclick', ({ cell, e }) => {
    const name = 'node-editor';
    cell.removeTool(name)
    cell.addTools({
      name,
      args: {
        event: e,
        attrs: {
          backgroundColor: '#fff',
        },
      },
    })
  })
}

接入画布

使用低代码引擎提供的插件API,移除低代码引擎默认的画布,添加自定义开发的画布组件(AntV X6),方式如下:

const PluginX6Designer = (ctx: ILowCodePluginContext) => {
  return {
    init() {
      const { skeleton, project } = ctx;
      skeleton.remove({
        name: 'designer',
        area: 'mainArea',
        type: 'Widget'
      });
      skeleton.add({
        area: 'mainArea',
        name: 'designer',
        type: 'Widget',
        content: X6Designer,
        contentProps: {
          ctx,
        }
      });
    }
  }
}

PluginX6Designer.pluginName = 'plugin-x6-designer';
export default PluginX6Designer;

// 注册X6画布
await plugins.register(PluginX6Designer);

最终效果

涉及业务敏感信息已做模糊处理
在这里插入图片描述

写在最后

基于AntV X6实现流程编排设计器,有如下几个方面的优点:

  1. 低成本定制能力:AntV X6是基于HTML和SVG的图表编辑引擎,提供了丰富的API和自定义事件,可以通过编写JavaScript代码,快速实现自定义的图表样式、布局和交互方式,满足具体业务需求。
  2. 高度可扩展性:AntV X6提供了众多内置扩展,包括DAG图、ER图、流程图等应用,可以帮助你根据自己的需要,自由扩展和定制功能和组件,实现更加灵活的业务需求。
  3. 强大的交互功能:AntV X6提供了丰富的图表组件和交互功能,支持拖拽、连线、缩放等多种交互方式,还可以集成自定义的图表组件和逻辑处理,实现更加丰富的功能需求。

开发过程还是漫长和曲折的,需要不断熟悉查阅官网API,做各种类型和效果的尝试。需要说明的是,在本项目开发时,低代码引擎和X6相结合还没有开源的解决方案,因此在熟悉了两者的文档后,终于探索出一条结合的道路。因此在实现拖拽的交互逻辑中则是采用了AntV X6DND插件。同样也可以采用低代码引擎本身的拖拽机制,有兴趣可以官网了解开源的方案。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/721622.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

漏洞复现 || NginxWebUI 后台命令执行runCmd远程命令执行

免责声明 技术文章仅供参考&#xff0c;任何个人和组织使用网络应当遵守宪法法律&#xff0c;遵守公共秩序&#xff0c;尊重社会公德&#xff0c;不得利用网络从事危害国家安全、荣誉和利益&#xff0c;未经授权请勿利用文章中的技术资料对任何计算机系统进行入侵操作。利用此…

【Linux】yum git 的使用

yum 的使用 yum 源的查看 yum 源就是一个配置文件。 可以看到 yum 源的文件中包含有很多下载源的链接。我们使用 yum 命令进行安装的时候&#xff0c;就是通过这些链接去寻找下载源进行下载的。这些链接一般都是国内的镜像网址。 所谓的更新 yum 源&#xff0c;也就是将最新…

异步交互技术Ajax

Ajax 概念&#xff1a;Asynchronous JavaScr And XML 异步的JavaScript和XML作用&#xff1a; 数据交换&#xff1a;通过Ajax可以给服务器发送请求&#xff0c;并获取服务响应的数据异步交互&#xff1a;可以在不重新加载整个页面的情况下&#xff0c;与服务器交换数据并更新部…

mysqldump + python 定时备份数据库

场景&#xff1a; 需要对mysql进行定时备份&#xff0c;受限于硬盘空间的大小&#xff0c;需要对备份的数据需要定时清理 python代码实现&#xff1a; # -*- coding:UTF-8 -*- """ProjectName : HotelGo2DelonixPmxFileName : fix_missing_ratesDescripti…

第二章:L2JMobius学习 – 安装jdk17

L2JMobius是一套开源的 LineageII 的服务器端代码&#xff0c;使用Java语言编写。想要运行L2JMobius源程序的话&#xff0c;需要安装jdk17版本。首先&#xff0c;下载jdk17版本。 https://download.oracle.com/java/17/latest/jdk-17_windows-x64_bin.msi 当然&#xff0c;也…

华为OD机试真题 Java 实现【新员工座位安排系统】【2022Q4 100分】,附详细解题思路

目录 一、题目描述二、输入描述三、输出描述四、解题思路五、Java算法源码六、效果展示1、输入2、输出3、说明 一、题目描述 工位由序列F1,F2…Fn组成&#xff0c;Fi值为0、1或2。其中0代表空置&#xff0c;1代表有人&#xff0c;2代表障碍物。 1、某一空位的友好度为左右连续…

ModaHub魔搭社区:腾讯云发布的向量数据库有什么特点?技术架构是什么样的?

腾讯云发布的向量数据库有什么特点&#xff1f;技术架构是什么样的&#xff1f; Tencent Cloud VectorDB从性能上看&#xff0c;具备高性能、高可用、低成本等优势&#xff0c;比如单索引支持10亿级向量规模&#xff0c;最快支持毫秒级数据实时更新&#xff0c;适用于AI运算、…

【STM32】自举模式 和 程序下载(ST-Link和串口示例)

STM32官方网站STM32中文社区 如果遇到不清楚的概念&#xff0c;可以看之前的文章。 一、自举模式二、程序下载2.1 概述2.2 实操2.21 SWD 方式下载&#xff08;ST-Link&#xff09;2.22 串口下载 一、自举模式 STM32有一个特殊的功能&#xff0c;就是可以通过不同的方式启动程序…

maven 工程结构 和 archetype 模板

文章目录 一、maven 工程结构1.1. jar 工程结构1.2. war 工程结构 二、archetype 模板2.1. 推荐的 archetype 模板2.2. 自定义 archetype 模板2.2.1 自定义模板示例 有没有好奇过&#xff0c;通过 Maven 生命周期命令构建项目时&#xff0c;我们并没有指定源文件目录和编译后的…

5张图告诉你:同样是职场人,差距怎么这么大?

点赞 ➕ 评论 ➕ 收藏 养成三连好习惯 在职场中&#xff0c;我们常常会听到各种各样的抱怨&#xff1a; &#x1f469;‍⚖️‍ 小A: 凭什么别人每次述职绩效都是优秀呀&#xff1f; 感觉TA也没干啥呀! &#x1f575; 小B: 凭啥这个事情&#xff0c;领导指派TA去对接呀&#…

“设计模式”概述

设计模式代码样例&#xff1a;Git 设计原则 依赖倒置原则&#xff08;DIP&#xff09; 高层模块&#xff08;稳定&#xff09;不应依赖底层模块&#xff08;变化&#xff09;&#xff0c;二者都应依赖于抽象&#xff08;稳定&#xff09;抽象不应依赖于实现细节&#xff0c;…

YoloV5/YoloV7改进---注意力机制:线性上下文变换LCT,性能优于SE

目录 1.LCT介绍 2.LCT引入到yolov5 2.1 加入common.py中&#xff1a; 2.2 加入yolo.py中&#xff1a; 2.3 yolov5s_LCT.yaml 2.4 yolov5s_LCT1.yaml 3.YOLOv5/YOLOv7魔术师专栏介绍 1.LCT介绍 论文&#xff1a; https://arxiv.org/pdf/1909.03834v2.pdf AAAI 2020 摘要&a…

最长上升子序列 (从dp---->贪心)

最长上升子序列 思路&#xff1a; 题目要求找到最长上升的子序列&#xff0c;那么这个时候&#xff0c;我们可以假以第i个为结尾&#xff0c;那么此时以i为结尾的最大上升子序列是多少。但是这个时候&#xff0c;会发现&#xff0c;从第i个为结尾往前面找的话&#xff0c;是一…

20230705点亮STC32G实验箱9.6(STC32G12K128)开发板的跑马灯LED(深入了解)

08第六集&#xff1a;LED闪烁第六集&#xff1a;LED闪烁和花式点灯上和花式点灯上.mp4 09第六集&#xff1a;LED闪烁和花式点灯下.mp4 【大文哥学习32位8051】20230704【冲哥视频】第六集的晶振时钟的学习困惑 2023/7/5 17:36 delay_ms(3000); 刷机的时候如果使用11.0592M的Fos…

Ardupilot学习笔记

参考文献 【1】https://ardupilot.org 主打的就是一个炫酷 自驾仪 Ardupilot 一套开源的自驾仪&#xff0c;集成了各种各样的代码&#xff0c;包括其他开源代码(如PX4代码)和项目、驱动等。 即&#xff1a;自驾仪即集成了整个可以实现无人载具&#xff08;如无人机&#xff…

Day_61-62 决策树

目录 Day_61-62决策树(准备工作) 一. 算法的基本概念 1. 决策树的定义 2. 如何构建决策树&#xff1f; 2.1 熵 2.2 信息增益原则 2.3 计算步骤 二. 示例演示 1. 第一次节点决策分类&#xff1a; 2. 后续节点的决策分类 3. 决策分类的结束条件 三. 代码实现 1. 主函数 2. 两个构…

Matlab把两个不同的x轴和y轴画在同一个图里

我们知道画两个y轴可以用yyaxis. 那么画两个x轴呢? 这时候可以用神奇的tiledlayout. % 创建两组数据 x1 0:0.1:40; y1 4.*cos(x1)./(x12); x2 1:0.2:20; y2 x2.^2./x2.^3;t tiledlayout(1,1); % 创建一个tiledlayout % 第一个坐标系 ax1 axes(t); % 创建坐标系, 指定t为…

Go语言MinGW的安装

Go语言MinGW的安装 相比在 Linux 平台上安装 GCC 编译环境&#xff0c;在 Windows 平台上安装 MinGW 是比较简单的&#xff0c;只需经历以下几个过 程。 1、MINGW32位安装 1、打开 [MinGW 官网] https://osdn.net/projects/mingw/&#xff0c;下载 MinGW 安装包。 点击即可…

个人对于SAR的粗浅理解

个人对于SAR的粗浅理解 有同学问我是做成像的&#xff0c;让我解释一下SAR成像&#xff0c;我思索了一下&#xff0c;决定这样简单回答&#xff1a; 首先SAR的全称为Synthetic Aperture Radar&#xff0c;即合成孔径雷达&#xff0c;本质还是一种Radar 合成孔径&#xff0c;其…

CSS 制作动态蚂蚁线

效果&#xff1a; 代码&#xff1a; <html> <head> <meta http-equiv"Content-Type" content"text/html; charsetUTF-8"> <style type"text/css"> .line{position:relative;margin-bottom: 5px;width: 1200px;height: …