Flowable6.x导出/查看/跟踪流程图(续)

news2024/9/20 8:44:56

书接上回

项目源码仓库

无论是待办、已办,亦或是流转中、已结束的流程实例,通过使用JS绘制SVG格式的交互式流程图,与以上篇博文中三种方式相比,在效果上都具有明显优势。
运行效果如下图所示:
运行效果图

整合、改造Flowable中displaymodel页面

从flowable官方发布包获取前端源码

  • 下载官方数据包flowable-6.4.1.zip
  • 从压缩包中解压出flowable-6.4.1\wars下面的flowable-modeler.war
  • 从flowable-modeler.war中解压出WEB-INF\classes\static\display 文件夹下的11个文件,如下图所示:
    {% asset_img trac.gif 流程跟踪 %}
  • 在前端vue-element-admin的public下创建display文件夹,将11个文件放入
  • 在前端vue-element-admin的public创建displayModel.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
  <head>
    <meta charset="utf-8" />
    <meta name="renderer" content="webkit|ie-comp|ie-stand" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
    <meta name="viewport"
      content="width=device-width,initial-scale=1,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no" />
    <meta http-equiv="Cache-Control" content="no-siteapp" />
    <link type="text/css" rel="stylesheet" href="./display/jquery.qtip.min.css" />
    <link type="text/css" rel="stylesheet" href="./display/displaymodel.css" />
    <script type="text/javascript" src="./editor-app/editor-utils.js"></script>
    <script type="text/javascript" src="./jquery_1.11.0/jquery.min.js"></script>
    <script type="text/javascript" src="./jquery_1.11.0/jquery.cookie.js"></script>
    <script type="text/javascript" src="./display/jquery.qtip.min.js"></script>
    <script type="text/javascript" src="./display/raphael.js"></script>
    <script type="text/javascript" src="./display/bpmn-draw.js"></script>
    <script type="text/javascript" src="./display/bpmn-icons.js"></script>
    <script type="text/javascript" src="./display/Polyline.js"></script>
    <script type="text/javascript" src="./display/displaymodel.js"></script>
  </head>
  <body>
    <div id="bpmnModel" data-model-id="1"></div>
  </body>
</html>

通过vue组件iframe方式,将displaymodel页面嵌入

  • 将Dialog封闭为vue组件
import request from '@/utils/request'
//获取流程办理历史记录
export function fetchFlowLog(data) {
  return request({
    url: '/api/workflow/auth/activiti/task/log',
    method: 'post',
    data
  })
}

流程跟踪组件代码:

<template>
  <div class="app-container" style="background-color: #FFFFFF;">
    <el-dialog :visible.sync="dialogVisible" :close-on-click-modal="false" width="80%" height="100%" title="流程跟踪">
      <!-- 此处通过iframe显示流程图 -->
      <div>
        <iframe ref="IFrame" id="map" scrolling="auto" v-bind:src="contents"
          frameborder="0" style="top:0px;left: 0px;right:0px;bottom:0px;width: 100%;height: 300px;"></iframe>
      </div>
      <!-- 此处通过el-table显示流程任务办理日志 -->
      <div>
      <el-table v-loading="loading" :data="mainTableData" border fit style="width: 100%" max-height="500">
        <el-table-column type="index" label="序号" width="70">
        </el-table-column>
        <el-table-column prop="name" label="名称" show-overflow-tooltip sortable></el-table-column>
        <el-table-column prop="assignee" label="经办人" show-overflow-tooltip sortable></el-table-column>
        <el-table-column prop="notes" label="批注" show-overflow-tooltip sortable></el-table-column>
        <el-table-column prop="startTime" label="开始时间" show-overflow-tooltip sortable
          :formatter="(row,column,cellValue) => dateTimeColFormatter(row,column,cellValue)"></el-table-column>
        <el-table-column prop="endTime" label="完成时间" show-overflow-tooltip sortable
          :formatter="(row,column,cellValue) => dateTimeColFormatter(row,column,cellValue)"></el-table-column>
      </el-table>
      </div>
    </el-dialog>
  </div>
