之前实现的动态表单的启动功能,现在把审核功能也做个总结。
审核流程界面 最终效果图:
主要需要实现的是一下功能点:
1. 列表页面
1.1.待办任务页面。列表中显示当前用户可以处理的流程。
1.2.运行中的流程。列表中显示当前用户 待办 或者 参与过并且未结束 的流程。
1.3.已结束的流程。列表中显示当前用户 参与过并且已结束 的流程。
2.审核页面
2.1 审核列表。 显示流程审核节点的流程中相关审核情况。根据是是待办人决定是否显示 审核操作里的 【通过】 【退回】等按钮。非待办人不能操作流程。
2.2 具体业务的动态表单。根据节点配置的表单属性配置显示动态表单。类是启动流程功能里的start节点配置。同一流程实例不同审核人可以根据配置的不同显示不同的业务表单。如上面图片中的任务分配节点可以配置一个任务分配的下拉框。这个下拉框控件只在任务分配节点显示,其他节点则不会显示。
待办任务页面点击任务连接进入审核页面具体实现:
ProcInstController.java
/** * 审核流程页面 */ @RequestMapping(value = "index") public String index(Model model,String pid,RedirectAttributes attr) { String pageUrl = ""; ProcInst pi = procInstService.getEntityById(pid); //动态表单,外置表单,普通表单(普通表单使用c_前缀,外置表单使用ex_前缀,其他的是动态表单)。参考procDefList.jsp页面 ProcDef pd = procDefService.getEntityById(pi.getProcDefId()); pi.setProcDef(pd); if(pd.getKey().indexOf("c_") == 0){//普通表单 }else if(pd.getKey().indexOf("ex_") == 0){//外置表单 //外置表单, 与动态表单不同的是根据表单key获取事先定义好的表单,不需要系统自动生成。 }else { //动态表单 List<FormAttr> formAttrlist = procInstService.getFormAttrList(pi); model.addAttribute("pi", pi); model.addAttribute("formAttrlist", formAttrlist); //审核流程列表 List<TaskInst> tasks = taskInstService.getTaskList(pi.getId()); model.addAttribute("tasks",tasks); model.addAttribute("tasksSize",tasks.size()); //流程变量 List<HistoricVariableInstance> varList = historyService.createHistoricVariableInstanceQuery().processInstanceId(pi.getId()).list(); Map<String,Object> varMap = new HashMap<String,Object>(); for (HistoricVariableInstance variableInstance : varList) { varMap.put(variableInstance.getVariableName(), variableInstance.getValue()); } model.addAttribute("varMap",varMap); pageUrl = "/system/workflow/hi/reviewed"; } return pageUrl; }
reviewed.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%><%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%><!DOCTYPE html ><html><head><title>启动流程</title><%@include file="/common/base.jsp"%><script type="text/javascript"> $(function(){ $('input[type="checkbox"]').each(function(index,element){ $(element).click(function(){ element.value = element.checked; }); }) $('.easyui-datebox').each(function(index,element){ if($(element).attr("defaultVal")){ $(element).datebox('setValue', $(element).attr("defaultVal")); } }) $('#dg').datagrid(); $('#dg').remove(); }) function startProc(){ $('#fm').mySubmit({ url : 'backstage/workflow/hi/startProc', success: function(res){ closeWin(); } }); } function completeTask(){ $('#fm').mySubmit({ url : 'workflow/hi/ProcInstController/completeTask', success: function(res){ closeWin(); } }); } function closeWin(){ //关闭easyui弹出窗口 parent.window.$(".panel-tool-close").click(); //关闭layer弹出窗口 var index = parent.layer.getFrameIndex(window.name); //获取窗口索引 parent.layer.close(index); }</script><style type="text/css">.fitem { margin: 5px;}.fitem label { display: inline-block; width: 120px; text-align: right;}</style></head><body> <div class="ftitle" style="text-align: center; font-size: 26px; padding: 5px;">${pd.name }</div> <form id="fm" method="post"> <div class="container" class=""> <table id="dg" title="审核列表" toolbar="#tt" style="width: 100%; max-height: 300px; min-height: 120px; margin: 0 auto;"> <thead> <tr> <th width="110" align="center" data-options="field:'name'">任务名称</th> <th width="90" align="center" data-options="field:'assigneeName'">处理人</th> <th width="240" align="center" data-options="field:'comment'">批注</th> <th width="140" align="center" data-options="field:'endTime'">操作时间</th> </tr> </thead> <tbody> <c:forEach var="task" items="${tasks}"> <c:choose> <c:when test="${task.isCurAccount == 0}"> <tr> <td class="center">${task.name }</td> <td class="center">${task.assigneeName }</td> <td class="center">${task.comment }</td> <td class="center"><fmt:formatDate value="${task.endTime}" pattern="yyyy-MM-dd HH:mm" /></td> </tr> <c:forEach var="user" items="${task.candidateUsers}"> <tr> <td class="center"></td> <td class="center">${user.name }</td> <td class="center"></td> <td class="center"></td> </tr> </c:forEach> </c:when> <c:otherwise> <tr> <td class="center">${task.name }</td> <td class="center">${task.assigneeName }</td> <td class="center"><input type="hidden" name="taskId" value="${task.id}"> <input type="text" required='true' name="comment" style="width: 100%; height: 26px;" placeholder="请输入审核意见"> </td> <td class="center"></td> </tr> <c:forEach var="user" items="${task.candidateUsers}"> <tr> <td class="center"></td> <td class="center">${user.name }</td> <td class="center"></td> <td class="center"></td> </tr> </c:forEach> </c:otherwise> </c:choose> </c:forEach> </tbody> </table> <div id="tt"> <div style='${tasks.get(tasksSize-1).isCurAccount != 1? "display:none;" : ""} '> <a href="javascript:void(0)" title="通过" class="easyui-linkbutton" data-options="plain:true,iconCls:'icon-ok'" onclick="completeTask()">通过</a> <a href="javascript:void(0)" title="退回" class="easyui-linkbutton" data-options="plain:true,iconCls:'icon-back'">退回</a> <a href="javascript:void(0)" class="easyui-linkbutton" data-options="plain:true,iconCls:'icon-help'"></a> </div> </div> </div> <br /> <div id="p" class="easyui-panel" title="流程详细信息"> <c:forEach var="attr" items="${formAttrlist}" varStatus="status"> <div class="fitem" style="${attr.toReadableStr()}"> <label>${attr.name}:</label> <c:choose> <c:when test='${"string".equals(attr.type)}'> <input ${attr.toWritableStr()} class="easyui-validatebox" ${attr.toRequiredStr()} value="${varMap.get(attr.id) }"> </c:when> <c:when test='${"long".equals(attr.type)}'> <input ${attr.toWritableStr()} class="easyui-numberspinner" data-options="increment:1" ${attr.toRequiredStr() } value="${varMap.get(attr.id) }" /> </c:when> <c:when test='${"boolean".equals(attr.type)}'> <input type="checkbox" ${attr.toRequiredStr()} value="${varMap.get(attr.id)}" ${varMap.get(attr.id)? "checked='checked'" : ""} class="easyui-checkbox" ${attr.toWritableStr()} /> </c:when> <c:when test='${"date".equals(attr.type)}'> <input ${attr.toWritableStr()} class="easyui-datebox" defaultVal="${varMap.get(attr.id) }"> <%-- <input id="${attr.id}" ${attr.toWritableStr()} class="easyui-datebox" --%> <%-- data-options="sharedCalendar:'#div${attr.id}'" ${attr.toRequiredStr()}> --%> <%-- <div id="div${attr.id}" class="easyui-calendar"></div> --%> </c:when> <c:when test='${"enum".equals(attr.type)}'> <!-- <input class="easyui-combobox" name="fundKind.id" id="kindId" --> <!-- data-options="valueField:'id',textField:'name',url:'FundKind/list/false'"> --> <select class="easyui-combobox" ${attr.toWritableStr()} style="width: 173px;"> <c:forEach var="node" items="${attr.selects}"> <option value="${node.attrMap.id}" ${node.attrMap.id.equals(varMap.get(attr.id))? "selected" : "" }>${node.attrMap.name}</option> </c:forEach> </select> </c:when> <c:otherwise></c:otherwise> </c:choose> </div> </c:forEach> </div> </form></body></html>
1. 当前节点 业务表单
//动态表单List<FormAttr> formAttrlist = procInstService.getFormAttrList(pi);
这个是打开审核页面时获取流程节点当前表单配置属性列表。类似于启动流程中获取start节点一样,只是多了个判断,即先要判断流程当前运行到哪个节点了,然后再获取该节点的表单属性配置。具体实现:
public List<FormAttr> getFormAttrList(ProcInst pi) { List<FormAttr> formMap = new ArrayList<FormAttr>(); ByteArray ba = pi.getProcDef().getByteArray(); String xml = null; try { xml = new String(ba.getBytes(), "utf-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } Node root = XMLTools.Dom2Map(xml); Node processNode = root.getSubNodeByName("process"); //获取当前流程待执行的结点 String actId = getLastActId(pi.getId()); List<Node> list = processNode.getChildrenList(); Node lastNode = null; for (Node node : list) { if(actId.equals(node.getAttrMap().get("id"))){ lastNode = node; } } Node extensionElements = lastNode.getSubNodeByName("extensionElements"); if(extensionElements != null){ List<Node> formPropertyList = extensionElements.getChildrenList(); for (Node formProperty : formPropertyList) { FormAttr fa = new FormAttr(formProperty); formMap.add(fa); } } return formMap; }
2.获取当前实例的流程变量,然后对 业务表单 进行初始化赋值。
//流程变量 List<HistoricVariableInstance> varList = historyService.createHistoricVariableInstanceQuery().processInstanceId(pi.getId()).list(); Map<String,Object> varMap = new HashMap<String,Object>(); for (HistoricVariableInstance variableInstance : varList) { varMap.put(variableInstance.getVariableName(), variableInstance.getValue()); } model.addAttribute("varMap",varMap);
3.审核列表。比较复杂的和关键的一个逻辑功能模块,暂时先只考虑常用的情景。
List<TaskInst> tasks = taskInstService.getTaskList(pi.getId());
public List<TaskInst> getTaskList(String pid) { List<TaskInst> tasks = taskInstDao.getTaskList(pid); int size = tasks.size(); TaskInst lastTaskInst = tasks.get(size-1); Account curAccount = AccountShiroUtil.getCurrentUser(); if(lastTaskInst.getEndTime() == null){ List<Account> candidateUsers = null; if(lastTaskInst.getAssignee() != null && !lastTaskInst.getAssignee().equals("")){ //任务已签收,不用管配置的候选用户了,可以看做只有签收人一个候选用户 Account account = new Account(); account.setAccountId(lastTaskInst.getAssignee()); candidateUsers = accountDao.find(account); //这个可能size=0,表示配置的Assignee值可能不在用户表里 if(candidateUsers.size() == 0){ lastTaskInst.setAssignee(null); } } if(lastTaskInst.getAssignee() == null || lastTaskInst.getAssignee().equals("")){ //通过流程配置的候选用户和候选组获取所有候选用户 candidateUsers = taskInstDao.getCandidateUsers(lastTaskInst.getId()); } lastTaskInst.setCandidateUsers(candidateUsers); //判断当前用户是否在候选用户里,在则把当前用户移除,并且将最后一个任务指派人改为当前用户(没有考虑已签收,但还没有处理的情况) for (int i = 0; i < candidateUsers.size(); i++) { if(curAccount.getAccountId().equals(candidateUsers.get(i).getAccountId())){ Account account = candidateUsers.remove(i); lastTaskInst.setIsCurAccount(1); lastTaskInst.setAssignee(account.getAccountId()); lastTaskInst.setAssigneeName(account.getName()); break; } } } //第一行添加发起流程任务 TaskInst tk = taskInstDao.getStartProcTask(pid); tasks.add(0, tk); return tasks; }
4.相关的一些dao层sql
procInstService.getEntityById(pid)
<!-- 通过id获取对象 --> <select id="getEntityById" resultMap="pi" parameterType="String"> select pi.id_ as id,pi.proc_inst_id_ as procInstId,pi.business_key_ as businessKey, pi.proc_def_id_ as procDefId,pi.start_time_ as startTime,pi.end_time_ as endTime, pi.duration_ as duration, pi.start_user_id_ as startUserId,pi.start_act_id_ as startActId, pi.end_act_id_ as endActId, pi.super_process_instance_id_ as superPrcessInstanceId, pi.delete_reason_ as deleteReason, pi.tenant_id_ as tenantId,pi.name_ as name,pi.summary as summary from ACT_HI_PROCINST pi where pi.proc_inst_id_=#{id} </select>
procDefService.getEntityById(pi.getProcDefId())
<!-- 通过id获取对象 --> <select id="getEntityById" resultMap="procDef" parameterType="String"> select pd.id_ as id ,pd.name_ as name ,pd.key_ as key ,pd.version_ as version ,pd.category_ as category,pd.deployment_id_ as deploymentId, pd.resource_name_ as resourceName ,pd.dgrm_resource_name_ as dgrmResourceName ,pd.description_ as description ,ba.id_ as ba_id ,ba.rev_ as ba_rev ,ba.name_ as ba_name ,ba.deployment_id_ as ba_deploymentId ,ba.bytes_ as ba_bytes from ACT_RE_PROCDEF pd left join ACT_GE_BYTEARRAY ba on pd.deployment_id_=ba.deployment_id_ and pd.resource_name_=ba.name_ where pd.id_ = #{id} </select>
taskInstDao.getTaskList(pid)
<select id="getTaskList" resultMap="base" parameterType="String"> select t.id_, t.proc_Def_Id_, t.task_Def_Key_, t.proc_Inst_Id_, t.name_, t.description_, t.start_time_, t.end_time_, t.owner_, t.assignee_ , a.name as assigneeName, t.form_Key_ ,c.message_ as lastComment from ACT_HI_TASKINST t left join jy_base_account a on t.assignee_=a.id left join( select c.*,ROW_NUMBER() OVER(partition by c.task_id_ order by c.time_ desc) as req from act_hi_comment c )c on c.task_id_=t.id_ and req=1 where t.proc_inst_id_=#{pid} order by t.start_time_ </select>
taskInstDao.getCandidateUsers(lastTaskInst.getId())
<select id="getCandidateUsers" resultMap="com.jy.repository.system.account.AccountDao.base" parameterType="String"> select a.id, a.loginName, a.roleId, jbr.name as roleName, a.name, a.picUrl, a.email, a.isValid, a.createTime, a.updateTime, a.skin, a.description from ( select Translate(t.user_id_ USING CHAR_CS) as accountid from ACT_HI_IDENTITYLINK t where task_id_=#{taskId} and t.user_id_ is not null union select ap.accountid as accountid from ACT_HI_IDENTITYLINK t left join jy_base_position p on t.group_id_=p.name left join jy_base_account_position ap on ap.posid=p.id where task_id_=#{taskId} and t.group_id_ is not null )t1 inner join jy_base_account a on t1.accountid=a.id LEFT JOIN JY_BASE_ROLE jbr ON jbr.id=a.roleId </select>
taskInstDao.getStartProcTask(pid)
<!-- 获取流程发起人,并封装为发起流程任务 --> <select id="getStartProcTask" resultMap="base" parameterType="String"> select '发起流程' as id_,'发起流程' as name_,t.start_user_id_ as assignee_, a.name as assigneeName,t.start_time_ end_Time_ from act_hi_procinst t left join jy_base_account a on t.start_user_id_=a.id where t.proc_inst_id_=#{pid} </select>
5.流程相关的实体。
ProcDef.java
public class ProcDef extends BaseEntity{ private static final long serialVersionUID = 1L; private String id; private String name; private String key; private String version; private String category; private String deploymentId; private String resourceName; private String dgrmResourceName; private String description; private ByteArray byteArray;}
public class ProcInst extends BaseEntity{ private static final long serialVersionUID = 1L; private String id; private String procInstId; private String businessKey; private String procDefId; private Date startTime; private Date endTime; private Long duration; //耗时 private String startUserId; private Account startUser; private String startActId; private String endActId; private String superPrcessInstanceId; private String deleteReason; private String tenantId; private String name; private String summary; private ProcDef procDef;}
public class TaskInst extends BaseEntity{ private String id; private String processDefinitionId; private String taskDefinitionKey; private String processInstanceId; private ProcInst pi; private String name; private String description; private Date startTime; private Date endTime; private String owner; private String assignee; private String assigneeName; private String formKey; private String comment; private int isCurAccount;//0表示该任务不是当前用户处理,1表示是该用户处理 private List<Account> candidateUsers; private List<Comment> comments;}
ByteArray.java
public class ByteArray extends BaseEntity{ private static final long serialVersionUID = 1L; String id; String rev; String name; String deploymentId; byte[] bytes;}
再分享一下我老师大神的人工智能教程吧。零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!https://blog.csdn.net/jiangjunshow