宅急送 项目第五天 区域、分区、定区

1. 区域管理

增加、修改、删除功能 和之前 收派标准和取派员管理类似,留作课后练习

1.1. 区域信息批量导入

技术:文件上传 和 Excel解析 (已经 讲解POI如何读写Excel 文档)

1.1.1. 上传Excel文件页面一键上传

/WEB-INF/pages/base/region.jsp 区域管理页面
在datagrid的toolbar 添加按钮

{
    id : 'button-import',
    text : '批量导入',
    iconCls : 'icon-save'
}

文件上传基本要求 :

客户端 `form post`提交、 `enctype=multipart/form-data` 、`input file` 必须提供name属性
服务器 Apache commons-fileupload 、 COS、 JSPSmartUpload 

这里写图片描述

Struts2 框架默认使用文件上传 jakarta 是 apache commons-fileupload

企业中制作文件上传,大多使用一键上传的效果 (页面中只提供一个按钮,点击按钮就可以完成文件上传 ) ,原理???

一键上传,往往和Ajax效果结合 ,页面不刷新

按钮点击 --- 触发隐藏form input file浏览文件动作 ---- 选定文件 触发onChange事件 ---  提交form表单,target指向隐藏iframe ---- 服务器完成文件上传,将结果更新隐藏iframe 

使用 Jquery 插件 Ocupload
http://code.google.com/p/ocupload/ 下载 ocupload 1.1.2.js

将js插件导入项目

宅急送 项目第五天 区域、分区、定区_第1张图片

参照 Documentation - ocupload - One Click Upload - A jQuery Plugin - Google Project Hosting.mht 使用

宅急送 项目第五天 区域、分区、定区_第2张图片

在页面导入 一键上传js


<script type="text/javascript" 
src="${pageContext.request.contextPath }/js/ocupload/jquery.ocupload-1.1.2.js">script>

对按钮添加一键上传效果

// 对批量导入添加一键上传效果 
$('#button-import').upload({
    name : 'upload' , // 
    action : '${pageContext.request.contextPath}/region_importXls.action', // 表单提交路径
    onComplete : function(response){
        $.messager.alert('信息','文件上传成功!','info');
    }
});

1.1.2. 编写服务器端

编写RegionAction 接收上传文件 ,使用Struts2 内置FileUploadInterceptor 实现文件上传

public class RegionAction extends BaseAction implements ModelDriven<Region> {
}
public interface RegionService {
}
public class RegionServiceImpl extends BaseService implements RegionService {
}

将RegionDAO 注入 BaseService
将RegionService 注入BaseAction

导入区域数据,参考 region.xls、Mysql全国省市区以及邮编数据库

宅急送 项目第五天 区域、分区、定区_第3张图片

宅急送 项目第五天 区域、分区、定区_第4张图片

返回客户端 json

// 返回json
Map<String, Object> map = new HashMap<String, Object>();
map.put("result", "success");
map.put("msg", "区域导入完成");

ActionContext.getContext().put("map", map);

配置 struts.xml applicationContext.xml

<action name="region_*" class="regionAction" method="{1}">
    <result name="importXlsSUCCESS" type="json">
        <param name="root">mapparam>
    result>
action>

使用log4j 记录导入失败日志

// 使用log4j 日志记录器,记录日志
private static final Logger LOG = Logger.getLogger(RegionAction.class);

记录日志

try {
    regionService.saveRegion(region);
} catch (Exception e) {
    // 导入region失败,记录日志
    LOG.error("区域导入失败,编号:" + region.getId(), e);
}

1.2. 查询区域列表 (分页查询)

将 region.jsp 的 datagrid的url 指向服务器Action

url : "${pageContext.request.contextPath}/region_pageQuery.action" 

在RegionAction 添加 pageQuery方法,实现分页查询
Action 代码

宅急送 项目第五天 区域、分区、定区_第5张图片

Service接口继承PageQuery 接口

这里写图片描述

Service实现类调用 BaseService 抽取 pageQuery方法

这里写图片描述

配置返回结果



    pageResponseBean
    
        total,
        rows\[\d+\]\.id,
        rows\[\d+\]\.province,
        rows\[\d+\]\.city,
        rows\[\d+\]\.district,
        rows\[\d+\]\.postcode,
        rows\[\d+\]\.shortcode,
        rows\[\d+\]\.citycode
    

列表查询完成后,在每次批量导入后,重写加载datagrid 数据

// 使datagrid 数据刷新
$('#grid').datagrid('reload');

重新发起请求,加载datagrid的url

1.3. PinYin4j 工作类使用

