ruoyi-nbcio-plus基于vue3的flowable流程查看器组件的升级修改

简介: ruoyi-nbcio-plus基于vue3的flowable流程查看器组件的升级修改

更多ruoyi-nbcio功能请看演示系统

gitee源代码地址

前后端代码: https://gitee.com/nbacheng/ruoyi-nbcio

演示地址:RuoYi-Nbcio后台管理系统 http://218.75.87.38:9666/

更多nbcio-boot功能请看演示系统

gitee源代码地址

后端代码: https://gitee.com/nbacheng/nbcio-boot

前端代码:https://gitee.com/nbacheng/nbcio-vue.git

在线演示(包括H5) : http://218.75.87.38:9888

1、原先ProcessViewer的vue2代码如下:

<template>
  <div class="process-viewer">
    <div class="process-canvas" style="height: 100%;" ref="processCanvas" v-show="!isLoading" />
    <!-- 自定义箭头样式,用于成功状态下流程连线箭头 -->
    <defs ref="customSuccessDefs">
      <marker id="sequenceflow-end-white-success" viewBox="0 0 20 20" refX="11" refY="10" markerWidth="10" markerHeight="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;"></path>
      </marker>
      <marker id="conditional-flow-marker-white-success" viewBox="0 0 20 20" refX="-1" refY="10" markerWidth="10" markerHeight="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;"></path>
      </marker>
    </defs>
    <!-- 自定义箭头样式,用于失败状态下流程连线箭头 -->
    <defs ref="customFailDefs">
      <marker id="sequenceflow-end-white-fail" viewBox="0 0 20 20" refX="11" refY="10" markerWidth="10" markerHeight="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;"></path>
      </marker>
      <marker id="conditional-flow-marker-white-fail" viewBox="0 0 20 20" refX="-1" refY="10" markerWidth="10" markerHeight="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;"></path>
      </marker>
    </defs>
    <!-- 已完成节点悬浮弹窗 -->
    <el-dialog class="comment-dialog" :title="dlgTitle || '审批记录'" v-model="dialogVisible">
      <el-row>
        <el-table :data="taskCommentList" 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="候选办理" prop="candidate" width="150px" align="center" />
          <el-table-column label="实际办理" prop="assigneeName" width="100px" align="center" />
          <el-table-column label="处理时间" prop="createTime" width="140px" align="center" />
          <el-table-column label="办结时间" prop="finishTime" width="140px" align="center" />
          <el-table-column label="耗时" prop="duration" width="100px" align="center" />
          <el-table-column label="审批意见" align="center">
            <template #default="scope">
              {{scope.row.commentList&&scope.row.commentList[0]?scope.row.commentList[0].fullMessage:''}}
            </template>
          </el-table-column>
        </el-table>
      </el-row>
    </el-dialog>
    <div style="position: absolute; top: 0; left: 0; width: 100%;">
      <el-row type="flex" justify="end">
        <el-button-group key="scale-control">
          <el-button :plain="true" :disabled="defaultZoom <= 0.3" icon="ZoomOut" @click="processZoomOut()" />
          <el-button style="width: 90px;">{{ Math.floor(this.defaultZoom * 10 * 10) + "%" }}</el-button>
          <el-button :plain="true" :disabled="defaultZoom >= 3.9" icon="ZoomIn" @click="processZoomIn()" />
          <el-button icon="ScaleToOriginal" @click="processReZoom()" />
          <slot />
        </el-button-group>
      </el-row>
    </div>
  </div>
