Flowable6.x導(dǎo)出查看跟蹤流程圖(續(xù))

書(shū)接上回

項(xiàng)目源碼倉(cāng)庫(kù)

無(wú)論是待辦、已辦,亦或是流轉(zhuǎn)中、已結(jié)束的流程實(shí)例,通過(guò)使用JS繪制SVG格式的交互式流程圖,與以上篇博文中三種方式相比,在效果上都具有明顯優(yōu)勢(shì)。
運(yùn)行效果如下圖所示:


運(yùn)行效果圖

整合、改造Flowable中displaymodel頁(yè)面

從flowable官方發(fā)布包獲取前端源碼

  • 下載官方數(shù)據(jù)包flowable-6.4.1.zip
  • 從壓縮包中解壓出flowable-6.4.1\wars下面的flowable-modeler.war
  • 從flowable-modeler.war中解壓出WEB-INF\classes\static\display 文件夾下的11個(gè)文件,如下圖所示:
    {% asset_img trac.gif 流程跟蹤 %}
  • 在前端vue-element-admin的public下創(chuàng)建display文件夾,將11個(gè)文件放入
  • 在前端vue-element-admin的public創(chuàng)建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>

通過(guò)vue組件iframe方式,將displaymodel頁(yè)面嵌入

  • 將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="流程跟蹤">
      <!-- 此處通過(guò)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>
      <!-- 此處通過(guò)el-table顯示流程任務(wù)辦理日志 -->
      <div>
      <el-table v-loading="loading" :data="mainTableData" border fit style="width: 100%" max-height="500">
        <el-table-column type="index" label="序號(hào)" width="70">
        </el-table-column>
        <el-table-column prop="name" label="名稱" show-overflow-tooltip sortable></el-table-column>
        <el-table-column prop="assignee" label="經(jīng)辦人" 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="開(kāi)始時(shí)間" show-overflow-tooltip sortable
          :formatter="(row,column,cellValue) => dateTimeColFormatter(row,column,cellValue)"></el-table-column>
        <el-table-column prop="endTime" label="完成時(shí)間" show-overflow-tooltip sortable
          :formatter="(row,column,cellValue) => dateTimeColFormatter(row,column,cellValue)"></el-table-column>
      </el-table>
      </div>
    </el-dialog>
  </div>
</template>
<script>
  // 引入獲取辦理日志數(shù)據(jù)的方法
  import {
    fetchFlowLog
  } from '@/api/workflow-task'
  export default {
    name: 'iFrame',
    data() {
      return {
        dialogVisible: false,
        contents: "",
        loading: true,
        mainTableData: []
      }
    },
    mounted() {},
    methods: {
      // 設(shè)置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>
  • 以待辦頁(yè)面為例引入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: {
      // 調(diào)用子組件,顯示流程圖和辦理日志
      handleDisplay(row) {
        this.$nextTick(() => {
          //設(shè)置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)方法,優(yōu)化鼠標(biāo)懸浮在任務(wù)上時(shí)Tip顯示內(nèi)容
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') { //僅用戶任務(wù)顯示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\">開(kāi)始時(shí)間:</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\">結(jié)束時(shí)間:</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中獲取參數(shù)processInstanceId的代碼
var processInstanceId = EDITOR.UTIL.getParameterByName('processInstanceId');

此處使用了flowable源碼中EDITOR工具中g(shù)etParameterByName方法,代碼片斷如下:

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,添加獲取令牌功能,通過(guò)令牌,可實(shí)現(xiàn)授權(quán)訪問(wèn)流程圖的要求。
var token = 'Bearer' + $.cookie("Admin-Token");

讀取cookie中的令牌。

  • 改造displaymodel.js的訪問(wèn)后臺(tái)流程圖數(shù)據(jù)服務(wù)的功能
var modelUrl = 'http://網(wǎng)關(guān)IP:網(wǎng)關(guān)端口/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()
});

后臺(tái)代碼(為前臺(tái)準(zhǔn)備數(shù)據(jù))

  • 流程圖數(shù)據(jù)獲取源碼
@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="";
        /** 先取流轉(zhuǎn)中的流程實(shí)例 **/
        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
        if (processInstance == null) {
            /** 如果流程已結(jié)束,就取歷史流程實(shí)例 **/
            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;
    }
}
  • 辦理日志數(shù)據(jù)獲取方法源碼
    @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("系統(tǒng)");
                taskBO.setNotes("完成");
            }
            taskBOList.add(taskBO);
        }
        return ResultDTO.success(taskBOList);
    }
  • 相關(guān)bean對(duì)象定義
@Data
public class TaskBO {
    private String assignee;
    private Date startTime;
    private Date endTime;
    private String notes;
    private String name;
}

總結(jié):通過(guò)對(duì)flowable源碼中bpmnModel繪制功能的整合,可以較好的實(shí)現(xiàn)交互式的流程圖跟蹤展現(xiàn)功能。相較靜態(tài)圖方式展現(xiàn)的流程圖,這種實(shí)現(xiàn)方式用戶交互體驗(yàn)更好,獲取信息更加方便,具有明顯優(yōu)勢(shì)。
項(xiàng)目源碼倉(cāng)庫(kù)
下一篇將介紹對(duì)flowable中模型制作editor-app功能的深度整合。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容