简介:面向Vue.js开发者的bpmn.js集成示例项目,重点解决在Vue应用中渲染与编辑BPMN 2.0流程图的实现问题。压缩包共20个文件,其中6个vue组件负责页面与流程画布封装,6个js脚本涵盖路由、状态管理与bpmn.js接入逻辑,另有3个json配置、HTML入口、favicon图标和README说明,整体仅211KB,目录结构清晰。代码通过BpmnViewer组件在mounted中加载XML流程并自动缩放画布,同时利用moddle与modeler模块实现节点增删改等交互操作,并给出导入错误处理和视图居中示例;项目还预置Vuex store、vue-router及vue.config.js等Vue CLI配置文件,可直接用于二次开发。示例同时演示了依赖安装、项目创建与组件复用的完整思路,有助于读者理解流程建模与前端组件化的结合。已有1590人学习,适合需要快速构建Web端流程设计器的前端开发者参考。 bpmn.js 这套东西,说简单也简单,就是封装好的一个流程建模工具库;说复杂也复杂,从 Vue 2 迁到 Vue 3,从 Webpack 换到 Vite,从 bpmn-js 老版本升到 11.x、12.x,版本一变,坑就跟着变。这篇文章不是去抄官方文档,而是把我自己在项目里从零集成、二次开发到上线维护的完整经验写出来,包括版本怎么选、初始化怎么写、样式为什么丢了、自定义节点怎么做、流程图数据怎么回传后端这些实战问题。如果你正准备在前端项目里接入 BPMN 流程设计能力,或者已经接了一半被各种报错卡住,这篇应该能帮你省下不少排查时间。
1. bpmn.js 到底是个什么东西
1.1 三个核心概念:Viewer / Modeler / NavigatedViewer
bpmn.js 官方说它是 BPMN 2.0 的渲染库和 web 建模工具,但真正用起来,你其实是在跟它的三个顶层类打交道。
我习惯把它们理解成三个不同用途的“播放器”:
- bpmn-js 的 Modeler:最常用的完整模式,自带左侧工具栏(palette)、右侧属性面板(properties panel)、画布缩放、节点拖拽、连线、删除等一系列编辑能力。可以理解成“带界面的流程图编辑器”。常规业务系统里需要用户自己拖拖拽拽设计流程的,都用这个。
- bpmn-js 的 Viewer:纯展示模式,只负责把已有的 BPMN XML 渲染成图形,不涉及任何编辑操作。适合流程审批详情页、只读预览等场景。相比 Modeler,Viewer 打包体积更小,渲染更快,而且不会把用户误带到“可编辑”的交互里。
- bpmn-js 的 NavigatedViewer:在 Viewer 的基础上加了画布缩放和移动能力,适合“能看大图、能放大缩小”但不需要编辑的场景,比如流程图全览、嵌入到弹窗里做预览。
这些顶层对象在使用上其实差异不大,核心都是“把 XML 塞进去、渲染出图形、监听事件”。你选哪个模式,取决于业务需不需要编辑。
1.2 为什么前端要引入 bpmn.js
很多刚接触流程引擎的人会问:后端已经有 Flowable、Camunda 这些流程引擎了,前端为什么还需要 bpmn.js?
这里得先把边界弄清楚。后端流程引擎负责的是流程的运行、推进、状态管理,它保存的是“流程定义”和“流程实例”的数据。而前端 bpmn.js 解决的是“怎么让用户看得见、画得出、改得了这些流程”的问题。你可以理解成后端是汽车引擎,前端是方向盘和仪表盘——没有引擎车走不了,但没有可视化操作界面,用户根本没法跟流程打交道。
所以在实际项目里,前端流程模块基本都是这个套路:在页面上用 bpmn.js 绘制流程图,完成建模后把 BPMN XML 提交给后端,后端去解析 XML 并部署流程;待办审批页面再通过后端接口拿到 XML 字符串,前端用 Viewer 把它渲染出来给用户看。整个链路里,bpmn.js 承担了“流程可视化建模”和“流程展示”两个关键环节。
2. 集成前的方案选型与版本搭配
2.1 Vue 2 / Vue 3 与构建工具的差异
bpmn.js 本身不绑定 Vue,它是纯 JavaScript 的库,理论上 Vue 2、Vue 3、React 都能用。但集成的麻烦往往出在“打包工具”和“模块格式”上。
如果你用的是 Vue 2 + Vue CLI(Webpack),那整体还算顺利,bpmn-js 及其插件基本都是 CommonJS / UMD 格式,Webpack 处理这些很成熟。按照官方文档走,基本能一次跑通。
如果你用的是 Vue 3 + Vite,情况会稍微复杂一点。Vite 的开发服务器基于原生 ESM,对 CommonJS 依赖的处理依赖预构建,遇到 bpmn-js 的某些深层依赖时,可能会报does not provide an export named这类错误。我自己项目里是 Vue 3 + Vite,实测下来,需要在vite.config.js里做几个处理,后面实操部分我会给出完整方案。
还有一点需要提醒:如果你的项目是 Vue 2.6 及以下,并且集成过程中需要用 properties-panel 的vue渲染方式,会涉及 Vue 版本兼容问题,但如果你用的是纯 bpmn.js 自带的属性面板,那其实跟 Vue 版本没多大关系。
2.2 版本搭配建议
版本问题真的是 bpmn.js 集成里最容易翻车的地方。bpmn-js 官网 API 更新很快,不同大版本之间,模块导出方式、样式文件路径、配置参数都可能发生变化。
我整理一个当前阶段经过验证的稳定搭配组合,供参考:
| 依赖包 | 推荐版本 | 说明 |
|---|---|---|
| vue | 3.3.x / 3.4.x | Vue 3 项目使用 |
| vite | 4.x / 5.x | 能正常预构建 CJS 依赖 |
| bpmn-js | 11.5.x | 推荐 11.x,接口稳定,字体问题少 |
| bpmn-js-properties-panel | 1.21+ | 与 bpmn-js 11.x 配套 |
| diagram-js | 7.x | 一般随 bpmn-js 自动安装 |
| @bpmn-io/element-templates | 非必须 | 需要自定义模板时再引入 |
| camunda-bpmn-moddle | 非必须 | 需要 Camunda 扩展属性时引入 |
如果你是老项目,用的 bpmn-js 8.x / 9.x,那要注意:旧版本的样式文件路径是bpmn-js/dist/bpmn-navigated-viewer.css这类写法,而新版本 11.x 以后已经改为bpmn-js/dist/assets/...结构。网上很多教程还是老路径,照着写基本会报 CSS 找不到文件,或者样式没生效。别问我是怎么知道的,问就是踩过。
2.3 依赖安装和基础配置
直接贴一份 Vue 3 + Vite 项目中的依赖安装命令和基础配置,你在自己项目里可以照着用:
npm install bpmn-js@^11.5.0 npm install bpmn-js-properties-panel@^1.21.0如果还需要导出 SVG、PNG 等格式,需要额外安装:
npm install bpmn-js-to-image然后用vite.config.js做一些额外配置。这一步很多人会漏掉,它解决的是 bpmn-js 依赖中 CommonJS 模块在开发环境下被 Vite 预构建时导致的兼容问题:
import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' export default defineConfig({ plugins: [vue()], optimizeDeps: { include: ['bpmn-js', 'bpmn-js-properties-panel', 'diagram-js'] }, resolve: { alias: { // 某些依赖会引用到 camunda 相关模块,缺了会报错 'bpmn-js-properties-panel': 'bpmn-js-properties-panel/dist/bpmn-js-properties-panel.esm.js' } } })optimizeDeps是告诉 Vite 在预构建阶段把这两个库优先处理成 ESM,避免运行时报 “exports is not defined” 之类的错。resolve.alias是为了应对新版本 properties-panel 的exports字段指向问题,如果不加,有时候会报找不到模块。
3. 核心集成实现:在 Vue 组件里跑起来
3.1 初始化 Modeler 实例并渲染画布
把 bpmn.js 集成到页面上,通常需要写一个封装组件,把这个过程收敛起来。下面这个BpmnModeler.vue组件,是我在项目中沉淀下来的基础版,覆盖了初始化、导入、导出、监听这四块核心能力。
<template> <div class="bpmn-container"> <div ref="canvas" class="bpmn-canvas"></div> </div> </template> <script setup> import { ref, onMounted, onBeforeUnmount } from 'vue' import BpmnModeler from 'bpmn-js/lib/Modeler' // 引入 bpmn-js 相关样式,必须引入,否则画布没有样子 import 'bpmn-js/dist/assets/bpmn-js.css' import 'bpmn-js/dist/assets/diagram-js.css' import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css' const props = defineProps({ xml: { type: String, default: '' } }) const emit = defineEmits(['loaded', 'save']) const canvas = ref(null) let modeler = null onMounted(async () => { modeler = new BpmnModeler({ container: canvas.value, // 是否显示左侧工具栏,需要编辑就开,只读就关 propertiesPanel: { parent: '#js-properties-panel' }, additionalModules: [] }) // 创建默认空白流程图,或加载外部传入的 XML if (props.xml) { await importXml(props.xml) } else { await createNewDiagram() } }) </script>注意这里有两个很关键的细节:第一,new BpmnModeler()的container参数一定是一个已经挂载好的 DOM 元素,如果你是拿v-if或者异步数据控制显示,要确保初始化时容器已经在 DOM 里了。第二,初始化之后,模型是空的,你还需要手动调createDiagram或importXML来加载内容,这一步很多新手会忘。
3.2 加载已有流程 XML 和创建空白流程
接下来是内部两个核心方法。
创建空白流程图,核心是伪造一份最简单的 BPMN 流程内容,让 bpmn.js 可以解析。这个空流程里面包含一个流程定义(bpmn:process),里面暂时没有节点:
const emptyXML = ` <bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" id="Definitions_1" targetNamespace="http://bpmn.io/schema/bpmn"> <bpmn:process id="Process_1" isExecutable="false" /> </bpmn:definitions> ` async function createNewDiagram() { try { await modeler.importXML(emptyXML) // 导入完成后,把画布尺寸调整到适合视口 const canvasObj = modeler.get('canvas') canvasObj.zoom('fit-viewport', 'auto') emit('loaded', modeler) } catch (err) { console.error('创建空流程失败:', err) } } async function importXml(xmlString) { try { await modeler.importXML(xmlString) const canvasObj = modeler.get('canvas') canvasObj.zoom('fit-viewport', 'auto') emit('loaded', modeler) } catch (err) { console.error('导入流程 XML 失败:', err) } }zoom('fit-viewport', 'auto')这步很重要,它会在加载完成后自动把流程图缩放至视野内。如果没有这一步,你导入的 XML 可能只显示左上角一小块,用户一进来就懵了。
3.3 导出 BPMN XML / SVG 与数据回传
流程图画完了,最重要的就是拿数据。后端要的是 BPMN XML 字符串,同时有些场景还需要 SVG 图片做预览图。
导出的核心 API 是saveXML和saveSVG,封装成表单验证、保存数据流的标准动作即可:
function exportBpmn() { return new Promise((resolve, reject) => { if (!modeler) reject(new Error('modeler 未初始化')) modeler.saveXML({ format: true }, (err, xml) => { if (err) { reject(err) } else { resolve(xml) } }) }) } function exportSvg() { return new Promise((resolve, reject) => { if (!modeler) reject(new Error('modeler 未初始化')) modeler.saveSVG((err, svg) => { if (err) { reject(err) } else { resolve(svg) } }) }) }实际项目里的“保存”,往往不是单纯拿到 XML 就够了,还需要把 XML 和流程设计表单字段(比如流程名称、绑定的表单 key 等)绑定在一起。建议的接口请求体大概是这样:
{ "processKey": "leave_process", "processName": "请假审批流程", "bpmnXml": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>...", "svgImage": "<svg>...</svg>", "formConfig": { "formKey": "leaveForm", "version": 1 } }SVG 图片的作用是给前端列表页一个流程缩略图,有些项目还会用做流程审批记录里的预览,这比每次都用接口重新解析 XML 快得多。
4. 企业级项目里的常见定制需求
4.1 事件监听:拿到点击节点、连线、修改事件
当你把 bpmn.js 接入真实业务,会发现“能画图”只是开始,业务系统里有大量交互需要跟节点绑定,比如:
- 点击某个用户任务节点,右侧展示该节点绑定的表单字段;
- 用户拖动节点位置后,实时记录坐标变化;
- 删除某个节点后,后端需要同步逻辑删除相关配置。
这些都需要监听 bpmn.js 的事件。目前我常用的核心事件有这么几个:
function setupModelerListeners(modelerInstance) { const eventBus = modelerInstance.get('eventBus') // 节点点击事件 eventBus.on('element.click', (event) => { const { element } = event if (element.type.includes('Task') || element.type.includes('Event')) { console.log('点击了任务/事件节点:', element) } }) // 元素创建完成 eventBus.on('element.created', (event) => { const { element } = event console.log('新节点创建:', element) }) // 节点变化事件(移动、删除、更新属性等) eventBus.on('element.changed', (event) => { const { element } = event console.log('节点数据变更:', element) }) // 连线创建完成 eventBus.on('connection.created', (event) => { const { connection } = event console.log('新连线创建:', connection) }) // 节点删除完成 eventBus.on('element.deleted', (event) => { const { element } = event console.log('节点已删除:', element) }) }这里有个经验点:事件回调里拿到的element,是一个ModdleElement实例,它上面有id、type、businessObject等属性。你要取节点的业务属性,尽量通过element.businessObject去取,而不是直接在element上翻。例如拿到节点名称:
const name = element.businessObject.name4.2 自定义渲染节点
有时候默认的 BPMN 节点形状不够用。比如你们公司希望“开始节点”显示成绿色圆环,“审批节点”显示成带人像图标的圆角矩形,这就要走自定义渲染。
自定义渲染的核心是注册一个customRenderer,覆盖默认的BaseRenderer的绘制逻辑。这里给出一个简化但完整的示例。
先创建一个自定义渲染器文件custom-renderer.js:
import BaseRenderer from 'diagram-js/lib/draw/BaseRenderer' import { append as svgAppend, create as svgCreate, attr as svgAttr } from 'tiny-svg' const HIGH_PRIORITY = 1500 export default class CustomRenderer extends BaseRenderer { constructor(eventBus, bpmnRenderer) { super(eventBus, HIGH_PRIORITY) this.bpmnRenderer = bpmnRenderer } canRender(element) { // 指定对哪些节点类型做自定义渲染 if (element.type === 'bpmn:UserTask') { return true } return false } drawShape(parentNode, element) { // 先用默认渲染器画出这个节点 const shape = this.bpmnRenderer.drawShape(parentNode, element) // 然后追加一个自定义图形标识 const customIcon = svgCreate('image') svgAttr(customIcon, { x: 18, y: 18, width: 24, height: 24, href: 'data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><circle cx="12" cy="6" r="3" fill="%234caf50"/><path d="M12 10c-3 0-6 2-6 5v3h12v-3c0-3-3-5-6-5z" fill="%234caf50"/></svg>' }) svgAppend(parentNode, customIcon) return shape } getShapePath(shape) { // 这里可以返回自定义形状路径 return this.bpmnRenderer.getShapePath(shape) } } CustomRenderer.$inject = ['eventBus', 'bpmnRenderer']要把它集成到 modeler 里,需要在组件初始化时传入additionalModules:
import CustomRenderer from './custom-renderer' modeler = new BpmnModeler({ container: canvas.value, additionalModules: [ { renderer: ['type', CustomRenderer] } ] })这里容易踩的坑是CustomRenderer.$inject必须写对。如果你写漏了bpmnRenderer依赖,运行时大概率会报 “Cannot read properties of undefined (reading 'drawShape')” 的错。背后的原因是我们用默认渲染器做降级兜底,而默认渲染器实例是 bpmn.js 内部注入进来的,没有它,自定义渲染就没法“先画原版,再叠加图标”。
4.3 隐藏左侧工具栏或属性面板
很多系统只需要画布展示,不需要左侧那一排拖拽组件。隐藏它有几种做法:
最简单的是用 CSS:
.bpmn-container .djs-palette { display: none !important; }但更“正规”的做法是初始化时就不加载 palette 模块:
modeler = new BpmnModeler({ container: canvas.value, additionalModules: [ // 不传 palette 模块,就不会渲染左侧工具栏 ] })其实 bpmn.js 默认 Modeler 是会带 palette 的,如果你想完全取消,可以通过修改onlyBpmnElements等方式,也可以在初始化时把paletteProvider移除,但这个 API 在不同版本略有变化。相比之下,我建议项目初期用 CSS 隐藏来快速验证,等确定不需要交互了,再改用模块卸载的方式,避免改动组件结构。
5. 常见问题与排查技巧实录
5.1 白屏或样式错乱
第一个常见问题是:页面加载出来了,但画布区域空白,或者节点位置偏移。
绝大多数情况是“样式文件没引全”。bpmn-js 新版本(11.x 之后)要引入四份基础样式:
import 'bpmn-js/dist/assets/bpmn-js.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-embedded.css'bpmn-embedded.css会把 BMPN 图标字体以 base64 方式嵌入,适合项目部署到子路径或需要离线场景的情况,因为不需要额外请求字体文件。如果你部署后出现图标变成小方块的乱码,基本都是字体文件路径或引用方式不对,换用bpmn-embedded.css基本能解决。
另外,如果你是用index.html模板方式引入<style>链接而不是通过 JS import,需要注意路径必须正确。最简单稳妥的方法还是用import方式让打包工具处理。
5.2 字体缺失/渲染图标报错
老版本 bpmn-js 自带字体文件,但新版本把字体从包里拆出去了。如果你在控制台看到bpmn-font相关的 404,或者图形上出现奇奇怪怪的方块字体,直接检查你项目中 bpmn 字体是否被正确打包。
解决方案一般有两种:
- 引入
bpmn-embedded.css,字体以 base64 嵌入,不依赖外部请求; - 手动复制
node_modules/bpmn-js/dist/assets/bpmn-font/下的font目录到项目 public 目录,然后将bpmn.css里的字体路径改为相对路径。
我个人的建议是直接用方案 1,省事且稳定。
5.3 vite 构建时报模块格式错误
Vite 项目里另一个高频报错是:
SyntaxError: Named export 'A' not found. The requested module 'bpmn-js' is a CommonJS module...这种情况属于 Vite 预构建的兼容问题。先用我前面提到的optimizeDeps.include把 bpmn-js、properties-panel 加进去。如果还不行,再检查是不是多处引用了 bpmn-js,比如主组件和自定义渲染文件各引了一次,导致重复打包,也会产生诡异报错。
还有一个让我印象很深的坑:项目里同时安装了bpmn-js和camunda-bpmn-moddle不同大版本,结果导致bpmn:Definitions的 XML 解析成undefined,页面直接白屏。遇到这种问题,建议先跑一句:
npm ls bpmn-js diagram-js camunda-bpmn-moddle看下实际安装的版本树。如果有版本冲突,删掉node_modules和锁文件重新安装通常能解决。
5.4 属性面板不显示或功能报错
如果你使用bpmn-js-properties-panel却发现右侧面板空白的,先检查是否引入了对应 CSS。新版本属性面板样式文件在:
import 'bpmn-js-properties-panel/dist/assets/properties-panel.css'同时,你需要在 HTML 里预留一个挂载点,在初始化时告诉 Modeler 面板放在哪里:
<div ref="canvas" class="bpmn-canvas"></div> <div id="js-properties-panel" class="properties-panel"></div>propertiesPanel.parent这个配置项必须对应一个真实存在的 DOM id,否则面板初始化时找不到挂载点,控制台不一定会报红,但属性面板就是不出来。
还有,旧版properties-panel的入口是PropertiesPanelModule,但新版改成了BpmnPropertiesPanelModule,并且样式文件路径也变了。如果你在网上抄的代码是旧版的,会导致初始化报错。这里放一个当前新版配套的初始化方式:
import BpmnPropertiesPanelModule from 'bpmn-js-properties-panel' import BpmnPropertiesProviderModule from 'bpmn-js-properties-panel/lib/provider/bpmn' modeler = new BpmnModeler({ container: canvas.value, propertiesPanel: { parent: '#js-properties-panel' }, additionalModules: [ BpmnPropertiesPanelModule, BpmnPropertiesProviderModule ] })5.5 动态加载 XML 后无法操作画布
还有一个我实际遇到过的情况:流程通过接口获取 XML 后,渲染是正常的,但画布上的节点拖不动、连线也连不了,看起来像个“死图”。
排查方向有三个:
XML 里没有 DI 图元信息。 BPMN XML 里不仅要有
bpmn:process定义节点类型和连线,还要有bpmndi:BPMNDiagram描述节点坐标和连线路由。如果你后端返回的 XML 是用流程引擎直接生成的,通常没问题;但如果是自己拼的 XML,很容易少 DI,导致 bpmn.js 只能用默认布局展示,且无法进入编辑状态。导入接口没有走 Modeler,而是走了 Viewer。 有些集成代码把“详情预览”和“流程设计”写在同一个组件里,通过 props 控制模式,但如果传入的初始化实例是 Viewer,那自然无法编辑。
画布被 CSS 遮挡。 检查一下容器是否有
pointer-events: none,或者某个层级的z-index把渲染层的鼠标事件拦截了。这个在调试时很容易被忽略,因为看起来页面一切正常。
针对第一种情况,最稳妥的做法是用bpmn-moddle去创建并补齐 DI 信息,或者直接调用modeler.get('modeling')的方式创建节点,让 bpmn.js 自己生成 DI。后端生成的 XML 如果不带 DI,最好的修复方式是——别硬修,让前端生成并回传给后端。
5.6 组件卸载后内存泄漏
Vue 组件被销毁时,如果 Modeler 实例没有被清理,在 SPA 里频繁切换页面会导致内存持续上涨,尤其流程设计页这种重交互场景会更明显。
我通常会在onBeforeUnmount里调用modeler.destroy():
onBeforeUnmount(() => { if (modeler) { modeler.destroy() modeler = null } })同时也把事件总线上的监听器一并解绑,避免组件卸载后事件回调仍然触发,引起不可预知的报错。
6. 从 0 到 1 的上手建议
第一次在项目里接入 bpmn.js,别着急把所有功能都加上,我建议的推进节奏是:
第一阶段:只做“加载 XML + 画布展示 + 导出 XML”。先把链路走通,确保数据能进去、能出来。这一阶段可以用 Viewer,也可以直接用 Modeler,但不开工具栏和属性面板。核心目的是验证版本兼容、构建配置和基本渲染。
第二阶段:开放编辑能力。引入完整的 Modeler 模式,打开 palette 和 properties panel,让用户能拖拽建模,保存时能拿到完整的 BPMN XML 和 SVG。
第三阶段:结合业务深度定制。比如根据审批角色限制某些节点类型不能拖拽、把节点和表单配置绑定、监听流程校验规则等。这些可以结合事件监听和自定义渲染逐步沉淀。
我见过太多团队一上来就要“做一个像钉钉那样的审批流程设计器”,结果项目周期拉得很长,坑越踩越多。实际经验告诉我,bpmn.js 的扩展能力很强,但你要先学会走,再学着跑。
7. 踩坑经验总结
回头看整个集成过程,最让我个人印象深刻的其实是两件事。
第一件,是关于“版本焦虑”。BPMN 相关生态迭代非常快,网上教程质量参差不齐,而且大量教程是基于两三年前的版本写的。如果你在 2024 年之后新建的项目里照着老教程敲,大概率会踩到样式、模块格式、API 差异的坑。我踩过最深的一次,是照着老文章引入PropertiesPanelModule导致初始化直接抛异常,排查半天发现是版本接口改名了。所以遇到问题第一反应先查版本,再查代码。
第二件,是关于“别想着什么都自己画”。市面上确实有些团队决定自研流程图渲染引擎,最后无一例外都被 BPMN 规范的复杂度吞掉了。bpmn.js 的价值不在于它画出来的图多好看,而在于它完整实现了 BPMN 2.0 规范。企业级流程设计要兼容规范(包括网关、子流程、边界事件、事务等概念),自研的代价极其高昂。选择站在 bpmn.js 的肩膀上,是目前前端做流程可视化最务实的路线。
最后再分享一个小技巧:集成过程中,如果页面不出图,先用一个最简单的 bpmn-js 官方示例,不接任何业务代码,在原生的 HTML 页面里试一把(直接引 CDN 或 npm 包),确认运行环境没问题,再往 Vue 组件里迁移。这样做的好处是能把“bpmn.js 本身的问题”和“Vue 集成引入的问题”分开定位。好比水电进场前先检查管道通不通,别等瓷砖贴完才发现墙里的水管是堵的。记住这句话,能帮你未来少走很多弯路。
本文还有配套的精品资源,点击获取