前言

流程编辑器
什么是流程编辑器
流程编辑器是一种用于创建编辑和管理流程图工具。它提供了一个可视化界面使用能够图形化方式定义和配置流程的各个步骤、条件和流程间的关系
流程编辑器通常用于业务流程管理、工作流程管理和业务流程自动化领域。它可以帮助用户轻松地设计和管理复杂的流程,而无需编写大量的代码通过拖拽连接不同图形元素,用户可以定义流程的起始点、结束点、流程分支、条件判断任务执行等。
流程编辑器还通常提供了一些额外功能,如版本控制权限管理、流程模板导入导出等。它可以与其他系统集成以便将流程定义应用于实际的业务场景中。
流程编辑器的目的是简化流程设计和管理的过程提高工作效率,并确保流程的正确性和一致性。它在许多领域中都有广泛的应用,包括项目管理、工作流程自动化电子商务等。

流程编辑器有多种不同的种类,每种都具有不同的特点和用途。以下是一些常见的流程编辑器种类:

  1. 工作流程编辑器(Workflow Editors):用于创建和管理工作流程,包括定义任务、流程分支、条件和工作流程的执行顺序等。
  2. 业务流程管理(BPM)编辑器(Business Process Management Editors):用于设计和管理业务流程,支持复杂的流程建模和流程优化
  3. UML(统一建模语言)编辑器(UML Editors):用于创建和编辑UML图,包括类图时序图、用例图等,用于软件系统的设计和建模
  4. 数据流程编辑器(Data Flow Editors):用于创建和管理数据流程,包括数据输入处理输出流程图
  5. 网络拓扑编辑器(Network Topology Editors):用于设计和管理网络拓扑结构,包括节点连接网络设备的配置。
  6. 流程图编辑器(Flowchart Editors):用于创建和编辑流程图,包括流程的各个步骤、条件和流程控制图形表示
  7. 规则引擎编辑器(Rule Engine Editors):用于创建和管理规则引擎,包括定义规则、条件和规则执行顺序等。
    这只是一些常见的流程编辑器种类,实际上还有许多其他类型的流程编辑器,每种都有其特定的用途功能。具体使用哪种编辑器取决于具体的需求应用场景

