构建&部署

Bpmn 流程设计器使用说明

本项目是一个基于 bpmn.jsVue 2.xElementUI 开发的流程设计器。

二次开发

1. 安装依赖 Installation

yarn install

npm install

2. 运行 Quick Start

yarn run start

npm run start

3. 打包

npm run build

提示

注意: 本项目默认部署到二级目录/bpmn/designer下,如需要使用Nginx部署,请放到/bpmn/designer下面
配置如下:

location ^~ /bpmn/designer {
    try_files $uri $uri/ /bpmn/designer/index.html @rewrites;
    index  index.html index.htm;
    alias  /data/www/bpmn/designer/dist;
}

4. 集成业务系统

1、设计器存放

将打包后的文件夹dist放入项目的public目录下 如 /public/bpmn/designer/

2、引入设计器Iframe

<template>
  <div class="model-designer">
    <div class="designer-tool-bar">
      <Button type="primary" @click="doSave">保存</Button>
    </div>
    <div class="designer-main">
      <!-- Iframe引入设计器-->
      <iframe ref="designerIframeRef" src="/bpmn/designer/index.html"
              class="designer-iframe"></iframe>
    </div>
  </div>
</template>

提示

iframe引用的路径为编辑器的主页。因为编辑器发布的二级目录是/bpmn/designer,所以需要放入public中的/bpmn/designer目录下

2、初始化设计器对象

  mounted() {
    this.$nextTick(() => {
      // 获取Iframe的Window对象
      this.iframe = this.$refs.designerIframeRef.contentWindow;
      this.iframe.onload=()=>{
        // 初始化XML内容
        setTimeout(()=>{
          this.initData();
        }, 1000);
      }
    });
  }

初始化xml数据

// 初始化XML内容 -
    initData() {
      // TODO 从后端异步加载xml
      const tempXml = `<?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: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" xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://flowable.org/bpmn">
              <process id="xxx" name="xxxx" isExecutable="true">
                <startEvent id="aba1958401251475ca53b3f5d152fde3b" />
                ...
            </definitions>`;
      if (this.iframe) {
        this.iframe.setBpmnXml(tempXml).then(res => {
          debugger;
          console.log('======================XML加载成功!');
          console.log(res);
        });
      }
    }

提示

初始化数据时,要等iframe加载完成后才能获取, iframe中暴露了setBpmnXml(xml) 方法用来初始化模型
使用iframe中暴露了getBpmnXml()方法来获取设计器的xml

// 获取设计器的XML内容 - 保存
doSave() {
  if (this.iframe) {
    // 获取设计器的XML内容
    this.iframe.getBpmnXml().then(res => {
      console.log('======================XML获取成功!');
      console.log(res);
    });
  }
}

初始化加载结果:
获取mxl结果 得到xml后

1、Vue - Iframe集成

  <iframe src="/bpmn-designer/dist/index.html?modelKey=" ref="frameRef"></iframe>

1、流程相关的数据操作在iframe中完成

2、流程相关的数据操作在业务系统中完成,需要使用调用iframe内部的方法进行数据获取和绑定 获取流程xml内容

        const iframeWindow = iframe.contentWindow;
        iframe.getModelXml();
初始化xml
        const iframeWindow = iframe.contentWindow;
        iframe.initModelXml(xml);

2、React - Iframe集成

  <iframe class="w-full" :src="frameSrc" class="designer_main" ref="frameRef"></iframe>

流程图渲染

流程审批/预览时都需要查看流程图和审批记录,流程图渲染可以很清晰地看清流程当前处于哪个节点,各节点的审批人,当前待办人。

如图:

下面是流程图渲染的集成方法,以VUE为例
package.json中添加引用:

    "bpmn-js": "^8.8.2",

关键代码:

    <div class="main">
      <Spin tip="Loading..." :spinning="modelInfoLoading">
        <div class="diagram">
          <BpmnDiagram v-if="modelInfo&& modelInfo.modelXml" :model-info="modelInfo"/>
          <div v-else class="diagram-empty">
            请选择流程模板或输入流程实例进行预览!
          </div>
        </div>
        <!--
        <div>
          <ApproveHistory />
        </div>
        -->
      </Spin>
    </div>

