背景:
由于项目的需要,当用户在查看流程图时,当点击某个流程图片上的节点时,需要提示一些信息,这就需要获取各个节点的信息,此处获取id和name的值。
注意:这个并不是流程图的高亮,即当点击网点申请环节时,获取该节点的id和name,即B001和网点申请,点击部门申请时,获取B002和部门经理审批
解释说明:
1.下方说的x和y:
2.流程定义的key: 即为下方id的值
3.节点的id和name的值:
难点分析:
由 于activiti在部署时,如果没有流程图片,则activiti会自动生成一张图片,而我们项目中使用的是activiti modeler实现的在线画流程图,部署时没有图片,是由activiti自动生成。而activiti在生成图片时,会对图片做一个裁剪操作,所有最终 各个节点的坐标会比实际的要小。
(即:activiti自动生成的图片,会做一个裁剪操作,各个节点实际的x和y的值比xml文件中的要小)
而我们的难点在于,各个节点实际坐标的获取。如下图所示:
即我们实际上获取的坐标需要在减去一个minX和minY,得到的才是我们的各个节点实际的坐标。
步骤分析:
1.根据流程定义的key,重新生成流程图片,而不是获取流程图片。
2.还是根据流程定义的key,获取各个节点的信息。(此处需要注意的是各个节点实际的x和y的值的获取的方法)
3.在jsp页面上使用绝对定位,给点击的节点加上高亮。
步骤实现: 1.根据流程定义的key,重新生成流程图片,而不是获取流程图片。
此处重新生成图片的原因:
因为有些时候我们在部署流程时,将图片也部署进去了,此时使用的就是自己的图片,activiti不会进行图片的裁剪。因为我在下一步获取流程节点的信息时,对x和y的进行了特殊处理,因此此处需要重新生成 流程图片。
/**
* 根据流程的key生成图片
*
* @param request
* @param response
* @param wfKey 流程定义的key
*/
@RequestMapping("/genericImageByWfKey")
public void genericImageByWfKey(HttpServletRequest request, HttpServletResponse response, String wfKey) {
Context.setProcessEngineConfiguration(processEngineConfiguration);
RepositoryService repositoryService = this.processEngine.getRepositoryService();
ProcessDefinition pd = repositoryService.createProcessDefinitionQuery().processDefinitionKey(wfKey).latestVersion().singleResult();
BpmnModel bm = repositoryService.getBpmnModel(pd.getId());
InputStream is = ProcessDiagramGenerator.generatePngDiagram(bm); // 生成图片,获取图片的输入流
try {
int size = is.available();
byte data[] = new byte[size];
is.read(data);
response.setContentType("image/png"); // 设置返回的文件类型
OutputStream os = response.getOutputStream();
os.write(data);
os.flush();
os.close();
} catch (IOException e) {
log.error("读写流程图时出现异常!");
}
log.info("end....");
}
2.还是根据流程定义的key,获取各个节点的信息。
获取各个节点的坐标之前,我们先看一下activiti中是如果获取到最小的x和y的,然后是如何裁剪图片的
2.1获取节点包括线的最小x和最小y :
跟踪acticiti的源码可以发现,最小x和y的获取(org.activiti.engine.impl.bpmn.diagram.ProcessDiagramGenerator.initProcessDiagramCanvas(BpmnModel))
protected static ProcessDiagramCanvas initProcessDiagramCanvas(BpmnModel bpmnModel) {
// We need to calculate maximum values to know how big the image will be in its entirety
double minX = Double.MAX_VALUE;
double maxX = 0;
double minY = Double.MAX_VALUE;
double maxY = 0;
for (Pool pool : bpmnModel.getPools()) {
GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(pool.getId());
minX = graphicInfo.getX();
maxX = graphicInfo.getX() + graphicInfo.getWidth();
minY = graphicInfo.getY();
maxY = graphicInfo.getY() + graphicInfo.getHeight();
}
List<FlowNode> flowNodes = gatherAllFlowNodes(bpmnModel);
for (FlowNode flowNode : flowNodes) {
GraphicInfo flowNodeGraphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
// width
if (flowNodeGraphicInfo.getX() + flowNodeGraphicInfo.getWidth() > maxX) {
maxX = flowNodeGraphicInfo.getX() + flowNodeGraphicInfo.getWidth();
}
if (flowNodeGraphicInfo.getX() < minX) {
minX = flowNodeGraphicInfo.getX();
}
// height
if (flowNodeGraphicInfo.getY() + flowNodeGraphicInfo.getHeight() > maxY) {
maxY = flowNodeGraphicInfo.getY() + flowNodeGraphicInfo.getHeight();
}
if (flowNodeGraphicInfo.getY() < minY) {
minY = flowNodeGraphicInfo.getY();
}
for (SequenceFlow sequenceFlow : flowNode.getOutgoingFlows()) {
List<GraphicInfo> graphicInfoList = bpmnModel.getFlowLocationGraphicInfo(sequenceFlow.getId());
for (GraphicInfo graphicInfo : graphicInfoList) {
// width
if (graphicInfo.getX() > maxX) {
maxX = graphicInfo.getX();
}
if (graphicInfo.getX() < minX) {
minX = graphicInfo.getX();
}
// height
if (graphicInfo.getY() > maxY) {
maxY = graphicInfo.getY();
}
if (graphicInfo.getY()< minY) {
minY = graphicInfo.getY();
}
}
}
}
int nrOfLanes = 0;
for (Process process : bpmnModel.getProcesses()) {
for (Lane l : process.getLanes()) {
nrOfLanes++;
GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(l.getId());
// // width
if (graphicInfo.getX() + graphicInfo.getWidth() > maxX) {
maxX = graphicInfo.getX() + graphicInfo.getWidth();
}
if (graphicInfo.getX() < minX) {
minX = graphicInfo.getX();
}
// height
if (graphicInfo.getY() + graphicInfo.getHeight() > maxY) {
maxY = graphicInfo.getY() + graphicInfo.getHeight();
}
if (graphicInfo.getY() < minY) {
minY = graphicInfo.getY();
}
}
}
// Special case, see http://jira.codehaus.org/browse/ACT-1431
if (flowNodes.size() == 0 && bpmnModel.getPools().size() == 0 && nrOfLanes == 0) {
// Nothing to show
minX = 0;
minY = 0;
}
return new ProcessDiagramCanvas((int) maxX + 10,(int) maxY + 10, (int) minX, (int) minY);
}
2.2 图片的裁剪:
还是activiti的源码:(org.activiti.engine.impl.bpmn.diagram.ProcessDiagramCanvas.generateImage(String))
/** * Generates an image of what currently is drawn on the canvas. * * Throws an {@link ActivitiException} when {@link #close()} is already * called. */ public InputStream generateImage(String imageType) { if (closed) { throw new ActivitiException("ProcessDiagramGenerator already closed"); } ByteArrayOutputStream out = new ByteArrayOutputStream(); try { // Try to remove white space minX = (minX <= 5) ? 5 : minX; minY = (minY <= 5) ? 5 : minY; BufferedImage imageToSerialize = processDiagram; if (minX >= 0 && minY >= 0) { // 此处的最小x和最小y减去了5 imageToSerialize = processDiagram.getSubimage(minX - 5, minY - 5, canvasWidth - minX + 5, canvasHeight - minY + 5); // 此处可以看到,activiti对图像做了裁剪的操作。 } ImageIO.write(imageToSerialize, imageType, out); } catch (IOException e) { throw new ActivitiException("Error while generating process image", e); } finally { IoUtil.closeSilently(out); } return new ByteArrayInputStream(out.toByteArray()); }
2.3 我们自己的各个节点的信息获取
@RequestMapping("/getProcessTrace") @ResponseBody /** * 获取各个节点的具体的信息 * @param wfKey * 流程定义的key * @return */ public List<Map<String, Object>> getProcessTrace(String wfKey) throws Exception { List<Map<String, Object>> activityInfos = new ArrayList<Map<String, Object>>(); RepositoryService repositoryService = processEngine.getRepositoryService(); ProcessDefinition pd = repositoryService.createProcessDefinitionQuery().processDefinitionKey(wfKey).latestVersion().singleResult(); ProcessDefinitionEntity processDefinition = (ProcessDefinitionEntity) ((RepositoryServiceImpl) repositoryService).getDeployedProcessDefinition(pd.getId()); List<ActivityImpl> activitiList = processDefinition.getActivities(); InputStream xmlIs = repositoryService.getResourceAsStream(processDefinition.getDeploymentId(), processDefinition.getResourceName()); BpmnModel bm = new BpmnXMLConverter().convertToBpmnModel(new InputStreamSource(xmlIs), false, true); // 下方使用反射获取最小的x和y,仔细看就会发现调用的是上方2.1节的方法 Class<?> clazz = Class.forName("org.activiti.engine.impl.bpmn.diagram.ProcessDiagramGenerator"); Method method = clazz.getDeclaredMethod("initProcessDiagramCanvas", BpmnModel.class); method.setAccessible(true); ProcessDiagramCanvas pdc = (ProcessDiagramCanvas) method.invoke(clazz.newInstance(), bm); // 调用方法 clazz = Class.forName("org.activiti.engine.impl.bpmn.diagram.ProcessDiagramCanvas"); Field minXField = clazz.getDeclaredField("minX"); // 得到minX字段 Field minYField = clazz.getDeclaredField("minY"); minXField.setAccessible(true); minYField.setAccessible(true); int minX = minXField.getInt(pdc);// 最小的x值 int minY = minYField.getInt(pdc); // 最小的y的值 minX = minX > 0 ? minX - 5 : 0; // 此处为什么需要减5,上方2.2中activiti源码中有 minY = minY > 0 ? minY - 5 : 0; for (ActivityImpl activity : activitiList) { Map<String, Object> activityInfo = new HashMap<String, Object>(); activityInfo.put("width", activity.getWidth()); activityInfo.put("height", activity.getHeight()); activityInfo.put("x", activity.getX() - minX); activityInfo.put("y", activity.getY() - minY); activityInfo.put("actId", activity.getId()); activityInfo.put("name", activity.getProperty("name")); // ActivityImpl 中没有getName方法,所以此处需要这样获取。 activityInfos.add(activityInfo); } return activityInfos; }
3.在jsp页面上使用绝对定位,给点击的节点加上高亮。
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> <style type="text/css"> .activity-attr{border-radius: 10px; border: 3px solid red; transition:ease-out 0.5s; box-shadow:0px 0px 9px red;} #processKey{color: red;} #flowImageAndRect{position: relative;overflow: scroll;height:300px; heibackground-image: url('${ctx}/resources/images/workflow/grid_10.png')} body,html{margin: 0px;padding:0px;} </style> <script type="text/javascript"> $(function(){ var wfKey = '${param.wfKey}'; // 流程定义的key var $flowImageAndRect = $('#flowImageAndRect'); $('#processKey').html('流程定义的key --> ' + wfKey); // 加载流程图片 loadProcessImage(wfKey,$flowImageAndRect); // 加载各节点信息,最终实现,在点击图片上的各节点时,出现高亮 setTimeout(function(){ loadProcessTrace(wfKey,$flowImageAndRect); },200); var $revClickRect = null; // 上次点击的图形 // 绑定click事件,点击实现,只有点击的不是同一个时,才显示红色的边框(如果多次点击同一个,红色的边框只出现一次) $('#flowImageAndRect').off('click').on('click','.activity-attr',function(e){ var $this = $(this); var prevFlag = false; // 是上一个图形,避免多次点击同一个 if($revClickRect){ // 说明不是第一次点击 prevFlag = ($revClickRect.attr('actId')!=$this.attr('actId')) ? false : true;// 说明2次点击的不是同一个 if(!prevFlag) $revClickRect.css('opacity','0'); } if(!prevFlag){ // 此处可以请求后台,加载相关的数据(多次点击同一个,下方可确保只执行一次) $this.css('opacity','1'); // 显示当前的 $revClickRect = $this; // 将当前设置为上次点击的 $('#info').html('节点ID --> ' + $this.attr('actId') + " | " + "节点名称 --> " + $this.attr('name')); } }); }); /** * 加载图片 */ function loadProcessImage(wfKey,$flowImageAndRect){ var imageUrl = '${ctx}/workflow/monitor/genericImageByWfKey.do?wfKey='+wfKey; // 加载图片 $('<img />',{ "src" : imageUrl, "alt" : '' }).appendTo($flowImageAndRect); } /** * 加载流程中各节点的信息 * @param wfKey : 流程定义的key * @param $flowImageAndRect */ function loadProcessTrace(wfKey,$flowImageAndRect){ var traceUrl = '${ctx}/workflow/monitor/getProcessTrace.do?wfKey='+wfKey; $.getJSON(traceUrl,function(infos){ var html = ""; $.each(infos,function(i,v){ // 矩形的div var $div = $('<div/>', { 'class': 'activity-attr' }).css({ position: 'absolute', left: v.x, top: v.y, width: v.width - 3, height:v.height - 3, opacity: 0, zIndex: 100, cursor : 'pointer' }).attr({'actId':v.actId,'name':v.name}); html += $div.prop("outerHTML"); }); $('<div />',{'id':'processRect'}).html(html).appendTo($flowImageAndRect); }); } </script> </head> <body> <div id="main"> <div id="flowImageAndRect"> </div> <div id="processKey" style="font-size: 52px;text-align: center;margin-bottom: 50px;"> </div> <div id="info" style="font-size: 52px;text-align: center;"> </div> </div> </body> </html>
到此,已经完成了。
相关推荐
Activiti12流程跟踪任务节点和线高亮显示,能标记流程所走过节点和线为红色,未走过的节点和线不标记,支持驳回和子流程。
Activiti流程图部署及流程图部分操作 流程图部署及流程图部分操作 流程图部署有两种⽅式,⼀种是通过classpath,另⼀种是通过zip⽂件 通过classpath⽅式如下 public void deploymentProcessDefinition_classpath() {...
Activiti 学习笔记十:开始活动节点(判断流程是否结束及查询历史)
使用activiti工作流引擎显示流程图时高亮显示流程图中已执行节点和已执行路径方法源代码
Activiti流程跟踪监控图路径线和节点高亮显示,本实例提供了两种方式,都可以生成png图片。
Activiti6-流程跟踪监控图-节点-流程线高亮显示-支持通过、不通过、驳回、退回 支持内容: 已完成节点高亮显示、当前执行中节点红色显示 支持一个节点多条流出线,包括通过、不通过、驳回、退回,按照已执行操作正确...
伪汇总审批,就是每一条流程都是独立的,这些独立的流程在走到某个节点的时候,这个节点的审批人可以一次性进行多个任务的审批
在用activiti的时候经常遇到取会签人员的问题,这个文档解决怎么获取会签人员。
NULL 博文链接:https://cooperay.iteye.com/blog/1457161
activiti生成图片--解决坐标错位问题, 生成的图片高亮显示任务节点,图片中的组件坐标与bpmn文件里面的坐标一致,流程跟踪也方便,可执行main方法后,查看生成图片的效果
使用activiti工作流引擎显示流程图时,高亮显示流程图中已执行节点和已执行路径方法,的源代码
Activiti流程跟踪监控图路径线和节点高亮显示,本实例提供了网页生成的方式。
本代码,技术为springboot activiti 工作流引擎技术,人员可以动态设置自定义流程,后台生成流程图并且部署流程,
已完成节点高亮显示、当前执行中节点红色显示 支持一个节点多条流出线,包括通过、不通过、驳回、退回,按照已执行操作正确显示高亮流程线
7.6:获取流程定义文档的资源(查看流程图附件) 22 7.7:附加功能:查询最新版本的流程定义 23 7.8:附加功能:删除流程定义(删除key相同的所有不同版本的流程定义) 23 7.9:总结 24 8:流程实例、任务的执行 25 ...
6. ManagementService: ManagementService提供了对Activiti流程引擎的管理和维护功能,这些功能不在工作流驱动的应用程序中使用,主要用于Activiti系统的日常维护。 7. HistoryService: HistoryService用于获取正在...
Activiti 学习笔记九:并行网关(parallelGateWay)
15.68. 获得流程实例的流程图 - URL参数 15.69. 获得流程实例的流程图 - 响应码 15.70. 获得流程实例的参与者 - URL参数 15.71. 获得流程实例的参与者 - 响应码 15.72. 为流程实例添加一个参与者 - URL参数 ...
用的是activiti6,demo中有bpmn流程图、创建流程、启动流程、得到代办、代办人传值、代办审批、节点之间传数据以及删除流程实例等操作。bpmn图,你需要下载插件要不然看到的是XML文件