本文已参与「新人创作礼」活动,一起开启掘金创作之路。
- 安装依赖
npm install --save bpmn-js
npm i bpmn-js-properties-panel --save-D
npm i camunda-bpmn-moddle --save-D
- 编写代码
index.tsx
import React, { useEffect } from "react";
import { Button } from 'antd'
import BpmnModeler from 'bpmn-js/lib/Modeler'
import { xml as xmlstr } from '../../bpmn/create/xml'
import 'bpmn-js-properties-panel/dist/assets/properties-panel.css' // 右边工具栏样式
// 左边工具栏以及编辑节点的样式
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 customTranslate from './customTranslate/customTranslate'
import './index.less'
// 右侧属性栏
import {
BpmnPropertiesPanelModule,
BpmnPropertiesProviderModule,
CamundaPlatformPropertiesProviderModule
} from 'bpmn-js-properties-panel';
import CamundaExtensionModule from 'camunda-bpmn-moddle/lib'
// 一个描述的json
import camundaModdleDescriptors from 'camunda-bpmn-moddle/resources/camunda';
var customTranslateModule = {
translate: ['value', customTranslate]
};
const bpmnModeler = new BpmnModeler({
//添加控制板
propertiesPanel: {
parent: '#js-properties-panel'
},
})
function App() {
let bpmnModeler = null;
useEffect(() => {
bpmnModeler = null
initBpmn()
}, [])
const initBpmn = () => {
bpmnModeler = new BpmnModeler({
container: '#container',
height: '100vh',
propertiesPanel: {
parent: '#properties-panel'
},
additionalModules: [
BpmnPropertiesPanelModule,
BpmnPropertiesProviderModule,
CamundaPlatformPropertiesProviderModule,
CamundaExtensionModule,
customTranslateModule
],
moddleExtensions: {
//如果要在属性面板中维护camunda:XXX属性,则需要此
camunda: camundaModdleDescriptors
}
})
createBpmnDiagram()
}
const createBpmnDiagram = async () => {
try {
const result = await bpmnModeler.importXML(xmlstr)
} catch (e) {
console.log(e);
}
}
const 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;
}
const saveBpmn = (withActiviti = false) => {
console.log("saveBpmn");
bpmnModeler.saveXML({ format: true }, (err, xml) => {
if (!err) {
if (withActiviti) {
xml = xml.replace(/camunda/ig, "activiti");
}
// 获取文件名
const name = `${getFilename(xml)}.bpmn`;
// 将文件名以及数据交给下载方法
const encodedData = encodeURIComponent(xml);
if (xml) {
const link = document.createElement('a')
// 将数据给到链接
link.href = "data:application/bpmn20-xml;charset=UTF-8," + encodedData;
// 设置文件名
link.download = name;
// 触发点击事件开始下载
link.click();
}
}
});
}
const saveSvg = () => {
console.log("saveSvg");
bpmnModeler.saveXML({ format: true }, (err, xml) => {
if (!err) {
// 获取文件名
const name = `${getFilename(xml)}.svg`;
if (xml) {
// 从建模器画布中提取svg图形标签
let context = "";
const djsGroupAll = document.querySelectorAll(".djs-group");
for (let item of djsGroupAll) {
context += item.innerHTML;
}
// 获取svg的基本数据,长宽高
const viewport = document.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>
`;
const link = document.createElement('a')
// 将数据给到链接
// 将文件名以及数据交给下载方法
const encodedData = encodeURIComponent(svg);
link.href = "data:application/bpmn20-xml;charset=UTF-8," + encodedData;
// 设置文件名
link.download = name;
// 触发点击事件开始下载
link.click();
}
}
});
}
return (
<div className="content with-diagram">
<ul className="buttons">
<li>
下载
</li>
<li>
<Button type="primary" onClick={() => saveBpmn()}>BPMN FOR CAMUNDA</Button>
</li>
<li>
<Button type="primary" onClick={() => saveBpmn(true)}>BPMN FOR ACTIVITI</Button>
</li>
<li>
<Button type="primary" onClick={() => saveSvg()}>SVG</Button>
</li>
</ul>
<div
id="container"
className="canvas"
></div>
<div
id="properties-panel"
className="properties-panel-parent"
></div>
</div>
);
}
export default App;
index.less
* {
box-sizing: border-box;
}
body,
html {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 12px;
height: 100%;
max-height: 100%;
padding: 0;
margin: 0;
}
#js-properties-panel {
width: 400px;
}
a:link {
text-decoration: none;
}
.content {
position: relative;
width: 100%;
height: 100%;
display: flex;
>.message {
width: 100%;
height: 100%;
text-align: center;
display: table;
font-size: 16px;
color: #111;
.note {
vertical-align: middle;
text-align: center;
display: table-cell;
}
&.error {
.details {
max-width: 500px;
font-size: 12px;
margin: 20px auto;
text-align: left;
color: #BD2828;
}
pre {
border: solid 1px #BD2828;
background: #fefafa;
padding: 10px;
color: #BD2828;
}
}
}
&:not(.with-error) .error,
&.with-error .intro,
&.with-diagram .intro {
display: none;
}
.canvas {
width: 100%;
}
.canvas,
.properties-panel-parent {
display: none;
}
&.with-diagram {
.canvas,
.properties-panel-parent {
display: block;
}
}
}
.buttons {
position: fixed;
bottom: 20px;
left: 20px;
z-index: 99999;
padding: 0;
margin: 0;
list-style: none;
>li {
display: inline-block;
margin-right: 10px;
}
}
.properties-panel-parent {
border-left: 1px solid #ccc;
overflow: auto;
&:empty {
display: none;
}
>.djs-properties-panel {
padding-bottom: 70px;
min-height: 100%;
}
}
xml.ts
export const xml = `
<?xml version="1.0" encoding="UTF-8"?>
<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">
<bpmn2:process id="Process_1" isExecutable="false">
<bpmn2:startEvent id="StartEvent_1"/>
</bpmn2:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1">
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
<dc:Bounds height="36.0" width="36.0" x="412.0" y="240.0"/>
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn2:definitions>`
- 启动项目访问localhost:8080