BPMN.JS流程设计器

前言

最近工作需要,根据xml转成svg图片,用到了bpmn.js这个组件,故此记录一下。

bpmn.js官网

bpmn-js: BPMN 2.0 rendering toolkit and web modeler | Toolkits | bpmn.io

GitHub地址:bpmn.io · GitHub

安装

npm install bpmn-js --save

引入

import BpmnViewer from 'bpmn-js/lib/Viewer'

代码

<template>
  <div class="bpmn-containers" ref="content">
    <div class="bpmn-canvas" ref="canvas"></div>
    <el-button size="small" type="primary" @click="downloadSvg" style="float:right;">
      导出流程图
    </el-button>
    <a hidden ref="downloadLink"></a>
  </div>

</template>

<script>
  import BpmnModeler from 'bpmn-js/lib/Modeler';
  import BpmnViewer from 'bpmn-js/lib/Viewer'
  import {xmlStr} from '@/pages/mock/xmlStr2';
  export default {
    components: {},
    data() {
      return {
        bpmnModeler: null,
        container: null,
        canvas: null,
        uploadBpmnFileList: [],
        scale: 1,
        nodeDetail:{},
        nodeDetailLoading:{},
        detailInfo:{},
        showToolTip:false,
        tooltipStyle: {
          position: 'absoulte',
          top:0,
          left:0,
        }
      }
    },
    created() {
    },
    mounted() {
      this.init();
    },
    methods: {
      setViewerStyle(canvas){
        let highlightNode = ["Activity_0ud0he0"]
        let highlightLine = ["Flow_042g57v","Flow_10ltfzt"]
        canvas.addMarker("Activity_0ud0he0", 'highlight');
        if(highlightNode && highlightNode.length > 0){
          highlightNode.forEach(item=>{
            canvas.addMarker(item, 'highlight');
            const ele = document.querySelector('.highlight').querySelector('.djs-visual rect');
            if(ele){
              ele.setAttribute('stroke-dasharray', '4,4');
            }
          });
        }
        if(highlightLine && highlightLine.length > 0){
          highlightLine.forEach(item=>{
            canvas.addMarker(item, 'highlight-line');
          });
        }
      },
      // 以下代码为:为节点注册鼠标悬浮事件
      bindEvents(){
        const eventBus = this.bpmnModeler.get('eventBus');
        const overlays = this.bpmnModeler.get('overlays');
        let processInstanceId = '9df8a92b58e811eeab3e000c29421fbd'
        eventBus.on('element.hover', (e) => {
          if(e.element.type === "bpmn:UserTask" ){
            if(processInstanceId){
              const elementId = e.element.id;
              const cacheKey = this.getNodeDetailKey(processInstanceId, elementId);
              if(this.nodeDetail[cacheKey]){
                this.genNodeDetailBox(this.nodeDetail[cacheKey], e, overlays);
              }else{
                const currentNodeLoading = this.nodeDetailLoading[cacheKey];
                if(!currentNodeLoading || currentNodeLoading === false){
                  this.getNodePersonal(processInstanceId, e, overlays);
                }
              }
            }
          }
        });
        eventBus.on('element.out', (e) => {
          this.showToolTip = false
          if(e.element.type === "bpmn:UserTask" ){
            // popoverVisible.value = false;
          }
          overlays.clear();
        });
      },
      getNodePersonal(processInstanceId, e, overlays){
        const elementId = e.element.id;
        const cacheKey = this.getNodeDetailKey(processInstanceId, elementId);
        this.nodeDetailLoading[cacheKey] = true;
        // getOneActivityVoByProcessInstanceIdAndActivityId({procInstId:processInstanceId, elementId: e.element.id}).then(res=>{
        let data ={"code":"100","msg":"OK","data":{"id":"Activity_0ud0he0","x":330.0,"y":138.0,"width":100.0,"height":80.0,"documentation":null,"description":null,"name":"主管审核","approver":null,"type":null,"nodeType":"必审","status":"处理中","startDate":"2023-09-22 09:37:40","endDate":null,"duration":null,"approverNo":null,"proceInsId":"9df8a92b58e811eeab3e000c29421fbd","proceDefId":"svn_application_form:11:1724651d584611ee8e2a000c29421fbd","taskDefKey":"Activity_0ud0he0"},"success":true}
        let res = data.data
        this.nodeDetail[cacheKey] = res;
        this.nodeDetailLoading[cacheKey] = false;
        this.genNodeDetailBox(res, e, overlays);
        // });
      },
      getNodeDetailKey(processInstanceId, elementId){
        return processInstanceId + '_' + elementId;
      },
      genNodeDetailBox(detail, e, overlays) {
        this.showToolTip = true
        const tempDiv = document.createElement("div");
        this.detailInfo.value = detail;
        setTimeout(()=>{
          const popoverEl = document.getElementById('flowMsgPopover');
          this.tooltipComponent = {
            template: `
                    <h1 class="popover-title">${this.detailInfo.name||"-"}</h1>
                    <p>
                      审批人员:<span class="approvePersonCss">${this.detailInfo.approver||"666"}</span>
                    </p>
                    <p>节点类型:${this.detailInfo.nodeType||"-"}</p>
                    <p>节点状态:${this.detailInfo.status||"-"}</p>
                    <p>开始时间:${this.detailInfo.startDate||"-"}</p>
                    <p>结束时间:${this.detailInfo.endDate||"-"}</p>
                    <p>审批耗时:${this.detailInfo.duration||"-"}</p>
                `
          };
          tempDiv.innerHTML= this.tooltipComponent.template;
          // tempDiv.innerHTML= popoverEl.innerHTML;
          tempDiv.className = 'tipBox';
          tempDiv.style.width = '270px';
          tempDiv.style.background = 'rgba(255, 255, 255, .6)'
          overlays.add(detail.id, 'note', {
            position: {top: e.element.height, left: 0},
            html: tempDiv
          });
        });
      },
      /**
       * 初始化流程设计器对象
       * @returns {Promise<void>}
       */
      init() {
        // 获取到属性ref为“content”的dom节点
        this.container = this.$refs.content
        // 获取到属性ref为“canvas”的dom节点
        const canvas = this.$refs.canvas
        // 创建BpmnModeler
        this.bpmnModeler = new BpmnViewer({
          container: canvas,
          // 加入工具栏支持
          // propertiesPanel: {
          //   parent: '#js-properties-panel'
          // },
          additionalModules: [
            {
              paletteProvider:["value",''],//禁用/清空左侧工具栏
              labelEditingProvider:["value",''],//禁用节点编辑
              contextPadProvider:["value",''],//禁用图形菜单
              bendpoints:["value",{}],//禁用连线拖动
              zoomScroll:["value",''],//禁用滚动
              moveCanvas:['value',''],//禁用拖动整个流程图
              move:['value','']//禁用单个图形拖动
            }
          ],
          moddleExtensions: {
            // bpmn: bpmnModdleDescriptor
          }
        });
        // 创建新流程
        this.createNewDiagram(xmlStr);

      },
      /**
       * 创建新流程
       * @param bpmn BPMN流程XML报文
       * @returns {Promise<void>}
       */
      createNewDiagram(bpmn) {
        // 将字符串转换成图显示出来;
        this.bpmnModeler.importXML(bpmn, err => {
          if (err) {
            this.$message.error('打开模型出错,请确认该模型符合Bpmn2.0规范');
          } else {
            this.setViewerStyle(this.bpmnModeler.get('canvas'))
            this.bindEvents();
            console.log("成功导入模型");
          }
        });
      },
      handlerZoom(radio) {
        const newScale = !radio ? 1.0 : this.scale + radio;
        this.bpmnModeler.get("canvas").zoom(newScale);
        this.scale = newScale;
      },
      downloadSvg() {
        this.bpmnModeler.saveXML({format: true}, (err, xml) => {
          if (!err) {
            // 获取文件名
            const name = `${this.getFilename(xml)}.svg`;

            // 从建模器画布中提取svg图形标签
            let context = "";
            const djsGroupAll = this.$refs.canvas.querySelectorAll(".djs-group");
            for (let item of djsGroupAll) {
              context += item.innerHTML;
            }
            // 获取svg的基本数据,长宽高
            const viewport = this.$refs.canvas
                    .querySelector(".viewport")
                    .getBBox();

            // 将标签和数据拼接成一个完整正常的svg图形
            const svg = `
                        <svg
                          xmlns="http://www.w3.org/2000/svg"
                          xmlns:xlink="http://www.w3.org/1999/xlink"
                          width="${viewport.width}"
                          height="${viewport.height}"
                          viewBox="${viewport.x} ${viewport.y} ${viewport.width} ${viewport.height}"
                          version="1.1"
                          >
                          ${context}
                        </svg>
                      `;
            // 将文件名以及数据交给下载方法
            this.download({name: name, data: svg});
          }
        });
      },
      download({name = "diagram.bpmn", data}) {
        // 这里就获取到了之前设置的隐藏链接
        const downloadLink = this.$refs.downloadLink;
        // 把输就转换为URI,下载要用到的
        const encodedData = encodeURIComponent(data);
        if (data) {
          // 将数据给到链接
          downloadLink.href = "data:application/bpmn20-xml;charset=UTF-8," + encodedData;
          // 设置文件名
          downloadLink.download = name;
          // 触发点击事件开始下载
          downloadLink.click();
        }
      },
      getFilename(xml) {
        let start = xml.indexOf("process");
        let filename = xml.substr(start, xml.indexOf(">"));
        filename = filename.substr(filename.indexOf("id") + 4);
        filename = filename.substr(0, filename.indexOf('"'));
        return filename;
      },
    },
    computed: {}
  }