</template>
<script>
  // 引入获取办理日志数据的方法
  import {
    fetchFlowLog
  } from '@/api/workflow-task'
  export default {
    name: 'iFrame',
    data() {
      return {
        dialogVisible: false,
        contents: "",
        loading: true,
        mainTableData: []
      }
    },
    mounted() {},
    methods: {
      // 设置iframe的src
      setSrc(src){
        this.contents=src
      },
      showDialog() {
        this.dialogVisible = true
      },
      // 装入办理日志
      loadLog(processInstanceId){
        this.fetchProcessLog(processInstanceId)
      },
      async fetchProcessLog(processInstanceId){
        const guidContainer = {
          guid: processInstanceId
        }
        this.loading = true
        const response = await fetchFlowLog(guidContainer)
        this.loading = false
        if (100 !== response.code) {
          this.$message({
            message: response.message,
            type: 'warning'
          })
          return
        }
        const {
          data
        } = response
        this.mainTableData = data
      },
      dateTimeColFormatter(row, column, cellValue) {
        return this.$commonUtils.dateTimeFormat(cellValue)
      },
    }
  }
</script>
  • 以待办页面为例引入vue组件(概略)
<template>
  <div class="app-container background-white">

    <!-- 省略其他代码 -->

    <!-- 此处引入显示流程图和办理日志的子组件 -->
    <IFrame ref="displayComponent" />
  </div>
</template>
<script>
  // 此处引入显示流程图和办理日志的子组件
  // 其他代码省略
  import IFrame from '@/views/.../flowLog'
  export default {
    name: 'todoList',
    components: {
      IFrame
    },
    data() {
      return {},
    },
    methods: {
      // 调用子组件,显示流程图和办理日志
      handleDisplay(row) {
        this.$nextTick(() => {
          //设置iframe的src
          this.$refs.displayComponent.setSrc("/displayModel.html?processInstanceId=" + row.processInstanceId +
            "&nocaching=" +
            new Date().getTime())
          //装入办理日志
          this.$refs.displayComponent.loadLog(row.processInstanceId)
          //显示dialog
          this.$refs.displayComponent.showDialog()
        })
      },
    }
  }
</script>
<style>
</style>

改造display

  • 改造displaymodel.js中_showTip(htmlNode, element)方法,优化鼠标悬浮在任务上时Tip显示内容
