0


【Bpmn.js】activiti 流程编辑器

文章目录


前言

流程编辑器

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

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

  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.下载依赖

最简单的一种使用方式:直接使用

CDN

bpmn.js

引入到代码中

<!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><metaname="viewport"content="width=device-width, initial-scale=1.0"><metahttp-equiv="X-UA-Compatible"content="ie=edge"><title>BPMNJS</title><!--CDN加速--><scriptsrc="https://unpkg.com/[email protected]/dist/bpmn-viewer.development.js"></script><!--引入一个简单的xml字符串--><scriptsrc="./xmlStr.js"></script><style>#canvas{height: 400px;}</style></head><body><divid="canvas"></div><script>var bpmnJS =newBpmnJS({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

, 然后本地指定一个容器(也就是

id

canvas

的那个

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><divid="app"><divclass="container"><!-- 创建一个canvas画布 npmn-js是通过canvas实现绘图的,并设置ref让vue获取到element --><divclass="bpmn-container"><divclass="bpmn-canvas"ref="canvas"></div><!-- 工具栏显示的地方 --><divclass="bpmn-js-properties-panel"id="js-properties-panel"></div></div><!-- 把操作按钮写在这里面 --><divclass="action"><el-buttonicon="el-icon-download"@click="downloadBpmn"title="下载流程文件"></el-button><el-buttonicon="el-icon-picture"@click="downloadSvg"title="下载流程图"></el-button><el-buttontype="success"icon="el-icon-check"circletitle="保存修改"@click="editModel"></el-button><ahiddenref="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'exportdefault{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]}returnnull},// 编辑模型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 =thisthis.$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()}},asyncinit(){// 获取画布 elementconst that =this
      that.canvas = that.$refs.canvas
      // 将汉化包装成一个模块const customTranslateModule ={translate:['value', customTranslate]}// 创建Bpmn对象
      that.bpmnModeler =newBpmnModeler({// 设置bpmn的绘图容器为上门获取的画布 elementcontainer: that.canvas,// 加入工具栏支持propertiesPanel:{parent:'#js-properties-panel'},additionalModules:[// 工具栏模块
          propertiesProviderModule,
          propertiesPanelModule,// 汉化模块
          customTranslateModule
        ],moddleExtensions:{activiti: activitiModdleDescriptor
        }})await that.createNewDiagram(that.bpmnTemplate)},clearBpmn(){this.bpmnModeler.clear()},asynccreateNewDiagram(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'exportdefaultfunctioncustomTranslate(template, replacements){
  replacements = replacements ||{}// Translate
  template = translations[template]|| template

  // Replacereturn template.replace(/{([^}]+)}/g,function(_, key){var str = replacements[key]if(
      translations[replacements[key]]!==null&&
      translations[replacements[key]]!=='undefined'){
      str = translations[replacements[key]]}return str ||'{'+ key +'}'})}
exportdefault{// 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

的一些事件, 通过此章节你可以了解到:

  • 监听modeler并绑定事件
  • 监听element并绑定事件
  • 通过监听事件判断操作方式

1,监听modeler并绑定事件

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

是进行了

shape

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

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

  • shape.added 新增一个shape之后触发;
  • shape.move.end 移动完一个shape之后触发;
  • shape.removed 删除一个shape之后触发;

继续在项目案例

bpmn.vue

的基础上创建一个

event.vue

文件:

// event.vue<script>...success(){this.addModelerListener()},// 监听 modeleraddModelerListener(){const bpmnjs =this.bpmnModeler
  const that =this// 这里我是用了一个forEach给modeler上添加要绑定的事件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改变:

  • element.click 点击元素;
  • element.changed 当元素发生改变的时候(包括新增、移动、删除元素)

继续在

success()

上添加监听事件:

// event.vue<script>...success(){...this.addEventBusListener()},addEventBusListener(){let that =thisconst eventBus =this.bpmnModeler.get('eventBus')// 需要使用eventBusconst 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

信息(也就是该节点的

id

type

等等信息), 此时我们可以使用

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.通过监听事件判断操作方式

上面我们已经介绍了

modeler

element

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

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

  • 新增了shape
  • 新增了线(connection)
  • 删除了shape和connection
  • 移动了shape和线
// 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(){// 监听 modelerconst 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')}elseif(event ==='shape.move.end'){
            console.log('移动了shape')}elseif(event ==='shape.removed'){
            console.log('删除了shape')}})})},addEventBusListener(){// 监听 elementlet that =thisconst 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')returnif(eventType ==='element.changed'){
            that.elementChanged(eventType, e)}elseif(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
版权归原作者 百度攻城狮 所有, 如有侵权,请联系我们删除。

“【Bpmn.js】activiti 流程编辑器”的评论:

还没有评论