vue2集成bpmn流程图,可导入导出预览等
1.使用到的组件
| 组件 | 源码 | 版本 | license |
|---|---|---|---|
| bpmn-js | 地址 | 7.3.1 | bpmn.io |
| bpmn-js-properties-panel | 地址 | 0.37.2 | MIT |
| camunda-bpmn-moddle | 地址 | 4.5.0 | MIT |
版本一定要对,否则可能会报错
2.组件代码
<template>
<div id="app">
<div class="container">
<!-- 创建一个canvas画布 npmn-js是通过canvas实现绘图的,并设置ref让vue获取到element -->
<div class="bpmn-canvas" ref="canvas"></div>
<!-- 工具栏显示的地方 -->
<div class="bpmn-js-properties-panel" id="js-properties-panel"></div>
<!-- 把操作按钮写在这里面 -->
<div class="action">
<el-tooltip effect="light" content="导入Bpmn" placement="top">
<el-upload action class="upload-demo" :before-upload="openBpmn">
<el-button icon="el-icon-folder-opened"></el-button>
</el-upload>
</el-tooltip>
<el-tooltip effect="light" content="新建流程" placement="top">
<el-button class="new" icon="el-icon-circle-plus" @click="newDiagram"></el-button>
</el-tooltip>
<el-tooltip effect="light" content="下载Bpmn" placement="top">
<el-button icon="el-icon-download" @click="downloadBpmn"></el-button>
</el-tooltip>
<el-tooltip effect="light" content="下载Svg" placement="top">
<el-button icon="el-icon-picture" @click="downloadSvg"></el-button>
</el-tooltip>
<el-button-group style="margin-left: 10px;">
<el-button @click="handlerUndo">撤销</el-button>
<el-button @click="handlerRedo">恢复</el-button>
</el-button-group>
<el-button-group style="margin-left: 10px;">
<el-button @click="handlerZoom(0.1)">放大</el-button>
<el-button @click="handlerZoom(-0.1)">缩小</el-button>
<el-button @click="handlerZoom(0)">还原</el-button>
</el-button-group>
<el-button-group style="margin-left: 10px;">
<el-button @click="perviewXML">xml预览</el-button>
<el-button @click="perviewSVG">svg预览</el-button>
</el-button-group>
<!-- 预览弹出 -->
<el-dialog title="XML预览" append-to-body width="80%" :visible.sync="perviewXMLShow">
<div style="max-height: 65vh;overflow: auto;">
<pre v-highlight="perviewXMLStr"><code></code></pre>
</div>
</el-dialog>
<el-dialog title="SVG预览" append-to-body width="80%" :visible.sync="perviewSVGShow">
<div style="text-align: center;" v-html="perviewSVGData" />
</el-dialog>
<a hidden ref="downloadLink"></a>
</div>
</div>
</div>
</template>
<script>
// 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'
import BpmnModeler from "bpmn-js/lib/Modeler";
// 左边工具栏以及节点
import propertiesProviderModule from "bpmn-js-properties-panel/lib/provider/camunda";
// 右边的工具栏
import propertiesPanelModule from "bpmn-js-properties-panel";
import camundaModdleDescriptor from "camunda-bpmn-moddle/resources/camunda";
// BPMN国际化
import customTranslate from './customTranslate/customTranslate';
// 自定义汉化模块
var customTranslateModule = {
translate: ['value', customTranslate]
};
// XML预览代码高亮
import Highlight from "@/components/highlight/highlight";
import vue from "vue"
export default {
name: "Bpmn",
props: {
/* 编辑器的内容 */
value: {
type: String,
default: "",
},
/* 只读 */
readOnly: {
type: Boolean,
default: false,
},
},
components: {
},
data() {
return {
bpmnModeler: null,
scale: 1,
canvas: null,
bpmnTemplate: `
<?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:omgdi="http://www.omg.org/spec/DD/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" id="sid-38422fae-e03e-43a3-bef4-bd33b32041b2" targetNamespace="http://bpmn.io/bpmn" exporter="bpmn-js (https://demo.bpmn.io)" exporterVersion="5.1.2">
<process id="Process_1" isExecutable="false">
<startEvent id="StartEvent_1y45yut" name="开始">
<outgoing>SequenceFlow_0h21x7r</outgoing>
</startEvent>
<task id="Task_1hcentk">
<incoming>SequenceFlow_0h21x7r</incoming>
</task>
<sequenceFlow id="SequenceFlow_0h21x7r" sourceRef="StartEvent_1y45yut" targetRef="Task_1hcentk" />
</process>
<bpmndi:BPMNDiagram id="BpmnDiagram_1">
<bpmndi:BPMNPlane id="BpmnPlane_1" bpmnElement="Process_1">
<bpmndi:BPMNShape id="StartEvent_1y45yut_di" bpmnElement="StartEvent_1y45yut">
<omgdc:Bounds x="152" y="102" width="36" height="36" />
<bpmndi:BPMNLabel>
<omgdc:Bounds x="160" y="145" width="22" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Task_1hcentk_di" bpmnElement="Task_1hcentk">
<omgdc:Bounds x="240" y="80" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="SequenceFlow_0h21x7r_di" bpmnElement="SequenceFlow_0h21x7r">
<omgdi:waypoint x="188" y="120" />
<omgdi:waypoint x="240" y="120" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>
`,
perviewXMLStr: null,
perviewSVGData: null,
perviewXMLShow: false,
perviewSVGShow: false,
};
},
methods: {
newDiagram() {
this.createNewDiagram(this.bpmnTemplate);
},
handlerRedo() {
this.bpmnModeler.get("commandStack").redo();
},
handlerUndo() {
this.bpmnModeler.get("commandStack").undo();
},
handlerZoom(radio) {
const newScale = !radio ? 1.0 : this.scale + radio;
this.bpmnModeler.get("canvas").zoom(newScale);
this.scale = newScale;
},
async downloadBpmn() {
try {
const { xml } = await this.bpmnModeler.saveXML({ format: true });
// 获取文件名
const name = `${this.getFilename(xml)}.bpmn`;
// 将文件名以及数据交给下载方法
this.download({ name: name, data: xml });
} catch (error) {
this.toast.error('下载Bpmn失败,请重试')
}
},
async downloadSvg() {
try {
const { xml } = await this.bpmnModeler.saveXML({ format: true });
// 获取文件名
const name = `${this.getFilename(xml)}.svg`;
// 从建模器画布中提取svg图形标签
let context = "";
const djsGroupAll = this.$refs.canvas.querySelectorAll(".djs-group");
for (let item of djsGroupAll) {
context += item.innerHTML;
}
// 获取svg的基本数据,长宽高
const viewport = this.$refs.canvas
.querySelector(".viewport")
.getBBox();
// 将标签和数据拼接成一个完整正常的svg图形
const svg = `
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="${viewport.width}"
height="${viewport.height}"
viewBox="${viewport.x} ${viewport.y} ${viewport.width} ${viewport.height}"
version="1.1"
>
${context}
</svg>
`;
// 将文件名以及数据交给下载方法
this.download({ name: name, data: svg });
} catch (error) {
this.toast.error('下载Svg失败,请重试')
}
},
openBpmn(file) {
const reader = new FileReader();
// 读取File对象中的文本信息,编码格式为UTF-8
reader.readAsText(file, "utf-8");
reader.onload = () => {
//读取完毕后将文本信息导入到Bpmn建模器
this.createNewDiagram(reader.result);
};
return false;
},
getFilename(xml) {
let start = xml.indexOf("process");
let filename = xml.substr(start, xml.indexOf(">"));
filename = filename.substr(filename.indexOf("id") + 4);
filename = filename.substr(0, filename.indexOf('"'));
return filename;
},
download({ name = "diagram.bpmn", data }) {
// 这里就获取到了之前设置的隐藏链接
const downloadLink = this.$refs.downloadLink;
// 把输就转换为URI,下载要用到的
const encodedData = encodeURIComponent(data);
if (data) {
// 将数据给到链接
downloadLink.href =
"data:application/bpmn20-xml;charset=UTF-8," + encodedData;
// 设置文件名
downloadLink.download = name;
// 触发点击事件开始下载
downloadLink.click();
}
},
// XML预览
async perviewXML() {
try {
const { xml } = await this.bpmnModeler.saveXML({ format: true });
this.perviewXMLStr = xml
this.perviewXMLShow = true
} catch (error) {
this.toast.error('预览失败,请重试')
}
},
// SVG预览
async perviewSVG() {
try {
const { svg } = await this.bpmnModeler.saveSVG();
this.perviewSVGData = svg
this.perviewSVGShow = true
} catch (error) {
this.toast.error('预览失败,请重试')
}
},
async init() {
// 获取画布 element
this.canvas = this.$refs.canvas;
// 创建Bpmn对象
this.bpmnModeler = new BpmnModeler({
// 设置bpmn的绘图容器为上门获取的画布 element
container: this.canvas,
// 加入工具栏支持
propertiesPanel: {
parent: "#js-properties-panel"
},
additionalModules: [propertiesProviderModule, propertiesPanelModule, customTranslateModule],
moddleExtensions: {
camunda: camundaModdleDescriptor
}
});
await this.createNewDiagram(this.value?this.value:this.bpmnTemplate);
// 监听画布点击事件,存储变化值
var that = this
this.$refs.canvas.addEventListener("click", async function(){
try {
const { xml } = await that.bpmnModeler.saveXML({ format: true });
that.$emit("input",xml)
} catch (error) {
that.$modal.msgError('实时保存失败')
}
})
},
async createNewDiagram(bpmn) {
// 将字符串转换成图显示出来;
try {
const result = await this.bpmnModeler.importXML(bpmn);
} catch (err) {
this.$modal.msgError("打开模型出错,请确认该模型符合Bpmn2.0规范");
}
},
},
mounted() {
this.init();
vue.use(Highlight);
}
};
</script>
<style lang="scss" >
.bpmn-canvas {
width: 100%;
height: 100vh;
background: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PHBhdHRlcm4gaWQ9ImEiIHdpZHRoPSI0MCIgaGVpZ2h0PSI0MCIgcGF0dGVyblVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHBhdGggZD0iTTAgMTBoNDBNMTAgMHY0ME0wIDIwaDQwTTIwIDB2NDBNMCAzMGg0ME0zMCAwdjQwIiBmaWxsPSJub25lIiBzdHJva2U9IiNlMGUwZTAiIG9wYWNpdHk9Ii4yIi8+PHBhdGggZD0iTTQwIDBIMHY0MCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZTBlMGUwIi8+PC9wYXR0ZXJuPjwvZGVmcz48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSJ1cmwoI2EpIi8+PC9zdmc+')
repeat !important;
}
.action {
position: fixed;
bottom: 10px;
left: 10px;
display: flex;
}
.upload-demo {
margin-right: 10px;
}
// 右菜单样式(注释隐藏菜单)
// .bpmn-js-properties-panel {
// position: absolute;
// top: 0;
// right: 0px;
// width: 300px;
// max-height: 500px;
// overflow-y: auto;
// border: 1px solid rgba(0,0,0,0.1);
// }
.djs-palette {
width: 100px;
}
</style>
3.使用组件
<Bpmn v-model='form.content'></Bpmn>
import Bpmn from '@/components/Bpmn' export default { components: { Bpmn, }, data() { return { form: { content: null, }, } }, }
4.截图

5.本人的示例项目
示例项目一:gitee
6. 其他博文
自定义节点:vue中使用bpmn-js绘制流程图,并实现汉化、自定义Palette及ContextPad。_vue流程图组件_面向百度丶CV开发的博客-CSDN博客

浙公网安备 33010602011771号