function _showTip(htmlNode, element) {
  var documentation = undefined;
  if (customActivityToolTips) {
    if (customActivityToolTips[element.name]) {
      documentation = customActivityToolTips[element.name];
    } else if (customActivityToolTips[element.id]) {
      documentation = customActivityToolTips[element.id];
    } else {
      documentation = ''; // Show nothing if custom tool tips are enabled
    }
  }
  if (documentation === undefined) {
    var documentation = undefined;
    if (element.type === 'UserTask') { //仅用户任务显示tip
      documentation = "<div style=\"padding: 0px; \">";
      if (!element.completed) {
        element.endTime = '';
      }
      if (!element.completed && !element.current) {
        element.startTime = '';
      }
      if (element.startTime) {
        documentation = documentation +
          "<div style=\"font-size:9px;height:13px;line-height:10px;border-bottom:0px solid #D3D3D3;white-space:nowrap\">" +
          "<span style=\"font-weight:bold\">开始时间:</span><span style=\"margin-left:10px\">" + element.startTime +
          "</span></div>"
      }
      if (element.endTime) {
        documentation = documentation +
          "<div style=\"font-size:9px;height:13px;line-height:10px;border-bottom:0px solid #D3D3D3;white-space:nowrap\">" +
          "<span style=\"font-weight:bold\">结束时间:</span><span style=\"margin-left:10px\">" + element.endTime +
          "</span></div>";
      }
      if (element.assignee) {
        documentation = documentation +
          "<div style=\"font-size:9px;height:13px;line-height:10px;border-bottom:0px solid #D3D3D3;white-space:nowrap\">" +
          "<span style=\"font-weight:bold\">办理人员:</span><span style=\"margin-left:10px\">" + element.assignee +
          "</span></div>";
      }
      if (element.comments) {
        documentation = documentation +
          "<div style=\"font-size:9px;height:13px;line-height:10px;border-bottom:0px solid #D3D3D3;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;\">" +
          "<span style=\"font-weight:bold\">办理批注:</span><span style=\"margin-left:10px\">" + element.comments +
          "</span></div>";
      }
      documentation = documentation + "</div>";
    }
  • 改造displaymodel.js中获取参数processInstanceId的代码
var processInstanceId = EDITOR.UTIL.getParameterByName('processInstanceId');

此处使用了flowable源码中EDITOR工具中getParameterByName方法,代码片断如下:

var EDITOR = EDITOR || {};
EDITOR.UTIL = {
    getParameterByName: function (name) {
        name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
        var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
            results = regex.exec(location.search);
        return results == null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
    },
};
  • 改造displaymodel.js,添加获取令牌功能,通过令牌,可实现授权访问流程图的要求。
var token = 'Bearer' + $.cookie("Admin-Token");

读取cookie中的令牌。

  • 改造displaymodel.js的访问后台流程图数据服务的功能
var modelUrl = 'http://网关IP:网关端口/api/workflow/auth/activiti/task/process/instances';
var processInstanceId = EDITOR.UTIL.getParameterByName('processInstanceId');
var token = 'Bearer' + $.cookie("Admin-Token");
var request = jQuery.ajax({
  type: 'get',
  beforeSend: function(xhr) {
    xhr.setRequestHeader('X-Token', token);
  },
  url: modelUrl + '?processInstanceId=' + processInstanceId + '&nocaching=' + new Date().getTime()
});

后台代码(为前台准备数据)

  • 流程图数据获取源码
@Service
public class RuntimeDisplayJsonClientResourceImpl implements RuntimeDisplayJsonClientResource {
    @Autowired
    protected RepositoryService repositoryService;
    @Autowired
    protected RuntimeService runtimeService;
    @Autowired
    protected HistoryService historyService;
    @Autowired
    protected ManagementService managementService;
    protected ObjectMapper objectMapper = new ObjectMapper();
    protected List<String> eventElementTypes = new ArrayList<String>();
    protected Map<String, InfoMapper> propertyMappers = new HashMap<String, InfoMapper>();
    @Override
    public JsonNode getModelJSON(String processInstanceId) throws Exception {
        String processDefinitionId="";
        /** 先取流转中的流程实例 **/
        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
        if (processInstance == null) {
            /** 如果流程已结束,就取历史流程实例 **/
            HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
            if(historicProcessInstance == null) {
                throw new Exception("No process instance found with id " + processInstanceId);
            }else{
                processDefinitionId = historicProcessInstance.getProcessDefinitionId();
            }
        }else{
            processDefinitionId = processInstance.getProcessDefinitionId();
        }
        BpmnModel pojoModel = repositoryService.getBpmnModel(processDefinitionId);
        if (pojoModel == null || pojoModel.getLocationMap().isEmpty()) {
            throw new Exception("流程定义未找到:id " + processDefinitionId);
        }
        List<HistoricActivityInstance> activityInstances = historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).orderByHistoricActivityInstanceStartTime().asc().list();
        Map<String, TaskBO> taskInfo = this.getTaskInfo(activityInstances);
        Set<String> completedActivityInstances = new HashSet<String>();
        Set<String> currentElements = new HashSet<String>();
        if (CollectionUtils.isNotEmpty(activityInstances)) {
            for (HistoricActivityInstance activityInstance : activityInstances) {
                if (activityInstance.getEndTime() != null) {
                    completedActivityInstances.add(activityInstance.getActivityId());
                } else {
                    currentElements.add(activityInstance.getActivityId());
                }
            }
        }
        List<Job> jobs = managementService.createJobQuery().processInstanceId(processInstanceId).list();
        if (CollectionUtils.isNotEmpty(jobs)) {
            List<Execution> executions = runtimeService.createExecutionQuery().processInstanceId(processInstanceId).list();
            Map<String, Execution> executionMap = new HashMap<String, Execution>();
            for (Execution execution : executions) {
                executionMap.put(execution.getId(), execution);
            }
            for (Job job : jobs) {
                if (executionMap.containsKey(job.getExecutionId())) {
                    currentElements.add(executionMap.get(job.getExecutionId()).getActivityId());
                }
            }
        }
        // 收集已完成的 flows
        List<String> completedFlows = gatherCompletedFlows(completedActivityInstances, currentElements, pojoModel);
        Set<String> completedElements = new HashSet<String>(completedActivityInstances);
        completedElements.addAll(completedFlows);
        ObjectNode displayNode = processProcessElements(pojoModel, completedElements, currentElements,taskInfo);
        if (completedActivityInstances != null) {
            ArrayNode completedActivities = displayNode.putArray("completedActivities");
            for (String completed : completedActivityInstances) {
                completedActivities.add(completed);
            }
        }
        if (currentElements != null) {
            ArrayNode currentActivities = displayNode.putArray("currentActivities");
            for (String current : currentElements) {
                currentActivities.add(current);
            }
        }
        if (completedFlows != null) {
            ArrayNode completedSequenceFlows = displayNode.putArray("completedSequenceFlows");
            for (String current : completedFlows) {
                completedSequenceFlows.add(current);
            }
        }
        return displayNode;
    }
}
  • 办理日志数据获取方法源码
    @Override
    public ResultDTO getLog(String processInstanceId) throws Exception {
        List<HistoricActivityInstance> activityInstances = historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).orderByHistoricActivityInstanceStartTime().asc().orderByHistoricActivityInstanceEndTime().asc().list();
        List<TaskBO> taskBOList = new ArrayList<>();
        for(HistoricActivityInstance historicActivityInstance:activityInstances){
            if(!"userTask".equals(historicActivityInstance.getActivityType()) && !"startEvent".equals(historicActivityInstance.getActivityType()) && !"endEvent".equals(historicActivityInstance.getActivityType())){
                continue;
            }
            TaskBO taskBO = new TaskBO();
            taskBO.setStartTime(historicActivityInstance.getStartTime());
            taskBO.setEndTime(historicActivityInstance.getEndTime());
            String assignee=historicActivityInstance.getAssignee();
            taskBO.setAssignee(WorkflowTool.getHumanAssignee(assignee));
            taskBO.setName(historicActivityInstance.getActivityName());
            if(org.apache.commons.lang3.StringUtils.isNotBlank(historicActivityInstance.getTaskId())){
                List<Comment> commList = taskService.getTaskComments(historicActivityInstance.getTaskId());
                String msg = StringTool.join(";",commList,"fullMessage");
                taskBO.setNotes(msg);
            }
            if("startEvent".equals(historicActivityInstance.getActivityType())){
                String initiator = flowableUtis.getInitiatorByProcessInstanceId(historicActivityInstance.getProcessInstanceId());
                String initatorHuman = WorkflowTool.getHumanAssignee(initiator);
                taskBO.setAssignee(initatorHuman);
                taskBO.setNotes("提交");
            }
            if("endEvent".equals(historicActivityInstance.getActivityType())){
                taskBO.setAssignee("系统");
                taskBO.setNotes("完成");
            }
            taskBOList.add(taskBO);
        }
        return ResultDTO.success(taskBOList);
    }
  • 相关bean对象定义