</script>
<style lang="scss">
  .bpmn-containers {
    background-color: #ffffff;
    width: 100%;
    height: 420px;
  }
  .bpmn-canvas {
    width: 100%;
    height: 100%;
  }
  .highlight:not(.djs-connection) .djs-visual > :nth-child(1) {
    fill: var(--theme-custom-color_2) !important; /* color elements as green */
  }
  .highlight g.djs-visual >:nth-child(1) {
    stroke: var(--theme-custom-color) !important;
  }
  .highlight-line g.djs-visual >:nth-child(1) {
    stroke: rgba(0, 190, 0, 1) !important;
  }
  .highlight-line{
    path{
      marker-end: url('#greenMarker') !important;
    }
  }
  .highlight{
    .djs-visual{
      animation: dynamicNode 18S linear infinite;
      -webkit-animation: dynamicNode 18S linear infinite;
      -webkit-animation-fill-mode: forwards;
    }
  }

  @keyframes dynamicNode {
    to {
      stroke-dashoffset: 100%;
    }
  }
  @-webkit-keyframes dynamicNode {
    to {
      stroke-dashoffset: 100%;
    }
  }
  .bjs-powered-by{
    display: none;
  }

  .tipBox {
    font-size: var(--font-size-6);
    .ant-popover-arrow{
      display: none;
    }
  }
  .tipBox {
    width: 300px;
    background: #fff;
    border-radius: 4px;
    border: 1px solid #ebeef5;
    padding: var(--spacing-normal-2);
    .ant-popover-arrow{
      display: none;
    }
    .popover-title {
      font-size: 16px;
      line-height: 12px;
      margin: 0;
    }
    p{
      line-height: 28px;
      margin:0;
      padding:0;
    }
  }
  .approvePersonCss{
    color: var(--theme-custom-color);
    background-color: var(--theme-custom-color_1);
    border: 1px solid var(--theme-custom-color_2);
    padding: 4px;
  }
