news 2026/6/23 4:42:21

ruoyi-vue2前端集成DMN规则引擎

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ruoyi-vue2前端集成DMN规则引擎

环境说明

基于RuoYi-Vue2q前端如何集成DMN组件

版本号:3.9.0

更多关于ruoyi集成工作流,请访问若依工作流

集成步骤

  • 安装依赖
npminstalldmn-js dmn-js-properties-panel --savenpminstall--save dmn-moddle
  • vue.config.js增加dmn.js配置, 在transpileDependencies,alias 进行设置
lias:{'@':resolve('src'),'lezer-feel$':resolve('node_modules/lezer-feel/dist/index.js'),'@camunda/feel-builtins$':resolve('node_modules/@camunda/feel-builtins/dist/index.js'),'feelers$':resolve('node_modules/feelers/dist/index.js'),'feelin$':resolve('node_modules/feelin/dist/index.cjs'),'@bpmn-io/feel-lint$':resolve('node_modules/@bpmn-io/feel-lint/dist/index.js'),'@bpmn-io/lezer-feel$':resolve('node_modules/@bpmn-io/lezer-feel/dist/index.js'), // dmn-moddle 使用 ES 模块,webpack4 需要指向 CJS 版本'dmn-moddle$':resolve('node_modules/dmn-moddle/dist/index.cjs')}transpileDependencies:['quill','bpmn-js','diagram-js','bpmn-js-properties-panel','@bpmn-io/properties-panel','@bpmn-io/feel-editor','@bpmn-io/feel-lint','@bpmn-io/lezer-feel','feelers', //以下是dmn-js需要的配置,主要是因为dmn-js 使用了 ES6+ 语法,但 webpack 未转译 node_modules 中的这些文件'lezer-feel','dmn-js','dmn-js-properties-panel','dmn-js-boxed-expression','dmn-js-decision-table','dmn-js-literal-expression','dmn-js-shared','dmn-moddle'],
  • 前端页面编码
<template> <el-container class="dmn-modeler-container"> <!-- 头部操作区域 --> <el-header class="dmn-header"> <div class="header-content"> <div class="header-title"> <h3>DMN 决策表建模器</h3> </div> <div class="header-actions"> <el-button-group> <el-button icon="el-icon-folder-opened" @click="openFile">导入</el-button> <el-button icon="el-icon-download" @click="downloadDiagram">导出</el-button> <el-button icon="el-icon-document" type="primary" @click="saveDiagram">部署</el-button> </el-button-group> </div> </div> </el-header> <!-- 主要内容区域 --> <el-main class="dmn-main"> <div class="dmn-content"> <!-- DMN 画布区域 --> <div class="canvas-container"> <div id="canvas" class="dmn-canvas" v-loading="initializing"></div> </div> </div> </el-main> <!-- 文件输入 --> <input ref="fileInput" type="file" accept=".dmn,.xml" style="display: none" @change="handleFileImport" /> </el-container> </template> <script> import DmnModeler from 'dmn-js/lib/Modeler' import FileSaver from 'file-saver' import { deployDmnTable } from '@/api/camunda/dmn' // 样式引入 // 基础样式 import 'dmn-js/dist/assets/diagram-js.css' // DMN 字体样式 import 'dmn-js/dist/assets/dmn-font/css/dmn.css' // 决策表相关样式(确保决策表正确显示) import 'dmn-js/dist/assets/dmn-js-shared.css' import 'dmn-js/dist/assets/dmn-js-decision-table.css' import 'dmn-js/dist/assets/dmn-js-decision-table-controls.css' // DRD (Decision Requirements Diagram) 视图样式 import 'dmn-js/dist/assets/dmn-js-drd.css' export default { name: 'CamundaDmnModeler', data() { return { dmnModeler: null, canUndo: false, canRedo: false, isInitialized: false, // 标记是否初始化成功 initializing: false, // 初始化或导入中的 loading 状态 initPromise: null // 记录初始化 Promise,便于后续等待 } }, mounted() { this.$nextTick(() => { this.initModeler() }) }, beforeDestroy() { if (this.dmnModeler) { this.dmnModeler.destroy() this.dmnModeler = null } this.initPromise = null }, methods: { // 生成随机决策表ID generateDecisionId() { const randomNum = Math.floor(Math.random() * 10000) return `Decision_${randomNum}` }, initModeler() { if (this.initializing && this.initPromise) { return this.initPromise } try { // 如果已有实例,先销毁重新创建,避免残留状态 if (this.dmnModeler) { try { this.dmnModeler.destroy() } catch (destroyErr) { console.warn('销毁旧的 DMN Modeler 失败:', destroyErr) } } this.dmnModeler = new DmnModeler({ container: '#canvas' }) this.initializing = true this.isInitialized = false // 加载空白决策表 - 使用标准的 DMN 1.3 格式 // 根据 dmn-moddle 11.0.0,使用正确的命名空间 const decisionId = this.generateDecisionId() const decisionTableId = 'DecisionTable_' + Date.now() const diagramXML = `<?xml version="1.0" encoding="UTF-8"?> <definitions xmlns="https://www.omg.org/spec/DMN/20191111/MODEL/" xmlns:dmndi="http://www.omg.org/spec/DMN/20180521/DMNDI" xmlns:di="http://www.omg.org/spec/DMN/20180521/DI/" xmlns:dc="http://www.omg.org/spec/DMN/20180521/DC/" id="Definitions_1" name="决策表" namespace="http://camunda.org/schema/1.0/dmn"> <decision id="${decisionId}" name="决策表"> <decisionTable id="${decisionTableId}" hitPolicy="UNIQUE"> <input id="Input_1" label="输入"> <inputExpression id="InputExpression_1" typeRef="string"> <text></text> </inputExpression> </input> <output id="Output_1" label="输出" typeRef="string" /> </decisionTable> </decision> <dmndi:DMNDI> <dmndi:DMNDiagram id="DMNDiagram_1"> <dmndi:DMNShape id="DMNShape_${decisionId}" dmnElementRef="${decisionId}"> <dc:Bounds x="100" y="100" width="300" height="200" /> </dmndi:DMNShape> </dmndi:DMNDiagram> </dmndi:DMNDI> </definitions>` // 使用箭头函数确保 this 上下文正确 const initTask = this.dmnModeler.importXML(diagramXML) this.initPromise = initTask initTask.then(() => { // 只有在 importXML 成功后才标记为已初始化 this.isInitialized = true this.initializing = false this.$message.success('决策表初始化成功') // 确保 dmnModeler 已完全初始化后再访问服务 if (this.dmnModeler && typeof this.dmnModeler.get === 'function') { // 等待 DOM 更新 this.$nextTick(() => { // 监听撤销重做状态 const eventBus = this.dmnModeler.get('eventBus') if (eventBus) { eventBus.on('commandStack.changed', () => { if (this.dmnModeler && typeof this.dmnModeler.get === 'function') { const commandStack = this.dmnModeler.get('commandStack') if (commandStack) { this.canUndo = commandStack.canUndo() this.canRedo = commandStack.canRedo() } } }) } }) } }).catch(err => { console.error('初始化失败:', err) console.error('XML 内容:', diagramXML) this.isInitialized = false this.initializing = false this.$message.error('决策表初始化失败: ' + (err.message || '未知错误')) // 如果初始化失败,清空 dmnModeler,避免使用不完整的状态 if (this.dmnModeler) { try { this.dmnModeler.destroy() } catch (e) { console.warn('销毁失败的 modeler:', e) } this.dmnModeler = null } throw err }).finally(() => { // 保持 initPromise 只代表最近一次初始化 if (this.initPromise === initTask) { this.initPromise = null } }) return initTask } catch (err) { console.error('创建 DMN Modeler 失败:', err) this.$message.error('创建决策表建模器失败: ' + (err.message || '未知错误')) this.initializing = false this.isInitialized = false this.initPromise = null throw err } }, async ensureModelerReady() { debugger if (this.isInitialized && this.dmnModeler && typeof this.dmnModeler.get === 'function') { return true } if (!this.initializing || !this.initPromise) { try { await this.initModeler() } catch (err) { console.error('重新初始化决策表建模器失败:', err) return false } } if (this.initPromise) { try { await this.initPromise } catch (err) { console.error('等待决策表建模器初始化失败:', err) return false } } return this.isInitialized && this.dmnModeler && typeof this.dmnModeler.get === 'function' }, // 确保XML包含必要的命名空间 ensureDmnNamespace(xml) { // 检查是否包含正确的 DMN 1.3 命名空间 // MODEL 命名空间应该是 https://www.omg.org/spec/DMN/20191111/MODEL/ if (xml.indexOf('xmlns="https://www.omg.org/spec/DMN/20191111/MODEL/"') === -1 && xml.indexOf('xmlns:dmn="https://www.omg.org/spec/DMN/20191111/MODEL/"') === -1) { // 如果缺少默认命名空间,尝试添加 if (xml.indexOf('<definitions') !== -1) { // 替换 definitions 标签,添加默认命名空间 xml = xml.replace( /<definitions([^>]*)>/, '<definitions$1 xmlns="https://www.omg.org/spec/DMN/20191111/MODEL/" xmlns:dmndi="http://www.omg.org/spec/DMN/20180521/DMNDI" xmlns:di="http://www.omg.org/spec/DMN/20180521/DI/" xmlns:dc="http://www.omg.org/spec/DMN/20180521/DC/">' ) } else if (xml.indexOf('<dmn:definitions') !== -1) { // 如果使用 dmn: 前缀,也添加命名空间 xml = xml.replace( /<dmn:definitions([^>]*)>/, '<dmn:definitions$1 xmlns:dmn="https://www.omg.org/spec/DMN/20191111/MODEL/" xmlns:dmndi="http://www.omg.org/spec/DMN/20180521/DMNDI" xmlns:di="http://www.omg.org/spec/DMN/20180521/DI/" xmlns:dc="http://www.omg.org/spec/DMN/20180521/DC/">' ) } } return xml }, // 从 XML 中提取第一个 decision 的 name 属性 extractDecisionName(xml) { if (!xml || typeof xml !== 'string') { return null } try { if (typeof window !== 'undefined' && window.DOMParser) { const parser = new DOMParser() const doc = parser.parseFromString(xml, 'text/xml') const parserError = doc.getElementsByTagName('parsererror') if (parserError && parserError.length) { console.warn('DOMParser 解析 DMN XML 出错,退回正则解析') } else { // 先尝试不带命名空间的 decision let decisionEl = doc.getElementsByTagName('decision')[0] if (!decisionEl) { // 再尝试带命名空间的 decision decisionEl = doc.getElementsByTagNameNS('https://www.omg.org/spec/DMN/20191111/MODEL/', 'decision')[0] } if (decisionEl) { const name = decisionEl.getAttribute('name') if (name) { return name } } } } } catch (err) { console.warn('DOMParser 提取决策名称失败:', err) } // 正则后备方案,兼容单引号或双引号 const match = xml.match(/<\s*(?:dmn:)?decision\b[^>]*\bname=['"]([^'"]+)['"]/i) if (match && match[1]) { return match[1] } return null }, async saveDiagram() { try { // const ready = await this.ensureModelerReady() // if (!ready) { // this.$message.error('决策表建模器未初始化,请稍后再试') // return // } const modeler = this.dmnModeler // if (!modeler || typeof modeler.get !== 'function') { // this.$message.error('决策表建模器不可用,请刷新页面后重试') // return // } const { xml } = await modeler.saveXML({ format: true, preamble: true }) // 确保XML包含必要的命名空间 const processedXml = this.ensureDmnNamespace(xml) // 获取决策表名称:优先读取 XML 中 decision 的 name let decisionName = this.extractDecisionName(processedXml) if (!decisionName) { try { const elementRegistry = modeler.get('elementRegistry') if (elementRegistry) { // 尝试从决策表中获取名称 const decisions = elementRegistry.filter(el => el.type === 'dmn:Decision') if (decisions.length > 0) { const decision = decisions[0] const bo = decision.businessObject || decision decisionName = bo.name || bo.id || decisionName } } } catch (e) { console.warn('从 elementRegistry 获取决策表名称失败:', e) } } if (!decisionName) { decisionName = 'decision_' + Date.now() } // 准备部署参数 const deployData = { decisionName: decisionName, dmnXml: processedXml, tenantId: '', description: '决策表部署' } // 调用部署API this.$message.info('正在部署决策表...') const response = await deployDmnTable(deployData) this.$message.success(`决策表部署成功!决策名称: ${decisionName}`) console.log('Deployment response:', response) // 跳转到决策表列表页面 this.$router.push('/dmn/list') } catch (err) { console.error('Deployment error:', err) const errorMessage = err.response?.data?.message || err.message || '部署失败' this.$message.error('部署失败: ' + errorMessage) } }, async downloadDiagram() { try { // const ready = await this.ensureModelerReady() // if (!ready) { // this.$message.error('决策表建模器未初始化,请稍后再试') // return // } const modeler = this.dmnModeler // if (!modeler || typeof modeler.get !== 'function') { // this.$message.error('决策表建模器不可用,请刷新页面后重试') // return // } const { xml } = await modeler.saveXML({ format: true, preamble: true }) // 确保XML包含必要的命名空间 const processedXml = this.ensureDmnNamespace(xml) const blob = new Blob([processedXml], { type: 'application/xml' }) FileSaver.saveAs(blob, 'decision-table.dmn') } catch (err) { this.$message.error('导出失败: ' + (err.message || '未知错误')) } }, openFile() { this.$refs.fileInput.click() }, async handleFileImport(event) { const file = event.target.files[0] if (!file) return // const ready = await this.ensureModelerReady() // if (!ready) { // this.$message.error('决策表建模器初始化失败,请刷新页面后重试') // return // } const reader = new FileReader() reader.onload = (e) => { try { const xml = e.target.result this.initializing = true const modeler = this.dmnModeler // if (!modeler || typeof modeler.get !== 'function') { // this.initializing = false // this.$message.error('决策表建模器不可用,请刷新页面后重试') // return // } modeler.importXML(xml).then(() => { this.isInitialized = true this.initializing = false this.$message.success('文件导入成功') }).catch(error => { console.error('文件导入失败:', error) this.isInitialized = false this.initializing = false this.$message.error('文件导入失败: ' + (error.message || '未知错误')) }) } catch (error) { console.error('文件读取失败:', error) this.initializing = false this.$message.error('文件读取失败: ' + (error.message || '未知错误')) } } reader.readAsText(file) // 清空文件输入 event.target.value = '' } } } </script> <style scoped> .dmn-modeler-container { width: 100%; height: 100vh; min-width: 900px; display: flex; flex-direction: column; } /* 头部样式 */ .dmn-header { background-color: #f5f7fa; border-bottom: 1px solid #e4e7ed; padding: 0 20px; height: 60px !important; display: flex; align-items: center; } .header-content { width: 100%; display: flex; justify-content: space-between; align-items: center; } .header-title h3 { margin: 0; color: #303133; font-size: 18px; font-weight: 500; } .header-actions { display: flex; gap: 15px; flex-wrap: wrap; } .header-actions .el-button-group { margin-right: 0; } .header-actions .el-button-group .el-button { margin-right: 0; } /* 主内容区域样式 */ .dmn-main { padding: 0; height: calc(100vh - 60px); overflow: hidden; } .dmn-content { display: flex; height: 100%; width: 100%; } /* 画布容器样式 */ .canvas-container { flex: 1; position: relative; display: flex; flex-direction: column; min-width: 0; /* 允许 flex 子元素缩小 */ } .dmn-canvas { width: 100%; height: 100%; border: 1px solid #dcdfe6; background-color: #fff; } </style>

最终页面展示

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/22 6:00:36

23、Python 性能优化与设计模式详解

Python 性能优化与设计模式详解 1. 性能优化 1.1 多线程 多线程在性能优化中是一个重要手段。通常情况下,两个线程的运行速度大约是一个线程的两倍,但增加更多线程可能并不会带来速度提升,甚至可能因为开销问题导致性能下降,例如 24 个线程的运行速度可能比 12 个线程还…

作者头像 李华
网站建设 2026/6/22 17:47:22

IGBT结温估算:从算法到模型的深度探索

电机控制器&#xff0c;IGBT结温估算&#xff08;算法模型&#xff09;国际大厂机密算法&#xff0c;多年实际应用&#xff0c;准确度良好…… 能够同时对IGBT内部6个三极管和6个二极管温度进行估计&#xff0c;并输出其中最热的管子对应温度。 可用于温度保护&#xff0c;降额…

作者头像 李华
网站建设 2026/6/22 16:27:27

AI大模型:重构产业生态的核心引擎

当成都市民通过语音快速上报城市民生问题&#xff0c;几分钟内便收到智能响应&#xff1b;当医生借助AI辅助诊断系统精准识别早期肺部结节&#xff1b;当自动驾驶车辆在复杂路况中平稳避障——这些场景的背后&#xff0c;都离不开人工智能大模型的技术支撑。如今&#xff0c;AI…

作者头像 李华
网站建设 2026/6/22 13:58:58

Qt5 QWebEngine 调试最佳实践指南

公众号&#xff1a;cpp手艺人 Qt5 QWebEngine 调试最佳实践指南 最近在项目中遇到很多关于QWebEngine的疑难杂症&#xff0c;越发的发现调试手段的重要性。所以我这里做了一次总结。 总结来说三种&#xff1a;日志输出信息和自带的dev tools&#xff0c;以及远程调试。 1、开启…

作者头像 李华
网站建设 2026/6/22 11:15:44

探索级联H桥SVG高频阻抗模型

级联H桥svg高频阻抗模型 最近一直在研究级联H桥SVG&#xff08;静止无功发生器&#xff09;&#xff0c;今天来和大家分享一下其中的高频阻抗模型。 一、什么是级联H桥SVG 级联H桥SVG是一种用于电力系统无功补偿和谐波治理的重要装置。它由多个H桥级联而成&#xff0c;通过控…

作者头像 李华