activiti 动态表单+easyui 实现审核流程功能

               

之前实现的动态表单的启动功能,现在把审核功能也做个总结。

审核流程界面 最终效果图:


主要需要实现的是一下功能点:

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">.fitemmargin: 5px;}.fitem labeldisplay: inline-block; width: 120pxtext-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 BaseEntityprivate 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;}

ProcInst.java
public class ProcInst extends BaseEntityprivate 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;}

TaskInst.java
public class TaskInst extends BaseEntityprivate 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 BaseEntityprivate static final long serialVersionUID = 1L; String id; String rev; String name; String deploymentId; byte[] bytes;}








           

再分享一下我老师大神的人工智能教程吧。零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!https://blog.csdn.net/jiangjunshow

猜你喜欢

转载自blog.csdn.net/qq_43724942/article/details/86136423