</style>

xml文件

export const xmlStr = `<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:flowable="http://flowable.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://flowable.org/modeler" exporter="Flowable Open Source Modeler" exporterVersion="1.1.2-SNAPSHOT">
  <process id="svn_application_form" name="SVN申请流程表" isExecutable="true">
    <startEvent id="startEvent1"></startEvent>
    <userTask id="Activity_0eofm22" name="提交人" flowable:candidateGroups="测试用角色" flowable:skipExpression="">
      <extensionElements>
        <flowable:assigneeType><![CDATA[idm]]></flowable:assigneeType>
        <flowable:idmCandidateGroups><![CDATA[[{"id":"undefined_60070","name":"测试用角色","sn":"测试用角色"}]]]></flowable:idmCandidateGroups>
        <flowable:isEditdata><![CDATA[true]]></flowable:isEditdata>
        <flowable:nodeType><![CDATA[0]]></flowable:nodeType>
      </extensionElements>
    </userTask>
    <sequenceFlow id="Flow_042g57v" sourceRef="startEvent1" targetRef="Activity_0eofm22"></sequenceFlow>
    <userTask id="Activity_0ud0he0" name="主管审核">
      <extensionElements>
        <flowable:taskListener event="create" delegateExpression=""></flowable:taskListener>
        <flowable:taskListener event="create" delegateExpression=""></flowable:taskListener>
        <flowable:assigneeType><![CDATA[idm]]></flowable:assigneeType>
        <flowable:isEditdata><![CDATA[true]]></flowable:isEditdata>
        <flowable:nodeType><![CDATA[1]]></flowable:nodeType>
      </extensionElements>
    </userTask>
    <sequenceFlow id="Flow_10ltfzt" sourceRef="Activity_0eofm22" targetRef="Activity_0ud0he0"></sequenceFlow>
    <userTask id="Activity_0xhe8nm" name="经理审核" flowable:candidateUsers="JH70003">
      <extensionElements>
        <flowable:taskListener event="create" delegateExpression=""></flowable:taskListener>
        <flowable:taskListener event="complete" delegateExpression=""></flowable:taskListener>
        <flowable:assigneeType><![CDATA[idm]]></flowable:assigneeType>
        <flowable:idmCandidateUsers><![CDATA[[{"id":"1147160053359714304","name":"SVN审核经理","code":"JH70003","sex":0,"mobile":"13311112222","companyId":null,"companyName":null,"deptId":null,"deptName":null}]]]></flowable:idmCandidateUsers>
        <flowable:nodeType><![CDATA[1]]></flowable:nodeType>
      </extensionElements>
    </userTask>
    <sequenceFlow id="Flow_1k7t103" sourceRef="Activity_0ud0he0" targetRef="Activity_0xhe8nm"></sequenceFlow>
    <userTask id="Activity_1nim4j5" name="知会对接人" flowable:candidateUsers="JH70004">
      <extensionElements>
        <flowable:taskListener event="create" delegateExpression=""></flowable:taskListener>
        <flowable:assigneeType><![CDATA[idm]]></flowable:assigneeType>
        <flowable:idmCandidateUsers><![CDATA[[{"id":"1147160212286087168","name":"SVN知会对接人","code":"JH70004","sex":0,"mobile":"13312312333","companyId":null,"companyName":null,"deptId":null,"deptName":null}]]]></flowable:idmCandidateUsers>
        <flowable:nodeType><![CDATA[4]]></flowable:nodeType>
      </extensionElements>
    </userTask>
    <sequenceFlow id="Flow_0y1ezwh" sourceRef="Activity_0xhe8nm" targetRef="Activity_1nim4j5"></sequenceFlow>
    <endEvent id="Event_1l2ddd0"></endEvent>
    <sequenceFlow id="Flow_11rjv3u" sourceRef="Activity_1nim4j5" targetRef="Event_1l2ddd0"></sequenceFlow>
  </process>
  <bpmndi:BPMNDiagram id="BPMNDiagram_svn_application_form">
    <bpmndi:BPMNPlane bpmnElement="svn_application_form" id="BPMNPlane_svn_application_form">
      <bpmndi:BPMNShape bpmnElement="startEvent1" id="BPMNShape_startEvent1">
        <omgdc:Bounds height="30.0" width="30.0" x="100.0" y="163.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="Activity_0eofm22" id="BPMNShape_Activity_0eofm22">
        <omgdc:Bounds height="80.0" width="100.0" x="180.0" y="138.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="Activity_0ud0he0" id="BPMNShape_Activity_0ud0he0">
        <omgdc:Bounds height="80.0" width="100.0" x="330.0" y="138.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="Activity_0xhe8nm" id="BPMNShape_Activity_0xhe8nm">
        <omgdc:Bounds height="80.0" width="100.0" x="480.0" y="138.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="Activity_1nim4j5" id="BPMNShape_Activity_1nim4j5">
        <omgdc:Bounds height="80.0" width="100.0" x="630.0" y="138.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="Event_1l2ddd0" id="BPMNShape_Event_1l2ddd0">
        <omgdc:Bounds height="36.0" width="36.0" x="782.0" y="160.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge bpmnElement="Flow_10ltfzt" id="BPMNEdge_Flow_10ltfzt" flowable:sourceDockerX="50.0" flowable:sourceDockerY="40.0" flowable:targetDockerX="50.0" flowable:targetDockerY="40.0">
        <omgdi:waypoint x="279.0" y="178.0"></omgdi:waypoint>
        <omgdi:waypoint x="329.0" y="178.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="Flow_0y1ezwh" id="BPMNEdge_Flow_0y1ezwh" flowable:sourceDockerX="50.0" flowable:sourceDockerY="40.0" flowable:targetDockerX="50.0" flowable:targetDockerY="40.0">
        <omgdi:waypoint x="579.0" y="178.0"></omgdi:waypoint>
        <omgdi:waypoint x="629.0" y="178.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="Flow_11rjv3u" id="BPMNEdge_Flow_11rjv3u" flowable:sourceDockerX="50.0" flowable:sourceDockerY="40.0" flowable:targetDockerX="18.0" flowable:targetDockerY="18.0">
        <omgdi:waypoint x="729.0" y="178.0"></omgdi:waypoint>
        <omgdi:waypoint x="782.0" y="178.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="Flow_1k7t103" id="BPMNEdge_Flow_1k7t103" flowable:sourceDockerX="50.0" flowable:sourceDockerY="40.0" flowable:targetDockerX="50.0" flowable:targetDockerY="40.0">
        <omgdi:waypoint x="429.0" y="178.0"></omgdi:waypoint>
        <omgdi:waypoint x="479.0" y="178.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="Flow_042g57v" id="BPMNEdge_Flow_042g57v" flowable:sourceDockerX="15.0" flowable:sourceDockerY="15.0" flowable:targetDockerX="50.0" flowable:targetDockerY="40.0">
        <omgdi:waypoint x="129.0" y="178.0"></omgdi:waypoint>
        <omgdi:waypoint x="180.0" y="178.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</definitions>`