工作类作用可以完成汉字到拼音的转换 —- 在搜索时使用

pinyin4j-2.5.0.jar 开发jar包
pinyin4j的使用手册.doc

需求:
汉字 转换 拼音首字母缩写,城市简码
汉字 转换 拼音全拼 , 城市编码

搜索坐标

<dependency>
    <groupId>com.belerwebgroupId>
    <artifactId>pinyin4jartifactId>
    <version>2.5.0version>
dependency>

很多工作jar包在 中央仓库没有 ,手动将jar包放入本地仓库
参考: “安装jar包到仓库”

mvn install:install-file 
-DgroupId=pinyin4j 
-DartifactId=pinyin4j 
-Dversion=2.5.0 
-Dpackaging=jar 
-Dfile=D:\itcast\bak\项目_20130711_javaee就业班\day4_项目第四天(取派员、区域、分区导入)\扩展资料\pinyin4j\pinyin4j-2.5.0.jar

使用命令之前,先将 maven安装目录/bin 配置path 环境变量 !!!


<dependency>
    <groupId>pinyin4jgroupId>
    <artifactId>pinyin4jartifactId>
    <version>2.5.0version>
dependency>

宅急送 项目第五天 区域、分区、定区_第6张图片

2. 分区管理

进行物流管理时,不可能让取派员人员去负责一个自然区域 ,需要将自然区域按照 路段、小区进行划分,目的方便为区域指定对应取派人员

对管理分区进行维护,为定区管理,自动下单等后续操作提供基础数据支持。
打算引入一个复制功能按钮,用来增加信息基本相同的数据。

宅急送 项目第五天 区域、分区、定区_第7张图片

2.1. 分区添加操作

2.1.1. 显示区域下拉列表

subarea.jsp 存在两个隐藏window窗口 , 添加form 和 查询form

在添加form 表单中,加载Region区域列表 (分区属于区域 )

class="easyui-combobox" 
    data-options="valueField:'id', textField:'info',                        url:'${pageContext.request.contextPath }/region_ajaxlist.action', required:true"/>

在RegionAction 提供 ajaxlist 方法,返回 区域列表,生成 下拉项


<result name="ajaxlistSUCCESS" type="json">
    <param name="root">regionsparam>
    <param name="includeProperties">
        \[\d+\]\.id,
        \[\d+\]\.info
    param>
result>

Region 实体类,并没有getInfo方法,需要手动创建

// 自定义方法
public String getInfo() {
    // 返回省市区信息
    return province + "," + city + "," + district;
}

为区域列表添加 name属性

这里写图片描述

2.1.2. 提交添加分区form表单

// 为添加分区 ,保存按钮添加点击 事件
$('#save').click(function(){
    // 判断form 校验是否通过
    if($('#subareaForm').form('validate')){
        // 通过校验
        $('#subareaForm').submit();
    }else{
        // 提示
        $.messager.alert('警告','表单存在非法数据项,请重新输入!','warning');
    }
});

编写服务器端 SubareaAction 完成分区管理

public class SubareaAction extends BaseAction implements ModelDriven<Subarea> {
}
public interface SubareaService {
}
public class SubareaServiceImpl extends BaseService implements SubareaService {
}

将DAO 注入BaseService
将Service 注入BaseAction

统一配置 struts.xml applicationContext.xml


<action name="subarea_*" class="subareaAction" method="{1}">
    <result name="saveOrUpdateSUCCESS">/WEB-INF/pages/base/subarea.jspresult>
action>

2.2. 分区数据查询功能

2.2.1. 使用datagrid,通过分页列表查询所有数据

将datagrid 控件url 指向服务器代码,查询所有分页数据

url : "${pageContext.request.contextPath}/subarea_pageQuery.action"

在SubareaAction 添加pageQuery 查询方法



    pageResponseBean
    
        total,
        rows\[\d+\]\.id,
        rows\[\d+\]\.addresskey,
        rows\[\d+\]\.startnum,
        rows\[\d+\]\.endnum,
        rows\[\d+\]\.single,
        rows\[\d+\]\.position,
        rows\[\d+\]\.region\.province,
        rows\[\d+\]\.region\.city,
        rows\[\d+\]\.region\.district
    

2.2.2. 分区条件查询

问题一: 将条件查询参数,封装到model对象
分区查询 ,在subarea.jsp 提供隐藏窗口

宅急送 项目第五天 区域、分区、定区_第8张图片

在组合条件查询时,form表单中 不需要必须填写校验

修改 每个条件输入项的name属性
(name属性,参照目标Action的model对象,来编写)

