小工具知识网

当前位置: 首页 > 行业知识 > BPMN-JS在Vue3中的自定义

行业知识

BPMN-JS在Vue3中的自定义

2026-11-05 09:59:52 来源:互联网转载

文章目录

具体呈现效果

汉化包自定义

Pinia存储所需数据

组件自定义

组件左侧栏框的自定义

组件操作栏框的自定义

渲染组件的自定义

封装组件

调整Task大小弹框

总结


项目环境:

"vue": "^3.4.15"

"sass": "^1.71.1"

"element-plus": "^2.5.6"

"pinia": "^2.1.7"

"bpmn-js": "^17.0.2"

"bpmn-js-properties-panel": "^5.13.0"

"min-dash": "^4.2.1"

"tiny-svg": "^4.0.0"

"diagram-js": "^14.1.0"

具体呈现效果

汉化包自定义

 src/utils/bpmn/tanslatetranslations.js

const translations = {  Name: '名称',  Value: '值',  ID: '唯一标识(ID)',  General: '基础属性',  Documentation: '文档',  'Element documentation': '元素文档说明',  Executable: '可执行的',  'Activate hand tool': '拖动屏幕',  'Change element': '改变元素',  'Activate global connect tool': '连接',  'Activate lasso tool': '套索',  'Activate create/remove space tool': '创建/删除空间',  'Create start event': '开始事件',  'Create intermediate/boundary event': '中间/边界事件',  'Create end event': '结束事件',  'Create gateway': '网关',  'Create task': '任务',  'Create expanded sub-process': '扩展子流程',  'Create data store reference': '数据存储引用',  'Create data object reference': '数据对象引用',  'Create pool/participant': '池/参与者',  'Create group': '团队',  'Append end event': '结束事件',  'Append gateway': '网关',  'Append task': '任务',  'Append intermediate/boundary event': '中间/边界事件',  'Add text annotation': '文本注释',  Delete: '删除',  'Connect to other element': '连接另一个元素',  'Add lane above': '上方添加通道',  'Divide into two lanes': '分成两条通道',  'Divide into three lanes': '分成三条通道',  'Add lane below': '下方添加通道',}export const customTranslate = (template, replacements) => {  replacements = replacements || {}  template = translations[template] || template  return template.replace(/{([^}]+)}/g, (_, key) => {    return replacements[key] || '{' + key + '}'  })}

注:可能部分翻译不到位请谅解,毕竟是需要符合本人使用时习惯所翻译。而其中并未导入所有的汉化内容,而是根据本人使用具体使用时,用到的部分汉化。

Pinia存储所需数据

src\stores\index.js

import { createPinia } from 'pinia'const pinia = createPinia()export default piniaexport * from './bpmn'

src\stores\bpmn.js 

