前言

本文以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,学习后发现有以下缺点:

  1. 没有文档,只有一些例子,并且自定义属性的例子看不懂
  2. 不知道如何自定义下拉框组件的数据来源,也不知道如何自定义输入器
  3. 配置的属性会放到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(太少了,很多没翻译过来)

  1. 新建一个translations文件夹,文件夹下新建index.js(翻译器)、zh-cn.js(语言包)
  2. 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 + '}'})}]}
}
  1. zh-cn.js就是对应的语言包,中文语言包是我在官方包基础上改的,并且翻译了官方的属性面板,详见gitee
  2. bpmn.js支持多语言,但不是i18n的那种key(主键)-value(语言字符串)形式的,而是key(模板字符串)-value(目标语言字符串),全量匹配key然后将其替换为value,如下所示:
  3. 新建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一份出来,重新配置入库:

  1. 使用BpmnModeler实例导出xml并保存到本地
  2. 使用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简易教程相关推荐

  1. 基于JQUERY的WEB在线流程图设计器GOOFLOW 0.9版

    换工作后又结婚,一个版本拖了两年才出来--  跨浏览器,可兼容IE8--IE11, FireFox, Chrome, Opera等几大内核的浏览器,且不需要浏览器再加装任何控件. (IE8时,使用V ...

  2. 基于JQUERY的WEB在线流程图设计器GOOFLOW 0.7版

     跨浏览器,可兼容IE7--IE10, FireFox, Chrome, Opera等几大内核的浏览器,且不需要浏览器再加装任何控件. (IE7-IE8时,使用VML:IE9以上,FF,OPERA, ...

  3. html5 jquery版工作流设计器,基于jQuery的web在线流程图设计器GooFlow

    搭建Nginx+Java环境测试并且运行 一.简介: Tomcat在高并发环境下处理动态请求时性能很低,而在处理静态页面更加脆弱.虽然Tomcat的最新版本支持epoll,但是通过Nginx来处理静态 ...

  4. [转]由于项目原因看了一下vml,写了一个Web工作流的设计器雏形!

    主要使用了vml三个对象.line,group,RoundRect. 这里是运行截图: 测试环境:ie7(winxp). ie6 (win2003)   如果是发布到iis目录下运行,那么在ie安全级 ...

  5. [转]基于Prototype,利用Canvas绘图实现的web流程图设计器(原型)

    基于Prototype,利用Canvas绘图实现的web流程图设计器(原型) 关键字: javascript prototype script.aculo.us canvas 流程图 web画线 刚才 ...

  6. 制作自定义工作流(WWF)设计器

    注: l         这是一篇翻译,来自http://msdn2.microsoft.com/en-us/library/aa480213.aspx l         对于一些细节我没有完全翻译 ...

  7. 要想工作流程更简便,试试开源web表单设计器

    繁杂的工作流程,让您头疼不已?传统的表单制作效率低?内部数据迟迟得不到有效管理?-作为职场人的你,是否经常遇到上述问题.别着急,在如今的快节奏发展时代,传统的表单制作已经满足不了行业和市场的需求了,想 ...

  8. Web矢量图设计器可以应用在那些领域

    近几年来,随着Internet的迅猛发展,网页技术日新月异,人们都试图设计出精美.有特色的页面.其中,图形技术发挥着至关重要的作用,可升级矢量图像( SVG, Scalable Vector Grap ...

  9. Web矢量图设计器的应用场景

    近几年来,随着Internet的迅猛发展,网页技术日新月异,人们都试图设计出精美.有特色的页面.其中,图形技术发挥着至关重要的作用,可升级矢量图像( SVG, Scalable Vector Grap ...

最新文章

  1. arcalet云服务平台支持Unity3D开发实时多人联机游戏
  2. 业务活动监视器(BAM)2.0带来的革命
  3. [js] 微信的JSSDK都有哪些内容?如何接入?
  4. vue学习笔记-01-前端的发展历史(从后端到前端,再到前后端分离,再到全栈)
  5. tomcat配置url跳转_web和tomcat的区别
  6. 工作10以上老程序员都去哪了?作为新时代的程序员我们该何去何从
  7. php 实现 java com.sun.org.apache.xml.internal.security.utils.Base64 Byte数组加密
  8. MyBatis 【中文编码问题】
  9. Python webdriver 读取本地csv文件中数据 提示:IOError: [Errno 2] No such file or directory
  10. Java面试题:热情盛夏,分享Java大厂面试百题
  11. Atitit 远程存储与协议 mtp ptp rndis midi nfs smb webdav ftp hdfs v3 Atitit mtp ptp rndis midi协议的不同区别
  12. 2018华为数通技术大赛复赛拓扑具体配置
  13. 计算机组成原理数据通路实验报告,数据通路组成实验汇总_相关文章专题_写写帮文库...
  14. 如何批量重命名,文件批量重命名方法介绍
  15. 常用颜色RGB表 色值
  16. lvm卷的缩减和扩容
  17. JSTL自定义标签(三)
  18. Android Studio Shape属性(上)
  19. 香港喜运佳,承载着太多的回忆
  20. 照片变漫画的方法有哪些?推荐两个方法给你

热门文章

  1. 业务场景6(500w数据SQL条件查重)count inner join
  2. Linux虚拟机网络网卡配置nmcli用法命令详解
  3. Python办公自动化之三,操作PDF
  4. linux ubuntu 数据处理,linux使用---1.ubuntu使用记录
  5. 【大数据】未来网络经济的99个趋势报告!
  6. 6.23(343,96)
  7. matlab读取类别数据,Matlab-含有不同数据类型的csv文件的读取和输出
  8. 铁道部公布各地春运投诉电话 22 Jan 2010
  9. java基础知识总结:基础知识、面向对象、集合框架、多线程、jdk1.5新特性、IO流、网络编程
  10. 锁屏密码忘记了?教你40秒破iphone锁屏密码!