一、bpmn.js 简介
一个BPMN 2.0渲染工具包和Web建模器。使用JavaScript编写,在不需要后端服务器支持的前提下向现代浏览器内嵌入BPMN2.0流程图。这使得它很容易的嵌入到任何web应用中。这个库既可以是web查看器也可以是web建模器。使用查看器将BPMN2.0流程图嵌入到你的应用中并可以使用数据丰富你的流程图。使用建模器在你的应用内部创建BPMN2.0流程图。
github地址:https://github.com/bpmn-io
实例地址:https://bpmn.io/toolkit/bpmn-js/
http://wkd.to8to.com/
https://github.com/haoyanyu/vue-with-bpmn
二、安装组件
# bpmn组件
npm install --save vue-bpmn
# bpmn依赖
npm install --save bpmn-js
# 属性面板
npm install --save bpmn-js-properties-panel
# 扩展属性
npm install --save camunda-bpmn-moddle
# 导入bpmn组件所需
npm install --save raw-loader
三、显示bpmn
- bpmn文件(/public/diagram.bpmn)
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" targetNamespace="" xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL http://www.omg.org/spec/BPMN/2.0/20100501/BPMN20.xsd">
<collaboration id="sid-c0e745ff-361e-4afb-8c8d-2a1fc32b1424">
<participant id="sid-87F4C1D6-25E1-4A45-9DA7-AD945993D06F" name="Customer" processRef="sid-C3803939-0872-457F-8336-EAE484DC4A04" />
</collaboration>
<process id="sid-C3803939-0872-457F-8336-EAE484DC4A04" name="Customer" processType="None" isClosed="false" isExecutable="false">
<extensionElements />
<laneSet id="sid-b167d0d7-e761-4636-9200-76b7f0e8e83a">
<lane id="sid-57E4FE0D-18E4-478D-BC5D-B15164E93254">
<flowNodeRef>sid-52EB1772-F36E-433E-8F5B-D5DFD26E6F26</flowNodeRef>
<flowNodeRef>sid-E49425CF-8287-4798-B622-D2A7D78EF00B</flowNodeRef>
<flowNodeRef>sid-D7F237E8-56D0-4283-A3CE-4F0EFE446138</flowNodeRef>
<flowNodeRef>sid-E433566C-2289-4BEB-A19C-1697048900D2</flowNodeRef>
<flowNodeRef>sid-5134932A-1863-4FFA-BB3C-A4B4078B11A9</flowNodeRef>
<flowNodeRef>SCAN_OK</flowNodeRef>
</lane>
</laneSet>
<task id="sid-52EB1772-F36E-433E-8F5B-D5DFD26E6F26" name="Scan QR code">
<incoming>sid-4DC479E5-5C20-4948-BCFC-9EC5E2F66D8D</incoming>
<outgoing>sid-EE8A7BA0-5D66-4F8B-80E3-CC2751B3856A</outgoing>
</task>
<task id="sid-E49425CF-8287-4798-B622-D2A7D78EF00B" name="Open product information in mobile app">
<incoming>sid-8B820AF5-DC5C-4618-B854-E08B71FB55CB</incoming>
<outgoing>sid-57EB1F24-BD94-479A-BF1F-57F1EAA19C6C</outgoing>
</task>
<startEvent id="sid-D7F237E8-56D0-4283-A3CE-4F0EFE446138" name="Notices QR code">
<outgoing>sid-7B791A11-2F2E-4D80-AFB3-91A02CF2B4FD</outgoing>
</startEvent>
<endEvent id="sid-E433566C-2289-4BEB-A19C-1697048900D2" name="Is informed">
<incoming>sid-57EB1F24-BD94-479A-BF1F-57F1EAA19C6C</incoming>
</endEvent>
<exclusiveGateway id="sid-5134932A-1863-4FFA-BB3C-A4B4078B11A9">
<incoming>sid-7B791A11-2F2E-4D80-AFB3-91A02CF2B4FD</incoming>
<incoming>sid-337A23B9-A923-4CCE-B613-3E247B773CCE</incoming>
<outgoing>sid-4DC479E5-5C20-4948-BCFC-9EC5E2F66D8D</outgoing>
</exclusiveGateway>
<exclusiveGateway id="SCAN_OK" name="Scan successful? ">
<incoming>sid-EE8A7BA0-5D66-4F8B-80E3-CC2751B3856A</incoming>
<outgoing>sid-8B820AF5-DC5C-4618-B854-E08B71FB55CB</outgoing>
<outgoing>sid-337A23B9-A923-4CCE-B613-3E247B773CCE</outgoing>
</exclusiveGateway>
<sequenceFlow id="sid-337A23B9-A923-4CCE-B613-3E247B773CCE" name="Yes" sourceRef="SCAN_OK" targetRef="sid-5134932A-1863-4FFA-BB3C-A4B4078B11A9" />
<sequenceFlow id="sid-4DC479E5-5C20-4948-BCFC-9EC5E2F66D8D" sourceRef="sid-5134932A-1863-4FFA-BB3C-A4B4078B11A9" targetRef="sid-52EB1772-F36E-433E-8F5B-D5DFD26E6F26" />
<sequenceFlow id="sid-8B820AF5-DC5C-4618-B854-E08B71FB55CB" name="No" sourceRef="SCAN_OK" targetRef="sid-E49425CF-8287-4798-B622-D2A7D78EF00B" />
<sequenceFlow id="sid-57EB1F24-BD94-479A-BF1F-57F1EAA19C6C" sourceRef="sid-E49425CF-8287-4798-B622-D2A7D78EF00B" targetRef="sid-E433566C-2289-4BEB-A19C-1697048900D2" />
<sequenceFlow id="sid-EE8A7BA0-5D66-4F8B-80E3-CC2751B3856A" sourceRef="sid-52EB1772-F36E-433E-8F5B-D5DFD26E6F26" targetRef="SCAN_OK" />
<sequenceFlow id="sid-7B791A11-2F2E-4D80-AFB3-91A02CF2B4FD" sourceRef="sid-D7F237E8-56D0-4283-A3CE-4F0EFE446138" targetRef="sid-5134932A-1863-4FFA-BB3C-A4B4078B11A9" />
</process>
<bpmndi:BPMNDiagram id="sid-74620812-92c4-44e5-949c-aa47393d3830">
<bpmndi:BPMNPlane id="sid-cdcae759-2af7-4a6d-bd02-53f3352a731d" bpmnElement="sid-c0e745ff-361e-4afb-8c8d-2a1fc32b1424">
<bpmndi:BPMNShape id="sid-87F4C1D6-25E1-4A45-9DA7-AD945993D06F_gui" bpmnElement="sid-87F4C1D6-25E1-4A45-9DA7-AD945993D06F" isHorizontal="true">
<omgdc:Bounds x="83" y="105" width="933" height="250" />
<bpmndi:BPMNLabel labelStyle="sid-84cb49fd-2f7c-44fb-8950-83c3fa153d3b">
<omgdc:Bounds x="47.49999999999999" y="170.42857360839844" width="12.000000000000014" height="59.142852783203125" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="sid-57E4FE0D-18E4-478D-BC5D-B15164E93254_gui" bpmnElement="sid-57E4FE0D-18E4-478D-BC5D-B15164E93254" isHorizontal="true">
<omgdc:Bounds x="113" y="105" width="903" height="250" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="sid-52EB1772-F36E-433E-8F5B-D5DFD26E6F26_gui" bpmnElement="sid-52EB1772-F36E-433E-8F5B-D5DFD26E6F26">
<omgdc:Bounds x="393" y="170" width="100" height="80" />
<bpmndi:BPMNLabel labelStyle="sid-84cb49fd-2f7c-44fb-8950-83c3fa153d3b">
<omgdc:Bounds x="360.5" y="172" width="84" height="12" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="sid-E49425CF-8287-4798-B622-D2A7D78EF00B_gui" bpmnElement="sid-E49425CF-8287-4798-B622-D2A7D78EF00B">
<omgdc:Bounds x="728" y="170" width="100" height="80" />
<bpmndi:BPMNLabel labelStyle="sid-84cb49fd-2f7c-44fb-8950-83c3fa153d3b">
<omgdc:Bounds x="695.9285736083984" y="162" width="83.14285278320312" height="36" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="sid-EE8A7BA0-5D66-4F8B-80E3-CC2751B3856A_gui" bpmnElement="sid-EE8A7BA0-5D66-4F8B-80E3-CC2751B3856A">
<omgdi:waypoint x="493" y="210" />
<omgdi:waypoint x="585" y="210" />
<bpmndi:BPMNLabel>
<omgdc:Bounds x="494" y="185" width="90" height="20" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="sid-8B820AF5-DC5C-4618-B854-E08B71FB55CB_gui" bpmnElement="sid-8B820AF5-DC5C-4618-B854-E08B71FB55CB">
<omgdi:waypoint x="635" y="210" />
<omgdi:waypoint x="728" y="210" />
<bpmndi:BPMNLabel labelStyle="sid-e0502d32-f8d1-41cf-9c4a-cbb49fecf581">
<omgdc:Bounds x="642" y="185" width="16" height="12" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="sid-7B791A11-2F2E-4D80-AFB3-91A02CF2B4FD_gui" bpmnElement="sid-7B791A11-2F2E-4D80-AFB3-91A02CF2B4FD">
<omgdi:waypoint x="223" y="210" />
<omgdi:waypoint x="275" y="210" />
<bpmndi:BPMNLabel>
<omgdc:Bounds x="204" y="185" width="90" height="20" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="sid-4DC479E5-5C20-4948-BCFC-9EC5E2F66D8D_gui" bpmnElement="sid-4DC479E5-5C20-4948-BCFC-9EC5E2F66D8D">
<omgdi:waypoint x="325" y="210" />
<omgdi:waypoint x="393" y="210" />
<bpmndi:BPMNLabel>
<omgdc:Bounds x="314" y="185" width="90" height="20" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="sid-57EB1F24-BD94-479A-BF1F-57F1EAA19C6C_gui" bpmnElement="sid-57EB1F24-BD94-479A-BF1F-57F1EAA19C6C">
<omgdi:waypoint x="828" y="210" />
<omgdi:waypoint x="901" y="210" />
<bpmndi:BPMNLabel>
<omgdc:Bounds x="820" y="185" width="90" height="20" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="sid-337A23B9-A923-4CCE-B613-3E247B773CCE_gui" bpmnElement="sid-337A23B9-A923-4CCE-B613-3E247B773CCE">
<omgdi:waypoint x="611" y="234" />
<omgdi:waypoint x="610.5" y="299" />
<omgdi:waypoint x="300.5" y="299" />
<omgdi:waypoint x="301" y="234" />
<bpmndi:BPMNLabel labelStyle="sid-e0502d32-f8d1-41cf-9c4a-cbb49fecf581">
<omgdc:Bounds x="585" y="236" width="21" height="12" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="StartEvent_0l6sgn0_di" bpmnElement="sid-D7F237E8-56D0-4283-A3CE-4F0EFE446138">
<omgdc:Bounds x="187" y="192" width="36" height="36" />
<bpmndi:BPMNLabel>
<omgdc:Bounds x="182" y="229" width="46" height="24" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="EndEvent_0xwuvv5_di" bpmnElement="sid-E433566C-2289-4BEB-A19C-1697048900D2">
<omgdc:Bounds x="901" y="192" width="36" height="36" />
<bpmndi:BPMNLabel>
<omgdc:Bounds x="892" y="231" width="56" height="12" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="ExclusiveGateway_1g0eih2_di" bpmnElement="sid-5134932A-1863-4FFA-BB3C-A4B4078B11A9" isMarkerVisible="true">
<omgdc:Bounds x="275" y="185" width="50" height="50" />
<bpmndi:BPMNLabel>
<omgdc:Bounds x="210" y="160" width="90" height="12" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="ExclusiveGateway_0vci1x5_di" bpmnElement="SCAN_OK" isMarkerVisible="true">
<omgdc:Bounds x="585" y="185" width="50" height="50" />
<bpmndi:BPMNLabel>
<omgdc:Bounds x="568" y="157" width="88" height="24" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
<bpmndi:BPMNLabelStyle id="sid-e0502d32-f8d1-41cf-9c4a-cbb49fecf581">
<omgdc:Font name="Arial" size="11" isBold="false" isItalic="false" isUnderline="false" isStrikeThrough="false" />
</bpmndi:BPMNLabelStyle>
<bpmndi:BPMNLabelStyle id="sid-84cb49fd-2f7c-44fb-8950-83c3fa153d3b">
<omgdc:Font name="Arial" size="12" isBold="false" isItalic="false" isUnderline="false" isStrikeThrough="false" />
</bpmndi:BPMNLabelStyle>
</bpmndi:BPMNDiagram>
</definitions>
- vue组件
<template>
<div class="diagram-container">
<!-- 组件 url为文件路径-->
<vue-bpmn
url="/diagram.bpmn"
v-on:error="handleError"
v-on:shown="handleShown"
v-on:loading="handleLoading"
></vue-bpmn>
</div>
</template>
<script>
// 引入组件
import VueBpmn from "vue-bpmn";
export default {
components: {
VueBpmn
},
methods: {
handleError: function(err) {
console.error("failed to show diagram", err);
},
handleShown: function() {
console.log("diagram shown");
},
handleLoading: function() {
console.log("diagram loading");
}
}
};
</script>
<style scoped lang="scss">
.diagram-container {
height: 300px;
}
</style>
-
显示结果
四、整体框架
- 组件
bpmn.vue
<template>
<div class="containers" ref="content">
<!-- 画布 -->
<div class="canvas" ref="canvas"></div>
<!-- 面板 -->
<div id="js-properties-panel" class="panel"></div>
<!-- 下载图 -->
<ul class="buttons">
<li>下载</li>
<li>
<a ref="saveDiagram" href="javascript:" title="下载BPMN图">BPMN图</a>
</li>
<li>
<a ref="saveSvg" href="javascript:" title="下载SVG图">SVG图</a>
</li>
</ul>
</div>
</template>
<script>
// 引入组件
import VueBpmn from "vue-bpmn";
// 渲染器
import BpmnModeler from "bpmn-js/lib/Modeler";
// 左边工具栏
import propertiesPanelModule from "bpmn-js-properties-panel";
// 右边工具栏,扩展属性
import propertiesProviderModule from "bpmn-js-properties-panel/lib/provider/camunda";
import camundaModdleDescriptor from "camunda-bpmn-moddle/resources/camunda";
export default {
components: {
},
data() {
return {
// bpmn建模器
bpmnModeler: null,
container: null,
canvas: null,
xmlStr: null,
processName: ""
};
},
mounted() {
// 获取到属性ref为“content”的dom节点
this.container = this.$refs.content;
// 获取到属性ref为“canvas”的dom节点
const canvas = this.$refs.canvas;
// 建模,官方文档这里讲的很详细
this.bpmnModeler = new BpmnModeler({
container: canvas,
// 添加控制板
// propertiesPanel: {
// parent: '#js-properties-panel'
// },
additionalModules: [
// 左边工具栏以及节点
propertiesProviderModule,
// 右边的工具栏
propertiesPanelModule
],
moddleExtensions: {
camunda: camundaModdleDescriptor
}
});
// 下载画图
let _this = this
// 获取a标签dom节点
const downloadLink = this.$refs.saveDiagram
const downloadSvgLink = this.$refs.saveSvg
// 给图绑定事件,当图有发生改变就会触发这个事件
this.bpmnModeler.on('commandStack.changed', function () {
_this.saveSVG(function (err, svg) {
_this.setEncoded(downloadSvgLink, 'diagram.svg', err ? null : svg)
})
// 保存图
_this.saveDiagram(function (err, xml) {
_this.setEncoded(downloadLink, 'diagram.bpmn', err ? null : xml)
})
})
// 创建新图
this.createNewDiagram(this.bpmnModeler)
},
methods: {
createNewDiagram() {
const bpmnXmlStr =
'<?xml version="1.0" encoding="UTF-8"?>\n' +
'<bpmn2:definitions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmn2="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" xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL BPMN20.xsd" id="sample-diagram" targetNamespace="http://bpmn.io/schema/bpmn">\n' +
' <bpmn2:process id="Process_1" isExecutable="false">\n' +
' <bpmn2:startEvent id="StartEvent_1"/>\n' +
" </bpmn2:process>\n" +
' <bpmndi:BPMNDiagram id="BPMNDiagram_1">\n' +
' <bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1">\n' +
' <bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">\n' +
' <dc:Bounds height="36.0" width="36.0" x="412.0" y="240.0"/>\n' +
" </bpmndi:BPMNShape>\n" +
" </bpmndi:BPMNPlane>\n" +
" </bpmndi:BPMNDiagram>\n" +
"</bpmn2:definitions>";
// 将字符串转换成图显示出来
this.bpmnModeler.importXML(bpmnXmlStr, function(err) {
if (err) {
console.error(err);
}
});
},
// 下载为SVG格式,done是个函数,调用的时候传入的
saveSVG(done) {
// 把传入的done再传给bpmn原型的saveSVG函数调用
this.bpmnModeler.saveSVG(done);
},
// 下载为SVG格式,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);
// 获取到图的xml,保存就是把这个xml提交给后台
this.xmlStr = data;
// 下载图的具体操作,改变a的属性,className令a标签可点击,href令能下载,download是下载的文件的名字
if (data) {
link.className = "active";
link.href = "data:application/bpmn20-xml;charset=UTF-8," + encodedData;
link.download = name;
}
}
}
};
</script>
<style scoped lang="scss">
// 最外层容器
.containers{
position: absolute;
background-color: #ffffff;
width: 100%;
height: 100%;
}
// 画布
.canvas{
width: 100%;
height: 100%;
}
// 面板
.panel{
position: absolute;
right: 0;
top: 0;
width: 300px;
}
// 下载按钮
.buttons{
position: absolute;
left: 20px;
bottom: 20px;
&>li{
display:inline-block;margin: 5px;
&>a{
color: #999;
background: #eee;
cursor: not-allowed;
padding: 8px;
border: 1px solid #ccc;
&.active{
color: #333;
background: #fff;
cursor: pointer;
}
}
}
}
</style>
- 样式
main.js
import './plugins/element.js'
/*左边工具栏以及编辑节点的样式*/
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";
-
显示结果
五、工具栏添加
必须引入组件:
// 左边工具栏
import propertiesPanelModule from "bpmn-js-properties-panel";
// 右边工具栏,扩展属性
import propertiesProviderModule from "bpmn-js-properties-panel/lib/provider/camunda";
import camundaModdleDescriptor from "camunda-bpmn-moddle/resources/camunda";
- 左边工具栏
// 左边工具栏以及节点
propertiesProviderModule,
- 右边工具栏
// 右边的工具栏
propertiesPanelModule
六、下载保存事件
// 下载为SVG格式,done是个函数,调用的时候传入的
saveSVG(done) {
// 把传入的done再传给bpmn原型的saveSVG函数调用
this.bpmnModeler.saveSVG(done);
},
// 下载为SVG格式,done是个函数,调用的时候传入的
saveDiagram(done) {
// 把传入的done再传给bpmn原型的saveXML函数调用
this.bpmnModeler.saveXML({ format: true }, function(err, xml) {
done(err, xml);
});
},
七、获取自定义名称创建流程图
// 从用户输入获取流程名称(页面一加载就要求输入名称)
getProcessName() {
this.$prompt('请输入流程名称', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消创建'
}).then(({ value }) => {
if(value === ''){
this.$message({
type: 'error',
message: '流程名字不能为空'
});
this.getProcessName()
}
else {
this.processName = value;
// 创建新图
this.createNewDiagram();
}
}).catch(() => {
// this.back()
});
},
八、保存图到后台
- html
<el-button class="save" type="primary" @click="save" :disabled="!newDiagramXML">新增保存</el-button>
- js
// 保存修改操作
save() {
// 传给后台的参数
let params = {
id: this.id,
bpmnXml: this.xmlStr
};
// 调用API接口
// saveBpmnData(params).then(res => {
// // console.log(res)
// // console.log(this.xmlStr)
// });
}