宅急送 项目第五天 区域、分区、定区_第9张图片

问题二: 完成条件查询时,数据显示datagrid中,当需要分页时,分页完成带有查询条件的分页 ?
必须结合datagrid 完成条件查询 ,条件在datagrid中缓存

如果不使用datagrid

<a href= “xxx.action ?page=1&查询条件”>上一页a>

使用datagrid的load 方法,将查询条件缓存到datagrid的控件内部 !

宅急送 项目第五天 区域、分区、定区_第10张图片

执行load 方法时,需要json格式参数

问题三: 将form的查询条件,转换为json
借助于 jquery 提供 serializeArray 函数

宅急送 项目第五天 区域、分区、定区_第11张图片

最终需要格式 :

{
    name : [‘Hello’,’World ’]
}

自定义jquery函数

$.fn.serializeJson=function(){  
         var serializeObj={};  
         var array=this.serializeArray();  
         var str=this.serialize();  
         $(array).each(function(){  
             if(serializeObj[this.name]){  
                 if($.isArray(serializeObj[this.name])){  
                     serializeObj[this.name].push(this.value);  
                 }else{  
                     serializeObj[this.name]=[serializeObj[this.name],this.value];  
                 }  
             }else{  
                 serializeObj[this.name]=this.value;   
             }  
         });  
         return serializeObj;  
     };
// 执行条件查询
$("#btn").click(function(){
    // 将form的数据转换为 json 
    var params = $('#searchForm').serializeJson();
    // 调用datagrid 执行查询,在查询是,缓存条件 
    $('#grid').datagrid('load',params);// 重新加载datagrid指定url
});

编写服务器代码 ,修改 SubareaAction 的pageQuery 方法 !!!
方案一:拼接SQL语句

String sql = “select * from subarea where 1=1” ; 默认语句 

当存在一个条件, sql += “and key=value” 拼接一个条件

方案二:Hibernate QBC 面向对象,条件查询 
单表可以直接添加属性,如果多表关联,必须创建别名进行表关联
// 针对QBC添加查询条件
if (subarea.getAddresskey() != null && subarea.getAddresskey().trim().length() > 0) {
    // 添加关键字条件
    detachedCriteria.add(Restrictions.like("addresskey", "%" + subarea.getAddresskey() + "%"));
}
if (subarea.getDecidedZone() != null && subarea.getDecidedZone().getId() != null && subarea.getDecidedZone().getId().trim().length() > 0) {
    // 输入定区编号
    detachedCriteria.add(Restrictions.eq("decidedZone", subarea.getDecidedZone()));// 比较对象,实际上比较定区id 属性
}
if (subarea.getRegion() != null) {
    // 表关联,QBC解决方案 --- 别名
    detachedCriteria.createAlias("region", "r");
    if (subarea.getRegion().getProvince() != null && subarea.getRegion().getProvince().trim().length() > 0) {
        detachedCriteria.add(Restrictions.like("r.province", "%" + subarea.getRegion().getProvince() + "%"));
    }
    if (subarea.getRegion().getCity() != null && subarea.getRegion().getCity().trim().length() > 0) {
        detachedCriteria.add(Restrictions.like("r.city", "%" + subarea.getRegion().getCity() + "%"));
    }
    if (subarea.getRegion().getDistrict() != null && subarea.getRegion().getDistrict().trim().length() > 0) {
        detachedCriteria.add(Restrictions.like("r.district", "%" + subarea.getRegion().getDistrict() + "%"));
    }
}

问题:

DetachedCriteria.forClass(Subarea.class) ;  // select subarea.* from… 
    detachedCriteria.setProjection(Projections.rowCount()); // select count(*) from …
    detachedCriteria.setProjection(null); // select * from region, subarea 

返回不是分区数据,而是区域和分区对象数组

修改BaseService 调换 查询rows和total的顺序, 不要使用 setProjection(null) ;

// 分页通用代码
public  PageResponseBean pageQuery(PageRequestBean pageRequestBean, GenericDAO dao) {
    PageResponseBean pageResponseBean = new PageResponseBean();

    // 查询当前页显示数据
    int firstResult = (pageRequestBean.getPage() - 1) * pageRequestBean.getRows(); //  从哪条开始
    int maxResults = pageRequestBean.getRows(); // 返回记录数
    List data = dao.pageQuery(pageRequestBean.getDetachedCriteria(), firstResult, maxResults);
    pageResponseBean.setRows(data);

    // 满足当前条件,记录总条数
    long total = dao.findTotalCount(pageRequestBean.getDetachedCriteria());
    pageResponseBean.setTotal(total);

    return pageResponseBean;
}

