当前位置: 首页 > news >正文

Vue3 + bpmn.js 实战:从零搭建可定制化工作流设计器

1. 为什么选择Vue3 + bpmn.js开发工作流设计器

工作流引擎在现代企业应用中扮演着越来越重要的角色。想象一下,当你需要设计一个请假审批流程或者订单处理流程时,如果每次都要从头开始写代码,那将是一件多么痛苦的事情。这就是为什么我们需要一个可视化的工作流设计器。

bpmn.js是目前最流行的BPMN 2.0流程建模工具之一,它提供了完整的流程图绘制能力。而Vue3的Composition API让我们能够更好地组织代码逻辑,特别是对于复杂的状态管理场景。两者结合,可以打造出既强大又灵活的工作流设计器。

我最近在一个电商后台系统中就使用了这个组合。客户需要能够自定义他们的订单处理流程,包括退货、换货、补发等各种场景。使用Vue3+bpmn.js的方案,我们不仅快速实现了核心功能,还很容易地扩展了各种自定义节点和属性面板。

2. 环境准备与基础配置

2.1 创建Vue3项目

首先,我们需要创建一个新的Vue3项目。我推荐使用Vite,它的启动速度非常快:

npm create vite@latest bpmn-designer --template vue cd bpmn-designer npm install

安装完成后,我们需要添加bpmn.js的核心依赖:

npm install bpmn-js --save-dev

2.2 基础页面结构

在工作流设计器中,我们通常需要三个主要区域:

  1. 左侧的工具面板(提供各种流程节点)
  2. 中间的画布区域(用于绘制和编辑流程图)
  3. 右侧的属性面板(编辑选中节点的属性)

修改App.vue文件,添加基础结构:

