为了让大家看到最后的效果是否是自己想要的,所以我们先来看最后的效果图:

1.背景

我们都知道activiti在5.22的时候就有了流程图跟踪组件Diagram Viewer ,如图(图片来源《Activiti实战》):

但是,到6.0就找不到该组件了,可能因为6.0的api改动很大,有些类和包完全舍弃了,该组件就不适用了。所以在6.0的时候,我们要想展示高亮流程图,往往都是通过后端来生成图片,返回给前端展示。这种方式也是我们项目之前所用的,生成图片代码繁琐,在生产环境还有字体问题,我们就不多介绍。

无意中了解到bpmn.js可以设计和展示流程图(本篇只讲解展示流程图),所以就去搜索这方面的资料。找到了这篇博客:

bpmn整合流程图高亮显示流程进度图

在他的基础上,进行了改造和优化,就有了开头的效果图。

2.前端依赖

"bpmn-js": "^8.7.1",
"bpmn-js-properties-panel": "^0.44.0",
"bpmn-js-token-simulation": "^0.21.1",
"camunda-bpmn-moddle": "^5.1.2",
"xml-js": "^1.6.11"

引入这些依赖就可以了。然后就是页面,基本上与 bpmn整合流程图高亮显示流程进度图 一样,但是加入了自己的逻辑,修复了一些显示问题:

