基础篇

vue+ bpmn.js
建模BpmnModeler+将数据转图形bpmnModeler.importXML
// basic.vue
<script>
// 引入相关的依赖
import BpmnModeler from 'bpmn-js/lib/Modeler'
import {
xmlStr
} from '../mock/xmlStr' // 这里是直接引用了xml字符串
export default {
name: '',
components: {},
// 生命周期 - 创建完成(可以访问当前this实例)
created() { },
// 生命周期 - 载入后, Vue 实例挂载到实际的 DOM 操作完成,一般在该过程进行 Ajax 交互
mounted() {
this.init()
},
data() {
return {
// bpmn建模器
bpmnModeler: null,
container: null,
canvas: null
}
},
methods: {
init() {
// 获取到属性ref为“canvas”的dom节点
const canvas = this.$refs.canvas
// 建模
this.bpmnModeler = new BpmnModeler({
container: canvas
})
this.createNewDiagram()
},
createNewDiagram() {
// 将字符串转换成图显示出来
this.bpmnModeler.importXML(xmlStr, (err) => {
if (err) {
// console.error(err)
} else {
// 这里是成功之后的回调, 可以在这里做一系列事情
this.success()
}
})
},
success() {
// console.log('创建成功!')
}
}
}
</script>
创建BpmnModeler实例时配置additionalModules属性
左侧工具栏 prpertiesProviderModule
右侧属性栏 bpmn-js-properties-panel
// panel.vue
<script>
...
import propertiesProviderModule from 'bpmn-js-properties-panel/lib/provider/camunda'
...
methods: {
init() {
// 获取到属性ref为“canvas”的dom节点
const canvas = this.$refs.canvas
// 建模
this.bpmnModeler = new BpmnModeler({
container: canvas,
//添加控制板
propertiesPanel: {
parent: '#js-properties-panel'
},
additionalModules: [
// 左边工具栏以及节点
propertiesProviderModule,
// 右边的工具栏
propertiesPanelModule
]
})
this.createNewDiagram()
},
createNewDiagram() {
// 将字符串转换成图显示出来
this.bpmnModeler.importXML(xmlStr, (err) => {
if (err) {
// console.error(err)
} else {
// 这里是成功之后的回调, 可以在这里做一系列事情
this.success()
}
})
},
success() {
// console.log('创建成功!')
}
}
http篇