效果图

其中悬浮提示的效果,是通过为节点注册鼠标悬浮的事件实现的。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
好的,下面是Vue3整合bpmn.js实现flowable流程设计的代码实现。 首先,需要安装依赖: ``` npm install bpmn-js@8.0.1 npm install bpmn-js[email protected] npm install bpmn-js[email protected] npm install camunda-bpmn[email protected] npm install vue-bpmn ``` 接着,在Vue组件中引入需要的文件: ```javascript import BpmnModeler from 'bpmn-js/lib/Modeler'; import propertiesPanelModule from 'bpmn-js-properties-panel'; import propertiesProviderModule from 'bpmn-js-properties-panel/lib/provider/camunda'; import camundaModdleDescriptor from 'camunda-bpmn-moddle/resources/camunda'; import 'bpmn-js-properties-panel/styles/properties.less'; import 'bpmn-js/dist/assets/bpmn-font/css/bpmn.css'; ``` 然后,在Vue组件中定义bpmn.js的Modeler和其他必要的变量: ```javascript data() { return { bpmnModeler: null, propertiesPanel: null, xml: null, }; }, ``` 接下来,在Vue组件的mounted生命周期函数中初始化bpmn.js的Modeler和其他必要的变量: ```javascript mounted() { this.bpmnModeler = new BpmnModeler({ container: '#canvas', propertiesPanel: { parent: '#properties', }, additionalModules: [ propertiesPanelModule, propertiesProviderModule, ], moddleExtensions: { camunda: camundaModdleDescriptor, }, }); this.propertiesPanel = this.bpmnModeler.get('propertiesPanel'); }, ``` 然后,在Vue组件的methods中定义一些必要的方法,比如打开一个流程图、保存一个流程图、导出一个流程图: ```javascript methods: { openDiagram(xml) { this.bpmnModeler.importXML(xml, (err) => { if (err) { console.log(err); } else { console.log('success'); } }); }, saveDiagram() { this.bpmnModeler.saveXML({ format: true }, (err, xml) => { if (err) { console.log(err); } else { console.log(xml); } }); }, exportDiagram() { this.bpmnModeler.saveSVG((err, svg) => { if (err) { console.log(err); } else { console.log(svg); } }); }, }, ``` 最后,在Vue组件的template中定义UI界面: ```html <template> <div> <div id="canvas"></div> <div id="properties"></div> <button @click="saveDiagram">保存</button> <button @click="exportDiagram">导出</button> </div> </template> ``` 在这个例子中,我们使用了Vue3和bpmn.js来实现了flowable流程设计。通过这个例子,你可以了解到如何在Vue3中整合bpmn.js以及如何定义UI界面、打开、保存和导出流程图。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值