隐患: DetachedCriteria 缓存firsetResult 和 maxResults

在查询 select count(*) from subarea 存在数组越界问题 ,count(*) 结果只有一条 
List list = this.getHibernateTemplate().findByCriteria(detachedCriteria, 0, 1); // 解决越界问题

这里写图片描述

2.3. 分区查询结果导出功能

将查询当前页数据,进行Excel导出
(技术: Excel文件生成 和 文件下载 )

为了避免重复查询,先将查询数据缓存起来,Session 中 ,再通过页面点击导出按钮,生成Excel文件,提供下载 !

2.3.1. 将查询结果缓存到Session

SubareaAction的pageQuery方法

// 将查询结果 保存到Session
ServletActionContext.getRequest().getSession().setAttribute("pageResponseBean", pageResponseBean);

2.3.2. 点击subarea.jsp页面导出按钮,完成下载

function doExport(){    location.href="${pageContext.request.contextPath}/subarea_exportXls.action";
}

在SubareaAction 添加 exportXls 的方法
问题: 配置文件下载 (Struts2 框架提供 stream流结果集 )
一个流 默认 getInputStream
两个头信息 ContentType、ContentDisposition

宅急送 项目第五天 区域、分区、定区_第12张图片

inputName 默认值 “inputStream” ,需要在Action 提供 getInputStream 方法,用于返回文件流 ;
contentType 默认text/plain 文件形式,根据下载文件类型进行设置
contentDisposition 默认 inline ,在浏览器直接打开,弹出窗口 改为attachment

javaweb : ServletContext.getMimeType(文件名) 根据文件名 查询对应MIME类型
tomcat/conf/web.xml 存放很多MIME类型映射

在SubareaAction 提供 getInputStream 流方法


<result name="exportXlsSUCCESS" type="stream">
    <param name="contentType">application/vnd.ms-excelparam>
    <param name="contentDisposition">attachment;filename=subarea.xlsparam>
result>

宅急送 项目第五天 区域、分区、定区_第13张图片

将内存数据,生成Excel文件对象 中!

问题: 如何将内存Excel对象,转换输入流 ??
先将数据写入内存缓冲区中,在通过输入流读取进来 ByteArrayOutputStream

宅急送 项目第五天 区域、分区、定区_第14张图片

2.3.3. 使用中文文件名完成文件导出

下载中文文件名乱码解决 ,通用方案,FireFox 使用Base64编码,对IE(其它)浏览器使用 URL编码
提供 FileUitls 工具类,可以根据不同浏览器,完成下载文件名的编码 !

也有人说 new String(filename.getBytes() , “ISO-8859-1” )

public static String encodeDownloadFilename(String filename, String agent)
        throws IOException {
    if (agent.contains("Firefox")) { // 火狐浏览器
        filename = "=?UTF-8?B?"
                + new BASE64Encoder().encode(filename.getBytes("utf-8"))
                + "?=";
        filename = filename.replaceAll("\r\n", "");
    } else { // IE及其他浏览器
        filename = URLEncoder.encode(filename, "utf-8");
        filename = filename.replace("+"," ");
    }
    return filename;
}

在struts.xml 配置从值栈中获取,已经编码后文件名


<result name="exportXlsSUCCESS" type="stream">
    <param name="contentType">
application/vnd.ms-excelparam>
    <param name="contentDisposition">
attachment;filename=${downloadFileName}
param>
result>

${downloadFileName} 从值栈获取,在Action中对文件名手动进行编码,将编码结果放入值栈 !

// 对文件名进行编码
String downloadFileName = "分区数据.xls";
// 获得用户使用浏览器类型
String agent = ServletActionContext.getRequest().getHeader("user-agent");

// 对下载文件名编码
downloadFileName = FileUtils.encodeDownloadFilename(downloadFileName, agent);
// 将结果放入值栈
ActionContext.getContext().put("downloadFileName", downloadFileName);

其它

课前资料

宅急送 项目第五天 区域、分区、定区_第15张图片

Hessian

宅急送 项目第五天 区域、分区、定区_第16张图片

组合条件查询,分页投影问题

宅急送 项目第五天 区域、分区、定区_第17张图片

Ajax效果一键上传的原理

宅急送 项目第五天 区域、分区、定区_第18张图片

课程视频内容

宅急送 项目第五天 区域、分区、定区_第19张图片

宅急送 项目第五天 区域、分区、定区_第20张图片

你可能感兴趣的:(宅急送项目)