<template><div><div class="bpmn-viewer-container"><divstyle="width:100%;height:20px;position: absolute; left: 20px; top: 10px; color: #000000D9;font-size: 16px;font-weight: 500">{{title}}</div><div style="position: absolute; left: 10px; top: 40px;z-index: 999"><el-button-group key="scale-control"><el-tooltip effect="light" content="缩小视图"><el-button :size="headerButtonSize" :disabled="defaultZoom < 0.2" icon="el-icon-zoom-out"@click="processZoomOut()"/></el-tooltip><el-button :size="headerButtonSize">{{ Math.floor(this.defaultZoom * 10 * 10) + '%' }}</el-button><el-tooltip effect="light" content="放大视图"><el-button :size="headerButtonSize" :disabled="defaultZoom > 4" icon="el-icon-zoom-in"@click="processZoomIn()"/></el-tooltip><el-tooltip effect="light" content="重置视图并居中"><el-button :size="headerButtonSize" icon="el-icon-c-scale-to-original" @click="processReZoom()"/></el-tooltip></el-button-group></div><div id="bpmnCanvas" style="width:100%;height:500px;margin: 0 auto;"></div><div v-for="item in detailInfo" :key="item.activityId" style="width: 90%;margin: 0 auto;border-bottom: 1px dashed #333;"><el-row><el-col :span="12"><p>节点名称:{{item.activityName}}</p><p>审批人:{{item.assignee}}</p><p>审批状态:{{item.approvalStatus}}</p></el-col><el-col :span="12"><p>审批结果:{{item.result}}</p><p>审批意见:{{item.comment}}</p><p>审批时间:{{item.endTime}}</p></el-col></el-row></div></div></div>
</template><script>import BpmnViewer from 'bpmn-js'import MoveCanvasModule from 'diagram-js/lib/navigation/movecanvas'import { checkSpeed, getOneActivity } from '@/api/approval/build'let bpmnViewer = nullexport default {props: {headerButtonSize: {type: String,default: 'small',validator: value => ['default', 'medium', 'small', 'mini'].indexOf(value) !== -1},reviewObj: {type: Object}},name: 'reviewRuningFlow',components: {},data() {return {detailInfo: [],executedLightNode: [],highlightLine: [],activeLightNode:[],defaultZoom: 1,nodeDetail: {},scale: 1,title: '流程预览',showViewDialog: false,instanceId: undefined}},mounted() {// this.initPage()},methods: {initPage(instanceId, procDefId) {bpmnViewer && bpmnViewer.destroy()bpmnViewer = new BpmnViewer({container: '#bpmnCanvas',width: '100%',additionalModules: [MoveCanvasModule // 移动整个画布]})this.instanceId = instanceIdlet len = document.getElementsByTagName('svg').lengthdocument.getElementsByTagName('svg')[len-2].setAttribute('display', 'none')if(instanceId || procDefId) {checkSpeed({'instanceId':instanceId, 'procDefId':procDefId}).then(res => {if (res.code === 200) {this.title = res.data.modelNamethis.highlightLine = res.data.highlightedFlowIdsthis.executedLightNode = res.data.executedActivityIdsthis.activeLightNode = res.data.activeActivityIdsif (bpmnViewer) {this.importXml(res.data.modelXml)}} else {this.$message({message: res.data.msg,type: 'error'})}})}//以下注释代码是只展示流程图不需要高亮展示/*if(bpmnViewer){this.importXml(this.reviewObj.modelXml);} else {console.error('bpmnViewer is null or undefined!');}*/},getHtmlAttr(source, element, attr) {let result = []let reg = '<' + element + '[^<>]*?\\s' + attr + '=[\'"]?(.*?)[\'"]?\\s.*?>'let matched = source.match(new RegExp(reg, 'gi'))matched && matched.forEach(item => {item && result.push(item)})return result},importXml(modelXml) {// 处理排他网关, 注:流程图预览时,排他网关需要在对应的<bpmndi:BPMNShape>节点上添加属性isMarkerVisible="true"let gatewayIds = this.getHtmlAttr(modelXml, 'exclusiveGateway', 'id')let modelXmlTemp = modelXmlif (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, (err) => {if (err) {console.error(err, 1111)} else {this.importXmlSuccess()}})},importXmlSuccess() {// 使流程图自适应屏幕let canvas = bpmnViewer.get('canvas')canvas.zoom('fit-viewport', 'auto')//设置高亮线和高亮节点,需要配合style中的css样式一起使用,否则没有颜色this.setViewerStyle(canvas)//给任务节点加聚焦和离焦事件this.bindEvents()},setViewerStyle(canvas) {//已完成节点高亮let executedLightNode = this.executedLightNodeif (executedLightNode && executedLightNode.length > 0) {executedLightNode.forEach(item => {canvas.addMarker(item, 'highlight-executed')})}//顺序线高亮let highlightLines = this.highlightLineif (highlightLines && highlightLines.length > 0) {highlightLines.forEach(item => {canvas.addMarker(item, 'highlight-line')})}//正在执行节点高亮let activeLightNode = this.activeLightNodeif (activeLightNode && activeLightNode.length > 0) {activeLightNode.forEach((item,index) => {canvas.addMarker(item, 'highlight')})document.querySelectorAll('.highlight').forEach((item,index)=>{item.querySelector('.djs-visual rect').setAttribute('stroke-dasharray', '4,4')})}},// 以下代码为:为节点注册鼠标悬浮事件bindEvents() {let eventBus = bpmnViewer.get('eventBus')eventBus.on('element.hover', (e) => {if (e.element.type === 'bpmn:UserTask') {if (this.nodeDetail[e.element.id]) {this.detailInfo = this.nodeDetail[e.element.id]} else {getOneActivity({instanceId: this.instanceId,activityId: e.element.id}).then(res => {this.nodeDetail[e.element.id] = res.data;this.detailInfo = res.data})}}})eventBus.on('element.out', (e) => {if (e.element.type === 'bpmn:UserTask') {this.detailInfo = []}})},//悬浮框设置/*genNodeDetailBox(e, overlays) {let tempDiv = document.createElement('div')//this.detailInfo = detail;let popoverEl = document.querySelector('.flowMsgPopover')//let popoverEl = this.$refs.flowMsgPopover;console.log(this.detailInfo)tempDiv.innerHTML = popoverEl.innerHTMLtempDiv.className = 'tipBox'tempDiv.style.width = '260px'tempDiv.style.background = 'rgba(255, 255, 255)'overlays.add(e.element.id, {position: { top: e.element.height, left: 0 },html: tempDiv})},*/processZoomIn(zoomStep = 0.1) {let newZoom = Math.floor(this.defaultZoom * 100 + zoomStep * 100) / 100if (newZoom > 4) {throw new Error('[Process Designer Warn ]: The zoom ratio cannot be greater than 4')}this.defaultZoom = newZoombpmnViewer.get('canvas').zoom(this.defaultZoom)},processZoomOut(zoomStep = 0.1) {let newZoom = Math.floor(this.defaultZoom * 100 - zoomStep * 100) / 100if (newZoom < 0.2) {throw new Error('[Process Designer Warn ]: The zoom ratio cannot be less than 0.2')}this.defaultZoom = newZoombpmnViewer.get('canvas').zoom(this.defaultZoom)},processReZoom() {this.defaultZoom = 1bpmnViewer.get('canvas').zoom('fit-viewport', 'auto')}}}
</script><style lang="scss">@import '../../../../node_modules/bpmn-js/dist/assets/diagram-js.css';@import '../../../../node_modules/bpmn-js/dist/assets/bpmn-font/css/bpmn.css';@import '../../../../node_modules/bpmn-js/dist/assets/bpmn-font/css/bpmn-codes.css';@import '../../../../node_modules/bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css';@import '../../../../node_modules/bpmn-js-properties-panel/dist/assets/bpmn-js-properties-panel.css';/*.bjs-powered-by {display: none;}*/.flowMsgPopover {display: none;}.highlight:not(.djs-connection) .djs-visual > :nth-child(1) {fill: rgb(251, 233, 209) !important; /* color elements as green */}.highlight g.djs-visual > :nth-child(1) {stroke: rgb(214, 126, 125) !important;}.highlight-executed g.djs-visual > :nth-child(1) {stroke: rgb(0, 190, 0, 1) !important;fill: rgb(180, 241, 180) !important;}.highlight-line g.djs-visual > :nth-child(1) {stroke: rgb(0, 190, 0) !important;}@-webkit-keyframes dynamicNode {to {stroke-dashoffset: 100%;}}.highlight {.djs-visual {-webkit-animation: dynamicNode 18S linear infinite;-webkit-animation-fill-mode: forwards;}}.tipBox {width: 300px;background: #fff;border-radius: 4px;border: 1px solid #ebeef5;padding: 12px;/*.ant-popover-arrow{display: none;}*/p {line-height: 28px;margin: 0;padding: 0;}}</style>

这样前端的工作就完成了。

3.后端返回数据

我们从图片可以看出,前端渲染图片需要4种数据:

  • 已执行的节点
  • 正在执行的节点
  • 已执行的线
  • 原始流程文件(xml)

基本上我们原先后端的代码也能用,我们就不用通过代码去画图了,只需要将查找出的4种数据返回给前端。这部分的代码就不贴了,大家东拼西凑都能找到,我这边用的也上一个负责工作流的伙伴东拼西凑留下来的。

4.更新后端返回数据代码

返回实体:

@Data
public class ProcessHighlightEntity {/*** 当前正执行节点id*/private Set<String> activeActivityIds;/*** 已执行节点id*/private Set<String> executedActivityIds;/*** 高亮线id(流程已走过的线)*/private Set<String> highlightedFlowIds;/*** 流程xml文件 字符串*/private String modelXml;/*** 流程名称*/private String modelName;
}

获取流程图高亮所需数据:

public ProcessHighlightEntity getActivitiProcessHighlight(String instanceId, String procDefId) {ProcessDefinition processDefinition = getProcessDefinition(procDefId, instanceId);procDefId = processDefinition.getId();BpmnModel bpmnModel = getBpmnModel(procDefId);List<HistoricActivityInstance> histActInstances = historyService.createHistoricActivityInstanceQuery().processInstanceId(instanceId).orderByHistoricActivityInstanceId().asc().list();ProcessHighlightEntity highlightEntity = getHighLightedData(bpmnModel.getMainProcess(), histActInstances);highlightEntity.setModelName(processDefinition.getName());// Map缓存,提高获取流程文件速度if (ActivitiConstants.BPMN_XML_MAP.containsKey(procDefId)) {highlightEntity.setModelXml(ActivitiConstants.BPMN_XML_MAP.get(procDefId));} else {InputStream bpmnStream = repositoryService.getResourceAsStream(processDefinition.getDeploymentId(), processDefinition.getResourceName());try (Reader reader = new InputStreamReader(bpmnStream, StandardCharsets.UTF_8)) {String xmlString = IoUtil.read(reader);highlightEntity.setModelXml(xmlString);ActivitiConstants.BPMN_XML_MAP.put(procDefId, xmlString);} catch (IOException e) {log.error("[获取流程数据] 失败,{}", e.getMessage());throw new CustomException("获取流程数据失败,请稍后重试");}}return highlightEntity;
}

获取流程定义数据:

public ProcessDefinition getProcessDefinition(String procDefId, String instanceId) {if (StrUtil.isBlank(procDefId)) {if (StrUtil.isBlank(instanceId)) {throw new CustomException("流程实例id,流程定义id 两者不能都为空");}ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(instanceId).singleResult();if (processInstance == null) {HistoricProcessInstance histProcInst = historyService.createHistoricProcessInstanceQuery().processInstanceId(instanceId).singleResult();if (histProcInst == null) {throw new CustomException("查询失败,请检查传入的 instanceId 是否正确");}procDefId = histProcInst.getProcessDefinitionId();} else {procDefId = processInstance.getProcessDefinitionId();}}try {return repositoryService.getProcessDefinition(procDefId);} catch (ActivitiObjectNotFoundException e) {throw new CustomException("该流程属于之前流程,已删除");}
}

获取Bpmn模型数据:

public BpmnModel getBpmnModel(String procDefId) {try {return repositoryService.getBpmnModel(procDefId);} catch (ActivitiObjectNotFoundException e) {throw new CustomException("流程定义数据不存在");}
}

获取需要高亮的流程数据:

private ProcessHighlightEntity getHighLightedData(Process process,List<HistoricActivityInstance> historicActInstances) {ProcessHighlightEntity entity = new ProcessHighlightEntity();// 已执行的节点idSet<String> executedActivityIds = new HashSet<>();// 正在执行的节点idSet<String> activeActivityIds = new HashSet<>();// 高亮流程已发生流转的线id集合Set<String> highLightedFlowIds = new HashSet<>();// 全部活动节点List<FlowNode> historicActivityNodes = new ArrayList<>();// 已完成的历史活动节点List<HistoricActivityInstance> finishedActivityInstances = new ArrayList<>();for (HistoricActivityInstance historicActivityInstance : historicActInstances) {FlowNode flowNode = (FlowNode) process.getFlowElement(historicActivityInstance.getActivityId(), true);historicActivityNodes.add(flowNode);if (historicActivityInstance.getEndTime() != null) {finishedActivityInstances.add(historicActivityInstance);executedActivityIds.add(historicActivityInstance.getActivityId());} else {activeActivityIds.add(historicActivityInstance.getActivityId());}}FlowNode currentFlowNode = null;FlowNode targetFlowNode = null;// 遍历已完成的活动实例,从每个实例的outgoingFlows中找到已执行的for (HistoricActivityInstance currentActivityInstance : finishedActivityInstances) {// 获得当前活动对应的节点信息及outgoingFlows信息currentFlowNode = (FlowNode) process.getFlowElement(currentActivityInstance.getActivityId(), true);List<SequenceFlow> sequenceFlows = currentFlowNode.getOutgoingFlows();/*** 遍历outgoingFlows并找到已已流转的 满足如下条件认为已已流转:* 1.当前节点是并行网关或兼容网关,则通过outgoingFlows能够在历史活动中找到的全部节点均为已流转* 2.当前节点是以上两种类型之外的,通过outgoingFlows查找到的时间最早的流转节点视为有效流转*/if ("parallelGateway".equals(currentActivityInstance.getActivityType()) || "inclusiveGateway".equals(currentActivityInstance.getActivityType())) {// 遍历历史活动节点,找到匹配流程目标节点的for (SequenceFlow sequenceFlow : sequenceFlows) {targetFlowNode = (FlowNode) process.getFlowElement(sequenceFlow.getTargetRef(), true);if (historicActivityNodes.contains(targetFlowNode)) {highLightedFlowIds.add(sequenceFlow.getId());}}} else {List<Map<String, Object>> tempMapList = new ArrayList<>();for (SequenceFlow sequenceFlow : sequenceFlows) {for (HistoricActivityInstance historicActivityInstance : historicActInstances) {if (historicActivityInstance.getActivityId().equals(sequenceFlow.getTargetRef())) {Map<String, Object> map = new HashMap<>();map.put("highLightedFlowId", sequenceFlow.getId());map.put("highLightedFlowStartTime", historicActivityInstance.getStartTime().getTime());tempMapList.add(map);}}}if (!CollectionUtils.isEmpty(tempMapList)) {// 遍历匹配的集合,取得开始时间最早的一个long earliestStamp = 0L;String highLightedFlowId = null;for (Map<String, Object> map : tempMapList) {long highLightedFlowStartTime = Long.parseLong(map.get("highLightedFlowStartTime").toString());if (earliestStamp == 0 || earliestStamp >= highLightedFlowStartTime) {highLightedFlowId = map.get("highLightedFlowId").toString();earliestStamp = highLightedFlowStartTime;}}highLightedFlowIds.add(highLightedFlowId);}}}entity.setActiveActivityIds(activeActivityIds);entity.setExecutedActivityIds(executedActivityIds);entity.setHighlightedFlowIds(highLightedFlowIds);return entity;
}

activiti6.0通过bpmn.js展示高亮流程图(前端绘制流程图)相关推荐

  1. 如何绘制流程图?绘制流程图在线网站分享

    流程图是一种很常见的图表展示工具,在学习以及工作中出现的场合较多,一般绘制流程图有两种方法,一种是手绘,另一种是利用画图工具进行绘制,随着网络科技的发展,很多网站和软件已经替代了那些传统的手绘,那么, ...

  2. 前端绘制流程图、泳道图

    技术栈 使用magicFlow插件绘制. 原生HTML页面也可以,vue或react前端框架也可以,官网有详细安装方法,本文章后面也会详细说明. 官网链接: MagicFlow官网地址 需求 前端绘制 ...

  3. wps的流程图怎么导出_word流程图-WPS绘制流程图的正确打开方式,超级简单

    不知道大家在办公的时候习惯使用什么版本的软件?这几天小编在绘制流程图的时候发现一个特别有意思的,你会绘制带有动态走向的流程图吗?不信你看 看起来是不是挺高大上的,你知道是怎么完成的吗?今天小编大家快速 ...

  4. wps如何自己制作流程图_WPS绘制流程图的正确打开方式,超级简单-word流程图

    不知道大家在办公的时候习惯使用什么版本的软件?这几天小编在绘制流程图的时候发现一个特别有意思的,你会绘制带有动态走向的流程图吗?不信你看 看起来是不是挺高大上的,你知道是怎么完成的吗?今天小编大家快速 ...

  5. 【Activity】绘制流程图方式(部署,启动不讨论)

    activity绘制流程图大概有三种方式(可能还有其他的,我不知道,但这三种是最长用的) 归结起来份两类: 第一类:利用开发工具插件绘制(eclipse.idea等) 第二类:activity自带的A ...

  6. VUE+bpmn.js+iview 页面绘制流程图

    前言:业务需求需要在页面绘制流程图,之前后台的同事都是在eclipse画的流程图,为了方便点希望能在页面上画. 我用的是iview的ui框架,用原生的话记得把按钮等标签改改. 如果用的是element ...

  7. Activiti6.0版本如何显示在线流程图

    Activiti6.0版本如何显示在线流程图 Activiti6.0在线显示流程图和Activiti5.X有点差别,6.0版本去除了pvm包 5.X版本中的pvm包,但是在6.0版本并没有该包了,因此 ...

  8. Activiti6.0实战-画流程图(1)

    Activiti6.0实战-目录 目录 一.画图工具选择 二.Activiti-App下载启动 三.画一个工作流 扩展 一.画图工具选择 Activiti-App(官网推荐) 我是选的这个 Activ ...

  9. activiti6.0入门(三)-使用Eclipse绘制activiti流程图

    通过前面的学习,相信对activiti都有了个初步认识及大概了解,在之前绘制流程图时,是通过activiti服务链接来完成流程图的绘制,接下来我们不依赖服务来完成流程图绘制,IDE工具idea和ecl ...

  10. Vue+bpmn.js自定义流程图之palette(二)

    自定义bpmn.js左侧工具栏palette样式 一.回顾 1.预览 2.目录结构 二.代码 1.paletteProvider.js文件 2.css文件 3.index.js文件 三.总结 一.回顾 ...

最新文章

  1. active set + serving cell
  2. SQLServer之创建INSTEAD OF INSERT,UPDATE,DELETE触发器
  3. jQuery mobile 图标
  4. 设计模式——创建型模型
  5. jozj3419-最大利润【树形dp】
  6. vim中如何设置多行注释和删除注释
  7. iPhone失去反应咋办?
  8. 查看nginx php mysql apache编译安装参数
  9. 听说你还不会写观察者模式?
  10. Nginx学习笔记(二) Nginx--connectionrequest
  11. 使用国内源来安装pytorch(速度很快)
  12. echarts环形图加边框
  13. 如何将php改成mp4,如何将swf转换成mp4
  14. 最大流最小割经典例题_图像分割之最小割与最大流算法
  15. Jmeter个人使用部分整理2021
  16. 编辑距离——莱文斯坦距离(Levenshtein distance)
  17. esp8266舵机驱动_使用Arduino和ESP8266通过网页控制舵机
  18. SAPUI5学习第八天-----(12)图标Icons和重用对话框Reuse Dialogs
  19. “虚拟机使用的是此版本VM不支持的硬件版本”问题的解决方法
  20. IE浏览器中 img ... /标签不显示图片

热门文章

  1. 135编辑器怎么复制html,135编辑器复制粘贴文字的方法
  2. C# wpf确认取消MessageBox选择按钮
  3. oracle 统计同比 环比,。oracle 同期 同比 环比
  4. Marlin固件运动控制代码分析
  5. 【初学大数据】CentOS7安装hadoop3.3.2完全分布式详细流程
  6. 微信小程序云数据库中变量作为字段名与字段取值、字段值取值
  7. 腾讯QQ会员技术团队:人人都可以做深度学习应用:入门篇(下)
  8. JavaFX游戏制作:瓦片地图绘制
  9. 毕业答辩ppt怎么做?
  10. 用python制作上海疫情评论词云图-自定义形状