processViewer.vue 10.9 KB
<template>
  <div>
    <div class="process-viewer">
      <div v-show="!isLoading" ref="processCanvas" class="process-canvas" style="height: 280px;" />
      <!-- 自定义箭头样式,用于成功状态下流程连线箭头 -->
      <defs ref="customSuccessDefs">
        <marker id="sequenceflow-end-white-success" view-box="0 0 20 20" ref-x="11" ref-y="10" marker-width="10"
          marker-height="10" orient="auto">
          <path class="success-arrow" d="M 1 5 L 11 10 L 1 15 Z"
            style="stroke-width: 1px; stroke-linecap: round; stroke-dasharray: 10000, 1;" />
        </marker>
        <marker id="conditional-flow-marker-white-success" view-box="0 0 20 20" ref-x="-1" ref-y="10" marker-width="10"
          marker-height="10" orient="auto">
          <path class="success-conditional" d="M 0 10 L 8 6 L 16 10 L 8 14 Z"
            style="stroke-width: 1px; stroke-linecap: round; stroke-dasharray: 10000, 1;" />
        </marker>
      </defs>
      <!-- 自定义箭头样式,用于失败状态下流程连线箭头 -->
      <defs ref="customFailDefs">
        <marker id="sequenceflow-end-white-fail" view-box="0 0 20 20" ref-x="11" ref-y="10" marker-width="10"
          marker-height="10" orient="auto">
          <path class="fail-arrow" d="M 1 5 L 11 10 L 1 15 Z"
            style="stroke-width: 1px; stroke-linecap: round; stroke-dasharray: 10000, 1;" />
        </marker>
        <marker id="conditional-flow-marker-white-fail" view-box="0 0 20 20" ref-x="-1" ref-y="10" marker-width="10"
          marker-height="10" orient="auto">
          <path class="fail-conditional" d="M 0 10 L 8 6 L 16 10 L 8 14 Z"
            style="stroke-width: 1px; stroke-linecap: round; stroke-dasharray: 10000, 1;" />
        </marker>
      </defs>

      <div style="position: absolute; top: 0px; left: 0px; width: 100%;">
        <el-row type="flex" justify="end">
          <el-button-group key="scale-control" size="medium">
            <el-button size="medium" type="default" :plain="true" :disabled="defaultZoom <= 0.3" icon="el-icon-zoom-out"
              @click="processZoomOut()" />
            <el-button size="medium" type="default" style="width: 90px;">{{ Math.floor(this.defaultZoom * 10 * 10) + "%"
            }}</el-button>
            <el-button size="medium" type="default" :plain="true" :disabled="defaultZoom >= 3.9" icon="el-icon-zoom-in"
              @click="processZoomIn()" />
            <el-button size="medium" type="default" icon="el-icon-c-scale-to-original" @click="processReZoom()" />
            <slot />
          </el-button-group>
        </el-row>
      </div>
    </div>
    <!-- 已完成节点悬浮弹窗 -->
    <div class="information-list">
      <!-- <p>{{ dlgTitle ? dlgTitle : '浏览记录' }}</p> -->
      <el-select v-model="selectValue" @change="handleSelect">
        <el-option v-for="item in selectOptions" :key="item.value" :label="item.label" :value="item.value">
        </el-option>
      </el-select>
      <el-table :data="taskCommentList" size="mini" border header-cell-class-name="table-header-gray">
        <el-table-column label="序号" header-align="center" align="center" type="index" width="55px" />
        <el-table-column label="流程状态" header-align="center" align="center">
          <template slot-scope="scope">
            <div v-if="scope.row.finishTime == null">正在办理</div>
            <div v-else>已完结</div>
          </template>
        </el-table-column>
        <el-table-column label="环节名称" prop="taskName" minWidth="100" align="center" />
        <el-table-column label="办理人" prop="assigneeName" minWidth="120" align="center" />
        <el-table-column label="处理时间" prop="createTime" width="160" align="center" />
        <el-table-column label="办结时间" prop="finishTime" width="160" align="center" />
        <el-table-column label="操作方式" align="center">
        </el-table-column>
      </el-table>
    </div>
  </div>