字符串数据通过请求获取
// axios.vue
<script>
...
import axios from 'axios'
import { xmlStr } from '../mock/xmlStr' // 引入一个本地的xml字符串, 若是没有获取到后台的数据则用它
export default {
...
data () {
return {
...
loading: true,
xmlUrl: '',
defaultXmlStr: xmlStr
}
},
methods: {
async init () {
this.loading = true
this.xmlUrl = await this.getXmlUrl()
console.log(this.xmlUrl)
this.loading = false
this.$nextTick(() => { // 等待 DOM 更新之后再对工作流进行初始化
this.initBpmn()
})
},
getXmlUrl () { // 该方法模拟请求后台获取bpmn文件地址
return new Promise(resolve => {
setTimeout(() => {
const url = 'https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/bpmnMock.bpmn' // 模拟网络请求的一个地址
resolve(url)
}, 1000)
})
},
initBpmn () {
... // 这里是初始化工作流的代码
this.createNewDiagram()
},
async createNewDiagram () {
const that = this
let bpmnXmlStr = ''
if (this.xmlUrl === '') { // 若是后台没有数据则使用默认的一个xml
bpmnXmlStr = this.defaultXmlStr
this.transformCanvas(bpmnXmlStr)
} else {
let res = await axios({
method: 'get',
timeout: 120000,
url: that.xmlUrl,
headers: { 'Content-Type': 'multipart/form-data' }
})
console.log(res)
bpmnXmlStr = res['data']
this.transformCanvas(bpmnXmlStr)
}
},
transformCanvas(bpmnXmlStr) {
// 将字符串转换成图显示出来
this.bpmnModeler.importXML(bpmnXmlStr, (err) => {
if (err) {
console.error(err)
} else {
this.success()
}
// 让图能自适应屏幕
var canvas = this.bpmnModeler.get('canvas')
canvas.zoom('fit-viewport')
})
},
success () {
console.log('创建成功!')
}
}
}
</script>
将编辑之后的最新bpmn发送给后台
该功能就涉及到了bpmn.js中的事件绑定, 也就是前端需要给图形绑定一个事件来检测到图形的改变, 并获取到最新的xml 信息.
获取最新xml信息
bpmnModeler.on('commandStack.changed',function(){})
// save.vue
<script>
success () {
console.log('创建成功!')
this.addBpmnListener()
},
// 添加绑定事件
addBpmnListener () {
const that = this
// 给图绑定事件,当图有发生改变就会触发这个事件
this.bpmnModeler.on('commandStack.changed', function () {
that.saveDiagram(function(err, xml) {
console.log(xml) // 这里获取到的就是最新的xml信息
})
})
},
// 下载为bpmn格式,done是个函数,调用的时候传入的
saveDiagram(done) {
// 把传入的done再传给bpmn原型的saveXML函数调用
this.bpmnModeler.saveXML({ format: true }, function(err, xml) {
done(err, xml)
})
}
</script>
这段代码是一个Vue组件中的一部分,用于处理保存操作。首先定义了一个名为
success的方法,当保存成功时调用。在success方法中调用了addBpmnListener方法,用于添加BPMN图的事件监听器。
addBpmnListener方法中,通过**this.bpmnModeler.on**方法给BPMN图绑定了一个事件监听器,当图有发生改变时触发**commandStack.changed**事件。事件回调函数中调用了saveDiagram方法,并传入一个回调函数作为参数。
saveDiagram方法用于保存BPMN图为XML格式,并通过传入的回调函数将保存结果传递出去。在方法内部调用了this.bpmnModeler.saveXML方法来保存XML,并将结果通过传入的回调函数返回。总的来说,这段代码的逻辑是:当BPMN图有变化时,触发保存操作,并将保存的XML结果传递出去。在保存操作完成后,可以通过传入的回调函数获取保存的XML信息。
编辑完保存为bpmn文件或svg文件
API
bpmnModeler.saveSVG(function(){})
this.bpmnModeler.saveXML( function(err, xml) {callback(err, xml)} )
encodeURIComponent li标签
// save.vue
<script>
...
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)
})
})
},
// 下载为SVG格式,done是个函数,调用的时候传入的
saveSVG(done) {
// 把传入的done再传给bpmn原型的saveSVG函数调用
this.bpmnModeler.saveSVG(done)
},
// 下载为bpmn格式,done是个函数,调用的时候传入的
saveDiagram(done) {
// 把传入的done再传给bpmn原型的saveXML函数调用
this.bpmnModeler.saveXML({ format: true }, function(err, xml) {
done(err, xml)
})
},
// 当图发生改变的时候会调用这个函数,这个data就是图的xml
setEncoded(link, name, data) {
// 把xml转换为URI,下载要用到的
const encodedData = encodeURIComponent(data)
// 下载图的具体操作,改变a的属性,className令a标签可点击,href令能下载,download是下载的文件的名字
console.log(link, name, data)
let xmlFile = new File([data], 'test.bpmn')
console.log(xmlFile)
if (data) {
link.className = 'active'
link.href = 'data:application/bpmn20-xml;charset=UTF-8,' + encodedData
link.download = name
}
}
</script>
将最新的XML保存至后端(待补充)
事件篇
监听modeler并绑定事件
shape.added 新增一个shape之后触发;
shape.move.end 移动完一个shape之后触发;
shape.removed 删除一个shape之后触发;
API
var elementRegistry = bpmnjs.get('elementRegistry') var shape = e.element ? elementRegistry.get(e.element.id) : e.shape
// 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并绑定事件
上面介绍的是监听modeler并绑定事件, 可能你也需要监听用户点击图形上的element或者监听某个element改变:
element.click 点击元素;
element.changed 当元素发生改变的时候(包括新增、移动、删除元素)
// 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>
//获取Elememnt
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
})
通过监听事件判断操作方式
上面我们已经介绍了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() {
// 监听 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'
}
自定义Palette篇(左侧绘画板)

原型
$inject
类
自定义Renderer篇(画布上元素)
自定义ContextPad篇
编辑、删除节点篇
封装组件