BpmnDiagram组件: 模板相关代码:

<template>
  <div class="viewer-container">
    <div class="svg-controller">
      <div class="scale-rate">
        {{ Math.floor(defaultZoom * 10 * 10) + "%" }}
      </div>
      <Space>
        <Button title="缩小" shape="circle" size="small" @click="processZoomOut()" type="primary">
          <MinusOutlined/>
        </Button>
        <Button :title="isFitView?'按窗口大小显示':'实际大小'" shape="circle" size="small"
                  @click="processFitDialog(isFitView)" type="primary">
          <CompressOutlined v-if="isFitView"/>
          <ExpandOutlined v-else/>
        </Button>
        <Button title="放大" shape="circle" size="small" @click="processZoomIn()" type="primary">
          <PlusOutlined/>
        </Button>
      </Space>
    </div>
    <div class="containers" ref="container" @mousewheel="changeScale">
      <div :id="bpmnCanvasId"
           class="bpmnCanvas canvas"
           ref="bpmnCanvas"
      ></div>
    </div>
  </div>
</template>

js关键代码:

// 引入bpmnViewer组件
import BpmnViewer from 'bpmn-js/lib/Viewer'
import MoveCanvasModule from 'diagram-js/lib/navigation/movecanvas'

data() {
    return {
        bpmnCanvasId: 'bpmnCanvas' + new Date().getTime(),
        defaultZoom: 1,
        fitViewScaleRate: 1,
        isFitView: false,
    }
}

mounted() {
    this.initData();
    this.importXml(this.modelInfo);
}

methods: {
    initData() {
        bpmnViewer && bpmnViewer.destroy();
        bpmnViewer = new BpmnViewer({
            container: document.getElementById(this.bpmnCanvasId),
            width: '100%',
            additionalModules: [
                MoveCanvasModule // 移动整个画布
            ],
            /*keyboard: {
              bindTo: document
            }*/
        });
    },
    importXml(modelInfo) {
        const that = this;
        // 处理排他网关, 注:流程图预览时,排他网关需要在对应的<bpmndi:BPMNShape>节点上添加属性isMarkerVisible="true"
        const gatewayIds = this.getHtmlAttr(modelInfo.modelXml, 'exclusiveGateway', 'id');
        let modelXmlTemp = modelInfo.modelXml;
        if(gatewayIds && gatewayIds.length > 0){
            gatewayIds.forEach(item=>{
                const result = new RegExp('id="(.+?)"').exec(item)
                if(result && result[1]){
                    modelXmlTemp = modelXmlTemp.replace('bpmnElement="'+result[1]+'"', 'bpmnElement="'+result[1]+'" isMarkerVisible="true"');
                }
            })
        }

        bpmnViewer.importXML(modelXmlTemp, function (err) {
            if(err){
                console.error(err);
            }else{
                that.importXmlSuccess();
                that.genBpmnSvgMarker();
            }
        });
    }
}

高亮部分代码实现:

.hishighlight:not(.djs-connection) .djs-visual > :nth-child(1) {
  fill: rgba(211, 238, 211, 0.99) !important; /* color elements as green */
}
.hishighlight g.djs-visual >:nth-child(1) {
  stroke: rgba(0, 190, 0, 1) !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:not(.djs-connection) .djs-visual > :nth-child(1) {
  fill: rgba(251, 233, 209, 1) !important; /* color elements as green */
}
.highlight g.djs-visual >:nth-child(1) {
  stroke: rgba(214, 126, 125, 1) !important;
}

@keyframes dynamicNode {
  to {
    stroke-dashoffset: 100%;
  }
}

@-webkit-keyframes dynamicNode {
  to {
    stroke-dashoffset: 100%;
  }
}

.highlight {
  .djs-visual {
    animation: dynamicNode 18S linear infinite;
    -webkit-animation: dynamicNode 18S linear infinite;
    -webkit-animation-fill-mode: forwards;
  }
}