javascript技巧

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript技巧 > activiti 流程编辑器

Bpmn.js activiti 流程编辑器详细教程

作者:百度攻城狮

流程编辑器是一种用于创建、编辑和管理流程图的工具,它提供了一个可视化的界面,使用户能够以图形化的方式定义和配置流程的各个步骤、条件和流程间的关系,说明关于bpmn.js的一些事件, 通过本文你可以了解到,感兴趣的朋友一起看看吧

前言

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

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

我用的是业务流程编辑器(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有以下几个原因:

二、在vue中集成Bpmn.js

1.下载依赖

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

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <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实现绘图的,并设置ref让vue获取到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 &&
      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
  // 这里我是用了一个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改变:

继续在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 activiti 流程编辑器详细教程的文章就介绍到这了,更多相关activiti 流程编辑器内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文