Properties篇
我们在用bpmn.js画的每一个节点其实都被称之为diagram element(图表元素, 是不是很好理解😁)
而在bpmn文件中的每个xml标签称之为BPMN element.
将diagram element与BPMN element的一些属性关联起来靠的是一个叫做businessObject的属性. 从名称上理解你也可以知道它是一个对象(Object), 你可以在这个对象中添加上一些特殊的属性, 并且这些属性是可以直接插到BPMN element上的.
读取
var elementRegistry = bpmnJs.get('elementRegistry').get('ID').businessObject
//一致element情况
element.bussinessObject
const {bussinessObject}=element
修改
var moddle = bpmnJS.get('moddle');
// 创建一个BPMN element , 并且载入到导出的xml里
var newCondition = moddle.create('bpmn:FormalExpression', {
body: '${ value > 100 }'
});
// 写入属性, 但是不支持撤销
sequenceFlow.conditionExpression = newCondition;
//支撑撤销/重新
var modeling = bpmnJS.get('modeling');
modeling.updateProperties(sequenceFlowElement, {
conditionExpression: newCondition
});
//updateProperties()
//属性查看下面meta-model descriptor
modeling.updateProperties(startEventElement, {
name: '我是修改后的虚线节点',
isInterrupting: false
})
《meta-model descriptor》
XML Schema(XML Schema Definition,XSD)是一种用于描述 XML 文档结构和约束的语言。它本身是一种 XML 文档,用于定义 XML 文档中元素和属性的结构、数据类型以及约束规则。
XML Schema 的目的是为 XML 文档提供验证机制,以确保其符合特定的规范和要求。通过 XML Schema,您可以明确指定 XML 文档中包含的元素、属性以及它们之间的关系,指定数据类型,提供约束规则,以及定义文档的命名空间等。
XML Schema 是 XML 的一个重要标准,由 W3C(World Wide Web Consortium)制定和维护。它通常以 .xsd 文件扩展名保存,并可以使用各种 XML 解析器和编辑器进行创建、编辑和验证。
XML Schema 的基本结构包括:
-
元素声明(Element Declaration):定义 XML 文档中的元素,包括元素名称、类型、出现次数等信息。
-
属性声明(Attribute Declaration):定义 XML 元素的属性,包括属性名称、数据类型、默认值等信息。
-
简单类型(Simple Type):定义 XML 元素或属性的数据类型,如字符串、数字、日期等。
-
复杂类型(Complex Type):定义包含其他元素和属性的复杂结构,可以指定序列、选择和重复等约束规则。
通过定义这些结构,XML Schema 可以帮助确保 XML 文档的结构、内容和数据格式符合预期,从而提高文档的可靠性和互操作性。
Properties-panel篇(上)
// 这里引入的是右侧属性栏这个框 import propertiesPanelModule from 'bpmn-js-properties-panel' // 而这个引入的是右侧属性栏里的内容 import propertiesProviderModule from 'bpmn-js-properties-panel/lib/provider/camunda' additionalModules: [ propertiesPanelModule, propertiesProviderModule ]
//第一个propertiesPanelModule 表示的是属性栏这个框, 就是告诉别人这里要有个属性栏; //第二个propertiesProviderModule表示的是属性栏里的内容, 也就是点击不同的element该显示什么内容.
自定义
既然这样的话, 我们只需要重写propertiesProviderModule就可以了, 不要引入官方提供的(也就是从bpmn-js-properties-panel/lib/provider/camunda引入的), 而是自定义一个**propertiesProviderModule**来显示自己想要的内容.
扩展使用Properties-panel properties-panel-extension

Properties-panel篇(下)

样式主题切换
自定义properties-panel
input框双向绑定 @change
/**
* 改变控件触发的事件
* @param { Object } input的Event
* @param { String } 要修改的属性的名称
*/
changeField (event, type) {
const value = event.target.value
let properties = {}
properties[type] = value
this.element[type] = value
this.updateProperties(properties) // 调用属性更新方法
}
修改节点名称label属性
updateName(name) {
const { modeler, element } = this
const modeling = modeler.get('modeling')
modeling.updateLabel(element, name)
// 等同于 modeling.updateProperties(element, { name })
},
API:modeling.updateLabel()
修改节点颜色color属性
/**
* 改变控件触发的事件
* @param { Object } input的Event
* @param { String } 要修改的属性的名称
*/
changeField(event, type) {
const value = event.target.value
let properties = {}
properties[type] = value
if (type === 'color') { // 若是color属性
this.onChangeColor(value)
}
this.element[type] = value
this.updateProperties(properties)
},
onChangeColor(color) {
const { modeler, element } = this
const modeling = this.modeler.get('modeling')
modeling.setColor(element, {
fill: color,
stroke: null
})
},
//API:modeling.setColor()
修改event节点类型
实现这个功能我们需要用到bpmnReplace.replaceElement这个方法.
首先让我们看看event里这个属性是放在哪里的.
changeEventType(event) { // 改变下拉框
const { modeler, element } = this
const value = event.target.value
const bpmnReplace = modeler.get('bpmnReplace')
this.eventType = value
bpmnReplace.replaceElement(element, {
type: element.businessObject.$type,
eventDefinitionType: value
})
},
现在改变下拉框的值, 就可以改变eventDefinitionType的值了, 不过还有一个问题, 就是你点击了其它的节点, 然后再次点回开始节点的时候, 下拉框的默认值(应该是本身所选元素的eventDefinition属性值)就不对了, 也就是说我们还需要获取到这个开始节点本身的eventDefinitionType值.
init () {
modeler.on('selection.changed', e => {
this.selectedElements = e.newSelection
this.element = e.newSelection[0]
console.log(this.element)
this.setDefaultProperties() // 设置一些默认的值
})
}
setDefaultProperties() {
const { element } = this
if (element) {
const { type, businessObject } = element
if (this.verifyIsEvent(type)) { // 若是event类型
// 获取默认的 eventDefinitionType
this.eventType = businessObject.eventDefinitions ? businessObject.eventDefinitions[0]['$type'] : ''
}
}
}
修改Task节点的类型
changeTaskType(event) {
const { modeler, element } = this
const value = event.target.value // 当前下拉框选择的值
const bpmnReplace = modeler.get('bpmnReplace')
bpmnReplace.replaceElement(element, {
type: value // 直接修改type就可以了
})
}
初始化properties-panel并设置一些默认值
init () {
modeler.on('selection.changed', e => {
this.selectedElements = e.newSelection
this.element = e.newSelection[0]
console.log(this.element)
this.setDefaultProperties() // 设置一些默认的值
})
}
setDefaultProperties() {
const { element } = this
if (element) {
// 这里可以拿到当前点击的节点的所有属性
const { type, businessObject } = element
// doSomeThing
}
}
























