邳州网站开发网站建设基础学习
邳州网站开发,网站建设基础学习,搞笑资讯网站源码,wordpress优酷插件1. 为什么选择Vue3 BPMN-JS TS#xff1f;
如果你正在开发一个需要流程审批、工单流转或者自动化任务编排的后台管理系统#xff0c;那么一个可视化的流程设计器几乎是标配。几年前#xff0c;这类功能通常需要后端配合#xff0c;或者使用一些比较“重”的桌面端工具。但…1. 为什么选择Vue3 BPMN-JS TS如果你正在开发一个需要流程审批、工单流转或者自动化任务编排的后台管理系统那么一个可视化的流程设计器几乎是标配。几年前这类功能通常需要后端配合或者使用一些比较“重”的桌面端工具。但现在得益于像bpmn-js这样的前端库我们完全可以在浏览器里打造一个媲美专业软件的流程设计工具。我自己在最近的一个低代码平台项目里就深度用到了这个组合。当时的需求是让业务人员能自己拖拽节点配置出一个复杂的审批流程。一开始我也考虑过自己从头画canvas但一想到连线、节点规则、导入导出这些细节就头大。后来发现了bpmn-js它本质上是一个实现了 BPMN 2.0 标准的“渲染引擎”和“建模器”我们只需要关心业务集成底层的图形渲染、交互逻辑它都帮我们做好了这大大降低了开发门槛。那么为什么是 Vue3 TypeScript 呢Vue3 的 Composition API 写起来太爽了尤其是处理这种带有复杂状态比如当前选中的节点、画布缩放比例和副作用比如导入 XML、保存模型的组件时逻辑可以组织得非常清晰。而 TypeScript 更是救命稻草bpmn-js的 API 和它返回的各种模型对象结构都比较复杂没有 TS 的类型提示你很容易在console.log的海洋里迷失。用上 TS 之后编辑器能智能提示modeler有哪些方法、element对象里有什么属性开发效率和质量提升不是一点半点。所以这个组合可以理解为用 Vue3 组织我们的应用界面和逻辑用 TypeScript 来确保我们正确地使用bpmn-js这个强大的“引擎”最终快速、稳健地搭建出一个专业的流程设计器。接下来我就带你从零开始一步步把它实现出来。2. 环境搭建与项目初始化万事开头难但把环境搭好后面就顺了。这里我假设你已经有了 Node.js 和 npm 的环境我们直接从创建项目开始。2.1 创建Vue3 TypeScript项目打开你的终端我们使用 Vue 官方的脚手架工具来创建一个新项目。我强烈推荐使用 Vite它的启动速度和热更新都快得飞起对于需要频繁调试画布效果的项目来说体验提升巨大。# 使用 npm npm create vuelatest my-bpmn-designer # 或者使用 yarn yarn create vue my-bpmn-designer执行命令后命令行会交互式地让你选择一些特性。这里是我的选择你可以参考TypeScript Yes必须选JSX No我们用不到Vue Router 看需求如果设计器是独立页面可以选 YesPinia Yes状态管理后面管理流程数据可能用到ESLint Yes保持代码规范Prettier Yes代码格式化项目创建好后进入目录并安装依赖cd my-bpmn-designer npm install现在用npm run dev跑起来你应该能看到一个标准的 Vue 启动页面。先把src/components下的HelloWorld.vue删掉我们从头开始。2.2 安装核心依赖流程设计器的核心就是bpmn-js。但光有它还不够我们通常还需要属性面板来编辑节点详情。所以我们需要安装以下包npm install --save bpmn-js bpmn-js-properties-panel npm install --save-dev types/bpmn-js types/bpmn-js-properties-panel这里解释一下bpmn-js 核心库提供了Modeler建模器和Viewer仅查看器。bpmn-js-properties-panel 官方提供的右侧属性面板模块允许你编辑选中元素的名称、ID等属性。types/开头的包 这是 TypeScript 的类型定义文件安装了它们VS Code 才能给我们准确的代码提示和类型检查。这是提升开发体验的关键一步。你可能会在别的教程里看到还安装了camunda-bpmn-moddle。这是一个扩展包它允许你在流程中使用 Camunda 工作流引擎特有的一些属性比如任务指派人、表单Key。如果你的流程需要和 Camunda 这类引擎深度集成那就需要安装它。如果只是画一个通用的 BPMN 图可以暂时不装。为了教程的完整性我们先装上但不会深入使用它的特殊属性。npm install --save camunda-bpmn-moddle好了依赖安装完毕我们的“食材”已经备齐接下来开始“下锅炒菜”。3. 构建第一个流程画布理论说再多不如动手做一遍。这一节我们就来创建一个最基础的、能画流程图的 Vue 组件。3.1 组件结构与模板在src/components目录下新建一个文件BpmnDesigner.vue。我们先来搭建最基础的模板结构。template div classdesigner-container !-- 画布区域 -- div refcanvasContainer classcanvas-container/div !-- 属性面板区域 -- div idjs-properties-panel classproperties-panel/div /div /template script setup langts // 我们接下来的脚本将写在这里 /script style scoped .designer-container { width: 100%; height: 100vh; /* 先占满全屏 */ display: flex; position: relative; overflow: hidden; } .canvas-container { flex: 1; /* 占据剩余所有空间 */ border: 1px solid #dcdfe6; /* 一个淡淡的边框 */ background-color: #fafafa; } .properties-panel { width: 0; /* 初始宽度为0等属性面板模块加载后再动态设置宽度比如400px */ border-left: 1px solid #dcdfe6; background: white; overflow-y: auto; /* 面板内容多时可以滚动 */ transition: width 0.3s ease; /* 平滑展开动画 */ } /style注意几个关键点refcanvasContainer 这是 Vue 的模板引用我们将在脚本中通过这个引用来获取真实的 DOM 元素并交给bpmn-js作为渲染容器。idjs-properties-panel 这个 ID 很重要bpmn-js-properties-panel模块会查找这个 ID 的元素并将其作为属性面板的挂载点。名字最好不要改。样式 我们使用 Flex 布局让画布自适应剩余宽度。属性面板初始宽度为0等初始化成功后再展开避免页面闪动。3.2 初始化BPMN建模器现在我们来编写组件的逻辑部分。这是最核心的一步。script setup langts import { ref, onMounted, onUnmounted, markRaw } from vue; // 1. 引入核心建模器 import BpmnModeler from bpmn-js/lib/Modeler; // 2. 引入属性面板模块 import { BpmnPropertiesPanelModule, BpmnPropertiesProviderModule } from bpmn-js-properties-panel; // 3. 引入Camunda扩展定义可选但装了就用上 import camundaModdleDescriptor from camunda-bpmn-moddle/resources/camunda.json; // 画布容器的DOM引用 const canvasContainer refHTMLElement | null(null); // BPMN建模器实例用markRaw包裹避免Vue对其做不必要的响应式代理提升性能 const bpmnModeler refBpmnModeler | null(null); onMounted(async () { // 确保容器DOM已经渲染 if (!canvasContainer.value) return; try { // 创建建模器实例 bpmnModeler.value markRaw( new BpmnModeler({ container: canvasContainer.value, // 配置属性面板 propertiesPanel: { parent: #js-properties-panel }, // 加载额外的模块 additionalModules: [ BpmnPropertiesPanelModule, // 属性面板功能模块 BpmnPropertiesProviderModule // 默认的属性提供者编辑名称、ID等 ], // 加载模型扩展用于支持camunda等自定义属性 moddleExtensions: { camunda: camundaModdleDescriptor } }) ); // 创建一个空的流程图 await bpmnModeler.value.createDiagram(); console.log(BPMN设计器初始化成功); // 初始化后将属性面板的宽度展开 const panel document.getElementById(js-properties-panel); if (panel) { panel.style.width 400px; } } catch (err) { console.error(初始化BPMN设计器失败:, err); } }); onUnmounted(() { // 组件销毁时清理建模器实例释放内存 if (bpmnModeler.value) { bpmnModeler.value.destroy(); bpmnModeler.value null; } }); /script我来拆解一下这段代码markRaw 这是 Vue3 的一个 API。bpmnModeler实例是一个复杂的、自带内部状态管理的对象我们不需要 Vue 去追踪它的变化。用markRaw标记后Vue 会跳过对其的响应式转换能避免一些潜在的性能问题和副作用。new BpmnModeler(config) 这是核心。container指定画布挂载点。propertiesPanel.parent告诉建模器属性面板放在哪里。additionalModules数组是我们“插件化”加载功能的地方这里加载了属性面板。createDiagram() 这个方法调用后画布上才会出现那个可拖拽的“起点”事件图标。它是一个异步方法所以我用了await。生命周期 在onMounted中初始化在onUnmounted中调用destroy()进行清理这是防止内存泄漏的好习惯。现在你可以在App.vue中引入这个组件运行项目。如果一切顺利你应该能看到一个带有工具栏左侧、画布中间和属性面板右侧已展开为400px的完整流程设计界面了你可以尝试从左侧拖拽一个“用户任务”到画布上然后点击它右侧属性面板会显示它的ID和名称可以实时编辑。4. 加载与导出流程XML一个只能画新图的设计器是不完整的。我们肯定需要从后端加载已有的流程定义或者把设计好的图保存起来。在 BPMN 的世界里流程的存储格式就是 XML。4.1 导入XML字符串假设你的后端给你返回了一个 BPMN 2.0 标准的 XML 字符串你怎么把它渲染到画布上我们给组件添加一个方法。script setup langts // ... 之前的导入和ref定义保持不变 /** * 导入并渲染BPMN XML * param xmlString BPMN 2.0 XML字符串 */ const importXML async (xmlString: string) { if (!bpmnModeler.value) { console.warn(建模器实例未初始化); return; } try { // 这个方法会清空当前画布并渲染传入的XML const { warnings } await bpmnModeler.value.importXML(xmlString); console.log(流程图导入成功, warnings); // 导入后让视图自适应画布 const canvas bpmnModeler.value.get(canvas); canvas.zoom(fit-viewport); } catch (err) { console.error(导入流程图失败:, err); // 这里可以给用户一个友好的错误提示比如“流程图文件格式不正确” } }; // 示例在初始化后可以模拟导入一个简单的XML onMounted(async () { // ... 初始化建模器代码 ... // 初始化后可以加载一个示例XML const exampleXml ?xml version1.0 encodingUTF-8? bpmn2:definitions xmlns:bpmn2http://www.omg.org/spec/BPMN/20100524/MODEL idsample-diagram targetNamespacehttp://bpmn.io/schema/bpmn bpmn2:process idProcess_1 isExecutablefalse bpmn2:startEvent idStartEvent_1 / /bpmn2:process bpmn2:BPMNDiagram idBPMNDiagram_1 bpmn2:BPMNPlane idBPMNPlane_1 bpmnElementProcess_1 bpmn2:BPMNShape id_BPMNShape_StartEvent_2 bpmnElementStartEvent_1 dc:Bounds x150 y100 width36 height36 / /bpmn2:BPMNShape /bpmn2:BPMNPlane /bpmn2:BPMNDiagram /bpmn2:definitions; setTimeout(() { importXML(exampleXml); // 延迟一秒加载方便观察效果 }, 1000); }); /scriptimportXML方法非常直接。成功后画布就会被新的流程图覆盖。zoom(fit-viewport)是一个很实用的操作它让整个流程图自动缩放刚好充满整个画布区域用户体验很好。4.2 导出XML与SVG图像设计完了总要能保存成果。我们通常需要两种格式XML用于存储、后端解析和SVG用于生成图片、预览、打印。script setup langts // ... 之前的代码 ... /** * 导出当前的流程图为BPMN XML字符串 * returns Promisestring XML字符串 */ const exportXML async (): Promisestring { if (!bpmnModeler.value) { throw new Error(建模器实例未初始化); } try { const { xml } await bpmnModeler.value.saveXML({ format: true }); // format: true 表示格式化输出带缩进便于阅读 console.log(XML导出成功); return xml; } catch (err) { console.error(导出XML失败:, err); throw err; } }; /** * 导出当前的流程图为SVG图像字符串 * returns Promisestring SVG字符串 */ const exportSVG async (): Promisestring { if (!bpmnModeler.value) { throw new Error(建模器实例未初始化); } try { const { svg } await bpmnModeler.value.saveSVG(); console.log(SVG导出成功); return svg; } catch (err) { console.error(导出SVG失败:, err); throw err; } }; // 在模板中添加两个按钮来触发导出 /script template div classdesigner-container div classtoolbar button clickhandleExportXML导出XML/button button clickhandleExportSVG导出SVG/button /div !-- ... 画布和属性面板 ... -- /div /template script setup langts // 按钮处理函数 const handleExportXML async () { try { const xml await exportXML(); // 你可以选择1. 复制到剪贴板 2. 弹出对话框显示 3. 触发下载 console.log(xml); // 示例触发下载 downloadFile(xml, my-process.bpmn, application/xml); } catch (err) { alert(导出XML时出错); } }; const handleExportSVG async () { try { const svg await exportSVG(); downloadFile(svg, my-process.svg, image/svgxml); } catch (err) { alert(导出SVG时出错); } }; // 通用的下载文件函数 const downloadFile (content: string, fileName: string, mimeType: string) { const blob new Blob([content], { type: mimeType }); const link document.createElement(a); link.href URL.createObjectURL(blob); link.download fileName; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(link.href); }; /script现在你的设计器就具备了完整的“读写”能力。你可以把导出的 XML 发给后端保存导出的 SVG 可以嵌入报告或邮件中。在实际项目中handleExportXML函数里通常会调用一个 API将 XML 上传到服务器。5. 深度定制与样式美化默认的bpmn-js界面是黑灰风格的可能和你的项目UI格格不入。而且你可能想隐藏一些不需要的工具栏按钮或者自定义节点的颜色。别担心这些都可以做到。5.1 自定义样式与主题首先我们需要引入bpmn-js自带的默认样式否则画布上的图标、连线都会显示不正常。然后在它的基础上进行覆盖。style scoped /* 你的容器样式 */ .designer-container { ... } /* 引入bpmn-js的默认CSS - 非常重要 */ import bpmn-js/dist/assets/diagram-js.css; import bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css; /* 引入属性面板的默认CSS */ import bpmn-js-properties-panel/dist/assets/properties-panel.css; /style引入基础样式后我们就可以用更强大的 CSS 选择器来覆盖它了。比如我想把画布的网格背景从默认的灰色点阵换成浅蓝色的细线/* 在你的scoped style中可能需要使用 ::v-deep 或 :deep() 来穿透组件作用域影响子组件样式 */ /* Vue3 使用 :deep() */ .designer-container :deep(.djs-container .djs-palette) { border-right: 1px solid #e0e0e0; /* 左侧工具栏边框 */ } .designer-container :deep(.djs-container svg) { background-color: #f8fbff; /* 画布背景色 */ background-image: linear-gradient(90deg, #e6f7ff 1px, transparent 0), linear-gradient(180deg, #e6f7ff 1px, transparent 0); /* 自定义网格线 */ background-size: 20px 20px; }再比如我想把“用户任务”节点的默认颜色从黄色改成我们品牌的主蓝色.designer-container :deep(.djs-element .djs-shape[data-element-typebpmn:UserTask]) { fill: #409eff !important; /* 填充色 */ stroke: #337ecc !important; /* 边框色 */ }注意 修改bpmn-js内部元素的样式有时需要用!important来提升优先级因为它的默认样式可能内联了。修改前最好用浏览器的开发者工具仔细检查一下元素和类名。5.2 自定义上下文菜单与工具栏有时候默认的右键菜单或者左侧工具栏Palette里的项目太多了。比如我们只想让用户使用“开始事件”、“结束事件”、“用户任务”、“排他网关”这四种元素其他的都隐藏掉。这需要通过additionalModules来自定义模块来实现。我们需要创建一个自定义的“调色板”Palette模块。首先在src下创建一个modules文件夹然后新建CustomPalette.ts// src/modules/CustomPalette.ts export default class CustomPalette { // 这个静态属性是bpmn-js模块系统的约定用于注入依赖 static $inject [palette, create, elementFactory, bpmnFactory]; // 这些参数会在模块被实例化时由依赖注入系统传入 constructor(palette: any, create: any, elementFactory: any, bpmnFactory: any) { this.palette palette; this.create create; this.elementFactory elementFactory; this.bpmnFactory bpmnFactory; // 在调色板创建完成后注册我们的自定义项 palette.registerProvider(this); } // 这是关键方法返回一个对象键是分组名值是该分组下的工具项数组 getPaletteEntries() { return { // 覆盖默认的“工具”分组 tool: { // 只保留“抓手”工具用于拖动画布和“套索”工具用于框选 hand-tool: { group: tool, className: bpmn-icon-hand-tool, title: 激活抓手工具, action: { click: function(event: any) { // 这里可以写激活抓手工具的逻辑但通常bpmn-js内部会处理 console.log(切换至抓手工具); } } }, lasso-tool: { group: tool, className: bpmn-icon-lasso-tool, title: 激活套索工具, action: { click: function(event: any) { console.log(切换至套索工具); } } } }, // 覆盖默认的“创建”分组就是放各种图形节点的 create: { // 只允许创建这四种元素 create.start-event: { group: create, className: bpmn-icon-start-event-none, title: 创建开始事件, action: { click: (event: any) { // 调用内部方法创建一个开始事件 const shape this.elementFactory.createShape({ type: bpmn:StartEvent }); this.create.start(event, shape); } } }, create.user-task: { group: create, className: bpmn-icon-user-task, title: 创建用户任务, action: { click: (event: any) { const shape this.elementFactory.createShape({ type: bpmn:UserTask }); this.create.start(event, shape); } } }, create.exclusive-gateway: { group: create, className: bpmn-icon-gateway-xor, title: 创建排他网关, action: { click: (event: any) { const shape this.elementFactory.createShape({ type: bpmn:ExclusiveGateway }); this.create.start(event, shape); } } }, create.end-event: { group: create, className: bpmn-icon-end-event-none, title: 创建结束事件, action: { click: (event: any) { const shape this.elementFactory.createShape({ type: bpmn:EndEvent }); this.create.start(event, shape); } } } } }; } }然后在你的BpmnDesigner.vue组件中引入这个自定义模块并把它添加到additionalModules数组中script setup langts // ... 其他导入 ... import CustomPalette from /modules/CustomPalette; // 导入自定义模块 onMounted(async () { // ... 确保容器DOM已经渲染 ... bpmnModeler.value markRaw( new BpmnModeler({ container: canvasContainer.value, propertiesPanel: { parent: #js-properties-panel }, additionalModules: [ BpmnPropertiesPanelModule, BpmnPropertiesProviderModule, CustomPalette // 添加我们的自定义模块 ], moddleExtensions: { camunda: camundaModdleDescriptor } }) ); // ... 后续初始化 ... }); /script重启你的开发服务器你会发现左侧工具栏只剩下我们定义的四个节点和两个工具了。通过这种方式你可以实现非常精细的交互控制比如根据用户角色动态显示不同的工具栏。6. 实战技巧与避坑指南走到这一步一个功能完备的流程设计器已经成型了。但在真实项目开发中我踩过不少坑这里分享几个最实用的技巧和注意事项。6.1 处理中文乱码与属性面板当你使用属性面板编辑元素名称输入中文时有时保存再打开会发现中文变成乱码。这通常是因为 XML 的编码问题。确保你导出的 XML 字符串包含正确的 XML 声明头?xml version1.0 encodingUTF-8?。bpmnModeler.saveXML()方法默认会包含它。另外默认的属性面板只能编辑基本的name和id。如果你想添加自定义字段比如“处理人”、“截止时间”就需要扩展属性面板。这涉及到创建新的属性提供者模块会相对复杂一些。一个简单的替代方案是隐藏默认的属性面板自己基于当前选中的元素在Vue组件里渲染一个自定义的表单。你可以通过监听bpmnModeler的selection.changed和element.changed事件来获取当前选中的元素及其属性变化。// 在初始化建模器后添加事件监听 const eventBus bpmnModeler.value.get(eventBus); // 监听元素选择变化 eventBus.on(selection.changed, (event: any) { const newSelection event.newSelection; if (newSelection.length 0) { const element newSelection[0]; console.log(选中了元素:, element.id, element.type); // 这里可以触发一个Vue的ref或emit让父组件知道选中了谁 // selectedElement.value element; } else { // 清空选中状态 // selectedElement.value null; } }); // 监听元素属性变化比如通过属性面板修改了名称 eventBus.on(element.changed, (event: any) { const element event.element; console.log(元素被修改了:, element.id); // 这里可以同步更新你自定义的表单数据 });6.2 性能优化与大型流程图当流程图变得非常庞大节点超过100个时你可能会感到操作有些卡顿。这里有几个优化方向按需加载模块bpmn-js有很多可选模块比如label-editing标签编辑、modeling建模核心等。如果你不需要某些高级功能比如直接在画布上编辑连线标签可以不加载它们。但这对新手来说配置比较复杂一开始可以不用考虑。虚拟滚动/缩放 这是最有效的优化。bpmn-js本身没有直接提供但你可以通过监听画布变化只渲染视口内的元素。这通常需要修改bpmn-js的渲染层难度较大。分步加载 对于超大型流程图可以考虑在后端将其拆分成多个子流程前端先加载主流程点击某个节点时再动态加载对应的子流程XML。防抖保存 如果你的设计器是自动保存的记得为exportXML操作加上防抖debounce避免用户每拖拽一个节点就触发一次保存请求。6.3 与后端工作流引擎集成如果你设计的流程图最终是要在像 Camunda、Flowable、Activiti 这样的工作流引擎中执行的那么需要注意扩展属性。这就是为什么我们一开始安装了camunda-bpmn-moddle。这些引擎需要在 BPMN 的标准元素上添加自定义属性比如camunda:assignee表示任务处理人。在属性面板中编辑这些自定义属性需要安装并加载对应的属性提供者模块例如CamundaPlatformPropertiesProviderModule。同时在导出 XML 时也要确保这些扩展的命名空间和属性被正确包含。通常只要你正确配置了moddleExtensionsbpmn-js会帮你处理好这些。一个更常见的集成场景是前端只负责流程图的绘制和基本属性的配置如节点名称、分支条件表达式而像处理人、表单Key这类业务属性通过一个独立的、与后端接口对应的配置表单来填写。这样前后端职责更清晰前端设计器也更轻量。最后记得在开发过程中多打开浏览器的开发者工具看看 Console 有没有报错Network 里资源加载是否正常。bpmn-js的社区相对活跃遇到棘手的问题去它的 GitHub Issues 里搜一搜很可能已经有人遇到过并给出了解决方案。