предисловие
Поскольку мне нужно было использовать Activiti и Bpmn.js для требований предыдущего проекта, я отправился изучать волну операций.В процессе обучения и использования я часто заходил на Baidu и обнаруживал относительно мало статей об Activiti7 и Bpmn. .js, или Говорят, что описание недостаточно подробное и версия относительно старая, поэтому сейчас я запишу некоторые шаги и операции своей интеграции Bpmn.js.
Некоторые операции, связанные с интеграцией Spring Boot Activiti7 в серверную часть, в следующий раз будут записаны в специальном блоге.На этот раз давайте посмотрим, как Vue интегрирует Bpmn.js для реализации онлайн-рисования, экспорта Xml, svg, сохранения онлайн и т. д. , действовать.
Если у вас есть хорошие предложения и вопросы, вы можете отправить электронное письмо по адресу "18934086807@163.com"
Полный код Githubbpmn-demo Авторский блогWinily
подготовительная работа
Прежде всего, само собой разумеется, что сначала должен быть проект Vue, сначала собрать проект Vue и установить зависимости.
// 创建一个Vue项目
vue create bpmn-demo
// 安装一下项目依赖,我比较喜欢yarn,
yarn
// 或者使用npm
npm install
После завершения проекта все, что нам нужно сделать, это установить зависимости Bpmn-js и интегрировать код. Сначала установите зависимость
// yarn 安装
yarn add bpmn-js
// npm安装
npm install bpmn-js
Сначала откройте main.js проекта и импортируйте библиотеку шрифтов и файлы стилей, связанные с bpmn-js.Если вы не импортируете эти файлы стилей, это вызовет проблему, заключающуюся в том, что левое меню управления bpmn-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'
Создать средство моделирования Bpmn
После подготовки приступаем к использованию Bpmn-js во Vue, здесь я пишу код непосредственно в App.vue для удобства, а потом в компонент вписываю по факту использования.
Во-первых, моделер Bpmn-js фактически реализуется через Canvas, мы создаем div в шаблоне для Bpmn, чтобы создать моделлер.
<template>
<div id="app">
<div class="container">
<!-- 创建一个canvas画布 npmn-js是通过canvas实现绘图的,并设置ref让vue获取到element -->
<div class="bpmn-canvas" ref="canvas"></div>
</div>
</div>
</template>
После написания HTML-части давайте реализуем js-часть. Чтобы создать моделер, нам нужно использовать объект BpmnModeler, который в основном создается путем создания этого объекта.
// 在这里引入一下Bpmn建模器对象
import BpmnModeler from "bpmn-js/lib/Modeler";
export default {
data() {
return {
bpmnModeler: null,
canvas: null,
// 这部分具体的代码我放到了下面
initTemplate: `略`
};
},
methods: {
init() {
// 获取画布 element
this.canvas = this.$refs.canvas;
// 创建Bpmn对象
this.bpmnModeler = new BpmnModeler({
// 设置bpmn的绘图容器为上门获取的画布 element
container: this.canvas
});
// 初始化建模器内容
this.initDiagram(this.initTemplate);
},
initDiagram(bpmn) {
// 将xml导入Bpmn-js建模器
this.bpmnModeler.importXML(bpmn, err => {
if (err) {
this.$Message.error("打开模型出错,请确认该模型符合Bpmn2.0规范");
}
});
}
},
// 生命周期钩子,在组件加载完成后调用init函数进行创建初始化Bpmn-js建模器
mounted() {
this.init();
}
};
Эта часть xml представляет собой код шаблона блок-схемы Bpmn. Этот шаблон содержит начальный узел. Вы можете создать или изменить шаблон в соответствии с вашими потребностями. Конечно, я не рекомендую вручную изменять код xml. Способ создания шаблона, который я рекомендую, состоит в том, чтобы создать нужную модель в средстве моделирования Bpmn-js, затем экспортировать файл xml или bpmn, а затем скопировать код из файла для его использования.
В моем конкретном практическом приложении и сочетании с серверной частью Activiti7 я обнаружил, что в шаблоне xml следует обратить внимание на заголовок xml, то есть часть тега определений.Здесь представлены многие пространства имен.Если ваша блок-схема нуждается в быть развернут в Activiti, то вам нужно отметить, что он должен быть импортированПространство имен Активити. Следующий шаблон — это шаблон Xml, используемый Activiti. Неспособность импортировать или соответствовать каноническому пространству имен Activiti приведет к сбоям при развертывании модели. Если модель в конечном итоге будет развернута в Activiti, рекомендуется изменить и использовать ее на основе следующего шаблона.
<?xml version="1.0" encoding="UTF-8"?>
<definitions
xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC"
xmlns:camunda="http://camunda.org/schema/1.0/bpmn"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:activiti="http://activiti.org/bpmn"
id="m1577635100724"
name=""
targetNamespace="http://www.activiti.org/testm1577635100724"
>
<process id="process" processType="None" isClosed="false" isExecutable="true">
<extensionElements>
<camunda:properties>
<camunda:property name="a" value="1" />
</camunda:properties>
</extensionElements>
<startEvent id="_2" name="start" />
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_leave">
<bpmndi:BPMNPlane id="BPMNPlane_leave" bpmnElement="leave">
<bpmndi:BPMNShape id="BPMNShape__2" bpmnElement="_2">
<omgdc:Bounds x="144" y="368" width="32" height="32" />
<bpmndi:BPMNLabel>
<omgdc:Bounds x="149" y="400" width="23" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>
Выполните вышеуказанные шаги. Мы закончили создание моделлера, давайте взглянем на эффект.
Добавить плагин панели bpmn-js-properties-panel
Bpmn-js изначально не поддерживал настройки тех пользовательских значений Activity, и для интеграции требовались дополнительные плагины. Сначала установите два плагина: bpmn-js-properties-panel и camunda-bpmn-moddle.
bpmn-js-properties-panel предоставляет редактор свойств для разработчика моделей, а camunda-bpmn-moddle расширяет содержимое, которое можно редактировать с помощью редактора свойств.Такие свойства, как правопреемник Activitie, зависят от camunda-bpmn-moddle до Edited.
// yarn 安装
yarn add bpmn-js-properties-panel
yarn add camunda-bpmn-moddle
// npm 安装
npm install bpmn-js-properties-panel
npm install camunda-bpmn-moddle
После установки зависимостей внедрить стиль bpmn-js-properties-panel в main.js, иначе тулбар не будет отображаться
// 左边工具栏以及编辑节点的样式
import 'bpmn-js-properties-panel/dist/assets/bpmn-js-properties-panel.css'
Создайте div в App.vue, чтобы задать положение отображения панели инструментов. Я положил его прямо под холст
<!-- 工具栏显示的地方 -->
<div class="bpmn-js-properties-panel" id="js-properties-panel"></div>
Затем импортируйте панель инструментов и настройте поддержку панели инструментов
// 工具栏相关
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";
Пересмотренная функция init
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],
moddleExtensions: {
camunda: camundaModdleDescriptor
}
});
this.createNewDiagram(this.bpmnTemplate);
}
Эффект
Создание, импорт и экспорт операций
Для реализации этих функций мы сначала напишем кнопки и стили и добавим к ним соответствующие события кликов.Я использую здесь компонент Element.
Вот проблема. Если вы используете этот редактор процессов для редактирования процесса, а затем процесс необходимо развернуть в Activiti для запуска, здесь будет проблема. Используйте соответствующие свойства, установленные camunda-bpmn-moddle, например, установить правопреемника на «Чжан Сан». Окончательный вывод XML: camunda:assignee="Zhang San", но это не соответствует требованиям спецификации Activiti. В Activiti требуется activiti:assignee="Zhang San". В противном случае будет не распознается нормально, поэтому я за одно из решений этой проблемы - напрямую использовать обычный для замены camunda на activiti, когда текст xml получается непосредственно в функции saveXML, чтобы подпроцесс мог работать нормально.
<div class="action">
<!-- 关于打开文件的这个我使用了Element的文件上传组件,在上传前钩子获取到文件然后读取文件内容 -->
<el-upload class="upload-demo" :before-upload="openBpmn">
<el-button icon="el-icon-folder-opened"></el-button>
</el-upload>
<el-button class="new" icon="el-icon-circle-plus" @click="newDiagram"></el-button>
<el-button icon="el-icon-download" @click="downloadBpmn"></el-button>
<el-button icon="el-icon-picture" @click="downloadSvg"></el-button>
</div>
Наверное эффект такой
Далее выполнить операцию
Операция импорта файла ()
Я использовал компонент загрузки файла Element, использовал хук перед загрузкой файла (до загрузки), чтобы получить файловый объект, а затем получил содержимое файла из файлового объекта и ввел его в средство моделирования Bpmn.
openBpmn(file) {
const reader = new FileReader();
// 读取File对象中的文本信息,编码格式为UTF-8
reader.readAsText(file, "utf-8");
reader.onload = () => {
//读取完毕后将文本信息导入到Bpmn建模器
this.createNewDiagram(reader.result);
};
return false;
}
Новая операция диаграммы
Создать новую диаграмму очень просто, достаточно перезагрузить исходный шаблон
newDiagram() {
this.createNewDiagram(this.bpmnTemplate);
},
операция экспорта файла
Для операции экспорта я установил здесь скрытую ссылку, потому что мой подход заключается в том, чтобы получить содержимое файла, установить имя файла, формат файла, затем сгенерировать ссылку для скачивания для тега a, а затем вызвать событие щелчка тега a. чтобы запустить загрузку.
<a hidden ref="downloadLink"></a>
Загрузить связанные операции
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();
}
},
Для удобства я написал функцию для получения имени файла, здесь я просто использую идентификатор модели в качестве имени файла.
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;
},
1. Экспорт операции Bpmn
Операция экспорта файлов Bpmn относительно проста. Фактически, файлы Bpmn ничем не отличаются от файлов XML, за исключением того, что имена суффиксов не совпадают. Фактически, структура тегов XML хранится в файле Bpmn. Поэтому нам нужно экспортировать и загрузить файл Bpmn, пока мы получаем XML модели от средства моделирования Bpmn-js.
downloadBpmn() {
this.bpmnModeler.saveXML({ format: true }, (err, xml) => {
if (!err) {
// 获取文件名
const name = `${this.getFilename(xml)}.bpmn`;
// 将文件名以及数据交给下载函数
this.download({ name: name, data: xml });
}
});
},
2. Экспорт операции SVG
Операция экспорта svg немного более хлопотная, т.к. я не обнаружил, что у моделлера Bpmn-js есть функция, которая может обеспечить экспорт, поэтому мне пришлось извлекать теги SVG из канваса холста, а затем обрабатывать и загружать их.
downloadSvg() {
this.bpmnModeler.saveXML({ format: true }, (err, xml) => {
if (!err) {
// 获取文件名
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 });
}
});
},
Окончательный эффект и полный код
Скриншот эффекта
полный код
- main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
// 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 ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI);
Vue.config.productionTip = false
new Vue({
router,
render: h => h(App)
}).$mount('#app')
- App.vue
<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-upload action class="upload-demo" :before-upload="openBpmn">
<el-button icon="el-icon-folder-opened"></el-button>
</el-upload>
<el-button class="new" icon="el-icon-circle-plus" @click="newDiagram"></el-button>
<el-button icon="el-icon-download" @click="downloadBpmn"></el-button>
<el-button icon="el-icon-picture" @click="downloadSvg"></el-button>
<a hidden ref="downloadLink"></a>
</div>
</div>
</div>
</template>
<script>
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";
export default {
data() {
return {
bpmnModeler: null,
canvas: null,
bpmnTemplate: `
<?xml version="1.0" encoding="UTF-8"?>
<definitions
xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC"
xmlns:camunda="http://camunda.org/schema/1.0/bpmn"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:activiti="http://activiti.org/bpmn"
id="m1577635100724"
name=""
targetNamespace="http://www.activiti.org/testm1577635100724"
>
<process id="process" processType="None" isClosed="false" isExecutable="true">
<extensionElements>
<camunda:properties>
<camunda:property name="a" value="1" />
</camunda:properties>
</extensionElements>
<startEvent id="_2" name="start" />
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_leave">
<bpmndi:BPMNPlane id="BPMNPlane_leave" bpmnElement="leave">
<bpmndi:BPMNShape id="BPMNShape__2" bpmnElement="_2">
<omgdc:Bounds x="144" y="368" width="32" height="32" />
<bpmndi:BPMNLabel>
<omgdc:Bounds x="149" y="400" width="23" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>
`
};
},
methods: {
newDiagram() {
this.createNewDiagram(this.bpmnTemplate);
},
downloadBpmn() {
this.bpmnModeler.saveXML({ format: true }, (err, xml) => {
if (!err) {
// 获取文件名
const name = `${this.getFilename(xml)}.bpmn`;
// 将文件名以及数据交给下载方法
this.download({ name: name, data: xml });
}
});
},
downloadSvg() {
this.bpmnModeler.saveXML({ format: true }, (err, xml) => {
if (!err) {
// 获取文件名
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 });
}
});
},
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();
}
},
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],
moddleExtensions: {
camunda: camundaModdleDescriptor
}
});
await this.createNewDiagram(this.bpmnTemplate);
},
async createNewDiagram(bpmn) {
// 将字符串转换成图显示出来;
this.bpmnModeler.importXML(bpmn, err => {
if (err) {
this.$Message.error("打开模型出错,请确认该模型符合Bpmn2.0规范");
}
});
}
},
mounted() {
this.init();
}
};
</script>
<style>
.bpmn-canvas {
width: 100%;
height: 100vh;
}
.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;
}
</style>