</template>
<script>
import '@/package/theme/index.scss';
import BpmnViewer from 'bpmn-js/lib/Viewer';
import MoveCanvasModule from 'diagram-js/lib/navigation/movecanvas';
export default {
  props: {
    xml: {
      type: String
    },
    finishedInfo: {
      type: Object
    },
    // 所有节点审批记录
    allCommentList: {
      type: Array
    }
  },
  data () {
    return {
      dialogVisible: false,
      dlgTitle: undefined,
      defaultZoom: 1,
      // 是否正在加载流程图
      isLoading: false,
      bpmnViewer: undefined,
      // 已完成流程元素
      processNodeInfo: undefined,
      // 当前任务id
      selectTaskId: undefined,
      // 任务节点审批记录
      taskCommentList: [],
      // 已完成任务悬浮延迟Timer
      hoverTimer: null
    }
  },
  watch: {
    xml: {
      handler(newXml) {
        this.importXML(newXml);
      },
      immediate: true
    },
    finishedInfo: {
      handler(newInfo) {
        this.setProcessStatus(newInfo);
      },
      immediate: true
    }
  },
  created() {
    this.$nextTick(() => {
      this.importXML(this.xml)
      this.setProcessStatus(this.finishedInfo);
    })
  },
  methods: {
    processReZoom() {
      this.defaultZoom = 1;
      this.bpmnViewer.get('canvas').zoom('fit-viewport', 'auto');
    },
    processZoomIn(zoomStep = 0.1) {
      let 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) {
      let newZoom = Math.floor(this.defaultZoom * 100 - zoomStep * 100) / 100;
      if (newZoom < 0.2) {
        throw new Error('[Process Designer Warn ]: The zoom ratio cannot be less than 0.2');
      }
      this.defaultZoom = newZoom;
      this.bpmnViewer.get('canvas').zoom(this.defaultZoom);
    },
    // 流程图预览清空
    clearViewer() {
      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;
      if (this.processNodeInfo == null || this.processNodeInfo.finishedTaskSet == null) return;
      if (element == null || this.processNodeInfo.finishedTaskSet.indexOf(element.id) === -1) {
        return;
      }
      this.selectTaskId = element.id;
      this.dlgTitle = element.businessObject ? element.businessObject.name : undefined;
      // 计算当前悬浮任务审批记录,如果记录为空不显示弹窗
      this.taskCommentList = (this.allCommentList || []).filter(item => {
        return item.activityId === this.selectTaskId;
      });
      this.dialogVisible = true;
    },
    // 显示流程图
    async importXML(xml) {
      this.clearViewer();
      if (xml != null && xml !== '') {
        try {
          this.bpmnViewer = new BpmnViewer({
            additionalModules: [
              // 移动整个画布
              MoveCanvasModule
            ],
            container: this.$refs.processCanvas,
          });
          // 任务节点悬浮事件
          this.bpmnViewer.on('element.click', ({ element }) => {
            this.onSelectElement(element);
          });
          this.isLoading = true;
          await this.bpmnViewer.importXML(xml);
          this.addCustomDefs();
        } catch (e) {
          this.clearViewer();
        } finally {
          this.isLoading = false;
          this.setProcessStatus(this.processNodeInfo);
        }
      }
    },
    // 设置流程图元素状态
    setProcessStatus (processNodeInfo) {
      console.log("setProcessStatus processNodeInfo",processNodeInfo)
      this.processNodeInfo = processNodeInfo;
      if (this.isLoading || this.processNodeInfo == null || this.bpmnViewer == null) return;
      let { finishedTaskSet, rejectedTaskSet, unfinishedTaskSet, finishedSequenceFlowSet } = this.processNodeInfo;
      const canvas = this.bpmnViewer.get('canvas');
      console.log("setProcessStatus canvas")
      const elementRegistry = this.bpmnViewer.get('elementRegistry');
      if (Array.isArray(finishedSequenceFlowSet)) {
        finishedSequenceFlowSet.forEach(item => {
          if (item != null) {
            canvas.addMarker(item, 'success');
            let 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) {
            let element = elementRegistry.get(item);
            if (element.type.includes('Task')) {
              canvas.addMarker(item, 'danger');
            } else {
              canvas.addMarker(item, 'warning');
            }
          }
        })
      }
    }
  }
}
</script>

2、修改成vue3后的代码如下:

<template>
  <div class="process-viewer">
    <div class="process-canvas" style="height: 100%;" ref="processCanvas" v-show="!isLoading" />
    <!-- 自定义箭头样式,用于成功状态下流程连线箭头 -->
    <defs ref="customSuccessDefs">
      <marker id="sequenceflow-end-white-success" viewBox="0 0 20 20" refX="11" refY="10" markerWidth="10" markerHeight="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;"></path>
      </marker>
      <marker id="conditional-flow-marker-white-success" viewBox="0 0 20 20" refX="-1" refY="10" markerWidth="10" markerHeight="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;"></path>
      </marker>
    </defs>
    <!-- 自定义箭头样式,用于失败状态下流程连线箭头 -->
    <defs ref="customFailDefs">
      <marker id="sequenceflow-end-white-fail" viewBox="0 0 20 20" refX="11" refY="10" markerWidth="10" markerHeight="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;"></path>
      </marker>
      <marker id="conditional-flow-marker-white-fail" viewBox="0 0 20 20" refX="-1" refY="10" markerWidth="10" markerHeight="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;"></path>
      </marker>
    </defs>
    <!-- 已完成节点悬浮弹窗 -->
    <el-dialog class="comment-dialog" :title="dlgTitle || '审批记录'" v-model="dialogVisible">
      <el-row>
        <el-table :data="taskCommentList" 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="候选办理" prop="candidate" width="150px" align="center" />
          <el-table-column label="实际办理" prop="assigneeName" width="100px" align="center" />
          <el-table-column label="处理时间" prop="createTime" width="140px" align="center" />
          <el-table-column label="办结时间" prop="finishTime" width="140px" align="center" />
          <el-table-column label="耗时" prop="duration" width="100px" align="center" />
          <el-table-column label="审批意见" align="center">
            <template #default="scope">
              {{scope.row.commentList&&scope.row.commentList[0]?scope.row.commentList[0].fullMessage:''}}
            </template>
          </el-table-column>
        </el-table>
      </el-row>
    </el-dialog>
    <div style="position: absolute; top: 0; left: 0; width: 100%;">
      <el-row type="flex" justify="end">
        <el-button-group key="scale-control">
          <el-button :plain="true" :disabled="defaultZoom <= 0.3" icon="ZoomOut" @click="processZoomOut()" />
          <el-button style="width: 90px;">{{ Math.floor(defaultZoom * 10 * 10) + "%" }}</el-button>
          <el-button :plain="true" :disabled="defaultZoom >= 3.9" icon="ZoomIn" @click="processZoomIn()" />
          <el-button icon="ScaleToOriginal" @click="processReZoom()" />
          <slot />
        </el-button-group>
      </el-row>
    </div>
  </div>