@Data
public class TaskBO {
    private String assignee;
    private Date startTime;
    private Date endTime;
    private String notes;
    private String name;
}

总结:通过对flowable源码中bpmnModel绘制功能的整合,可以较好的实现交互式的流程图跟踪展现功能。相较静态图方式展现的流程图,这种实现方式用户交互体验更好,获取信息更加方便,具有明显优势。
项目源码仓库
下一篇将介绍对flowable中模型制作editor-app功能的深度整合。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/432462.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

110.【23种设计模式--创建者模式】

Java 23种设计模式 (一)、设计模式相关内容介绍1.软件设计模式概述(1).软件设计模式的产生背景(2).软件设计模式的概念(3).学习设计模式的重要性(4).设计模式分类 2.UML图(1).类图概述(2).类图的作用(3).类图表示法 3.软件设计原则(1).开闭原则 (重写不修改)(2).里氏代换原则 (…

node的安装与卸载

node的安装与卸载 今天遇到个问题 使用npm命令时报错显示栈溢出&#xff0c;所以将node重装了一下&#xff0c; 一.卸载node&#xff1a; 1.在程序与功能里卸载node.js&#xff0c;然后删除node相关文件夹&#xff0c;此次安装我将node安装在C盘里&#xff0c;将他的全局缓存…

10万字信用大数据一体化监管平台建设方案word

本资料来源公开网络&#xff0c;仅供个人学习&#xff0c;请勿商用&#xff0c;如有侵权请联系删除。 1.1、 系统主要功能需求分析 XX公共信用信息管理系统的主要业务流程包括信用信息资源编录管理流程、信用信息归集流程、信用信息服务流程和信用信息异议处理流程。 一.1.1…

NLP作业01:利用HMM实现词性标注

作业头 这个作业属于哪个课程自然语言处理这个作业要求在哪里利用HMM实现词性标注作业要求我在这个课程的目标实现词性标注这个作业在哪个具体方面帮助我实现目标代码实现参考文献1.隐马尔科夫模型 2.基于HMM的词性标注  3.基于HMMViterbi算法的词性标注 Python 文章目录 作…

【LeetCode: 673. 最长递增子序列的个数 | 动态规划】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

LeetCode 热题 HOT 100:从前序与中序遍历序列构造二叉树、二叉树展开为链表、二叉树中的最大路径和

LeetCode 热题 HOT 100 105. 从前序与中序遍历序列构造二叉树 题目&#xff1a; 给定两个整数数组 preorder 和 inorder &#xff0c;其中 preorder 是二叉树的先序遍历&#xff0c; inorder 是同一棵树的中序遍历&#xff0c; 请构造二叉树并返回其根节点。 示例 1&#xff1…

webassembly——同源策略问题的处理(浏览器不能加载本地资源的问题)

原因&#xff1a;在用chatGPT生成可视化地图前端文件后&#xff0c;打开不能正常显示 WebAssembly是一种新的二进制代码格式&#xff0c;它可以提供更高的性能和更好的安全性。WebAssembly遵循同源策略&#xff0c;这意味着只有与运行WebAssembly代码相同域名下的JavaScript代码…

Linux --- 常用命令

一、常用命令 1.1、常用命令演示 在这一部分中&#xff0c;我们主要介绍几个常用的命令&#xff0c;让大家快速感受以下Linux指令的操作方式。主要包含 以下几个指令&#xff1a; 1.2、Linux命令使用技巧 在我们使用Linux系统命令时&#xff0c;可以使用以下几个技巧&#x…

丹麦PR electronics信号隔离器3185A1 3185A2安全栅

应用 1:1 标准电流信号隔离&#xff0c;信号范围 0(4)...20 mA。标准 DIN 导轨安装。无论在技术或是成本上&#xff0c;都是电流信号电气隔离的选择。能有效抑制浪涌电流&#xff0c;保护控制系统远离信号噪声和瞬变的影响。消除接地环路和测量浮地信号。安装于安全区域或 Zon…

03、Cadence使用记录之超多引脚元器件的快速创建方法(OrCAD Capture CIS)

03、Cadence使用记录之超多引脚元器件的快速创建方法&#xff08;OrCAD Capture CIS&#xff09; 参考的教程是B站的视频&#xff1a;allegro软件入门视频教程全集100讲 前置教程&#xff1a; 01、Cadence使用记录之新建工程与基础操作&#xff08;原理图绘制&#xff1a;OrC…

个人建议:真的不要去小公司...

软件测试人员如果想要有更好的发展真的不要去小公司&#xff01; 为什么&#xff1f; 小公司的测试团队相对较小&#xff0c;往往只有一两个人&#xff0c;缺乏资源和技术支持&#xff0c;难以优化测试流程和提高测试效率。 小公司一般缺乏完善的软件开发流程和质量管理体系&a…

基于LSTM神经网络的通用股票预测源代码+模型+数据集

基于神经网络的通用股票预测模 下载地址&#xff1a;基于LSTM神经网络的通用股票预测源代码模型数据集 0 使用方法 How to use 使用getdata.py下载数据&#xff0c;或者使用自己的数据源&#xff0c;将数据放在stock_daily目录下 使用data_preprocess.py预处理数据&#xff…

基于RK3568的Linux驱动开发——GPIO知识点(一)

authordaisy.skye的博客_CSDN博客-Qt,嵌入式,Linux领域博主系列基于RK3568的Linux驱动开发—— GPIO知识点&#xff08;二&#xff09;_daisy.skye的博客-CSDN博客 gpio bank RK3568 有 5 组 GPIO bank&#xff1a;GPIO0~GPIO4&#xff0c;每组又以 A0-A7、B0-B7、 C0-C7、 D0…

CSS基础样式

1.高度和宽度 .c1{height:300px;width:500px; } 注意事项&#xff1a; 宽度&#xff0c;支持百分比 行内标签&#xff1a;默认无效 块级标签&#xff1a;默认有效&#xff08;右侧区域就算是空白&#xff0c;也不给占用&#xff09; 2.块级和行内标签 css样式&#xff1a;标签…

软件工程实验:用例图设计

目录 前言 实验目的 实验要求 实验步骤 结果展示 总结 前言 软件工程导论实验是一门旨在培养学生掌握软件开发过程中的基本方法和技能的课程。本实验的主题是用例图设计&#xff0c;用例图是一种描述系统功能需求的图形化工具&#xff0c;它可以帮助分析和设计系统的行为…

初识Tkinter弹窗

Tkinter弹窗 Tkinter是什么 Tkinter 是使用 python 进行窗口视窗设计的模块。Tkinter模块(“Tk 接口”)是Python的标准Tk GUI工具包的接口。作为 python 特定的GUI界面&#xff0c;是一个图像的窗口&#xff0c;tkinter是python 自带的&#xff0c;可以编辑的GUI界面&#xff…

【蓝桥杯省赛真题17】python删除字符串 青少年组蓝桥杯python编程省赛真题解析

目录 python删除字符串 一、题目要求 1、编程实现 2、输入输出 二、解题思路

Qt创建SDK库(dll动态库)并调用SDK库(dll动态库)

Qt创建SDK库(dll动态库)并调用SDK库(dll动态库) 一、项目场景 在日常的项目中&#xff0c;我们经常会遇到调用别人的数学库、线程库、图形库等操作。这些库通常就被称为SDK&#xff0c;SDK全称是Software Development Kit&#xff08;软件开发工具包&#xff09;&#xff0c;…

【C++ 五】结构体

结构体 文章目录 结构体前言1 结构体基本概念2 结构体定义和使用3 结构体数组4 结构体指针5 结构体嵌套结构体6 结构体做函数参数7 结构体中 const 使用场景8 结构体案例8.1 案例一8.2 案例二 总结 前言 本文包含结构体基本概念、结构体定义和使用、结构体数组、结构体指针、结…

gitlab CI CD基础概念

gitlab CI CD基础概念 本文目录 gitlab CI CD基础概念基础概念Pipelines&#xff1a;流水线JobsStage .gitlab-ci.yml使用模式1&#xff1a;官网gitlab 本地gitlab runner使用模式2&#xff1a;docker gitlab docker gitlab runner 基础概念 开发模式转变&#xff1a;瀑布模…