书接上回
项目源码仓库
无论是待办、已办,亦或是流转中、已结束的流程实例,通过使用JS绘制SVG格式的交互式流程图,与以上篇博文中三种方式相比,在效果上都具有明显优势。
运行效果如下图所示:
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>
import request from '@/utils/request'
//获取流程办理历史记录
export function fetchFlowLog(data) {
return request({
url: '/api/workflow/auth/activiti/task/log',
method: 'post',
data
})
}
流程跟踪组件代码:
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 = "";
if (!element.completed) {
element.endTime = '';
}
if (!element.completed && !element.current) {
element.startTime = '';
}
if (element.startTime) {
documentation = documentation +
"" +
"开始时间:" + element.startTime +
""
}
if (element.endTime) {
documentation = documentation +
"" +
"结束时间:" + element.endTime +
"";
}
if (element.assignee) {
documentation = documentation +
"" +
"办理人员:" + element.assignee +
"";
}
if (element.comments) {
documentation = documentation +
"" +
"办理批注:" + element.comments +
"";
}
documentation = documentation + "";
}
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, " "));
},
};
var token = 'Bearer' + $.cookie("Admin-Token");
读取cookie中的令牌。
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);
}
@Data
public class TaskBO {
private String assignee;
private Date startTime;
private Date endTime;
private String notes;
private String name;
}
总结:通过对flowable源码中bpmnModel绘制功能的整合,可以较好的实现交互式的流程图跟踪展现功能。相较静态图方式展现的流程图,这种实现方式用户交互体验更好,获取信息更加方便,具有明显优势。
项目源码仓库
下一篇将介绍对flowable中模型制作editor-app功能的深度整合。