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'"
						οnclick="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;
}








评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值