</template>
<script lang="ts" setup>
  import '@/package/theme/index.scss';
  import BpmnViewer from 'bpmn-js/lib/Viewer';
  import MoveCanvasModule from 'diagram-js/lib/navigation/movecanvas';
  const props = defineProps({
      xml: String,
      finishedInfo: Object,
      allCommentList: Array
  })
  const processCanvas = ref(null)
  const customSuccessDefs = ref(null)
  const customFailDefs = ref(null)
  const dialogVisible = ref(false)
  const dlgTitle = ref(undefined)
  const defaultZoom = ref(1)
  // 是否正在加载流程图
  const isLoading = ref(false)
  let bpmnViewer
  // 已完成流程元素
  const processNodeInfo = ref(null)
  // 当前任务id
  const selectTaskId = ref(undefined)
  // 任务节点审批记录
  const taskCommentList = ref<any>([])
  // 已完成任务悬浮延迟Timer
  const hoverTimer =  ref(null)
  const processReZoom = () => {
    defaultZoom.value = 1;
    bpmnViewer.get('canvas').zoom('fit-viewport', 'auto');
  }
  const processZoomIn = (zoomStep = 0.1) => {
    let newZoom = Math.floor(efaultZoom.value * 100 + zoomStep * 100) / 100;
    if (newZoom > 4) {
      throw new Error('[Process Designer Warn ]: The zoom ratio cannot be greater than 4');
    }
    defaultZoom.value = newZoom;
    bpmnViewer.get('canvas').zoom(defaultZoom.value);
  }
  const processZoomOut = (zoomStep = 0.1) => {
    let newZoom = Math.floor(defaultZoom.value * 100 - zoomStep * 100) / 100;
    if (newZoom < 0.2) {
      throw new Error('[Process Designer Warn ]: The zoom ratio cannot be less than 0.2');
    }
    defaultZoom.value = newZoom;
    bpmnViewer.get('canvas').zoom(defaultZoom.value);
  }
  // 流程图预览清空
  const clearViewer = () => {
    if (processCanvas.value) {
      processCanvas.value.innerHTML = '';
    }
    if (bpmnViewer) {
      bpmnViewer.destroy();
    }
    bpmnViewer = null;
  }
  // 添加自定义箭头
  const addCustomDefs = () => {
    const canvas = bpmnViewer.get('canvas');
    const svg = canvas._svg;
    svg.appendChild(customSuccessDefs.value);
    svg.appendChild(customFailDefs.value);
  }
  // 任务悬浮弹窗
  const onSelectElement = (element) => {
    selectTaskId.value = undefined;
    dlgTitle.value = undefined;
    if (processNodeInfo.value == null || processNodeInfo.value['finishedTaskSet'] == null) return;
    if (element == null || processNodeInfo.value['finishedTaskSet'].indexOf(element.id) === -1) {
      return;
    }
    selectTaskId.value = element.id;
    dlgTitle.value = element.businessObject ? element.businessObject.name : undefined;
    // 计算当前悬浮任务审批记录,如果记录为空不显示弹窗
    taskCommentList.value = (props.allCommentList || []).filter(item => {
      return item.activityId === selectTaskId.value;
    });
    dialogVisible.value = true;
  }
  // 显示流程图
  const importXML = async (xml) => {
    clearViewer();
    if (xml != null && xml !== '') {
      try {
        bpmnViewer = new BpmnViewer({
          additionalModules: [
            // 移动整个画布
            MoveCanvasModule
          ],
          container: processCanvas.value,
        });
        // 任务节点悬浮事件
        bpmnViewer.on('element.click', ({ element }) => {
          onSelectElement(element);
        });
        isLoading.value = true;
        await bpmnViewer.importXML(xml);
        addCustomDefs();
      } catch (e) {
        clearViewer();
      } finally {
        isLoading.value = false;
        setProcessStatus(processNodeInfo.value);
      }
    }
  }
  // 设置流程图元素状态
  const setProcessStatus = (processNodeInfo) => {
    if (processNodeInfo == null) return;
    processNodeInfo.value = processNodeInfo;
    if (isLoading.value || processNodeInfo.value == null || bpmnViewer == null) return;
    let { finishedTaskSet, rejectedTaskSet, unfinishedTaskSet, finishedSequenceFlowSet } = processNodeInfo.value;
    const canvas = bpmnViewer.get('canvas');
    const elementRegistry = bpmnViewer.get('elementRegistry');
    if (Array.isArray(finishedSequenceFlowSet)) {
      finishedSequenceFlowSet.forEach(item => {
        if (item != null) {
          canvas.addMarker(item, 'success');
          let 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) {
          let element = elementRegistry.get(item);
          if (element.type.includes('Task')) {
            canvas.addMarker(item, 'danger');
          } else {
            canvas.addMarker(item, 'warning');
          }
        }
      })
    }
  }
  onMounted(() => {
    nextTick(() => {
      importXML(props.xml)
      setProcessStatus(props.finishedInfo)
    })
  })
  watch(
    () => props.xml,
    (newXml) => {
      importXML(newXml);
    },
    { immediate: true }
  )
  watch(
    () => props.finishedInfo,
    (newInfo) => {
      console.log("watch newInfo=",newInfo)
      setProcessStatus(newInfo);
    },
    { immediate: true }
  )
