1. 为什么选择Vue3 bpmn.js开发工作流设计器工作流引擎在现代企业应用中扮演着越来越重要的角色。想象一下当你需要设计一个请假审批流程或者订单处理流程时如果每次都要从头开始写代码那将是一件多么痛苦的事情。这就是为什么我们需要一个可视化的工作流设计器。bpmn.js是目前最流行的BPMN 2.0流程建模工具之一它提供了完整的流程图绘制能力。而Vue3的Composition API让我们能够更好地组织代码逻辑特别是对于复杂的状态管理场景。两者结合可以打造出既强大又灵活的工作流设计器。我最近在一个电商后台系统中就使用了这个组合。客户需要能够自定义他们的订单处理流程包括退货、换货、补发等各种场景。使用Vue3bpmn.js的方案我们不仅快速实现了核心功能还很容易地扩展了各种自定义节点和属性面板。2. 环境准备与基础配置2.1 创建Vue3项目首先我们需要创建一个新的Vue3项目。我推荐使用Vite它的启动速度非常快npm create vitelatest bpmn-designer --template vue cd bpmn-designer npm install安装完成后我们需要添加bpmn.js的核心依赖npm install bpmn-js --save-dev2.2 基础页面结构在工作流设计器中我们通常需要三个主要区域左侧的工具面板提供各种流程节点中间的画布区域用于绘制和编辑流程图右侧的属性面板编辑选中节点的属性修改App.vue文件添加基础结构template div classcontainer div classtoolbox reftoolbox/div div classcanvas refcanvas/div div classproperties-panel refpropertiesPanel/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; } /style3. 初始化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 version1.0 encodingUTF-8? definitions xmlnshttp://www.omg.org/spec/BPMN/20100524/MODEL xmlns:bpmndihttp://www.omg.org/spec/BPMN/20100524/DI xmlns:dchttp://www.omg.org/spec/DD/20100524/DC targetNamespacehttp://bpmn.io/schema/bpmn process idProcess_1 isExecutablefalse startEvent idStartEvent_1 / /process bpmndi:BPMNDiagram idBPMNDiagram_1 bpmndi:BPMNPlane idBPMNPlane_1 bpmnElementProcess_1 bpmndi:BPMNShape id_BPMNShape_StartEvent_2 bpmnElementStartEvent_1 dc:Bounds x173 y102 width36 height36 / /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 classcustom-toolbox div classtool-item v-foritem in tools :keyitem.type clickcreateElement(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; } /style5.2 实现自定义属性面板有时候默认的属性面板不能满足我们的业务需求。我们可以创建一个Vue组件来替代template div classcustom-properties div v-ifselectedElement h3{{ getElementName(selectedElement.type) }}/h3 div classform-group label节点名称/label input v-modelelementName changeupdateElementName /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 } /script6. 保存和加载流程设计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. 实际项目中的经验分享在最近的一个电商后台项目中我们使用Vue3bpmn.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() })最后性能优化也是一个重要考虑。当流程图变得非常复杂时渲染性能可能会下降。我们通过以下方式优化使用debounce处理频繁的状态更新对大型流程图实现懒加载在保存时只序列化变化的部分而不是整个流程图import { debounce } from lodash const saveDiagram debounce(async () { try { const { xml } await bpmnModeler.value.saveXML({ format: true }) // 保存到服务器... } catch (err) { console.error(保存失败, err) } }, 500)
Vue3 + bpmn.js 实战:从零搭建可定制化工作流设计器
发布时间:2026/5/27 15:32:17
1. 为什么选择Vue3 bpmn.js开发工作流设计器工作流引擎在现代企业应用中扮演着越来越重要的角色。想象一下当你需要设计一个请假审批流程或者订单处理流程时如果每次都要从头开始写代码那将是一件多么痛苦的事情。这就是为什么我们需要一个可视化的工作流设计器。bpmn.js是目前最流行的BPMN 2.0流程建模工具之一它提供了完整的流程图绘制能力。而Vue3的Composition API让我们能够更好地组织代码逻辑特别是对于复杂的状态管理场景。两者结合可以打造出既强大又灵活的工作流设计器。我最近在一个电商后台系统中就使用了这个组合。客户需要能够自定义他们的订单处理流程包括退货、换货、补发等各种场景。使用Vue3bpmn.js的方案我们不仅快速实现了核心功能还很容易地扩展了各种自定义节点和属性面板。2. 环境准备与基础配置2.1 创建Vue3项目首先我们需要创建一个新的Vue3项目。我推荐使用Vite它的启动速度非常快npm create vitelatest bpmn-designer --template vue cd bpmn-designer npm install安装完成后我们需要添加bpmn.js的核心依赖npm install bpmn-js --save-dev2.2 基础页面结构在工作流设计器中我们通常需要三个主要区域左侧的工具面板提供各种流程节点中间的画布区域用于绘制和编辑流程图右侧的属性面板编辑选中节点的属性修改App.vue文件添加基础结构template div classcontainer div classtoolbox reftoolbox/div div classcanvas refcanvas/div div classproperties-panel refpropertiesPanel/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; } /style3. 初始化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 version1.0 encodingUTF-8? definitions xmlnshttp://www.omg.org/spec/BPMN/20100524/MODEL xmlns:bpmndihttp://www.omg.org/spec/BPMN/20100524/DI xmlns:dchttp://www.omg.org/spec/DD/20100524/DC targetNamespacehttp://bpmn.io/schema/bpmn process idProcess_1 isExecutablefalse startEvent idStartEvent_1 / /process bpmndi:BPMNDiagram idBPMNDiagram_1 bpmndi:BPMNPlane idBPMNPlane_1 bpmnElementProcess_1 bpmndi:BPMNShape id_BPMNShape_StartEvent_2 bpmnElementStartEvent_1 dc:Bounds x173 y102 width36 height36 / /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 classcustom-toolbox div classtool-item v-foritem in tools :keyitem.type clickcreateElement(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; } /style5.2 实现自定义属性面板有时候默认的属性面板不能满足我们的业务需求。我们可以创建一个Vue组件来替代template div classcustom-properties div v-ifselectedElement h3{{ getElementName(selectedElement.type) }}/h3 div classform-group label节点名称/label input v-modelelementName changeupdateElementName /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 } /script6. 保存和加载流程设计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. 实际项目中的经验分享在最近的一个电商后台项目中我们使用Vue3bpmn.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() })最后性能优化也是一个重要考虑。当流程图变得非常复杂时渲染性能可能会下降。我们通过以下方式优化使用debounce处理频繁的状态更新对大型流程图实现懒加载在保存时只序列化变化的部分而不是整个流程图import { debounce } from lodash const saveDiagram debounce(async () { try { const { xml } await bpmnModeler.value.saveXML({ format: true }) // 保存到服务器... } catch (err) { console.error(保存失败, err) } }, 500)