接下来,就是来开发咱们的投诉受理管理模块了…..咱们来看看原型图与需求吧:javascript
查询用户提交的投诉信息,能够根据投诉部门(部门A/B)、投诉时间段、状态进行查询。在列表信息中展现投诉标题、被投诉部门、被投诉人、投诉时间、状态(待受理、已受理、已失效)、操做;其中操做栏内内容为“处理”,点击“处理”则在打开的查询页面中查看具体的投诉信息而且能够屡次回复投诉信息;一旦回复则说明已受理该投诉。css
投诉详细信息:在本页面中首先要明显地展现出当前投诉是否已经受理;而后再显示投诉人信息、被投诉信息、受理信息(历史受理信息)三部份内容,而且在页面中能够无限次的对本次受理进行回复。投诉人信息包括:是否匿名投诉、投诉人单位、投诉人姓名、投诉人手机,若是是匿名投诉,则不显示投诉人单位、姓名并对手机号中间4位号码使用*号代替。被投诉信息包括:投诉时间、被投诉部门、被投诉人、投诉标题、投诉内容。受理信息:若是有屡次回复则将屡次的回复信息显示,显示内容包括回复时间、回复部门、回复人、受理回复内容;能够再次回复。java
根据上面两张原型图以及文字说明,咱们能够发现:一个投诉信息可对应多个回复。ajax
在“工做主页”中点击“我要投诉”进入页面,添加内容包括:投诉标题、被投诉部门(部门A/B)、被投诉人、投诉详情、是否匿名投诉。sql
关键在于匿名投诉的那一部分,咱们该怎么写….数据库
统计:根据年度将相应年度的每月的投诉数进行统计,并以图表的形式展现在页面中;在页面中能够选择查看当前年度及其前4年的投诉数。在页面中能够选择不一样的年度,而后页面展现该年度的曲线统计图。apache
这个统计图,大概也须要用到组件来生成出来的吧???json
自动投诉受理:在每月月底最后一天对本月以前的投诉进行自动处理;将投诉信息的状态改成 已失效。在后台管理中不能对该类型投诉进行回复。浏览器
自动投诉受理??在每月的最后一天判断投诉信息,程序对其自动受理。。服务器
咱们首先来画一个流程图看看它的大概思路是怎么样的:
咱们通过上面的分析,知道了:一个投诉信息可对应多个回复。是一对多的关系。咱们下面使用powerdesginer来画出它的概念数据模型图
生成物理模型图:
生成数据库表:
/*==============================================================*/ /* DBMS name: MySQL 5.0 */ /* Created on: 2017/6/12 19:06:20 */ /*==============================================================*/ drop table if exists complain; drop table if exists complain_reply; /*==============================================================*/ /* Table: complain */ /*==============================================================*/ create table complain ( comp_id varchar(32) not null, comp_company varchar(100), comp_name varchar(20), comp_mobile varchar(20), is_NM bool, comp_time datetime, comp_title varchar(200) not null, to_comp_name varchar(20), to_comp_dept varchar(100), comp_content text, state varchar(1), primary key (comp_id) ); /*==============================================================*/ /* Table: complain_reply */ /*==============================================================*/ create table complain_reply ( reply_id varchar(32) not null, comp_id varchar(32) not null, replyer varchar(20), reply_dept varchar(100), reply_time datetime, reply_content varchar(300), primary key (reply_id) ); alter table complain_reply add constraint FK_comp_reply foreign key (comp_id) references complain (comp_id) on delete restrict on update restrict;
生成实体与配置文件:
Intellij idea下生成出来的映射文件是没有对应的关联关系的。也就是说:一对多或多对多的关系,它是不会帮你自动生成的【好像是这样子的】。。。所以,须要咱们本身添加Set【若是须要】
package zhongfucheng.complain.entity; import java.sql.Timestamp; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; /** * Created by ozc on 2017/6/12. */ public class Complain { private String compId; private String compCompany; private String compName; private String compMobile; private Byte isNm; private Timestamp compTime; private String compTitle; private String toCompName; private String toCompDept; private String compContent; private String state; //状态 public static String COMPLAIN_STATE_UNDONE = "0"; public static String COMPLAIN_STATE_DONE = "1"; public static String COMPLAIN_STATE_INVALID = "2"; public static Map<String, String> COMPLAIN_STATE_MAP; static { COMPLAIN_STATE_MAP = new HashMap<String, String>(); COMPLAIN_STATE_MAP.put(COMPLAIN_STATE_UNDONE, "待受理"); COMPLAIN_STATE_MAP.put(COMPLAIN_STATE_DONE, "已受理"); COMPLAIN_STATE_MAP.put(COMPLAIN_STATE_INVALID, "已失效"); } //添加set集合 private Set complainReplies = new HashSet(0); public Set getComplainReplies() { return complainReplies; } public void setComplainReplies(Set complainReplies) { this.complainReplies = complainReplies; } public String getCompId() { return compId; } public void setCompId(String compId) { this.compId = compId; } public String getCompCompany() { return compCompany; } public void setCompCompany(String compCompany) { this.compCompany = compCompany; } public String getCompName() { return compName; } public void setCompName(String compName) { this.compName = compName; } public String getCompMobile() { return compMobile; } public void setCompMobile(String compMobile) { this.compMobile = compMobile; } public Byte getIsNm() { return isNm; } public void setIsNm(Byte isNm) { this.isNm = isNm; } public Timestamp getCompTime() { return compTime; } public void setCompTime(Timestamp compTime) { this.compTime = compTime; } public String getCompTitle() { return compTitle; } public void setCompTitle(String compTitle) { this.compTitle = compTitle; } public String getToCompName() { return toCompName; } public void setToCompName(String toCompName) { this.toCompName = toCompName; } public String getToCompDept() { return toCompDept; } public void setToCompDept(String toCompDept) { this.toCompDept = toCompDept; } public String getCompContent() { return compContent; } public void setCompContent(String compContent) { this.compContent = compContent; } public String getState() { return state; } public void setState(String state) { this.state = state; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Complain complain = (Complain) o; if (compId != null ? !compId.equals(complain.compId) : complain.compId != null) return false; if (compCompany != null ? !compCompany.equals(complain.compCompany) : complain.compCompany != null) return false; if (compName != null ? !compName.equals(complain.compName) : complain.compName != null) return false; if (compMobile != null ? !compMobile.equals(complain.compMobile) : complain.compMobile != null) return false; if (isNm != null ? !isNm.equals(complain.isNm) : complain.isNm != null) return false; if (compTime != null ? !compTime.equals(complain.compTime) : complain.compTime != null) return false; if (compTitle != null ? !compTitle.equals(complain.compTitle) : complain.compTitle != null) return false; if (toCompName != null ? !toCompName.equals(complain.toCompName) : complain.toCompName != null) return false; if (toCompDept != null ? !toCompDept.equals(complain.toCompDept) : complain.toCompDept != null) return false; if (compContent != null ? !compContent.equals(complain.compContent) : complain.compContent != null) return false; if (state != null ? !state.equals(complain.state) : complain.state != null) return false; return true; } @Override public int hashCode() { int result = compId != null ? compId.hashCode() : 0; result = 31 * result + (compCompany != null ? compCompany.hashCode() : 0); result = 31 * result + (compName != null ? compName.hashCode() : 0); result = 31 * result + (compMobile != null ? compMobile.hashCode() : 0); result = 31 * result + (isNm != null ? isNm.hashCode() : 0); result = 31 * result + (compTime != null ? compTime.hashCode() : 0); result = 31 * result + (compTitle != null ? compTitle.hashCode() : 0); result = 31 * result + (toCompName != null ? toCompName.hashCode() : 0); result = 31 * result + (toCompDept != null ? toCompDept.hashCode() : 0); result = 31 * result + (compContent != null ? compContent.hashCode() : 0); result = 31 * result + (state != null ? state.hashCode() : 0); return result; } }
<set name="complainReplies" inverse="true" cascade="save-update,delete" lazy="false" > <key> <column name="comp_id" length="32" not-null="true" /> </key> <one-to-many class="zhongfucheng.complain.entity.ComplainReply" /> </set>
编写dao、service、action都很是简单。记得要把模块的配置文件加载到总配置文件中!
ComplainAction代码以下:
public class ComplainAction extends BaseAction { /*************注入Service************************/ @Autowired private ComplainService complainServiceImpl; /************数据自动封装,给出setter和getter*************************/ private Complain complain; public Complain getComplain() { return complain; } public void setComplain(Complain complain) { this.complain = complain; } /************Action中7大方法*************************/ //抛出Action异常 public String listUI() throws ServiceException, UnsupportedEncodingException { //把状态的集合带过去 QueryHelper queryHelper = new QueryHelper(Complain.class, "c"); //当前页数没有值,那么赋值为1 if (currentPageCount == 0) { currentPageCount = 1; } //把状态带过去给JSP页面 ActionContext.getContext().getContextMap().put("complainStateMap", Complain.COMPLAIN_STATE_MAP); pageResult = complainServiceImpl.getPageResult(queryHelper,currentPageCount); return "listUI"; } }
导入对应的JSP页面…..获得的效果以下:
咱们来看一下条件查询有几个:能够根据投诉的标题、投诉的时间、投诉的状态进行查询。
对于投诉标题和投诉的状态咱们均可以很容易地拿到条件:
//根据complain标题是否为null来判断是不是条件查询。若是complain为空,那么是查询全部。 if (complain != null) { if (org.apache.commons.lang.StringUtils.isNotBlank(complain.getCompTitle())) { selectCondition = URLDecoder.decode(complain.getCompTitle(),"UTF-8"); complain.setCompTitle(selectCondition); queryHelper.addCondition(" c.compTitle like ? ", "%" + complain.getCompTitle() + "%"); } if (org.apache.commons.lang.StringUtils.isNotBlank(complain.getState())) { queryHelper.addCondition(" c.state like ? ", "%" + complain.getState() + "%"); } }
那么根据投诉时间来进行查询,咱们要怎么作呢???咱们约定时间是这样的格式:yyyy-MM-dd HH:mm。这样的格式Struts2默认是不支持解析的,那么咱们怎么获取呢???
有的同窗可能会想到类型转换器,咱们在Struts2的时候的确是学过类型转换器。。。可是呢,这种方法并非最好的。咱们能够使用DateUtils工具类来获得日期数据!
//先判断时间,经过时间进行筛选后,再进行like模糊查询。那么性能会好一些 if (StringUtils.isNotBlank(startTime)) { startTime = URLDecoder.decode(startTime,"UTF-8"); queryHelper.addCondition(" c.compTime >= ? ", DateUtils.parseDate(startTime, new String[]{"yyyy-MM-dd HH:mm"})); } if (StringUtils.isNotBlank(endTime)) { endTime = URLDecoder.decode(endTime,"UTF-8"); queryHelper.addCondition(" c.compTime <= ? ", DateUtils.parseDate(endTime, new String[]{"yyyy-MM-dd HH:mm"})); }
咱们在JSP页面上也使用datepicker组件来让用户选择日期:
投诉时间:<s:textfield id="startTime" name="startTime" cssClass="s_text" cssStyle="width:160px;" readonly="true" onfocus="WdatePicker({'skin':'whyGreen','dateFmt':'yyyy-MM-dd HH:mm'});"/> - <s:textfield id="endTime" name="endTime" cssClass="s_text" cssStyle="width:160px;" readonly="true" onfocus="WdatePicker({'skin':'whyGreen','dateFmt':'yyyy-MM-dd HH:mm'});"/>
提供处理受理的UI界面。根据id查找投诉的所有信息。
//提供受理的UI public String dealUI() { //把状态传递过去 ActionContext.getContext().getContextMap().put("complainStateMap", Complain.COMPLAIN_STATE_MAP); //获得想要受理的记录 if (complain != null) { complain = complainServiceImpl.findObjectById(complain.getCompId()); } return "dealUI"; }
在处理受理的JSP页面上要把投诉的id给发送给Action处理。否则在保存信息的时候,就会把投诉信息丢失了!。在Action中经过id从新查找回投诉的信息!
//受理 public String deal() { //修改投诉信息的处理状态 if (complain != null) { //查找到信息 complain = complainServiceImpl.findObjectById(complain.getCompId()); //若是状态是已处理了,那么就不用再修改了 if (!complain.getState().equals(Complain.COMPLAIN_STATE_DONE)) { complain.setState(Complain.COMPLAIN_STATE_DONE); } } //保存回复的信息 if (reply != null) { //更新回复的日期 reply.setReplyTime(new Timestamp(new Date().getTime())); //把回复信息添加到投诉信息中【关联关系】 reply.setComplain(complain); complain.getComplainReplies().add(reply); } //级联更新 complainServiceImpl.update(complain); return "list"; }
咱们在处理投诉的时候,应该把回复的历史信息给处理的人看…..
把回复的信息遍历出来。
<s:iterator value="complain.complainReplies" status="st"> <tr> <%--获得全部回复的信息--%> <td colspan="2"> <fieldset style="border: solid 1px #c0c0c0;margin-top:5px;"><legend style="color:green;font-weight:bold;"> 回复<s:property value="#st.count"/> </legend> <div style="width:100%; text-align:center;color:#ccc;maring-top:5px;"> 回复部门:<s:property value="replyDept"/> 回复人:<s:property value="replyer"/> 回复时间:<s:date name="replyTime" format="yyyy-MM-dd HH:mm"/> </div> <div style="width:100%;maring-top:10px;font-size:13px;padding-left:5px;"><s:property value="replyContent"/></div> </fieldset> </td> </tr> </s:iterator>
如今有一个问题,就是咱们使用的是set集合,它全部的回复信息并非按照顺序来排列的。咱们能够在hbm配置文件中指定咱们set集合的顺序:
那么在显示的时候,咱们的回复顺序就不会被搞乱了。
在需求中,咱们已经看到了,若是投诉的人是匿名投诉的,那么咱们不能显示该投诉人的名字、部门。他的号码应该设置成137****2342类型的。
其实咱们只要在显示对应值的前面判断该投诉人是不是匿名投诉就好了。
用户能够在首页上经过“我要投诉”超连接对工做人员进行投诉..固然了,用户点击“我要投诉”超连接的时候,应该在新的页面上给出对应的页面,因此指定target为“_blank”..
咱们在指定部门的时候,下拉菜单应该在后台给出对应的的员工。这就须要咱们用到ajax进行二级菜单的二级联动了。
咱们在返回JSON格式有两种方式:第一种就是没有使用Struts2框架的时候,
使用三个开发包commons-beanutils-1.8.0,ezmorph-1.0.6,json-lib-2.3-jdk15。使用JSONObject对象来构建JSON字符串,使用流对象返回给浏览器。
咱们若是使用了Struts2框架的话,直接导入:struts2-json-plugin-2.3.20这么一个开发包,而且在配置文件中指定继承json-defalut包,返回的类型是JSON的话。那么Struts2框架就会自动帮咱们在该Action中所拥有getter方法的属性就生成JSON格式返回给浏览器。。。固然了,咱们可能不想Struts2把所有带有getter的属性都生成JSON返回给浏览器,咱们只要在返回JSON类型上指定参数root,就能够指定生成哪个属性自动生成JSON字符串返回给浏览器了。
固然了,不管是没有使用Struts2框架,仍是有使用Struts2框架,咱们都是有过Demo的。详情请参考博文:http://blog.csdn.net/hon_3y/article/details/72468761和http://blog.csdn.net/hon_3y/article/details/72480126
另外,咱们手动访问Acttion给出对应的参数,就能够看到服务器返回的JSON是什么了。最后咱们使用HiJson这样的工具,就能够把返回的JSON进行格式化。
那么,咱们的代码是这样的:
使用ajax返回服务器。
function doSelectDept() { var $dept = $("#toCompDept option:selected").val(); $.ajax({ type: "post", url: "${basePath}sys/home_getUserJson.action", data: {"dept":$dept}, dataType: "json", success: function (data) { if("success" == data.msg){ var toCompName = $("#toCompName"); toCompName.empty(); $.each(data.userList, function(index, user){ toCompName.append("<option value='" + user.name + "'>" + user.name + "</option>"); }); } else {alert("获取被投诉人列表失败!");} }, error: function () { alert("失败咯") } }); }
使用一个Map集合装载这些数据,Struts2自动把Map集合的数据转成是JSON格式的,返回给浏览器。
private Map<String, Object> return_map; public Map<String, Object> getReturn_map() { return return_map; } public String getUserJson() { //获得带过来的dept String dept = ServletActionContext.getRequest().getParameter("dept"); if (dept != null) { //根据部门查询全部的员工 QueryHelper queryHelper = new QueryHelper(User.class, "u"); queryHelper.addCondition(" u.dept like ? ", "%" +dept); //二、根据部门查询用户列表 return_map = new HashMap(); return_map.put("msg", "success"); return_map.put("userList", userServiceImpl.findObjects(queryHelper)); } return "success"; }
咱们在投诉的内容上添加上富文本框,让用户能够在文本域上传上图片….
加上一个富文本框是很是简单的,只要导入对应的js文件,在textarea上写上ueditor的id就能够完成效果了。。。
<script type="text/javascript" charset="utf-8" src="${basePath}js/ueditor/ueditor.config.js"></script> <script type="text/javascript" charset="utf-8" src="${basePath}js/ueditor/ueditor.all.min.js"> </script> <script type="text/javascript" charset="utf-8" src="${basePath}js/ueditor/lang/zh-cn/zh-cn.js"></script> <script type="text/javascript"> //配置ueditor的根路径 var UEDITOR_HOME_URL = "${basePath}js/ueditor/"; var ue = UE.getEditor('editor'); </script> <td><s:textarea id="editor" name="comp.compContent" cssStyle="width:90%;height:160px;"/></td>
再次观察咱们的投诉页面,表单里面的值只有是被投诉人的信息,投诉人的信息是没有的。因而咱们在表单中把投诉人的信息经过隐藏域将其添加进去….
<s:hidden name="comp.compCompany" value="%{#session.SYS_USER.dept}"></s:hidden> <s:hidden name="comp.compName" value="%{#session.SYS_USER.name}"></s:hidden> <s:hidden name="comp.compMobile" value="%{#session.SYS_USER.mobile}"></s:hidden>
那咱们保存“我要投诉”信息的流程应该是怎么样的呢???为了达到更好的用户体验,咱们应该先把提示用户数据已经保存起来了,而后刷新父窗口,接着把“我要投诉”本页面给关闭了。这样用户看起来,就以为他的操做已是成功了!
下面是整个“我要投诉”操做的时序图:
代码:
public void saveComplain() { try { if (comp != null) { //把投诉的缺乏的信息补全 comp.setState(Complain.COMPLAIN_STATE_UNDONE); comp.setCompTime(new Timestamp(new Date().getTime())); //调用service保存 complainServiceImpl.save(comp); //告诉浏览器保存信息成功了。 ServletActionContext.getResponse().getWriter().write("success"); } } catch (IOException e) { e.printStackTrace(); } }
提示用户已经投诉成功,把父窗口刷新,本页面关闭。
function saveComplain() { $.ajax({ url: "${basePath}sys/home_saveComplain.action", /*将整个表单的属性转成是JSON*/ data: $("form").serialize(), type: "post", success: function (backdata) { if(backdata == "success"){ //告诉用户,保存成功了。 alert("投诉成功!!!"); //把父窗口刷新 window.opener.parent.location.reload(true); //把本页面关闭 window.close(); } }, error:function () { alert("保存投诉信息失败了!"); } }); }
在信息管理模块的时候,咱们就提出了三圈的问题了。何为三圈问题呢???就是当咱们使用条件查询出数据的时候,再对查询出的数据进行操做【修改、保存】,当保存完以后回到列表显示页面上的时候,查询条件就会丢失掉了。也就是说,咱们原来查询出的数据不见了。
首先,咱们在Action中使用两个变量把有可能成为查询条件的变量记住:
/************三圈问题数据回显*************************/ private String compTitle; private String state; public String getCompTitle() { return compTitle; } public void setCompTitle(String compTitle) { this.compTitle = compTitle; } public String getState() { return state; } public void setState(String state) { this.state = state; }
接着,在跳转处处理投诉页面的JSP上的时候,把查询条件的数据查询出来,把它赋值给变量。而后使用request域对象把数据发给JSP页面
//把查询条件带过去给JSP页面 ActionContext.getContext().getContextMap().put("compTitle", complain.getCompTitle()); ActionContext.getContext().getContextMap().put("state", complain.getState()); ActionContext.getContext().getContextMap().put("startTime", startTime); ActionContext.getContext().getContextMap().put("endTime", endTime);
而后在处理投诉页面的JSP上,经过隐藏域把数据给回Action。。Action在重定向到listUI页面的时候,就经过配置文件,把参数带过去:
<!--返回列表展现页面,重定向到列表展现--> <result name="list" type="redirectAction"> <param name="actionName">complain_listUI</param> <param name="complain.state">${state}</param> <param name="complain.compTitle">${compTitle}</param> <param name="endTime">${startTime}</param> <param name="startTime">${startTime}</param> <param name="encode">true</param> </result>
这样一来,咱们的查询条件就没有丢失了。当咱们操做完数据的时候,咱们的查询出来的数据仍是原来那部分。