<template> <div class="container"> <div class="toolbox" ref="toolbox"></div> <div class="canvas" ref="canvas"></div> <div class="properties-panel" ref="propertiesPanel"></div> </div> </template> <script setup> import { ref, onMounted } from 'vue' import BpmnModeler from 'bpmn-js/lib/Modeler' const canvas = ref(null) const toolbox = ref(null) const propertiesPanel = ref(null) onMounted(() => { // 初始化代码将在后面添加 }) </script> <style scoped> .container { display: flex; height: 100vh; width: 100vw; } .toolbox { width: 80px; background: #f0f0f0; } .canvas { flex: 1; } .properties-panel { width: 300px; background: #f5f5f5; border-left: 1px solid #ddd; } </style>

3. 初始化bpmn.js模型编辑器

3.1 基本模型初始化

在setup函数中,我们需要初始化bpmn.js的Modeler。这是整个设计器的核心:

import { ref, onMounted } from 'vue' import BpmnModeler from 'bpmn-js/lib/Modeler' const bpmnModeler = ref(null) onMounted(() => { bpmnModeler.value = new BpmnModeler({ container: canvas.value }) // 创建一个空的BPMN图 createNewDiagram() }) const createNewDiagram = async () => { try { const result = await bpmnModeler.value.createDiagram() console.log('Diagram created', result) } catch (err) { console.error('Failed to create diagram', err) } }

3.2 加载示例流程图

在实际项目中,我们通常会从服务器加载已有的流程图,或者使用一个默认模板。让我们添加这个功能:

const defaultXML = ` <?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:dc="http://www.omg.org/spec/DD/20100524/DC" targetNamespace="http://bpmn.io/schema/bpmn"> <process id="Process_1" isExecutable="false"> <startEvent id="StartEvent_1" /> </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 x="173" y="102" width="36" height="36" /> </bpmndi:BPMNShape> </bpmndi:BPMNPlane> </bpmndi:BPMNDiagram> </definitions> ` const loadDiagram = async (xml) => { try { const { warnings } = await bpmnModeler.value.importXML(xml) if (warnings.length) { console.warn('导入成功,但有警告:', warnings) } else { console.log('流程图导入成功') } } catch (err) { console.error('导入流程图失败:', err) } } // 在createNewDiagram中调用 const createNewDiagram = async () => { try { await loadDiagram(defaultXML) } catch (err) { console.error('创建流程图失败', err) } }

4. 添加中文支持和自定义面板

4.1 实现中文汉化

bpmn.js默认是英文界面,这对于国内用户不太友好。我们可以通过自定义翻译模块来实现汉化。

首先创建一个翻译模块:

// src/utils/customTranslate.js const translations = { 'Append {type}': '添加 {type}', 'Remove': '删除', 'Activate the global connect tool': '激活全局连接工具', // 更多翻译项... } export default function customTranslate(template, replacements) { replacements = replacements || {} // 翻译模板 template = translations[template] || template // 替换变量 return template.replace(/{([^}]+)}/g, function(_, key) { return replacements[key] || `{${key}}` }) }

然后在初始化Modeler时使用这个翻译模块:

import customTranslate from './utils/customTranslate' const bpmnModeler = ref(null) onMounted(() => { bpmnModeler.value = new BpmnModeler({ container: canvas.value, additionalModules: [ { translate: ['value', customTranslate] } ] }) createNewDiagram() })

4.2 添加属性面板

属性面板允许用户编辑选中元素的属性。我们需要安装额外的模块:

npm install --save bpmn-js-properties-panel npm install --save camunda-bpmn-moddle

然后更新我们的初始化代码:

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' onMounted(() => { bpmnModeler.value = new BpmnModeler({ container: canvas.value, propertiesPanel: { parent: propertiesPanel.value }, additionalModules: [ propertiesProviderModule, propertiesPanelModule, { translate: ['value', customTranslate] } ], moddleExtensions: { camunda: camundaModdleDescriptor } }) createNewDiagram() })

5. 实现自定义节点和扩展功能

5.1 添加自定义工具栏

默认的工具栏可能不符合我们的业务需求。我们可以创建一个自定义工具栏组件:

<template> <div class="custom-toolbox"> <div class="tool-item" v-for="item in tools" :key="item.type" @click="createElement(item.type)"> <span>{{ item.name }}</span> </div> </div> </template> <script setup> import { ref } from 'vue' const tools = ref([ { type: 'bpmn:UserTask', name: '用户任务' }, { type: 'bpmn:ServiceTask', name: '服务任务' }, { type: 'bpmn:ExclusiveGateway', name: '排他网关' }, // 更多自定义节点类型... ]) const createElement = (type) => { // 获取BPMN的ElementFactory和Create模块 const elementFactory = bpmnModeler.value.get('elementFactory') const create = bpmnModeler.value.get('create') // 创建形状 const shape = elementFactory.createShape({ type }) // 在画布上开始绘制 create.start(event, shape) } </script> <style scoped> .custom-toolbox { padding: 10px; } .tool-item { padding: 8px; margin-bottom: 5px; background: #fff; border: 1px solid #ddd; cursor: pointer; text-align: center; } .tool-item:hover { background: #f0f0f0; } </style>

5.2 实现自定义属性面板

有时候默认的属性面板不能满足我们的业务需求。我们可以创建一个Vue组件来替代:

<template> <div class="custom-properties"> <div v-if="selectedElement"> <h3>{{ getElementName(selectedElement.type) }}</h3> <div class="form-group"> <label>节点名称</label> <input v-model="elementName" @change="updateElementName"> </div> <!-- 更多自定义属性字段 --> </div> <div v-else> 请选择一个节点 </div> </div> </template> <script setup> import { ref, watch } from 'vue' const selectedElement = ref(null) const elementName = ref('') // 监听选中的元素变化 watch(() => bpmnModeler.value.get('selection').get(), (newSelection) => { if (newSelection.length) { selectedElement.value = newSelection[0] elementName.value = selectedElement.value.businessObject.name || '' } else { selectedElement.value = null } }) const updateElementName = () => { const modeling = bpmnModeler.value.get('modeling') modeling.updateLabel(selectedElement.value, elementName.value) } const getElementName = (type) => { const typeMap = { 'bpmn:UserTask': '用户任务', 'bpmn:ServiceTask': '服务任务', // 更多类型映射... } return typeMap[type] || type } </script>

6. 保存和加载流程设计

6.1 导出流程图XML

设计完成后,我们需要能够保存流程图。bpmn.js提供了导出XML的方法:

const exportDiagram = async () => { try { const { xml } = await bpmnModeler.value.saveXML({ format: true }) console.log('导出的XML:', xml) // 可以发送到服务器保存 // await saveToServer(xml) return xml } catch (err) { console.error('导出失败:', err) throw err } }

6.2 从服务器加载流程图

在实际应用中,我们通常需要从服务器加载已有的流程图:

const loadFromServer = async (diagramId) => { try { // 从服务器获取XML const response = await fetch(`/api/diagrams/${diagramId}`) const xml = await response.text() // 加载到设计器 await loadDiagram(xml) } catch (err) { console.error('加载流程图失败:', err) throw err } }

7. 高级定制与优化

7.1 自定义节点样式

我们可以通过重写BPMN的样式模块来改变节点的外观。首先创建一个自定义样式模块:

// src/modules/customStyles.js export default function CustomStyles() { this.getStyles = function() { return [ { selector: '.djs-shape.highlight', style: { stroke: '#FF0000', strokeWidth: 3 } }, { selector: '.djs-shape.custom-task', style: { fill: '#FFF0F0', stroke: '#FF9999' } } ] } }

然后在初始化Modeler时添加这个模块:

import CustomStyles from './modules/customStyles' onMounted(() => { bpmnModeler.value = new BpmnModeler({ container: canvas.value, additionalModules: [ // 其他模块... CustomStyles ] }) })

7.2 实现撤销/重做功能

对于复杂的工作流设计,撤销和重做功能非常重要。bpmn.js内置了这些功能:

const undo = () => { const commandStack = bpmnModeler.value.get('commandStack') commandStack.undo() } const redo = () => { const commandStack = bpmnModeler.value.get('commandStack') commandStack.redo() } // 可以绑定到按钮上

7.3 添加键盘快捷键

为了提高用户体验,我们可以添加一些常用的键盘快捷键:

import { onMounted, onUnmounted } from 'vue' const setupKeyboardShortcuts = () => { const handleKeyDown = (event) => { if (event.ctrlKey && event.key === 'z') { undo() event.preventDefault() } else if (event.ctrlKey && event.key === 'y') { redo() event.preventDefault() } else if (event.key === 'Delete') { const selection = bpmnModeler.value.get('selection') const modeling = bpmnModeler.value.get('modeling') if (selection.get().length) { modeling.removeElements(selection.get()) } } } window.addEventListener('keydown', handleKeyDown) return () => { window.removeEventListener('keydown', handleKeyDown) } } onMounted(() => { const cleanup = setupKeyboardShortcuts() onUnmounted(() => { cleanup() }) })

8. 实际项目中的经验分享

在最近的一个电商后台项目中,我们使用Vue3+bpmn.js开发了一个复杂的工作流设计器。这个设计器需要支持多种自定义节点类型,如"发送短信"、"调用API"等业务特定节点。

我们通过扩展bpmn.js的paletteProvider实现了这些自定义节点:

// src/modules/customPalette.js export default function CustomPalette(palette, create, elementFactory) { this.create = create this.elementFactory = elementFactory palette.registerProvider(this) } CustomPalette.$inject = ['palette', 'create', 'elementFactory'] CustomPalette.prototype.getPaletteEntries = function() { return { 'create.custom-task': { group: 'activity', className: 'bpmn-icon-task custom-task', title: '创建自定义任务', action: { click: (event) => { const shape = this.elementFactory.createShape({ type: 'bpmn:Task', businessObject: { name: '自定义任务', isCustom: true } }) this.create.start(event, shape) } } } // 更多自定义节点... } }

另一个挑战是实现复杂的属性验证逻辑。例如,某些节点必须设置特定的属性才能保存。我们通过监听属性变化并添加验证逻辑来实现:

const validateProperties = () => { const eventBus = bpmnModeler.value.get('eventBus') eventBus.on('element.changed', (event) => { const element = event.element const businessObject = element.businessObject if (businessObject.isCustom && !businessObject.apiUrl) { console.warn('自定义任务必须设置API URL') // 可以显示错误提示或阻止保存 } }) } onMounted(() => { // ...其他初始化代码 validateProperties() })

最后,性能优化也是一个重要考虑。当流程图变得非常复杂时,渲染性能可能会下降。我们通过以下方式优化:

  1. 使用debounce处理频繁的状态更新
  2. 对大型流程图实现懒加载
  3. 在保存时只序列化变化的部分而不是整个流程图
import { debounce } from 'lodash' const saveDiagram = debounce(async () => { try { const { xml } = await bpmnModeler.value.saveXML({ format: true }) // 保存到服务器... } catch (err) { console.error('保存失败', err) } }, 500)
http://www.cnnetsun.cn/news/2600267.html

相关文章:

  • Flutter状态管理Bloc详解:实现响应式架构
  • python连接DM数据库
  • 鸣潮智能助手:基于图像识别的全自动游戏自动化方案
  • 无DAC数字可重构智能表面架构:射频开关与传输线实现超低功耗高阶调制
  • 体验Taotoken旗舰模型首发更新第一时间用上最新最强模型
  • 神经形态硬件通信系统:从AER协议到路由架构的深度解析
  • 动态异构图神经网络硬件加速器COSH:FLAG模型如何消除跨快照冗余计算
  • 告别论文焦虑!9 款 AI 毕业论文工具测评
  • 软件实体的自动抽取与学术影响力方法【附程序】
  • Fluidd完整指南:10个技巧打造高效3D打印控制界面
  • 终极跨平台UI自动化方案:Midscene.js视觉AI驱动的创新实践
  • 如何修复损坏的视频文件:Untrunc的智能恢复方案
  • 规范井下作业秩序,无感定位优化矿山透明化空间管理,摒弃UWB老旧模式
  • 宇宙七级文明倒计时:人类从0.73到神级文明,每一步都是仰望
  • 【限时公开】ChatGPT时间管理黑箱操作手册:微软/谷歌资深PM都在用的4层任务过滤协议
  • 从零搭建AI商业引擎,ChatGPT画布9宫格全拆解,错过这版将淘汰下一代创业者
  • 从棋盘格到三维重建:OpenCV相机标定实战与参数解析
  • 基于 RV1126B 评估板的 GUI 应用开发实战(一)
  • GRID32.OCX文件丢失找不到问题解决
  • AI视频生成里的角色一致性问题:为什么同一个人会越生成越不像?
  • OPENCODE+spec-kit安装
  • Outfit字体:9种字重+可变字体,打造品牌视觉统一性的终极解决方案
  • 如何在Android设备上实现钉钉虚拟定位:XposedRimetHelper完全指南
  • AcWing 2189:有源汇上下界最大流 ← Dinic算法
  • 论文查重竟然能免费?书匠策AI这个功能太香了,毕业党必看!
  • 紫垣商驿三轴试验数据处理软件
  • Modelsim和Vivado仿真器下,Testbench文件编写有哪些“坑”?我总结了3个避雷点
  • 从零打造可落地的直流电机 PID 驱动系统 (十四):编码器测速原理与速度环阶跃响应实测
  • VCAM虚拟相机:安卓摄像头替换的终极解决方案深度解析
  • 基于簇稀疏贝叶斯学习的混合大规模MIMO信道估计技术解析