import { defineStore } from 'pinia'import { ref, toRaw } from 'vue'export const useBpmnStore = defineStore(  'bpmn',  () => {    //存储链接所需信息    const linkAppendServiceLinkEnd = ref('')    //存储bpmn的操作所需要的对象    const modeler = ref()    const elementRegistry = ref()    //控制调整Task大小弹窗的弹出与关闭    const bpmnObjectInformation = ref({      isOpen: false,      windowName: '编辑元素宽高',      item: {},      baseWidth: 0,      baseHeight: 0,      open: function (item) {        this.item = item        this.baseWidth = item.width        this.baseHeight = item.height        this.isOpen = true      },      close: function () {        this.isOpen = false      },      //关键函数,负责重设某个元素的大小      resetItem: function () {        let modeling = modeler.value.get('modeling')        modeling.resizeShape(toRaw(this.item), {          x: this.item.x,          y: this.item.y,          width: parseInt(this.baseWidth),          height: parseInt(this.baseHeight)        })        this.isOpen = false      }    })    const getLinkAppendServiceLinkEnd = () => {      return linkAppendServiceLinkEnd.value    }    const setLinkAppendServiceLinkEnd = (newLinkAppendServiceLinkEnd) => {      linkAppendServiceLinkEnd.value = newLinkAppendServiceLinkEnd    }    const removeLinkAppendServiceLinkEnd = () => {      linkAppendServiceLinkEnd.value = ''    }    //获取bpmn的操作所需要的对象    const setModeler = (newModeler, newElementRegistry) => {      modeler.value = newModeler      elementRegistry.value = newElementRegistry    }    return {      getLinkAppendServiceLinkEnd,      setLinkAppendServiceLinkEnd,      removeLinkAppendServiceLinkEnd,      setModeler,      bpmnObjectInformation    }  })

组件自定义

这部分主要是为了自定义组件,并能够显示出来,能够实现颜色、大小等自定义。

src\utils\bpmn\palette\index.js

import CustomContextPad from './CustomContextPad'import CustomPalette from './CustomPalette'import CustomRenderer from './CustomRenderer'/** * 在基础上添加CustomPalette/CustomContextPad的自定义(保留最开始的组件) */// export default {//   __init__: ['customContextPad', 'customPalette', 'customRenderer'],//   customPalette: ['type', CustomPalette],//   customContextPad: ['type', CustomContextPad],//   customRenderer: ['type', CustomRenderer]// }/** * CustomPalette/CustomContextPad的自定义(不保留最开始的组件) */export default {  __init__: ['contextPadProvider', 'paletteProvider', 'customRenderer'],  paletteProvider: ['type', CustomPalette],  contextPadProvider: ['type', CustomContextPad],  customRenderer: ['type', CustomRenderer]}

src/assets/bpmn.scss

//作为css文件,为后续渲染操作栏中各个元素颜色提供条件.canvas {  width: 100%;  height: 100%;}.properties {  position: absolute;  top: 16px;  right: 24px;  width: 210px;  flex: 1;  z-index: 1;}.general-education-compulsory-course {  color: rgba(249, 197, 153, 1) !important;}.subject-based-course {  color: rgba(141, 177, 226, 1) !important;}.professional-basic-compulsory-course {  color: rgba(242, 220, 218, 1) !important;}.practice-section {  color: rgba(153, 255, 204, 1) !important;}.strong-link {  color: rgba(93, 93, 93, 1) !important;}.week-link {  color: rgba(163,187,223, 1) !important;}

src\utils\bpmn\palette\item.js

//存储一些基本信息,确保自定义文件中的信息不会产生歧义const itemColor = {  'general-education-compulsory-course': '#f9c599',  'subject-based-course': '#8db1e2',  'professional-basic-compulsory-course': '#f2dcda',  'practice-section': '#99ffcc',  'strong-link': '#5d5d5d',  'week-link': '#a3bbdf'}const itemText = {  'general-education-compulsory-course': '通识必修课',  'subject-based-course': '学科基础课',  'professional-basic-compulsory-course': '专业基础必修课',  'practice-section': '实践部分'}const itemClass = {  'strong-link': 'marker-strong-end',  'week-link': 'marker-week-end'}const General_Education_Compulsory_Course = 'general-education-compulsory-course'const Subject_Based_Course = 'subject-based-course'const Professional_Basic_Compulsory_Course = 'professional-basic-compulsory-course'const Practice_Section = 'practice-section'const Strong_Link = 'strong-link'const Week_Link = 'week-link'export {  itemColor,  itemText,  itemClass,  General_Education_Compulsory_Course,  Subject_Based_Course,  Professional_Basic_Compulsory_Course,  Practice_Section,  Week_Link,  Strong_Link}

组件左侧栏框的自定义

src\utils\bpmn\palette\CustomPalette.js

import '@/assets/bpmn.scss'import { General_Education_Compulsory_Course, Subject_Based_Course, Professional_Basic_Compulsory_Course, Practice_Section} from './item'export default class CustomPalette {  constructor(bpmnFactory, create, elementFactory, palette, translate) {    this.bpmnFactory = bpmnFactory    this.create = create    this.elementFactory = elementFactory    this.translate = translate    palette.registerProvider(this)  }  // 这个函数就是绘制palette的核心  getPaletteEntries() {    const { bpmnFactory, create, elementFactory, translate } = this    //构建Task,作为最基本元素,操作它可以类比出其他的组件    function createTask(suitabilityScore) {      return function (event) {        const businessObject = bpmnFactory.create('bpmn:Task')        const documentationObject = bpmnFactory.create('bpmn:Documentation')        documentationObject.text = suitabilityScore        //存储数据到document中,方便于后端获取,以及重新渲染        businessObject.documentation = [documentationObject]        businessObject.suitable = suitabilityScore        const shape = elementFactory.createShape({          type: 'bpmn:Task',          businessObject: businessObject,          height: 40        })        create.start(event, shape)      }    }    // 返回需要的组件    return {      'create.general-education-compulsory-course': {        group: 'activity',        //控制操作栏中元素的颜色        className: 'bpmn-icon-task general-education-compulsory-course',        //控制操作栏中元素的提示文字        title: translate('通识必修课'),        action: {          //控制操作栏中元素的各种函数          dragstart: createTask(General_Education_Compulsory_Course),          click: createTask(General_Education_Compulsory_Course)        }      },      'create.subject-based-course': {        group: 'activity',        className: 'bpmn-icon-task subject-based-course',        title: translate('学科基础课'),        action: {          dragstart: createTask(Subject_Based_Course),          click: createTask(Subject_Based_Course)        }      },      'create.professional-basic-compulsory-course': {        group: 'activity',        className: 'bpmn-icon-task professional-basic-compulsory-course',        title: translate('专业基础必修课'),        action: {          dragstart: createTask(Professional_Basic_Compulsory_Course),          click: createTask(Professional_Basic_Compulsory_Course)        }      },      'create.practice-section': {        group: 'activity',        className: 'bpmn-icon-task practice-section',        title: translate('事件部分'),        action: {          dragstart: createTask(Practice_Section),          click: createTask(Practice_Section)        }      }    }  }}CustomPalette.$inject = ['bpmnFactory', 'create', 'elementFactory', 'palette', 'translate']

组件操作栏框的自定义

src\utils\bpmn\palette\CustomContextPad.js

import '@/assets/bpmn.scss'import { General_Education_Compulsory_Course, Subject_Based_Course, Professional_Basic_Compulsory_Course, Practice_Section, Strong_Link, Week_Link } from './item'import { useBpmnStore } from '@/stores'const bpmnStore = useBpmnStore()export default class CustomContextPad {  constructor(    bpmnFactory,    contextPad,    create,    elementFactory,    translate,    modeling,    globalConnect,    connect  ) {    this.bpmnFactory = bpmnFactory    this.create = create    this.elementFactory = elementFactory    this.translate = translate    this.modeling = modeling    this.globalConnect = globalConnect    this.connect = connect    this.suitabilityScore = undefined    contextPad.registerProvider(this)  }  getContextPadEntries(element) {    const { bpmnFactory, create, elementFactory, translate, modeling, connect } = this    //构建Task,作为最基本元素,操作它可以类比出其他的组件    function appendServiceTaskStart(suitabilityScore) {      return function (event) {        const businessObject = bpmnFactory.create('bpmn:Task')        const documentationObject = bpmnFactory.create('bpmn:Documentation')        documentationObject.text = suitabilityScore        //存储数据到document中,方便于后端获取,以及重新渲染        businessObject.documentation = [documentationObject]        businessObject.suitable = suitabilityScore        const shape = elementFactory.createShape({          type: 'bpmn:Task',          businessObject: businessObject,          height: 40        })        create.start(event, shape, element)      }    }    //连接开始时,pinia存储所需要渲染的数据,由于不知道如何在渲染结束时获取数据    function appendServiceLinkStart(suitabilityScore) {      return function (event) {        // this.suitabilityScore = suitabilityScore        bpmnStore.setLinkAppendServiceLinkEnd(suitabilityScore)        connect.start(event, element, undefined)      }    }    //连接结束时,从pinia存储的数据中,取出所需要存储在document中的数据,并存储    function appendServiceLinkEnd(item) {      if (item.type === 'bpmn:SequenceFlow' && item.suitable === undefined) {        item.suitable = bpmnStore.getLinkAppendServiceLinkEnd()        if (item.di?.bpmnElement) {          const documentationObject = bpmnFactory.create('bpmn:Documentation')          documentationObject.text = item.suitable          item.di.bpmnElement.documentation = [documentationObject]        }      }    }    // 返回需要的组件    return {      'create.general-education-compulsory-course': {        group: 'model',        //控制操作栏中元素的颜色        className: 'bpmn-icon-task general-education-compulsory-course',        //控制操作栏中元素的提示文字        title: translate('通识必修课'),        action: {          //控制操作栏中元素的各种函数          click: appendServiceTaskStart(General_Education_Compulsory_Course),          dragstart: appendServiceTaskStart(General_Education_Compulsory_Course)        }      },      'create.subject-based-course': {        group: 'model',        className: 'bpmn-icon-task subject-based-course',        title: translate('学科基础课'),        action: {          click: appendServiceTaskStart(Subject_Based_Course),          dragstart: appendServiceTaskStart(Subject_Based_Course)        }      },      'create.professional-basic-compulsory-course': {        group: 'model',        className: 'bpmn-icon-task professional-basic-compulsory-course',        title: translate('专业基础必修课'),        action: {          click: appendServiceTaskStart(Professional_Basic_Compulsory_Course),          dragstart: appendServiceTaskStart(Professional_Basic_Compulsory_Course)        }      },      'create.practice-section': {        group: 'model',        className: 'bpmn-icon-task practice-section',        title: translate('事件部分'),        action: {          click: appendServiceTaskStart(Practice_Section),          dragstart: appendServiceTaskStart(Practice_Section)        }      },      'create.strong-link': {        group: 'activity',        className: 'bpmn-icon-connection-multi strong-link',        title: translate('强连接'),        action: {          click: appendServiceLinkStart(Strong_Link),          dragstart: appendServiceLinkStart(Strong_Link),          drag: connect.move,          dragend: appendServiceLinkEnd(arguments[0])        }      },      'create.week-link': {        group: 'activity',        className: 'bpmn-icon-connection-multi week-link',        title: translate('弱连接'),        action: {          //控制线段开始时,去进行一些自定义操作          click: appendServiceLinkStart(Week_Link),          dragstart: appendServiceLinkStart(Week_Link),          //调用原生方法,做成原生一样的效果          drag: connect.move,          //控制线段结束,取出相关信息,并在渲染组件中,将相关信息渲染          dragend: appendServiceLinkEnd(arguments[0])        }      },      'create.delete': {        group: 'edit',        className: 'icon-custom bpmn-icon-trash',        title: translate('删除'),        action: {          click: () => {            //删除对应的元素,你可以在此处处理,如添加二次确认框            modeling.removeElements([element])          }        }      },      'create.edit': {        group: 'edit',        className: 'icon-custom bpmn-icon-screw-wrench',        title: translate('编辑'),        action: {          click: () => {            //打开编辑弹出,去重写图像大小            bpmnStore.bpmnObjectInformation.open(element)          }        }      }    }  }}CustomContextPad.$inject = [  'bpmnFactory',  'contextPad',  'create',  'elementFactory',  'translate',  'modeling',  'globalConnect',  'connect']

 注:作者不知道如何将起点元素和终点元素的链接,换为自定义的链接,或者说我所写的项目也并没有提出这样的要求,而网上找到的解决方案为修改源文件,本人才疏学浅,如有大佬也望能够沟通斧正。

渲染组件的自定义

src\utils\bpmn\palette\CustomRenderer.js

import BaseRenderer from 'diagram-js/lib/draw/BaseRenderer'import { itemColor, itemText, itemClass } from './Item'import {  attr as svgAttr} from 'tiny-svg'import { getBusinessObject } from 'bpmn-js/lib/util/ModelUtil'import { isNil } from 'min-dash'const HIGH_PRIORITY = 1500export default class CustomRenderer extends BaseRenderer {  constructor(eventBus, bpmnRenderer) {    super(eventBus, HIGH_PRIORITY)    this.bpmnRenderer = bpmnRenderer    this.isShow = false  }  canRender(element) {    return !element.labelTarget  }  //绘画普通图像  drawShape(parentNode, element) {    const shape = this.bpmnRenderer.drawShape(parentNode, element)    const suitabilityScoreBase = this.getSuitabilityScore(element)    const suitabilityScore = this.getText(suitabilityScoreBase)    //从document中取出信息,方便于渲染颜色    if (      element.di?.bpmnElement?.documentation &&      element.di?.bpmnElement?.documentation.length > 0    ) {      svgAttr(shape, {        fill: this.getColor(element.di?.bpmnElement?.documentation[0].text),        rx: 0,        ry: 0,        height: element.height,        width: element.width      })    } else if (!isNil(suitabilityScore)) {      svgAttr(shape, {        fill: this.getColor(suitabilityScoreBase),        rx: 0,        ry: 0,        height: 40      })    }    return shape  }  getShapePath(shape) {    return this.bpmnRenderer.getShapePath(shape)  }  getSuitabilityScore(element) {    const businessObject = getBusinessObject(element)    const { suitable } = businessObject    return Number.isFinite(suitable) ? suitable : suitable  }  getColor(suitabilityScore) {    return itemColor[suitabilityScore]  }  getText(suitabilityScore) {    return itemText[suitabilityScore]  }  getClass(suitabilityScore) {    return itemClass[suitabilityScore]  }  //绘画连线图案  drawConnection(parentNode, element, number) {    const connection = this.bpmnRenderer.drawConnection(parentNode, element)    if (isNaN(number)) {      number = 0    }    //控制图像渲染此处,以防数字太大而导致一直渲染    //由于本函数渲染通过全局Store去存储并获取信息,所以使用定时器并结合这种方式,以防驻留在内存中,导致卡顿    //若有更好方式解决,请斧正,十分感谢    if (number >= 12) {      element.suitable = ''      return connection    }    if (element.suitable) {      svgAttr(connection, {        stroke: this.getColor(element.suitable)      })      this.isShow = true    } else if (element.di?.bpmnElement?.documentation) {      svgAttr(connection, {        stroke: this.getColor(element.di?.bpmnElement?.documentation[0]?.text)      })      this.isShow = true    } else {      if (this.isShow) {        this.isShow = false        return connection      }      setTimeout(() => {        number++        this.drawConnection(parentNode, element, number)      }, 100)    }    return connection  }}CustomRenderer.$inject = ['eventBus', 'bpmnRenderer']

封装组件

src\components\teacher\index\auxiliaryFunctions\children\course_topology_page.vue

import { ref, onMounted } from 'vue'import Modeler from 'bpmn-js/lib/Modeler'import customModule from '@/utils/bpmn/palette'/** 本处导入自己封装的请求文件,并修改下列请求相关内容 */import instance from '@/utils/request.js'import 'bpmn-js/dist/assets/diagram-js.css'import 'bpmn-js/dist/assets/bpmn-font/css/bpmn.css'import 'bpmn-js/dist/assets/bpmn-js.css'import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-codes.css'import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css'import 'bpmn-js-properties-panel/dist/assets/element-templates.css'import 'bpmn-js-properties-panel/dist/assets/properties-panel.css'import { customTranslate } from '@/utils/bpmn/tanslate/translations'import { useBpmnStore } from '@/stores'import bpmn_edit_dialog from '@/components/dialog/bpmn_edit_dialog.vue'const bpmnStore = useBpmnStore()const canvas = ref(null)const properties = ref(null)const modeler = ref(null)const elementRegistry = ref(null)onMounted(() => {  initCurrentBpmn()})//初始化BPMN内容,此处从后端获取,若需要改为从前端获取,请自行修改const initCurrentBpmn = async () => {  if (modeler.value) {    //清空相关内容,以防多次渲染bpmn,造成多个bpmn页面共存    modeler.value.clear()  } else {    //创建新对象,去渲染相关内容    modeler.value = new Modeler({      container: canvas.value,      propertiesPanel: {        parent: properties.value      },      additionalModules: [        customModule,        {          translate: ['value', customTranslate]        }      ]    })  }  let bpmnXML = ''  try {    await instance      .get('/api/CourseTopology/get', {        grade: bpmnStore.currentGrade,        majorId: bpmnStore.currentMajorId      })      .then((res) => {        if (res.data?.xmlJsonString) {          //获取后端数据,去渲染图像          bpmnXML = res.data.xmlJsonString        }      })      .catch((err) => {        console.log(err)      })    // ...  } catch (err) {    // err...  }  await modeler.value.importXML(bpmnXML)  elementRegistry.value = modeler.value.get('elementRegistry')  //将数据存储到pinia中,为了自定义渲染所需要使用时能够获取  bpmnStore.setModeler(modeler.value, elementRegistry.value)}const updateCourseTopology = () => {  const xmlString = modeler.value.saveXML({ format: true })  xmlString.then((data) => {    const fileName = 'test.bpmn20.xml'    const blob = new Blob([data.xml], { type: 'application/xml' })    const file = new File([blob], fileName, { type: 'application/xml' })    updateCourseTopologyOp(file)  })}const setCurrentTopology = () => {  bpmnObjectSetCurrentInformation.value.open()}//将bpmn转变为svg图像,能够用于后续操作const saveSVG = async () => {  const { svg } = await modeler.value.saveSVG({ format: true })  const dataTrack = 'bpmn'  const a = document.createElement('a')  const name = `.${dataTrack}20.svg`  a.setAttribute('href', `data:application/bpmn20-xml;charset=UTF-8,${encodeURIComponent(svg)}`)  a.setAttribute('target', '_blank')  a.setAttribute('dataTrack', `diagram:download-${dataTrack}`)  a.setAttribute('download', name)  document.body.appendChild(a)  a.click()  document.body.removeChild(a)}const emit = defineEmits([  'saveSVG',])onMounted(() => {  emit('saveSVG', saveSVG)})                  @import url('bpmn-js-properties-panel/dist/assets/properties-panel.css');@import url('@/assets/bpmn.scss');

调整Task大小弹框

src\components\dialog\bpmn_edit_dialog.vue 

import { ref } from 'vue'const props = defineProps({ bpmnObjectInformation: Object })const bpmnObjectInformation = ref(props.bpmnObjectInformation)                                                                    取消         确认             

总结

        这次是我第一次使用CSDN所作的笔记,网上大多数的教程大多操作繁琐,并且希望一步能过够完整了解BPMN。而我作为一个初学者,站在我的角度上,我希望有一个入门的教程,使得我首先能够快速的入手BPMN,并能够从中获取其基本使用。故出此笔记,如果能给你帮助,那么我十分荣幸,若做得不到位,请斧正而非人身攻击。

        本文章为BPMN-JS在VUE3中的入门应用,并非深入剖析,主要做到BPMN设置其元素改变元素颜色及其大小。并能够将BPMN给最终导出为SVG图标,进一步处理。

        关于后端与前端交互问题,能够解决,由于前端将数据写入document中,可供后端进行操作并且存储,而也能有后端填入相关字段,使得前端渲染出不同的颜色及大小。

上一篇:进去服务器怎么看ip?

下一篇:宝塔面板主题轻松切换秘籍,一键搞定,打造个性化界面!