1. 程式人生 > >activiti 動態表單+easyui 實現稽核流程功能

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">
.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;
}
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;
}