什么是 bpmn-js ?
一个BPMN 2.0 渲染工具,简单来说,就是流程设计器。前端利用这样的一个流程设计器,设计流程,导出 xml,然后传给后端。启动 activities工作流引擎,最后生成流程实例。 官网地址:https://bpmn.io/toolkit/bpmn-js/ Github地址:https://github.com/bpmn-io
流对象
一个业务流程图有三个流对象的核心元素
- 事件
- 一个事件用圆圈来描述,表示一个业务流程期间发生的东西
- 事件影响流程的流动.一般有一个原因(触发器)或一个影响(结果)
- 基于它们对流程的影响,有三种事件:开始事件,中间事件,终止事件
- 活动
- 用圆角矩形表示,一个流程由一个活动或多个活动组成
- 条件
- 条件用菱形表示,用于控制序列流的分支与合并。
- 可以作为选择,包括路径的分支与合并
- 内部的标记会给出控制流的类型
- 在项目内安装 bpmn-js
npm install bpmn-js --save
- 使用 bpmn-js
palette 左侧工具栏
中间绘画区域
properties-panel 右侧属性栏
使用左侧工具栏, 需要在项目中引用相应的样式:
// 以下为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' // 右边工具栏样式
开发属性面板,还会涉及到自定义标签的插入、人员配置、会签属性以及条件表达式等
将基础api , 高级特性刷一遍的
- 添加自定义元素
- 自定义调色板/上下文板
- 自定义形状渲染
项目简介
一个基于 bpmn.js,Vue 2.x 和 ElementUI 开发的流程设计器。 Bpmn Process Designer
支持监听器,扩展属性,表单等配置,可自由扩展
文档说明 Documentation
Attributes
Attribute | Description | Type | Accepted Values | Default |
---|---|---|---|---|
value/v-model | 初始化流程对应的xml字符串 | String | - | - |
translations | 翻译文件 | Object | - | zh.js |
additionalModel | 自定义的附加模块 | Object[] / Object | - | - |
moddleExtension | 自定义的扩展模块 | Object | - | - |
onlyCustomizeAddi | 仅使用开发时的自定义附加模块 | Boolean | - | false |
onlyCustomizeModdle | 仅使用开发时的自定义扩展模块 | Boolean | - | false |
prefix | 流程引擎对应扩展属性前缀 | String | camunda,activiti,flowable | camunda |
events | 需要使用的事件列表,可用事件见Bpmn.js 中文文档 | Array | - | [element.click] |
headerButtonSize | 头部按钮组的大小 | String | “default”, “medium”, “small”, “mini” | small |
功能说明
- 工具栏:包含常见操作,比如打开文件、下载文件、预览、对齐方式、缩放管理、撤销删除等
- 常规信息:id、名称、扩展属性、元素文档
- 特殊节点属性:
- 流程全局消息与信号
- 执行监听器
- 用户任务节点 任务监听器
- 表单配置
- 任务配置
- 多实例任务
- 流转条件
- 内置常用camunda,flowable,activiti解析文件
- 自定义左侧元素栏platte与弹出菜单contentPad示例模块
- 自定义渲染方法 renderer 模块实例
通过http请求获取数据并渲染
监听modeler并绑定事件
在用户在进行不同操作的时候能够监听到他操作的是什么, 从而做想要做的事情.
是进行了shape的新增还是进行了线的新增.
比如如下的一些监听事件:
- shape.added 新增一个shape之后触发;
- shape.move.end 移动完一个shape之后触发;
- shape.removed 删除一个shape之后触发;
并在success()函数中添加上监听事件的函数:
// 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)
})
})
},
监听element并绑定事件
监听用户点击图形上的element或者监听某个element改变:
- element.click 点击元素;
- element.changed 当元素发生改变的时候(包括新增、移动、删除元素)
继续在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
- 新增了线(connection)
- 删除了shape和connection
- 移动了shape和线
```javascript
… success () {// event.vue
}, // 添加绑定事件 addBpmnListener () {this.addModelerListener() this.addEventBusListener()
}, addModelerListener() {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) }) })
}, addEventBusListener() {// 监听 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') } }) })
}, elementChanged(eventType, e) {// 监听 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') } }) })
}, getShape(id) {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('改变了线') } }
}, isInvalid (param) { // 判断是否是无效的值var elementRegistry = this.bpmnModeler.get('elementRegistry') return elementRegistry.get(id)
}, isSequenceFlow (type) { // 判断是否是线return param === null || param === undefined || param === ''
}return type === 'bpmn:SequenceFlow'
** 实际开发过程中,点击某个类型为bpmn:Task的节点的时候, 修改它的name属性,应该会这么做的:**
- 给节点添加点击事件
- 判断节点类型为bpmn:Task , 只对这种类型的节点做后续处理
- 使用updateProperties更新name
```javascript
createNewDiagram () {
// 将字符串转换成图显示出来
this.bpmnModeler.importXML(xmlStr, (err) => {
if (err) {
// console.error(err)
} else {
// 这里是成功之后的回调, 可以在这里做一系列事情
this.success()
}
})
},
success () {
this.addModelerListener() // 添加监听事件
},
addModelerListener () {
const eventBus = this.bpmnModeler.get('eventBus')
const modeling = this.bpmnModeler.get('modeling')
const elementRegistry = this.bpmnModeler.get('elementRegistry');
const eventTypes = ['element.click', 'element.changed'];
eventTypes.forEach(function(eventType) {
eventBus.on(eventType, function (e) {
if (!e || !e.element) {
console.log('无效的e', e)
return
}
if (eventType === 'element.click') {
console.log('点击了element', e)
var shape = e.element ? elementRegistry.get(e.element.id) : e.shape
if (shape.type === 'bpmn:Task') {
modeling.updateProperties(shape, {
name: '我是修改后的Task名称'
})
}
}
})
})
}
当然你也可以一次性修改多个属性
modeling.updateProperties(startEventElement, {
name: '我是修改后的虚线节点',
isInterrupting: false
})
通过查找前面的 meta-model descriptor 知道StartEvent还有一个isInterrupting属性, 于是试着修改它, 结果竟然成功了, 将开始节点变为了虚线为边框的节点
bpmnModeler5.png
当然你也可以加一些除了meta-model descriptor里的一些自定义属性:
modeling.updateProperties(shape, {
name: '我是修改后的虚线节点',
isInterrupting: false,
customText: '我是自定义的text属性'
})
自定义Renderer
额外功能
- 获取流程图最外层的属性(根节点)
const rootElements = this.bpmnModeler.getDefinitions().rootElements[0];