Web工作流(流程图)设计器的方案研究及功能实现之bpmn-js简易教程
前言
本文以2020年10月为时间节点,功能早就做了,但文章一直没有写
研究过程
根据项目需求,需要实现一个工作流/流程图设计器,并且可配置流转、活动节点、流程的各项属性,也是研究了多个方案
自研方案
使用svg方案(使用 svg.js库)来实现流程图的绘制,图形的拖拽、旋转、缩放、属性面板基本都实现了,但是无法解决连接线绘制的功能,后面尝试使用d3和jsplumb库来解决连接线的绘制,效果不是很好,研发时间给的不够、人手也不够,最后就放弃了
- svg.js+d3.js
- svg.js+jsplumb.js
半成品界面如下:
开源方案
- VFD: 这应该是我找到的第一个工作流设计器成品,基于jsplumb开发,但是封装的不太好,逻辑都在vue组件中,最后没有用此方案
- svgedit: 一个svg编辑器,本来打算在此基础上二次开发,但是没文档,代码量还有点大,研究了几天没找到入口点,最后放弃此方案
- flowchart : 一位个人作者基于d3.js实现的,长时间没有维护了,不满足需求
- antv-xflow: 依稀记得当时是在ant-design官网中看到的一个组件例子,当时还没有这么强大,也不知道叫这个名字,所以也没有仔细研究,最近写文章的时候看了下demo,感觉不错,貌似只支持react不支持vue?
- bpmn-js: 这个库在最开始研究的时候就看到过,看了demo觉得功能比较完善,因为几乎没有文档被我忽略了,在一次交流中后端同事说准备采用activiti和camunda这两个开源流程引擎做参考来实现自研工作流引擎(这两个引擎的前端都是基于bpmn-js开发),最后还是硬着头皮使用这个方案了
- bpmn-process-designer:我之前基于bpmn-js开发好的设计器1.0版本ui不太好看,这是今年在开发2.0版本的时候才发现的,一个基于bpmn-js+element-ui实现的工作流设计器,做的挺好的,有参考它界面来做2.0版本
最终成品界面如下
1.0:
2.0:
直接使用现有开源引擎
没有过多了解,只举例一些比较出名的开源库,听后端同事说这几个库大同小异
- activiti
- flowable
- camunda
- 各引擎对比见此
bpmn-js是什么
bpmn-js是一个基于bpmn规范的流程图设计器js库,在diagram-js库的基础上开发(图片来源:PL-FE)
bpmn-js官网基本没有提供文档,只有官方例子,所以学习起来比较吃力:
- 官网
- github
- bpmn-js-examples
- 官方简单介绍
这里推荐这位程序媛写的的教程bpmn-chinese-document,写的比较详细的,也感谢这位大佬的总结和分享
本文使用到各库的版本为:bpmn-js@8.8.1,bpmn-js-properties-panel@0.46.0,camunda-bpmn-moddle@6.1.2
相关代码上传到了gitee
基础使用
安装
npm install bpmn-js@8.8.1 -S
bpmn库导出模块
- Viewer(lib/Viewer): BPMN 图表查看器,功能简单,只能用于展示
- NavigatedViewer(lib/NavigatedViewer): BPMN 图表导航查看器,继承Viewer ,包含MoveCanvasModule(鼠标导航)、KeyboardMoveModule(键盘导航)、ZoomScrollModule(缩放滚动)工具的图表查看器
- Modeler(lib/Modeler): BPMN 图表建模器,融合Viewer、NavigatedViewer类,有元素对齐、工具栏、属性面板等,实现建模能力
BPMN2.0规范的xml结构
上面所展示的流程图,其xml结构如下:
<bpmn:definitions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_1oszlza" targetNamespace="http://bpmn.io/schema/bpmn"><bpmn:process id="Process_0qbzwnl" name="测试" isExecutable="true"><bpmn:startEvent id="StartEvent_1142pjw" name="开始"><bpmn:outgoing>Flow_0udz675</bpmn:outgoing></bpmn:startEvent><bpmn:task id="Activity_1umsb7z" name="审批"><bpmn:incoming>Flow_0udz675</bpmn:incoming><bpmn:outgoing>Flow_1fwu6d6</bpmn:outgoing></bpmn:task><bpmn:sequenceFlow id="Flow_0udz675" sourceRef="StartEvent_1142pjw" targetRef="Activity_1umsb7z" /><bpmn:task id="Activity_11qrnub" name="执行"><bpmn:incoming>Flow_1fwu6d6</bpmn:incoming><bpmn:outgoing>Flow_0gs9y2g</bpmn:outgoing></bpmn:task><bpmn:sequenceFlow id="Flow_1fwu6d6" sourceRef="Activity_1umsb7z" targetRef="Activity_11qrnub" /><bpmn:endEvent id="Event_09ptfxq" name="结束"><bpmn:incoming>Flow_0gs9y2g</bpmn:incoming></bpmn:endEvent><bpmn:sequenceFlow id="Flow_0gs9y2g" sourceRef="Activity_11qrnub" targetRef="Event_09ptfxq" /></bpmn:process><bpmndi:BPMNDiagram id="BPMNDiagram_1"><bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_0qbzwnl"><bpmndi:BPMNEdge id="Flow_0udz675_di" bpmnElement="Flow_0udz675"><di:waypoint x="209" y="120" /><di:waypoint x="260" y="120" /></bpmndi:BPMNEdge><bpmndi:BPMNEdge id="Flow_1fwu6d6_di" bpmnElement="Flow_1fwu6d6"><di:waypoint x="360" y="120" /><di:waypoint x="420" y="120" /></bpmndi:BPMNEdge><bpmndi:BPMNEdge id="Flow_0gs9y2g_di" bpmnElement="Flow_0gs9y2g"><di:waypoint x="520" y="120" /><di:waypoint x="582" y="120" /></bpmndi:BPMNEdge><bpmndi:BPMNShape id="StartEvent_1142pjw_di" bpmnElement="StartEvent_1142pjw"><dc:Bounds x="173" y="102" width="36" height="36" /><bpmndi:BPMNLabel><dc:Bounds x="180" y="145" width="22" height="14" /></bpmndi:BPMNLabel></bpmndi:BPMNShape><bpmndi:BPMNShape id="Activity_1umsb7z_di" bpmnElement="Activity_1umsb7z"><dc:Bounds x="260" y="80" width="100" height="80" /></bpmndi:BPMNShape><bpmndi:BPMNShape id="Activity_11qrnub_di" bpmnElement="Activity_11qrnub"><dc:Bounds x="420" y="80" width="100" height="80" /></bpmndi:BPMNShape><bpmndi:BPMNShape id="Event_09ptfxq_di" bpmnElement="Event_09ptfxq"><dc:Bounds x="582" y="102" width="36" height="36" /><bpmndi:BPMNLabel><dc:Bounds x="589" y="145" width="22" height="14" /></bpmndi:BPMNLabel></bpmndi:BPMNShape></bpmndi:BPMNPlane></bpmndi:BPMNDiagram>
</bpmn:definitions>
创建一个流程图实例
可以使用createDiagram方法创建一个流程图
import Modeler from 'bpmn-js/lib/Modeler'let bpmnModeler = new Modeler({container: "#bpmn-canvas",
})bpmnModeler.createDiagram()
但以上方式存在一个缺陷,createDiagram方式实际是调用了importXML传入一个常量xml字符串,会导致元素ID重复,源码截图如下:
优化后的方法如下,仅供参考:
function createDiagram(bpmnModeler, processId) {let moddle = bpmnModeler.get('moddle'),processId = moddle.ids.next(),startEventId = moddle.ids.next()return bpmnModeler.importXML(`<?xml version="1.0" encoding="UTF-8"?><bpmn:definitions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" targetNamespace="http://bpmn.io/schema/bpmn" id="Definitions_${moddle.ids.next()}"><bpmn:process id="Process_${processId}" isExecutable="true"><bpmn:startEvent id="StartEvent_${startEventId}"/></bpmn:process><bpmndi:BPMNDiagram id="BPMNDiagram_1"><bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_${processId}"><bpmndi:BPMNShape id="StartEvent_${startEventId}_di" bpmnElement="StartEvent_${startEventId}"><dc:Bounds height="36.0" width="36.0" x="173.0" y="102.0"/></bpmndi:BPMNShape></bpmndi:BPMNPlane></bpmndi:BPMNDiagram></bpmn:definitions>`)
}
界面布局结构
- palette(工具栏) :提供拖拽工具、框选工具、连线工具、基本图元等
- contextPad(上下文面板):可以理解为快捷面板
- propertiesPanel(属性面板):定义流程图中图形元素属性
- shape(图形) 是所有图形的基类(比如Connection,Root)
图片来源:PL-FE
导入与导出
导入
// 异步方式(推荐)
let result = await bpmnModeler.importXML(xml)// 回调方式
bpmnModeler.importXML(xml, (result) => {} )
导入的生命周期事件如下:
- import.parse.start (即将从xml读取模型)
- import.parse.complete (模型读取完成)
- import.render.start (图形导入开始)
- import.render.complete (图形导入完成)
- import.done (一切都完成)
然而在import.done之后还会触发shape.added,我觉得这是一个bug,后续会讲到如何处理
导出
- 导出xml
// 异步方式
let { xml } = await bpmnModeler.saveXML()// 回调方式
bpmnModeler.saveXML({ format: false },({ xml }) => {})// 格式化导出的xml
let { xml } = await bpmnModeler.saveXML({ format: true })
- 导出svg
// 异步方式
let { svg } = await bpmnModeler.saveSVG()// 回调方式
bpmnModeler.saveXML(( { svg } )=>{ })
内部模块/供应商/服务
diagram-js提供了一个get方法来获取器内部模块(有些文章叫核心服务、其内部方法定义英文为_providers),简单介绍一下几个常用模块经常用到的方法
- 获取一个模块
// 第一个参数为模块名称,第二参数表示是否严格模式
bpmnModeler.get("模块名称",false)
- eventBus - 事件总线,管理bpmn实例中所有事件
- canvas - 画布,管理svg元素、连线/图形的添加/删除、缩放等
- commandStack - 命令堆栈,管理bpmn内部所有命令操作,提供撤销、重做功能等
- elementRegistry - 元素注册表,管理bpmn内部所有元素
- moddle - 模型管理,用于管理bpmn的xml结构
- modeling - 建模器,绘图时用到,提供用于更新画布上元素的 API(移动、删除)
事件总线 - eventBus
- 获取事件总线模块
let eventBus = bpmnModeler.get("eventBus")
- 监听事件
// 监听事件
eventBus.on('element.changed', (ev) => {})// 监听多个事件
eventBus.on(['shape.added', 'connection.added', 'shape.removed', 'connection.removed'],(ev) => { }
)// 设置优先级
eventBus.on('element.changed', 100, (ev) => {})// 传入上下文
eventBus.on('element.changed', (ev) => {}, that)// 使用所有参数
eventBus.on('事件名称', 优先级(可选), 回调函数, 上下文(可选))
- 只监听一次事件
// 用法同on
eventBus.once('事件名称', 优先级(可选), 回调函数, 上下文(可选))
- 取消监听事件
// 取消监听
eventBus.off('element.changed', callback)// 取消监听多个事件
eventBus.off(['shape.added', 'connection.added', 'shape.removed', 'connection.removed'], callback)
- 触发事件
eventBus.fire('element.changed', data)
bpmn内部事件非常之多,我这里举例几个常用事件:
导入导出相关
‘import.parse.start’
‘import.parse.complete’
‘import.render.start’
‘import.render.complete’
‘import.done’
‘saveXML.start’
‘saveXML.serialized’
‘saveXML.done’
‘saveSVG.start’
‘saveSVG.done’画布相关
‘canvas.destroy’
‘canvas.init’
‘canvas.resized’
‘canvas.viewbox.changed’
‘canvas.viewbox.changing’图形相关
‘shape.added’
‘shape.changed’
‘shape.remove’
‘shape.removed’
‘connection.added’
‘connection.changed’
‘connection.remove’
‘connection.removed’元素相关
‘element.changed’
‘element.click’
‘element.dblclick’
‘element.hover’
‘element.mousedown’
‘element.mousemove’
‘element.updateId’选集相关
selection.changed 当前选集改变(实际上,每次鼠标点击都会触发)
具体类型定义见此
画布 - canvas
- 获取画布模块
let canvas = bpmnModeler.get("canvas")
- 缩放
/**** @param {'fit-viewport' | 'fit-content' | number} lvl* @param {'auto'|{ x: number, y: number }} center*/
function zoom(lvl, center) {let canvas = bpmnModeler.get('canvas')canvas.zoom(lvl, center)
}// 适应容器缩放
zoom('fit-canvas','auto')// 完全显示内容
zoom('fit-content','auto')
- 对齐(选择多个元素使用shift+鼠标左键)
/*** 获取当前选集并对齐* @param {'left'|'right'|'top'|'bottom'|'middle'|'center'} mode*/
function align(mode) {const align = bpmnModeler.get('alignElements')const selection = bpmnModeler.get('selection')const elements = selection.get()if (!elements || elements.length === 0) {return}align.trigger(elements, mode)
}
具体类型定义见此
命令堆栈 - commandStack
- 获取命令堆栈模块
let commandStack = bpmnModeler.get('commandStack')
- 重做、撤销
// 是否可以重做
let canRedo = commandStack.canRedo()// 是否可以撤销
let canUndo= commandStack.canUndo()// 撤销
commandStack.undo()// 重做
commandStack.redo()
- 获取堆栈当前位置
let index = commandStack._stackIdx
- 清空堆栈
commandStack.clear()
具体类型定义见此
元素注册表 - elementRegistry
- 获取元素注册表模块
let elementRegistry = bpmnModeler.get('elementRegistry')
- 遍历所有元素
elementRegistry.forEach((shape, svgElement) => { })
- 获取指定元素
let shape = elementRegistry.get(元素id或者SVGElement)
- 获取过滤后的元素
let shapes = elementRegistry.filter((shape) => shape.type === 'bpmn:Task')
- 更新元素ID
elementRegistry.updateId(shape, "123xxxxsssd")
- 删除一个元素
elementRegistry .remove(传入SVGElement)
具体类型定义见此
模型 - moddle
基本上没有用到,具体类型定义见此
建模器 - modeling
- 获取建模器模块
let modeling= bpmnModeler.get('modeling')
- 修改元素显示文本(常用)
modeling.updateLabel(shape, '审核')
- 修改元素属性(常用)
modeling.updateProperties(shape, { 属性名称: 属性值 })
- 对齐元素集合
const selection = bpmnModeler.get('selection')
const elements = selection.get()
modeling.updateProperties(selection, 'left')
具体类型定义见此
属性面板
流程图的绘制基本实现了,但一个完整的工作流还需要在其流程、每个节点、流转上配置一些属性,比如审核规则、参与者、流程名称、变量等,属性面板的作用就是如此。
官方属性面板
如果你想使用官方提供的属性面板,需要先安装两个插件:
- bpmn-js-properties-panel@0.46.0(注意版本,0.46.0后面就是1.0.0版本,改动比较大,详见更新日志)
- camunda-bpmn-moddle@6.1.2
npm install bpmn-js-properties-panel@0.46.0 --S
npm install camunda-bpmn-moddle@6.1.2 --S
在创建实例时导入模块
import BpmnModeler from 'bpmn-js/lib/Modeler'
import propertiesPanelModule from 'bpmn-js-properties-panel'
// camunda提供的属性(一般用这个)
import propertiesProviderModule from 'bpmn-js-properties-panel/lib/provider/camunda'
// bpmn原生属性
// import propertiesProviderModule from 'bpmn-js-properties-panel/lib/provider/bpmn'import camundaModdleDescriptor from 'camunda-bpmn-moddle/resources/camunda'let bpmnModeler= new BpmnModeler({additionalModules: [propertiesPanelModule, propertiesProviderModule],container: '#canvas',propertiesPanel: {parent: '#properties'},moddleExtensions: {camunda: camundaModdleDescriptor}
})
界面如下(经过汉化,后面会介绍)
随便输入几个属性值,调用saveXML,可以发现保存到了xml中
如果你想要基于bpmn-js-properties-panel的实现自定义属性面板,请记住modeling.updateProperties这个方法,一定会用到,可参考以下文章、例子:
- properties-panel-extension
- 全网最详bpmn.js教材-properties-panel篇(上)
自定义属性面板
工作流设计器最重要的就是属性面板了,在绘制阶段要针对流转、活动节点要配置不同的属性,以支持后端工作流引擎的运行工作流实例。
最开始是想使用现有的东西来实现,即bpmn官方提供的bpmn-js-properties-panel,学习后发现有以下缺点:
- 没有文档,只有一些例子,并且自定义属性的例子看不懂
- 不知道如何自定义下拉框组件的数据来源,也不知道如何自定义输入器
- 配置的属性会放到bpmn的xml中,然而后端用不到读取也麻烦,前端保存时提取属性数据出来保存也比较麻烦(除非后端拉取camunda分支来实现工作流引擎,这样就可以通过camunda解析)
最终决定自行开发一个属性面板组件,考虑到易维护性,采用了属性描述数据模式来实现面板的渲染,这样各个业务系统要使用到工作流设计器的话,只需要针对业务来配置属性即可,降低了维护成本,
由于自定义属性面板组件跟项目公共库有些耦合,在这里我只提供开发思路,就不放源码了
自定义属性面板组件目录结构如下:
目前支持的输入器类型有:
- string:el-input
- checkbox-group:el-radio-group+el-checkbox-group
- select\enum:uiot-tree-select
- 以下未经测试过
- boolean:el-switch
- date:el-date-picker
- time:el-time-picker
- color:el-color-picker
- number:el-input-number
- custom: 自定义输入器组件
属性描述定义数据是一个三级树结构,分为tab、group、prop:
export default {name: 'general',label: '常规',groups: [{name: 'base',label: '基础',props: [{name: 'element-id',field: 'id',hidden: true,type: 'string',get: function ({ propsData, bpmn, element } = {}) {return element.id}},{name: 'process-name',field: 'name',label: '名称',type: 'string',required: true,supportNodes: ['bpmn:Process']}]}]
}
其中定义的set、get方法的作用是为了获取\设置bpmn元素中已有属性,会通过Object.defineProperty方法将指定属性定义到业务数据中
PropTabDefine、PropGroupDefine、PropItemDefine对象定义如下图所示:
主要是通过监听‘import.done’,‘shape.added’, ‘connection.added’, ‘shape.removed’, 'connection.removed’四个事件来构建每一个活动节点、流转的业务属性数据
实际使用与界面展示:
高级使用/优化扩展
完善类型推断
bpmn-js没有提供ts类型定义文件,导致无法使用类型推断,又没有官方文档,无法知道其提供的类、模块有哪些方法、属性,每次想用到啥功能的时候只能去百度,然后到源码中全局搜索,我这里看源码总结了一些d.ts文件,欢迎各位一起维护,具体见此
启用快捷键功能
使用bpmn-js官方demo你会发现,bpmn-js是支持快捷键的
不过在开发过程中发现按下键盘无论如何都没法触发内部的快捷键,在容器的div上加tabindex也没用,去看官方的例子,也没找到相关内容,最后让我扒官方demo的源码扒到了,官方是完全没介绍这个参数(这个参数在在diagram-js源码的lib\features\keyboard\Keyboard.js 69行处被使用)
const container = document.getElementById('bpmn-container')// 创建实例传入opions时,加上keyboard这个属性
let bpmnModeler = new BpmnModeler({container: container,keyboard: {bindTo: container // 绑定到哪个元素上(按键事件的目标元素)},
});
控制Viewer可缩放、可拖动
当只需要预览一个流程图时,会用到bpmn-js提供的NavigatedViewer类,某些场景下我们需要控制拖拽和缩放这两个功能的启用和禁用,可通过监听element.mousedown、wheel两个事件来处理
let eventBus = bpmnViewer.get('eventBus')
let canvas = bpmnViewer.get('canvas')
eventBus.on('element.mousedown', (ev) => {if (!this._enables.moveable) {ev.preventDefault()ev.stopPropagation()// event.stopImmediatePropagation()}
})canvas._svg.addEventListener('wheel', (ev) => {if (!this._enables.zoomable) {// ev.preventDefault()ev.stopPropagation()// ev.stopImmediatePropagation()}
})
国际化/多语言
bpmnjs国际化(汉化)的官方例子:bpmn-js-examples/i18n/
官方的语言包:zn(太少了,很多没翻译过来)
- 新建一个translations文件夹,文件夹下新建index.js(翻译器)、zh-cn.js(语言包)
- index.js代码如下:
import zhCn from './zh-cn'
// https://github.com/bpmn-io/bpmn-js-examples/tree/master/i18n/app/customTranslate
// https://github.com/bpmn-io/bpmn-js-i18n/blob/master/translations/zn.js (不全,很多没翻译过来)const translations = {'zh-cn': zhCn
}export default function(lang = 'zh-cn') {return {translate: ['value',function customTranslate(template, replacements) {replacements = replacements || {}// 找到目标语言对应的字符串template = translations[lang] && translations[lang][template] ? translations[lang][template] : template// 替换文本return template.replace(/{([^}]+)}/g, function(_, key) {return replacements[key] || '{' + key + '}'})}]}
}
- zh-cn.js就是对应的语言包,中文语言包是我在官方包基础上改的,并且翻译了官方的属性面板,详见gitee
- bpmn.js支持多语言,但不是i18n的那种key(主键)-value(语言字符串)形式的,而是key(模板字符串)-value(目标语言字符串),全量匹配key然后将其替换为value,如下所示:
- 新建bpmn实例时导入翻译模块即可:
import customTranslateModule from '../translations'const bpmnModeler = new BpmnModeler({additionalModules: [customTranslateModule('zh-cn')]
})
判断流程图是否改动
可以通过element.changed事件来检测,图形的新增、删除、属性变化都会触发element.changed
let hasEdited = false
bpmnModeler.on('element.changed',() => { hasEdited = true })
对import生命周期事件的优化
前面已经说到了importXML的一个缺陷,就是在import.done事件后还会触发shape.added或者element.changed事件,这个问题不知道是bpmnjs本来就是这样还是我二次开发做了些什么,没找到什么原因
按照我的常规理解,import.done就是导入已经完成了,如果不对图形操作,不应该再有任何事件抛出
具体解决方法如下:
// #region 触发导入完成事件
let events = ['shape.added', 'element.changed']
let imporFinshedDeb = debounce(() => {bpmnModeler.off(events, imporFinshedDeb)bpmnModeler.fire('import.finshed')
}, 60)bpmnModeler.on('import.done', () => {console.log('bpmn -> import.done')// 先调第一次,防止创建图形时不会触发element.changedimporFinshedDeb()bpmnModeler.on(events, imporFinshedDeb)
})
// #endregion
获取当前选中元素
推荐监听selection.changed事件来判断当前元素是哪一个,官方的bpmn-js-properties-panel也是这么做的
let eventBus = bpmnModeler.get('eventBus'),canvas = bpmnModeler.get('canvas'),currentElement
eventBus.on('selection.changed', (ev) => {let element = ev.newSelection?.[0] ?? canvas.getRootElement()if (element.type === 'label') {element = element.labelTarget}currentElement = element
})
导入与导出的元素id重复
现在有一个场景,需要将一个现有的工作流copy一份出来,重新配置入库:
- 使用BpmnModeler实例导出xml并保存到本地
- 使用BpmnModeler导入xml再保存到数据库
由于后端插入数据库主键用的是bpmn中元素的id(不是可忽略本小节),此时会产生一个问题,bpmn导入一个xml不会对已有id的元素重新分配ID(可以见源码lib/features/modeling/BpmnFactory.js _ensureId方法),这样会导致copy的工作流中流程、元素的id跟原始工作流的重复了,如果入的是同一个数据库,调用保存接口实际上是修改,而不是新增
具体解决代码如下(我这里是继承了Modeler,super就是Modeler实例):
importXML(xml, updateID = false) {this.clear()if (updateID) {return super.importXML(xml).then((res) => {// let prefixlet bpmnFactory = this.get('bpmnFactory')// 这里是为了解决导出之后再导入,元素ID重复的问题this.elementRegistry.forEach((element) => {element.businessObject.set('id', '')bpmnFactory._ensureId(element.businessObject)element.id = element.businessObject.id// 请勿修改以下代码// 见bpmn-js源码:lib/features/modeling/BpmnFactory.js _ensureId方法(44行)// if (is(element, 'bpmn:Activity')) {// prefix = 'Activity'// } else if (is(element, 'bpmn:Event')) {// prefix = 'Event'// } else if (is(element, 'bpmn:Gateway')) {// prefix = 'Gateway'// } else if (isAny(element, ['bpmn:SequenceFlow', 'bpmn:MessageFlow'])) {// prefix = 'Flow'// } else {// prefix = (element.$type || '').replace(/^[^:]*:/g, '')// }// prefix += '_'// element.id = this.moddle.ids.nextPrefixed(prefix, element)})return res})}return super.importXML(xml)}
其他学习资料
最近在总结本文章的时候,发现了不少优质bpmn相关介绍文章(说实话,之前学习bpmn-js的时候我都没找到,简直痛苦),有些东西我写的不是很详细,可以再看看这几位大佬的总结
- BPMN系列原创文章
- 全网最详bpmn.js教材目录
- bpmn.js-doc
后语
- 写文章是真的累,如果对你有帮助的话就点个赞吧
- bpmn-js这个库还是比较庞大的,有些基础原理没有深入了解,比如didi,有错误的话可以在评论中指出
Web工作流(流程图)设计器的方案研究及功能实现之bpmn-js简易教程相关推荐
- 基于JQUERY的WEB在线流程图设计器GOOFLOW 0.9版
换工作后又结婚,一个版本拖了两年才出来-- 跨浏览器,可兼容IE8--IE11, FireFox, Chrome, Opera等几大内核的浏览器,且不需要浏览器再加装任何控件. (IE8时,使用V ...
- 基于JQUERY的WEB在线流程图设计器GOOFLOW 0.7版
跨浏览器,可兼容IE7--IE10, FireFox, Chrome, Opera等几大内核的浏览器,且不需要浏览器再加装任何控件. (IE7-IE8时,使用VML:IE9以上,FF,OPERA, ...
- html5 jquery版工作流设计器,基于jQuery的web在线流程图设计器GooFlow
搭建Nginx+Java环境测试并且运行 一.简介: Tomcat在高并发环境下处理动态请求时性能很低,而在处理静态页面更加脆弱.虽然Tomcat的最新版本支持epoll,但是通过Nginx来处理静态 ...
- [转]由于项目原因看了一下vml,写了一个Web工作流的设计器雏形!
主要使用了vml三个对象.line,group,RoundRect. 这里是运行截图: 测试环境:ie7(winxp). ie6 (win2003) 如果是发布到iis目录下运行,那么在ie安全级 ...
- [转]基于Prototype,利用Canvas绘图实现的web流程图设计器(原型)
基于Prototype,利用Canvas绘图实现的web流程图设计器(原型) 关键字: javascript prototype script.aculo.us canvas 流程图 web画线 刚才 ...
- 制作自定义工作流(WWF)设计器
注: l 这是一篇翻译,来自http://msdn2.microsoft.com/en-us/library/aa480213.aspx l 对于一些细节我没有完全翻译 ...
- 要想工作流程更简便,试试开源web表单设计器
繁杂的工作流程,让您头疼不已?传统的表单制作效率低?内部数据迟迟得不到有效管理?-作为职场人的你,是否经常遇到上述问题.别着急,在如今的快节奏发展时代,传统的表单制作已经满足不了行业和市场的需求了,想 ...
- Web矢量图设计器可以应用在那些领域
近几年来,随着Internet的迅猛发展,网页技术日新月异,人们都试图设计出精美.有特色的页面.其中,图形技术发挥着至关重要的作用,可升级矢量图像( SVG, Scalable Vector Grap ...
- Web矢量图设计器的应用场景
近几年来,随着Internet的迅猛发展,网页技术日新月异,人们都试图设计出精美.有特色的页面.其中,图形技术发挥着至关重要的作用,可升级矢量图像( SVG, Scalable Vector Grap ...
最新文章
- arcalet云服务平台支持Unity3D开发实时多人联机游戏
- 业务活动监视器(BAM)2.0带来的革命
- [js] 微信的JSSDK都有哪些内容?如何接入?
- vue学习笔记-01-前端的发展历史(从后端到前端,再到前后端分离,再到全栈)
- tomcat配置url跳转_web和tomcat的区别
- 工作10以上老程序员都去哪了?作为新时代的程序员我们该何去何从
- php 实现 java com.sun.org.apache.xml.internal.security.utils.Base64 Byte数组加密
- MyBatis 【中文编码问题】
- Python webdriver 读取本地csv文件中数据 提示:IOError: [Errno 2] No such file or directory
- Java面试题:热情盛夏,分享Java大厂面试百题
- Atitit 远程存储与协议 mtp ptp rndis midi nfs smb webdav ftp hdfs v3 Atitit mtp ptp rndis midi协议的不同区别
- 2018华为数通技术大赛复赛拓扑具体配置
- 计算机组成原理数据通路实验报告,数据通路组成实验汇总_相关文章专题_写写帮文库...
- 如何批量重命名,文件批量重命名方法介绍
- 常用颜色RGB表 色值
- lvm卷的缩减和扩容
- JSTL自定义标签(三)
- Android Studio Shape属性(上)
- 香港喜运佳,承载着太多的回忆
- 照片变漫画的方法有哪些?推荐两个方法给你
热门文章
- 业务场景6(500w数据SQL条件查重)count inner join
- Linux虚拟机网络网卡配置nmcli用法命令详解
- Python办公自动化之三,操作PDF
- linux ubuntu 数据处理,linux使用---1.ubuntu使用记录
- 【大数据】未来网络经济的99个趋势报告!
- 6.23(343,96)
- matlab读取类别数据,Matlab-含有不同数据类型的csv文件的读取和输出
- 铁道部公布各地春运投诉电话 22 Jan 2010
- java基础知识总结:基础知识、面向对象、集合框架、多线程、jdk1.5新特性、IO流、网络编程
- 锁屏密码忘记了?教你40秒破iphone锁屏密码!