我用的是业务流程编辑器(bpmn.js


一、bpmn.js什么

1.bpmn.js简介

bpmn.js一个用于在Web应用程序渲染和编辑BPMN(Business Process Model and Notation流程图的JavaScript库。它提供了一套功能强大的API和工具,可以帮助开发人员应用程序集成BPMN流程图显示和编辑功能。
使用bpmn.js开发人员可以将BPMN流程图嵌入他们应用程序中,并与其它组件进行交互。它支持创建、修改删除BPMN元素,如任务网关事件等,并提供了丰富的事件回调函数以便开发人员可以根据用户操作进行相应的处理
bpmn.js还支持将BPMN流程图导入导出为XML格式以便与其他BPMN工具进行交互共享。它还提供了丰富的样式主题选项,使开发人员可以自定义流程图的外观样式
总的来说,bpmn.js一个强大的工具,可以帮助开发人员在Web应用程序实现BPMN流程图的显示和编辑功能,并与其它组件进行集成
官网https://bpmn.io/.

2.为什么选择bpmn.js

activiti 官方支持的流程编辑器是ActivitiModeler,现在已经停止维护而且如果需要前后分离使用流程编辑器,并不是很友好。

选择使用bpmn.js有以下几个原因

  1. 完整的BPMN支持:bpmn.js是一个专门用于处理BPMN流程图的库,它提供了完整的BPMN规范支持,包括各种BPMN元素、事件和流程控制等。这使得它成为构建和管理BPMN流程图的理想选择
  2. 强大的功能和灵活性:bpmn.js提供了丰富的API和工具,使开发人员可以轻松地创建、修改删除BPMN元素。它还支持导入和导出BPMN流程图,以便与其他BPMN工具进行交互共享。此外,bpmn.js还提供了自定义样式和主题选项,使开发人员可以根据需要自定义流程图的外观和样式。
  3. 跨平台和易于集成bpmn.js是基于JavaScript的库,可以在各种Web应用程序中使用。它与现代Web技术框架(如React、Angular和Vue.js)兼容,并且可以与其他组件和工具进行无缝集成。这使得它非常适合在现有应用程序添加BPMN流程图的显示和编辑功能。
  4. 社区支持和活跃度:bpmn.js拥有庞大的开源社区支持,并且由Camunda等知名公司进行维护和更新。这意味着它有一个活跃的开发者社区,可以提供帮助、解决问题分享经验。
    总而言之,选择使用bpmn.js可以让开发人员轻松地在Web应用程序实现BPMN流程图的显示和编辑功能,并且具有强大的功能、灵活性和跨平台的特点。

二、在vue中集成Bpmn.js

1.下载依赖

简单的一种使用方式:直接使用CDNbpmn.js引入代码

<!DOCTYPE html&gt;
<html lang="en"&gt;

<head&gt;
    <meta charset="UTF-8"&gt;
    <meta name="viewport" content="width=device-width, initial-scale=1.0"&gt;
    <meta http-equiv="X-UA-Compatible" content="ie=edge"&gt;
    <title>BPMNJS</title>
    <!--CDN加速-->
    <script src="https://unpkg.com/bpmn-js@6.0.2/dist/bpmn-viewer.development.js"></script><!--引入一个简单xml字符串-->
    <script src="./xmlStr.js"></script>
    <style>
        #canvas {
            height: 400px;
        }
    </style>
</head>

<body>
    <div id="canvas"></div>
    <script>
        var bpmnJS = new BpmnJS({
            container: '#canvas'
        });
        bpmnJS.importXML(xmlStr, err => {
            if (!err) {
                // 让图能自适应屏幕
                var canvas = bpmnJS.get('canvas')
                canvas.zoom('fit-viewport')
            } else {
                console.log('something went wrong:', err);
            }
        });
    </script>
</body>
</html>

(上面的xmlStr.js就是自定义文件里面放置了关于流程的xml,也可以不引用直接在vue文件中定义)
如上面的案例所示, 我们使用CDN加速直接引入bpmn.js, 然后本地指定一个容器(也就是idcanvas的那个div), 接着用bpmn.js提供的方法importXML就可以解析xml字符串生成对应的工作流图了。

运行代码
在这里插入图片描述
上面提供的使用方式是一种最基本方式,仅仅是将图展示出来,不能自己绘画也不能操作. 所以在工作中使用更多的还是采用npm安装项目中使用. 我们可以使用以下命令进行安装:

使用npm下载

npm install --save bpmn-js

注意: 如果在已有项目引入可能会因为版本问题导致启动失败,最好是看看相对于的版本
这里使用的版本是:

"dependencies": {
    "bpmn-js": "^6.2.1",
    "bpmn-js-properties-panel": "^0.33.1",
    "camunda-bpmn-moddle": "^4.3.0",
    "core-js": "^3.4.4",
    "houtaroy-bpmn-js-properties-panel-activiti": "0.0.1",
    "svg-sprite-loader": "3.7.3",
  },

2.引入样式

安装依赖后,在main.js文件中引入样式:

// bpmn 相关依赖
import 'bpmn-js/dist/assets/diagram-js.css'
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn.css'
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-codes.css'
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css'

// 左边工具栏以及编辑节点的样式
import 'bpmn-js-properties-panel/dist/assets/bpmn-js-properties-panel.css'

新建一个bpmn.vue页面,编写html代码

<template>
  <div id="app">
    <div class="container">
      <!-- 创建一个canvas画布 npmn-js是通过canvas实现绘图的,并设置refvue获取element -->
      <div class="bpmn-container">
        <div class="bpmn-canvas" ref="canvas"></div>
        <!-- 工具栏显示的地方 -->
        <div class="bpmn-js-properties-panel" id="js-properties-panel"></div>
      </div>

      <!-- 把操作按钮写在这里面 -->
      <div class="action">
        <el-button icon="el-icon-download" @click="downloadBpmn" title="下载流程文件"></el-button>
        <el-button icon="el-icon-picture" @click="downloadSvg" title="下载流程图"></el-button>
        <el-button type="success" icon="el-icon-check" circle title="保存修改" @click="editModel"></el-button>
        <a hidden ref="downloadLink"></a>
      </div>
    </div>
  </div>
</template>

编写js代码

<script>
import BpmnModeler from 'bpmn-js/lib/Modeler'
// 工具栏相关
// import propertiesProviderModule from 'bpmn-js-properties-panel/lib/provider/camunda'
import propertiesPanelModule from 'bpmn-js-properties-panel'
// import camundaModdleDescriptor from 'camunda-bpmn-moddle/resources/camunda'
import activitiModdleDescriptor from './activiti.json'
// 引入
import propertiesProviderModule from 'houtaroy-bpmn-js-properties-panel-activiti/lib/provider/activiti'

// 汉化
import customTranslate from './customTranslate.js'

export default {
  data () {
    return {
      modelId: '',
      bpmnModeler: null,
      canvas: null,
      bpmnTemplate: ``
    }
  },
  methods: {
    newDiagram () {
      this.createNewDiagram(this.bpmnTemplate)
    },

	// 下载bpmn xml文件
    downloadBpmn () {
      const that = this
      that.bpmnModeler.saveXML({ format: true }, (err, xml) => {
        if (!err) {
          // 获取文件名
          const name = `${that.getFilename(xml)}.bpmn20.xml`
          // 将文件名以及数据交给下载方法
          that.download({ name: name, data: xml })
        }
      })
    },

	// 下载bpmn.svg流程图片
    downloadSvg () {
      const that = this
      that.bpmnModeler.saveXML({ format: true }, (err, date) => {
        if (!err) {
          // 获取文件名
          const name = `${that.getFilename(date)}.svg`
          // 从建模画布提取svg图形标签
          let context = ''
          const djsGroupAll = that.$refs.canvas.querySelectorAll('.djs-group')
          for (let item of djsGroupAll) {
            context += item.innerHTML
          }
          // 获取svg基本数据,长宽高
          const viewport = that.$refs.canvas
            .querySelector('.viewport')
            .getBBox()

          // 将标签数据拼接成一个完整正常的svg图形
          const svg = `
            <svg
              xmlns='http://www.w3.org/2000/svg'
              xmlns:xlink='http://www.w3.org/1999/xlink'
              width='${viewport.width}'
              height='${viewport.height}'
              viewBox='${viewport.x} ${viewport.y} ${viewport.width} ${viewport.height}'
              version='1.1'
              >
              ${context}
            </svg>
          `
          // 将文件名以及数据交给下载方法
          that.download({ name: name, data: svg })
        }
      })
    },

	// 获取文件名
    getFilename (xml) {
      const regex = /<process.*?id="(.*?)"/
      const match = xml.match(regex)
      if (match) {
        return match[1]
      }
      return null
    },

    // 编辑模型
    editModel () {
      const that = this
      that.bpmnModeler.saveXML({ format: true }, (err, xml) => {
        if (!err) {
            // 获取文件名
          const name = `${that.getFilename(xml)}`
          // // 从建模器画布中提取svg图形标签
          // let context = ''
          // const djsGroupAll = this.$refs.canvas.querySelectorAll('.djs-group')
          // for (let item of djsGroupAll) {
          //   context += item.innerHTML
          // }
          // // 获取svg的基本数据,长宽高
          // const viewport = this.$refs.canvas
          //   .querySelector('.viewport')
          //   .getBBox()
          // // 将标签和数据拼接成一个完整正常的svg图形
          // const svg = `
          //   <svg
          //     xmlns='http://www.w3.org/2000/svg'
          //     xmlns:xlink='http://www.w3.org/1999/xlink'
          //     width='${viewport.width}'
          //     height='${viewport.height}'
          //     viewBox='${viewport.x} ${viewport.y} ${viewport.width} ${viewport.height}'
          //     version='1.1'
          //     >
          //     ${context}
          //   </svg>
          // `
          that.$http({
            url: '',
            method: 'post',
            data: that.$http.adornData({
              modelId: that.modelId,
              name: name,
              bpmnXml: xml,
              svg: '',
              descritpion: ''
            })
          }).then(({ data }) => {
            that.$message({
              message: that.$i18n.t('publics.operation'),
              type: 'success',
              duration: 1500,
              onClose: () => {}
            })
          })
        }
      })
    },
    // 获取流程图数据
    getModel () {
      const that = this
      this.$http({
        url: '',
        method: 'get',
        params: this.$http.adornParams({
          modelId: that.modelId
        })
      }).then(({ data }) => {
        that.bpmnTemplate = '`' + data + '`'
        this.$message({
          message: this.$i18n.t('publics.operation'),
          type: 'success',
          duration: 1500,
          onClose: () => {
            that.init()
          }
        })
      })
    },

    download ({ name = 'diagram.bpmn', data }) {
      // 这里就获取到了之前设置隐藏链接
      const downloadLink = this.$refs.downloadLink
      // 把数据转换为URI,下载要用到
      const encodedData = encodeURIComponent(data)
      if (data) {
        // 将数据给到链接
        downloadLink.href =
          'data:application/bpmn20-xml;charset=UTF-8,' + encodedData
        // 设置文件名
        downloadLink.download = name
        // 触发点击事件开始下载
        downloadLink.click()
      }
    },

    async init () {
      // 获取画布 element
      const that = this
      that.canvas = that.$refs.canvas
      // 将汉化包装成一个模块
      const customTranslateModule = {
        translate: ['value', customTranslate]
      }
      // 创建Bpmn对象
      that.bpmnModeler = new BpmnModeler({
        // 设置bpmn的绘图容器上门获取的画布 element
        container: that.canvas,
        // 加入工具栏支持
        propertiesPanel: {
          parent: '#js-properties-panel'
        },
        additionalModules: [
          // 工具栏模块
          propertiesProviderModule,
          propertiesPanelModule,
          // 汉化模块
          customTranslateModule
        ],
        moddleExtensions: {
          activiti: activitiModdleDescriptor
        }
      })
      await that.createNewDiagram(that.bpmnTemplate)
    },

    clearBpmn () {
      this.bpmnModeler.clear()
    },

    async createNewDiagram (bpmnTemplate) {
      const that = this
      // 将字符串转换成图显示出来;
      this.bpmnModeler.importXML(bpmnTemplate, err => {
        if (err) {
          that.$Message.error('打开模型出错,请确认该模型符合Bpmn2.0规范')
        } else {
          // 让图能自适应屏幕
          var canvas = that.bpmnModeler.get('canvas')
          canvas.zoom('fit-viewport')
        }
      })
    }
  },
  created () {
    this.getModel()
    // // 删除 bpmn logo  bpmn.io官方要求不给删或者隐藏,否则侵权   内部使用
    // const bjsIoLogo = document.querySelector('.bjs-powered-by')
    // while (bjsIoLogo.firstChild) {
    //   bjsIoLogo.removeChild(bjsIoLogo.firstChild)
    // }
  },
  beforeDestroy () {
    this.clearBpmn()
  }
}
</script>

编写styly样式


<style>
.bpmn-container {
  width: 100%;
  height: 100vh;
  display: flex;
}

.bpmn-canvas {
  width: calc(100% - 300px);
  height: 100vh;
}

.bpmn-js-properties-panel {
  width: 320px;
  height: inherit;
  overflow-y: auto;
}

.action {
  position: fixed;
  bottom: 40px;
  left: 800px;
  display: flex;
}
</style>

这里需要注意我的代码中,在import导入camunda时我注释了,是因为Bpmn.js默认支持的是camunda,而我的后端使用的是activiti,两者是不兼容的。所以需要丢弃camunda,换成activiti

下载activiti 插件

 "houtaroy-bpmn-js-properties-panel-activiti": "0.0.1",

更换对应引入的camunda
在这里插入图片描述
在这里插入图片描述
汉化包:customTranslate.js+translationsGerman

import translations from './translationsGerman'

export default function customTranslate (template, replacements) {
  replacements = replacements || {}

  // Translate
  template = translations[template] || template

  // Replace
  return template.replace(/{([^}]+)}/g, function (_, key) {
    var str = replacements[key]
    if (
      translations[replacements[key]] !== null &amp;&amp;
      translations[replacements[key]] !== 'undefined'
    ) {
      str = translations[replacements[key]]
    }
    return str || '{' + key + '}'
  })
}
export default {
  // Labels
  'Activate the global connect tool': '激活全局连接工具',
  'Append {type}': '追加 {type}',
  'Append EndEvent': '追加 结束事件 ',
  'Append Task': '追加 任务',
  'Append Gateway': '追加 网关',
  'Append Intermediate/Boundary Event': '追加 中间/边界 事件',
  'Add Lane above': '在上面添加道',
  'Divide into two Lanes': '分割两个道',
  'Divide into three Lanes': '分割成三个道',
  'Add Lane below': '在下面添加道',
  'Append compensation activity': '追加补偿活动',
  'Change type': '修改类型',
  'Connect using Association': '使用关联连接',
  'Connect using Sequence/MessageFlow or Association': '使用顺序/消息流或者关联连接',
  'Connect using DataInputAssociation': '使用数据输入关联连接',
  'Remove': '移除',
  'Activate the hand tool': '激活抓手工具',
  'Activate the lasso tool': '激活套索工具',
  'Activate the create/remove space tool': '激活创建/删除空间工具',
  'Create expanded SubProcess': '创建扩展过程',
  'Create IntermediateThrowEvent/BoundaryEvent': '创建中间抛出事件/边界事件',
  'Create Pool/Participant': '创建池/参与者',
  'Parallel Multi Instance': '并行多重事件',
  'Sequential Multi Instance': '时序多重事件',
  'DataObjectReference': '数据对象参考',
  'DataStoreReference': '数据存储参考',
  'Loop': '循环',
  } // 这里只是部分的汉化,多的就不写出来了,如果有需要的可以去网上找找有很多