</script>

3、效果图如下:

目录
打赏
0
1
1
0
84
分享
相关文章
vue2和vue3的响应式原理有何不同?
大家好,我是V哥。本文详细对比了Vue 2与Vue 3的响应式原理:Vue 2基于`Object.defineProperty()`,适合小型项目但存在性能瓶颈;Vue 3采用`Proxy`,大幅优化初始化、更新性能及内存占用,更高效稳定。此外,我建议前端开发者关注鸿蒙趋势,2025年将是国产化替代关键期,推荐《鸿蒙 HarmonyOS 开发之路》卷1助你入行。老项目用Vue 2?不妨升级到Vue 3,提升用户体验!关注V哥爱编程,全栈开发轻松上手。
高效工作流:用Mermaid绘制你的专属流程图;如何在Vue3中导入mermaid绘制流程图
mermaid是一款非常优秀的基于 JavaScript 的图表绘制工具,可渲染 Markdown 启发的文本定义以动态创建和修改图表。非常适合新手学习或者做一些弱交互且自定义要求不高的图表 除了流程图以外,mermaid还支持序列图、类图、状态图、实体关系图等图表可供探索。 博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
你真的会使用Vue3的onMounted钩子函数吗?Vue3中onMounted的用法详解
onMounted作为vue3中最常用的钩子函数之一,能够灵活、随心应手的使用是每个Vue开发者的必修课,同时根据其不同写法的特性,来选择最合适最有利于维护的写法。博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
Pinia 如何在 Vue 3 项目中进行安装和配置?
Pinia 如何在 Vue 3 项目中进行安装和配置?
管理数据必备;侦听器watch用法详解,vue2与vue3中watch的变化与差异
一篇文章同时搞定Vue2和Vue3的侦听器,是不是很棒?不要忘了Vue3中多了一个可选项watchEffect噢。 博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
ry-vue-flowable-xg:震撼来袭!这款基于 Vue 和 Flowable 的企业级工程项目管理项目,你绝不能错过
基于 Vue 和 Flowable 的企业级工程项目管理平台,免费开源且高度定制化。它覆盖投标管理、进度控制、财务核算等全流程需求,提供流程设计、部署、监控和任务管理等功能,适用于企业办公、生产制造、金融服务等多个场景,助力企业提升效率与竞争力。
131 12
创建vue3项目步骤以及安装第三方插件步骤【保姆级教程】
这是一篇关于创建Vue项目的详细指南,涵盖从环境搭建到项目部署的全过程。
379 1
|
4月前
|
vue使用iconfont图标
vue使用iconfont图标
187 1
Vue实现动态数据透视表(交叉表)
Vue实现动态数据透视表(交叉表)
74 13