2024.7.23
开始
上周五接到临时需求,要求用bpmn.js技术,做一个简单的流程编排设计页面,gitHub项目地址
开发过程
网上教程都很详细,但是版本号必须对应上
安装bpmn-js
yarn add bpmn-js@7.3.1
yarn add bpmn-js-properties-panel@0.37.2
yarn add camunda-bpmn-moddle@^4.5.0
yarn add codemirror@^6.0.1
问题
1. 解决 bpmn-js-properties-panel 缺少文件问题
原因:版本问题
解决:
- 版本要对应
bpmn-js@7.3.1
bpmn-js-properties-panel@0.37.2
- 右侧属性面板div需要设置样式
.panel {
width: 320px;
position: absolute;
top: 0px;
right: 0px;
height: 100%;
overflow: auto;
}
2. Error: Passing callbacks to importXML is deprecated and will be removed in a future major release
原因:bpmnModeler.importXML不支持回调函数
解决:7.3.0之后版本的回调写法
import BpmnModeler from 'bpmn-js/lib/Modeler';
const bpmnModeler = new BpmnModeler();
try {
const result = await bpmnModeler.importXML(xml);
const { warnings } = result;
console.log(warnings);
} catch (err) {
console.log(err.message, err.warnings);
}
参考文档:
3. bpmnModeler.createDiagram也不支持回调函数
用importXML和createDiagram两个API二选一
代码
<template>
<div class="design-container">
<div>
<a-button @click="handleBack()">返回</a-button>
<a-button @click="handleSave(0)">保存</a-button>
<a-button @click="handleRedo()">前进</a-button>
<a-button @click="handleUndo()">后退</a-button>
<a-button @click="handleZoom(1)">放大</a-button>
<a-button @click="handleZoom(-1)">缩小</a-button>
<a-button @click="handleDownload()">下载</a-button>
</div>
<div class="wf-container">
<div id="wf-designer" ref="container"></div>
<div id="js-properties-panel" class="panel"></div>
</div>
</div>
</template>
<script lang="ts" setup>
import { markRaw, watch } from 'vue';
import BpmnModeler from 'bpmn-js/lib/Modeler';
import 'bpmn-js/dist/assets/diagram-js.css';
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css';
// bpmn-js-properties-panel相关
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.json';
// BPMN国际化
import customTranslate from '@/components/bpmn/custom/CustomTranslate.js';
import type { WorkflowModel } from '@/api/workflowDesignApi';
import { getXmlData, save } from '@/api/workflowDesignApi';
import { useRouter, useRoute } from 'vue-router';
const router = useRouter();
const route = useRoute();
//流程图 保存内容
const modelBpmn = ref<WorkflowModel>({
id: '',
keycode: '',
name: '',
nameEn: '',
group: 'general',
model: '',
status: '0',
});
// bpmn建模器
const container = ref(null);
const type = ref(route.query.type || '');
onMounted(() => {
initModeler();
});
watch(
() => route.query,
newVal => {
type.value = newVal.type || '';
if (newVal) {
createNewDiagram();
}
console.log(type.value, 'newVal');
},
);
let bpmnModeler: any = null;
const initModeler = () => {
// 加markRaw去除双向绑定作用域
bpmnModeler = markRaw(
new BpmnModeler({
container: container.value,
// 添加控制板
propertiesPanel: {
parent: '#js-properties-panel',
},
// 右侧属性面板
additionalModules: [
propertiesPanelModule,
propertiesProviderModule,
// 汉化
{ translate: ['value', customTranslate] },
],
moddleExtensions: {
camunda: camundaModdleDescriptor,
},
}),
);
//判断是否编辑还是创建状态
createNewDiagram();
// 监听节点变化
watchBpmnChanged();
};
// 创建空白流程图 == 渲染
const createNewDiagram = async () => {
if (type.value == 'edit') {
//根据请求数据渲染流程图
await getXmlData().then(res => {
modelBpmn.value = res.data || {};
// 将字符串转换成图显示出来
try {
const bpmnXmlStr = JSON.parse(modelBpmn.value?.model || '');
const result = bpmnModeler.importXML(bpmnXmlStr);
const { warnings } = result;
console.log(warnings);
} catch (err: any) {
console.log(err.message, err.warnings);
}
});
} else {
//创建空白流程图
await bpmnModeler.createDiagram();
}
};
//保存
const handleSave = (isDeploy: number) => {
const model: WorkflowModel = {
id: Math.random().toString().slice(2),
keycode: '',
name: '',
nameEn: '',
group: 'general',
model: '',
status: isDeploy ? '1' : '0',
};
bpmnModeler.saveXML({ format: true }).then(data => {
model.model = JSON.stringify(data.xml);
console.log(JSON.stringify(model));
alert(JSON.stringify(model));
// save(model);
});
};
// 监听节点变化
const watchBpmnChanged = () => {
// 监听节点选择变化
bpmnModeler.on('selection.changed', e => {
const element = e.newSelection[0];
if (element) {
// 渲染节点参数
} else {
// 渲染主表单配置参数
}
// console.log(element);
});
// 监听节点属性变化
bpmnModeler.on('element.changed', e => {
const { element } = e;
// console.log(element);
});
};
// 放大缩放
let scale = ref<number>(1);
const handleZoom = (zoom: number) => {
if (scale.value + zoom < 1) {
console.log('workflow desinger zoom limit 1');
return;
}
scale.value = scale.value + zoom;
bpmnModeler.get('canvas').zoom(scale.value);
};
//前进
const handleRedo = () => {
bpmnModeler.get('commandStack').redo();
};
//后退
const handleUndo = () => {
bpmnModeler.get('commandStack').undo();
};
//返回
const handleBack = () => {
router.push({ name: 'workflowList' });
};
// 下载
const handleDownload = () => {
bpmnModeler.saveXML({ format: true }, (err, data) => {
const dataTrack = 'bpmn';
const a = document.createElement('a');
const name = `diagram.${dataTrack}`;
a.setAttribute('href', `data:application/bpmn20-xml;charset=UTF-8,${encodeURIComponent(data)}`);
a.setAttribute('target', '_blank');
a.setAttribute('dataTrack', `diagram:download-${dataTrack}`);
a.setAttribute('download', name);
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
console.log(err);
});
};
</script>
<style>
.design-container {
padding: 16px;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
}
.wf-container {
position: relative;
flex: auto;
background: #fff;
position: relative;
margin-top: 16px;
border: 1px solid #d9d9d9;
}
.wf-container #wf-designer {
height: 100%;
}
.bjs-powered-by {
display: none;
}
svg.new-parent {
background-color: #f7f9ff !important;
}
.panel {
width: 320px;
position: absolute;
top: 0px;
right: 0px;
height: 100%;
overflow: auto;
}
</style>
最后效果图
自定义Palette
参考链接
- Bpmn.js 中文文档
- Bpmn.js--浏览页面
- 流程设计模板
- vue3 集成 bpmn-js——适配 activiti 工作流
- bpmn.js 基础篇(二)-自定义 Palette(1)
- bpmn-js 改造(一)添加会签节点
- 在 vue 中使用 bpmn-js(进阶)
- 全网最详 bpmn.js 教材-自定义 contextPad 篇
- 全网最详 bpmn.js 教材-自定义 renderer 篇
- 全网最详bpmn.js教材-自定义contextPad篇
- 全网最详bpmn.js教材-properties篇
- Vue3.0+bpmn.js+国际化(一)
- gitHub 地址
- mac-使用 ngrok 实现项目本地部署公网访问(内网穿透)
总结
没有定制化开发的话其实相对简单,但是版本号必须要对应才行,因为在开发中遇到版本不一致,导致右侧属性面板不显示。