纳税服务系统【投诉受理管理,显示投诉信息、处理回复、我要投诉】

投诉受理管理模块

接下来,就是来开发我们的投诉受理管理模块了…..我们来看看原型图与需求吧:

查询用户提交的投诉信息,可以根据投诉部门(部门A/B)、投诉时间段、状态进行查询。在列表信息中展示投诉标题、被投诉部门、被投诉人、投诉时间、状态(待受理、已受理、已失效)、操作;其中操作栏内内容为“处理”,点击“处理”则在打开的查询页面中查看具体的投诉信息并且可以多次回复投诉信息;一旦回复则说明已受理该投诉。

投诉详细信息:在本页面中首先要明显地展示出当前投诉是否已经受理;然后再显示投诉人信息、被投诉信息、受理信息(历史受理信息)三部分内容,并且在页面中可以无限次的对本次受理进行回复。投诉人信息包括:是否匿名投诉、投诉人单位、投诉人姓名、投诉人手机,如果是匿名投诉,则不显示投诉人单位、姓名并对手机号中间4位号码使用*号代替。被投诉信息包括:投诉时间、被投诉部门、被投诉人、投诉标题、投诉内容。受理信息:如果有多次回复则将多次的回复信息显示,显示内容包括回复时间、回复部门、回复人、受理回复内容;可以再次回复。

这里写图片描述

这里写图片描述

根据上面两张原型图以及文字说明,我们可以发现:一个投诉信息可对应多个回复。


在“工作主页”中点击“我要投诉”进入页面,添加内容包括:投诉标题、被投诉部门(部门A/B)、被投诉人、投诉详情、是否匿名投诉

这里写图片描述

关键在于匿名投诉的那一部分,我们该怎么写….


统计:根据年度将相应年度的每个月的投诉数进行统计,并以图表的形式展示在页面中;在页面中可以选择查看当前年度及其前4年的投诉数。在页面中可以选择不同的年度,然后页面展示该年度的曲线统计图。

这里写图片描述

这个统计图,大概也需要用到组件来生成出来的吧???


自动投诉受理:在每个月月底最后一天对本月之前的投诉进行自动处理;将投诉信息的状态改为 已失效。在后台管理中不能对该类型投诉进行回复。

自动投诉受理??在每个月的最后一天判断投诉信息,程序对其自动受理。。


投诉受理开发

我们首先来画一个流程图看看它的大概思路是怎么样的:

这里写图片描述

Hibernate逆向工程

我们经过上面的分析,知道了:一个投诉信息可对应多个回复。是一对多的关系。我们下面使用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【如果需要】

  • 添加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,设置级联、懒加载

        <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

编写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"/> &nbsp;</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/72468761http://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);

                //2、根据部门查询用户列表
                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>

这样一来,我们的查询条件就没有丢失了。当我们操作完数据的时候,我们的查询出来的数据还是原来那部分。


posted @ 2017-06-30 19:52  zhongfucheng  阅读(309)  评论(0编辑  收藏  举报