关于网站开发的学校,中国设计之窗官方网站,网站建设价格需要多少钱,长沙seo优化排名推广环境说明
基于RuoYi-Vue2q前端如何集成DMN组件 版本号:3.9.0 更多关于ruoyi集成工作流#xff0c;请访问若依工作流
集成步骤
安装依赖
npm install dmn-js dmn-js-properties-panel --save
npm install --save dmn-moddlevue.config.js增加dmn.js配置, 在transpileDepend…环境说明基于RuoYi-Vue2q前端如何集成DMN组件版本号:3.9.0更多关于ruoyi集成工作流请访问若依工作流集成步骤安装依赖npminstalldmn-js dmn-js-properties-panel --savenpminstall--save dmn-moddlevue.config.js增加dmn.js配置, 在transpileDependenciesalias 进行设置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 classdmn-modeler-container !-- 头部操作区域 -- el-header classdmn-header div classheader-content div classheader-title h3DMN 决策表建模器/h3 /div div classheader-actions el-button-group el-button iconel-icon-folder-opened clickopenFile导入/el-button el-button iconel-icon-download clickdownloadDiagram导出/el-button el-button iconel-icon-document typeprimary clicksaveDiagram部署/el-button /el-button-group /div /div /el-header !-- 主要内容区域 -- el-main classdmn-main div classdmn-content !-- DMN 画布区域 -- div classcanvas-container div idcanvas classdmn-canvas v-loadinginitializing/div /div /div /el-main !-- 文件输入 -- input reffileInput typefile accept.dmn,.xml styledisplay: none changehandleFileImport / /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 version1.0 encodingUTF-8? definitions xmlnshttps://www.omg.org/spec/DMN/20191111/MODEL/ xmlns:dmndihttp://www.omg.org/spec/DMN/20180521/DMNDI xmlns:dihttp://www.omg.org/spec/DMN/20180521/DI/ xmlns:dchttp://www.omg.org/spec/DMN/20180521/DC/ idDefinitions_1 name决策表 namespacehttp://camunda.org/schema/1.0/dmn decision id${decisionId} name决策表 decisionTable id${decisionTableId} hitPolicyUNIQUE input idInput_1 label输入 inputExpression idInputExpression_1 typeRefstring text/text /inputExpression /input output idOutput_1 label输出 typeRefstring / /decisionTable /decision dmndi:DMNDI dmndi:DMNDiagram idDMNDiagram_1 dmndi:DMNShape idDMNShape_${decisionId} dmnElementRef${decisionId} dc:Bounds x100 y100 width300 height200 / /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(xmlnshttps://www.omg.org/spec/DMN/20191111/MODEL/) -1 xml.indexOf(xmlns:dmnhttps://www.omg.org/spec/DMN/20191111/MODEL/) -1) { // 如果缺少默认命名空间尝试添加 if (xml.indexOf(definitions) ! -1) { // 替换 definitions 标签添加默认命名空间 xml xml.replace( /definitions([^]*)/, definitions$1 xmlnshttps://www.omg.org/spec/DMN/20191111/MODEL/ xmlns:dmndihttp://www.omg.org/spec/DMN/20180521/DMNDI xmlns:dihttp://www.omg.org/spec/DMN/20180521/DI/ xmlns:dchttp://www.omg.org/spec/DMN/20180521/DC/ ) } else if (xml.indexOf(dmn:definitions) ! -1) { // 如果使用 dmn: 前缀也添加命名空间 xml xml.replace( /dmn:definitions([^]*)/, dmn:definitions$1 xmlns:dmnhttps://www.omg.org/spec/DMN/20191111/MODEL/ xmlns:dmndihttp://www.omg.org/spec/DMN/20180521/DMNDI xmlns:dihttp://www.omg.org/spec/DMN/20180521/DI/ xmlns:dchttp://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最终页面展示