</template>
<script>
  import '@/styles/package/theme/index.scss'
  import BpmnViewer from 'bpmn-js/lib/Viewer'
  import MoveCanvasModule from 'diagram-js/lib/navigation/movecanvas'
  export default {
    props: {
      formData: {
        type: Object,
        default: {}
      }
    },
    data () {
      return {
        dlgTitle: undefined,
        defaultZoom: 1,
        // 是否正在加载流程图
        isLoading: true,
        bpmnViewer: undefined,
        // 已完成流程元素
        processNodeInfo: undefined,
        // 当前任务id
        selectTaskId: undefined,
        // 任务节点审批记录
        taskCommentList: [],
        // 已完成任务悬浮延迟Timer
        hoverTimer: null,
        // 下拉
        selectValue: '',
        selectOptions: []
      }
    },
    created () {
      this.$nextTick(() => {
        this.importXML(this.formData.xml)
        this.setProcessStatus(this.formData.finishedInfo);
      })
    },
    destroyed () {
      this.clearViewer()
    },
    methods: {
      processReZoom () {
        this.defaultZoom = 1
        this.bpmnViewer.get('canvas').zoom('fit-viewport', 'auto')
      },
      processZoomIn (zoomStep = 0.1) {
        const newZoom = Math.floor(this.defaultZoom * 100 + zoomStep * 100) / 100
        if (newZoom > 4) {
          throw new Error('[Process Designer Warn ]: The zoom ratio cannot be greater than 4')
        }
        this.defaultZoom = newZoom
        this.bpmnViewer.get('canvas').zoom(this.defaultZoom)
      },
      processZoomOut (zoomStep = 0.1) {
        const newZoom = Math.floor(this.defaultZoom * 100 - zoomStep * 100) / 100
        if (newZoom < 0.2) {
          throw new Error('[Process Designer Warn ]: The zoom ratio cannot be scss than 0.2')
        }
        this.defaultZoom = newZoom
        this.bpmnViewer.get('canvas').zoom(this.defaultZoom)
      },
      getOperationTagType (type) {
        return 'success'
      },
      // 流程图预览清空
      clearViewer (a) {
        if (this.$refs.processCanvas) {
          this.$refs.processCanvas.innerHTML = ''
        }
        if (this.bpmnViewer) {
          this.bpmnViewer.destroy()
        }
        this.bpmnViewer = null
      },
      // 添加自定义箭头
      addCustomDefs () {
        const canvas = this.bpmnViewer.get('canvas')
        const svg = canvas._svg
        const customSuccessDefs = this.$refs.customSuccessDefs
        const customFailDefs = this.$refs.customFailDefs
        svg.appendChild(customSuccessDefs)
        svg.appendChild(customFailDefs)
      },
      // 任务悬浮弹窗
      onSelectElement (element) {
        this.selectTaskId = undefined
        this.dlgTitle = undefined
        let allfinishedTaskSet = [...this.processNodeInfo.finishedTaskSet, ...this.processNodeInfo.unfinishedTaskSet]
        if (this.processNodeInfo == null || allfinishedTaskSet == null)
          return
        if (element == null || allfinishedTaskSet.indexOf(element.id) === -1) {
          return
        }
        this.selectTaskId = element.id
        this.selectValue = element.id
        this.dlgTitle = element.businessObject ? element.businessObject.name : undefined
        // 计算当前悬浮任务审批记录,如果记录为空不显示弹窗
        this.taskCommentList = (this.formData.allCommentList || []).filter(item => {
          return item.taskDefKey === this.selectTaskId
        })
      },
      // 下拉列表切换
      handleSelect (val) {
        this.taskCommentList = (this.formData.allCommentList || []).filter(item => {
          return item.taskDefKey === val
        })
      },
      // 显示流程图
      async importXML (xml) {
        let xmlData = this.$x2js.xml2js(xml).definitions.process;
        this.selectOptions = xmlData.userTask.map(item => {
          return { value: item._id, label: item._name }
        })
        this.selectOptions = [{ value: xmlData.startEvent._id, label: '浏览记录' }, ...this.selectOptions]
        this.selectOptions = this.selectOptions.map(item => {
          if (this.formData.finishedInfo.finishedTaskSet.includes(item.value)) {
            return item
          }
          if (this.formData.finishedInfo.unfinishedTaskSet.includes(item.value)) {
            return item
          }
        }).filter(Boolean);
        this.selectValue = xmlData.startEvent._id
        this.clearViewer('a')
        if (xml != null && xml !== '') {
          try {
            this.bpmnViewer = new BpmnViewer({
              additionalModules: [
                // 移动整个画布
                MoveCanvasModule
              ],
              container: this.$refs.processCanvas
            })
            // 任务节点悬浮事件
            this.bpmnViewer.on('element.click', ({ element }) => {
              this.onSelectElement(element)
            })
            await this.bpmnViewer.importXML(xml)
            this.isLoading = true
            this.addCustomDefs()
          } catch (e) {
            this.clearViewer('b')
          } finally {
            this.isLoading = false
            this.setProcessStatus(this.processNodeInfo)
            this.$nextTick(() => {
              this.processReZoom()
            })
          }
        }
      },
      // 设置流程图元素状态
      setProcessStatus (processNodeInfo) {
        this.taskCommentList = this.formData.allCommentList;
        this.processNodeInfo = processNodeInfo
        if (this.isLoading || this.processNodeInfo == null || this.bpmnViewer == null) return
        const { finishedTaskSet, rejectedTaskSet, unfinishedTaskSet, finishedSequenceFlowSet } = this.processNodeInfo
        const canvas = this.bpmnViewer.get('canvas')
        const elementRegistry = this.bpmnViewer.get('elementRegistry')
        if (Array.isArray(finishedSequenceFlowSet)) {
          finishedSequenceFlowSet.forEach(item => {
            if (item != null) {
              canvas.addMarker(item, 'success')
              const element = elementRegistry.get(item)
              const conditionExpression = element.businessObject.conditionExpression
              if (conditionExpression) {
                canvas.addMarker(item, 'condition-expression')
              }
            }
          })
        }
        if (Array.isArray(finishedTaskSet)) {
          finishedTaskSet.forEach(item => canvas.addMarker(item, 'success'))
        }
        if (Array.isArray(unfinishedTaskSet)) {
          unfinishedTaskSet.forEach(item => canvas.addMarker(item, 'primary'))
        }
        if (Array.isArray(rejectedTaskSet)) {
          rejectedTaskSet.forEach(item => {
            if (item != null) {
              const element = elementRegistry.get(item)
              if (element.type.includes('Task')) {
                canvas.addMarker(item, 'danger')
              } else {
                canvas.addMarker(item, 'warning')
              }
            }
          })
        }
      }
    }
  }
</script>
<style scoped lang="scss">
  .information-list {
    // height: 150px;
    // margin-top: 10px;

    p {
      font-size: 16px;
      line-height: 24px;
    }
  }
  /deep/.bjs-powered-by {
    display: none;
  }
</style>