activiti 動態表單+easyui 實現稽核流程功能
阿新 • • 發佈:2019-01-02
之前實現的動態表單的啟動功能,現在把稽核功能也做個總結。
稽核流程介面 最終效果圖:
主要需要實現的是一下功能點:
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.javapublic 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;
}
ProcInst.java
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;
}
TaskInst.java
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;
}