然后还有activiti.json这个更换activiti不可少的,可以看看示例

{
    "name": "Activiti",
    "uri": "http://activiti.org/bpmn",
    "prefix": "activiti",
    "xml": {
      "tagAlias": "lowerCase"
    },
    "associations": [],
    "types": [
      {
        "name": "Process",
        "isAbstract": true,
        "extends": [
          "bpmn:Process"
        ],
        "properties": [
          {
            "name": "diagramRelationId",
            "isAttr": true,
            "type": "String"
          }
        ]
      },
      {
        "name": "InOutBinding",
        "superClass": [
          "Element"
        ], // 就是将camunda用activiti替换掉,还有挺多的无法全部展示

引入完成后就可以看看流程编辑器的样子。
在这里插入图片描述
这里一个完整bpmn.js就引入完成了,下面再讲讲bpmn.js的事件以及监听器


三,bpmn.js事件

这里主要是说明关于bpmn.js的一些事件, 通过章节你可以了解到:

1,监听modeler并绑定事件

有些时候我们期望的是在用户在进行不同操作的时候能够监听到他操作的是什么, 从而做想要做的事情.

是进行了shape新增还是进行了线的新增.

比如如下的一些监听事件:

继续在项目案例bpmn.vue的基础上创建一个event.vue文件:

// event.vue
<script>
...
success () {
  this.addModelerListener()
},
// 监听 modeler
addModelerListener() {
  const bpmnjs = this.bpmnModeler
  const that = this
  // 这里我是用了一个forEachmodeler上添加绑定的事件
  const events = ['shape.added', 'shape.move.end', 'shape.removed', 'connect.end', 			'connect.move']
  events.forEach(function(event) {
    that.bpmnModeler.on(event, e => {
      console.log(event, e)
      var elementRegistry = bpmnjs.get('elementRegistry')
      var shape = e.element ? elementRegistry.get(e.element.id) : e.shape
      console.log(shape)
    })
  })
},

然后就可以获取到相关节点信息
在这里插入图片描述

其实具体有哪些事件我在官网上没有找到说明, 以上只是我在查找到bpmn.io/diagram.js/…文件之后, 取的一些我项目里有用到的事件.

2,监听element并绑定事件

上面介绍的是监听modeler并绑定事件, 可能你也需要监听用户点击图形上的element或者监听某个element改变:

继续在success()添加监听事件:

// event.vue
<script>
...
success () {
	...
	this.addEventBusListener()
},
addEventBusListener () {
	let that = this
  const eventBus = this.bpmnModeler.get('eventBus') // 需要使用eventBus
  const eventTypes = ['element.click', 'element.changed'] // 需要监听的事件集合
  eventTypes.forEach(function(eventType) {
    eventBus.on(eventType, function(e) {
      console.log(e)
    })
  })
}
</script>

配置好addEventBusListener()函数后, 在进行元素的点击、新增移动删除的时候都能监听到了.
但是有一点很不好, 你在点击“画布”的时候, 也就是根元素可能会触发此事件, 我们一般都不希望此时会触发, 因此我们可以在on回调中添加一些判断, 来避免掉不需要的情况:

eventBus.on(eventType, function(e) {
  if (!e || e.element.type == 'bpmn:Process') return // 这里我的根元素是bpmn:Process
  console.log(e)
})

此时我们可以把监听到返回节点信息打印出来看看:
在这里插入图片描述
如上图, 它会打印出该节点Shape信息DOM信息等, 但我们可能关注Shape信息(也就是该节点idtype等等信息), 此时我们可以使用elementRegistry来获取Shape信息:

eventBus.on(eventType, function(e) {
  if (!e || e.element.type == 'bpmn:Process') return // 这里我的根元素是bpmn:Process
  console.log(e)
  var elementRegistry = this.bpmnModeler.get('elementRegistry')
  var shape = elementRegistry.get(e.element.id) // 传递id进去
  console.log(shape) // {Shape}
  console.log(e.element) // {Shape}
  console.log(JSON.stringify(shape)===JSON.stringify(e.element)) // true
})

或者你也可以直接就用e.element获取到Shape的信息, 我比较了一下它们两是一样的. 但是官方推荐使用elementRegistry方式.

3.通过监听事件判断操作方式

上面我们已经介绍modelerelement的监听绑定方式, 在事件应用中, 你更多的需要知道用户要进行什么操作, 好写对应的业务逻辑.

这里就以工作中要用到场景案例进行讲解.

// event.vue
    ...
    success () {
      this.addModelerListener()
      this.addEventBusListener()
    },
    // 添加绑定事件
    addBpmnListener () {
      const that = this
      // 获取a标签dom节点
      const downloadLink = this.$refs.saveDiagram
      const downloadSvgLink = this.$refs.saveSvg
        // 给图绑定事件,当图有发生改变就会触发这个事件
      this.bpmnModeler.on('commandStack.changed', function () {
        that.saveSVG(function(err, svg) {
            that.setEncoded(downloadSvgLink, 'diagram.svg', err ? null : svg)
        })
        that.saveDiagram(function(err, xml) {
            that.setEncoded(downloadLink, 'diagram.bpmn', err ? null : xml)
        })
      })
    },
    addModelerListener() {
      // 监听 modeler
      const bpmnjs = this.bpmnModeler
      const that = this
      // 'shape.removed', 'connect.end', 'connect.move'
      const events = ['shape.added', 'shape.move.end', 'shape.removed']
      events.forEach(function(event) {
        that.bpmnModeler.on(event, e => {
          var elementRegistry = bpmnjs.get('elementRegistry')
          var shape = e.element ? elementRegistry.get(e.element.id) : e.shape
          // console.log(shape)
          if (event === 'shape.added') {
            console.log('新增了shape')
          } else if (event === 'shape.move.end') {
            console.log('移动了shape')
          } else if (event === 'shape.removed') {
            console.log('删除了shape')
          }
        })
      })
    },
    addEventBusListener() {
      // 监听 element
      let that = this
      const eventBus = this.bpmnModeler.get('eventBus')
      const eventTypes = ['element.click', 'element.changed']
      eventTypes.forEach(function(eventType) {
        eventBus.on(eventType, function(e) {
          if (!e || e.element.type == 'bpmn:Process') return
          if (eventType === 'element.changed') {
            that.elementChanged(eventType, e)
          } else if (eventType === 'element.click') {
            console.log('点击了element')
          }
        })
      })
    },
    elementChanged(eventType, e) {
      var shape = this.getShape(e.element.id)
      if (!shape) {
        // 若是shape为null则表示删除, 无论是shape还是connect删除都调用此处
        console.log('无效的shape')
        // 由于上面已经用 shape.removed 检测了shape的删除, 因此这里只判断是否是线
        if (this.isSequenceFlow(shape.type)) {
          console.log('删除了线')
        }
      }
      if (!this.isInvalid(shape.type)) {
        if (this.isSequenceFlow(shape.type)) {
          console.log('改变了线')
        }
      }
    },
    getShape(id) {
      var elementRegistry = this.bpmnModeler.get('elementRegistry')
      return elementRegistry.get(id)
    },
    isInvalid (param) { // 判断是否是无效的值
      return param === null || param === undefined || param === ''
    },
    isSequenceFlow (type) { // 判断是否是线
      return type === 'bpmn:SequenceFlow'
    }

更多关于bpmn.js的学习,可以看看这个大佬写的:
系列相关推荐:
《全网最详bpmn.js教材-基础篇》
《全网最详bpmn.js教材-http请求篇》
《全网最详bpmn.js教材-renderer篇》
《全网最详bpmn.js教材-contextPad篇》
《全网最详bpmn.js教材-编辑、删除节点篇》

原文地址:https://blog.csdn.net/m0_71621983/article/details/131824421

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任

如若转载,请注明出处:http://www.7code.cn/show_34560.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱suwngjj01@126.com进行投诉反馈,一经查实,立即删除!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注