JXLS 2.4.0系列教程(四)——多sheet是怎么作到的

注:本文代码在第一篇文章基础上修改而成,请务必先阅读第一篇文章。html

http://www.cnblogs.com/foxlee1024/p/7616987.htmljava

本文也不会过多的讲解模板中遍历表达式的写法和说明,请先阅读第二篇文章。数据库

http://www.cnblogs.com/foxlee1024/p/7617120.htmleclipse

 

  好吧,今天是国庆次日,大清早起来先把文章给写了吧!工具

  这篇内容主要讲解一些如何导出多sheet的报表,我将用一个学生成绩表做为讲解案例。多sheet的导出不仅仅是简单分sheet而已,还加入了分页的功能。效果先看下图:this

  Sheet1编码

 

  Sheet2spa

 

  你们能够看到,两个不一样sheet中,表结构是同样,可是里面的学生数据有了个分页的效果。这是怎么作到的呢?3d

  这里,咱们得从模板制做讲起,知道了多sheet模板的原理后,写代码就轻松多了。excel

 

  你们看红框里的注释:

jx:area(lastCell="I8")

jx:each(items="pages", var="page", lastCell="I8" multisheet="sheetNames")

  第一行不用说,是划定模板区域。第二行看起来是一个很正常的遍历注释,可是里面多了一个multisheet="sheetNames"参数,这个参数就是分sheet的参数。咱们连起来看就是,以整个Excel报表为一个遍历输出的对象,每个sheet就是一条输出的记录。简单的说,就是遍历一个集合(Listitems,将集合内每个对象输出到每个独立的sheet中。

  只要写上multisheet”属性JXLS就会自动的分sheet了。那么还有一个问题,就是multisheet属性的值sheetNames是什么意思?这个值是从model中传来的一个集合(通常用List),里面存放着每一页sheet应该起的名字,JXLS在读取pages进行分sheet遍历的时候,也会读取sheetNames进行遍历给每个sheet更名。

  接下来咱们看看A4单元格的注释,你们看过第三篇文章应该知道怎么嵌套循环了,这里本质上也是一个嵌套,因此A4里的遍历标签的items写的值就是A1(红框)遍历标签里的page,而后点上对应的属性。

  标签讲完了,具体怎么作呢?咱们打算用一个叫作Page的类做为分页的javaBean对象,这个对象里存放着这一页应该显示的学生记录(List<Student>)和每一页的页名(sheet名)。而后将这个page对象放进一个链表中,传给model

 

  上图就是文章开头两张图片中学生成绩分页的链表示意图。

  接下来咱们开始写代码,假设数据库查询出来的结果就是一个装有学生对象的一长串链表。那么咱们从最基本的学生类(student)开始写起,

public class Student { String id; String name; Integer chinese; Integer math; Integer english; Integer politics; Integer history; Integer geography; public Student(String id, String name, Integer chinese, Integer math, Integer english, Integer politics, Integer history, Integer geography) { super(); this.id = id; this.name = name; this.chinese = chinese; this.math = math; this.english = english; this.politics = politics; this.history = history; this.geography = geography; } public Student() { } /** 如下省略了get/set方法,请自行补全 */ }

  学生类没什么好说的,学生的基本信息。接下来咱们写页面类(Page),也就是每个sheet应该包含什么信息。

/** * 该类用来封装每一页的数据 */
public class Page { /** * 页面信息 */
    private String sheetName; // 每一个sheet名字
    private String currentPage; // 当前页
    private String tolalPage; // 总页

    /** * 页面遍历的数据 List 的泛型自行设置,若是全部数据都来着同一个类就写那个类, * 不是同一个类有继承就写继承类的泛型,没有就写问号。 */
    private List<?> data; public Page(String sheetName, String currentPage, String tolalPage, List<?> data) { super(); this.sheetName = sheetName; this.currentPage = currentPage; this.tolalPage = tolalPage; this.data = data; } public Page() { } /** 如下省略了get/set方法,请自行补全 */ }

  这个类说两句,属性data的类型的List,泛型若是你能肯定传进来的对象就写上该对象,或者泛型继承,不能就写上问号。还有两个属性:currentPagetolalPage这算是保留属性,本篇教程中没有用到,可是我仍是写上了,建议同窗们也能够写上,由于当前页或总页码能够日后使用工具标签时候能够判断是否最后一页。

  接下来是重点了,咱们已经有了从数据库中查询的一长串装有学生对象的链表,有了每一页应该装什么数据的页面对象,接下来咱们要作的就是分页了。

  把一长串装有学生对象的链表截成一段段的数据,而后装进page对象中,而后再把一节一节装有数据的page对象装进一个新链表中。返回给JXSLmodel

  咱们看下分页代码:

/** * 此类用于分页,就是把从数据库查询出来的一个完整的List链表变成一截一截是数据。 * @author foxlee1024 */
public class DataByPage { static int pagesize = 3; // 每页记录数
    
    /** * 根据每页显示多少条数据计算总页数 * @param dataList 数据库查询的数据 */
    public static int countPages(List<?> dataList) { int recordcount = dataList.size(); // 总记录数
        return (recordcount + pagesize - 1) / pagesize; } public static List<Page> byPage(List<?> dataList) { int pagecount; // 总页数
        int nowDataListPoint = 0; // 读取到接收的哪一条数据
 pagecount = countPages(dataList); // 计算页码
        List<Page> pageList = new ArrayList<Page>(); // 页面分页
        for (int i = 0; i < pagecount; i++) { List<Object> pagedata = new ArrayList<Object>(); // 把传来的数据取出
            while (nowDataListPoint < dataList.size()) { pagedata.add(dataList.get(nowDataListPoint)); nowDataListPoint += 1; if (nowDataListPoint != 0 && nowDataListPoint % pagesize == 0) { break; } } Page page = new Page("page_" + (i + 1), (i + 1) + "", pagecount + "", pagedata); pageList.add(page); } return pageList; } }

  原理就是遍历传进来的一长串链表,而后根据判断将他截成一段后装进链表中,而后把链表封装进page类的data属性里,接着再把page类装进链表中,而后返回装有page对象的链表。

  好了,全都齐全了,咱们能够开始写main方法了。

public class TestMain { public static void main(String[] args) throws Exception { // 模板位置,输出流
        String templatePath = "E:/template5.xls"; OutputStream os = new FileOutputStream("E:/out5.xls"); List<Student> list = generateData(); // 模拟数据库获取数据
        List<Page> page = DataByPage.byPage(list); // 把获取的数据进行分页转换 
        Map<String, Object> model = new HashMap<String, Object>(); model.put("pages", page); model.put("sheetNames", getSheetName(page)); model.put("className", "六年三班"); model.put("teacherComment", "已核实"); model.put("directorComment", "已核实"); JxlsUtils.exportExcel(templatePath, os, model); os.close(); System.out.println("完成"); } /** * Excel 的分页名(页码)的封装 * 此方法用来获取分好页的页名信息,将信息放入一个链表中返回 */
    public static ArrayList<String> getSheetName(List<Page> page) { ArrayList<String> al = new ArrayList<String>(); for (int i = 0; i < page.size(); i++) { al.add(page.get(i).getSheetName()); } return al; } /** * 模拟生成数据 */
    public static List<Student> generateData(){ List<Student> list = new ArrayList<Student>(); Student stu1 = new Student("001", "AAA", 10, 20, 30, 40, 50, 60); Student stu2 = new Student("002", "BBB", 20, 30, 40, 50, 60, 70); Student stu3 = new Student("003", "CCC", 30, 40, 50, 60, 70, 80); Student stu4 = new Student("004", "DDD", 40, 50, 60, 70, 80, 90); Student stu5 = new Student("005", "EEE", 50, 60, 70, 80, 90, 100); list.add(stu1); list.add(stu2); list.add(stu3); list.add(stu4); list.add(stu5); return list; } }

  其余没要讲的,惟一有一个就是须要一个getSheetName() 方法,遍历获取每个pagesheeName,而后装进链表中。而后putmodelsheetNames键里。

  模板就按照前边开头咱们讲解的那个模板写,接下来咱们运行一下代码。

  当你看到控制台打出“完成”,欣喜的打开excel文件时候,你会发现第一页sheet是空白的......从第二页开始才是真正的内容。而后你看到第一页的sheet名是你模板的sheet名,你就知道确定是JXLS在复制模板时候没有删除模板页面形成的。

 

  这个问题我没办法解决,我尝试过在JxlsUtils中设置JxlsHelper的属性:jxlsHelper.setDeleteTemplateSheet(true); 然而并没什么卵用,不知道是我设置的地方不对,仍是别的缘由。请知道解决方案的同窗务必留言告知一下,万分感谢!

  虽然没办法从根本上解决,可是能够找到凑活解决的办法,就是利用POI把多余的sheet给删掉,写一个工具类,代码以下:

public class DelSheet { /** * 删除指定的Sheet * @param targetFile 目标文件 * @param sheetName Sheet名称 */ 
    public static void deleteSheet(String targetFile,String sheetName) { try { FileInputStream fis = new FileInputStream(targetFile); HSSFWorkbook wb = new HSSFWorkbook(fis); fileWrite(targetFile, wb); //删除Sheet 
 wb.removeSheetAt(wb.getSheetIndex(sheetName)); fileWrite(targetFile, wb); fis.close(); } catch (Exception e) { e.printStackTrace(); } } /** * 写隐藏/删除后的Excel文件 * @param targetFile 目标文件 * @param wb Excel对象 * @throws Exception */ 
    public static void fileWrite(String targetFile,HSSFWorkbook wb) throws Exception{ FileOutputStream fileOut = new FileOutputStream(targetFile); wb.write(fileOut); fileOut.flush(); fileOut.close(); } }

  接下来咱们就在main方法中,执行完excel导出的代码后调用下删除sheet的语句:

JxlsUtils.exportExcel(templatePath, os, model); os.close(); // 删除多出来的sheet
DelSheet.deleteSheet("E:/out5.xls", "template"); System.out.println("完成");

  传入的是excel导出的路径和要删除的sheet名字,其实能够传入sheet的编号的,可是我以为传入名字能够防止误删除。要传入编号的同窗请自行修改deleteSheet方法,wb.getSheetIndex(sheetName)既能够接收String也能够接收Integer,若是我没有记错的话。

  咱们再运行一遍代码看看,咱们就能够看到开头的那样的效果了!

  通常来讲,这篇文章到这里应该就结束了。可是还有一个问题,就是若是我想作每个学生的成绩分页呢?怎么作?就是一个学生独占一个sheet

  我不卖关子了,咱们记得,每个sheet的信息实际上是对应一个page对象的,这个对象里有一个List<Student> data,因此在一个sheet里才可以将这个data取出,交给模板进行遍历。若是咱们要一个学生独占一个sheet,咱们只须要在Page类中加入一个对象类型的属性就能够了,在模板中直接取这个对象具体的属性。这也是我为何要用page对象的缘由,扩展性高。

public class Page { /** * 页面信息 */
    private String sheetName; // 每一个sheet名字
    private String currentPage; // 当前页
    private String tolalPage; // 总页

    /** * 页面遍历的数据 List 的泛型自行设置,若是全部数据都来着同一个类就写那个类, * 不是同一个类有继承就写继承类的泛型,没有就写问号。 */
    private List<?> data; /** * 一页只保存一我的的信息 */
    private Object onlyOne;   /** 省略构造器和其余get/set方法 */

    public Object getOnlyOne() { return onlyOne; } public void setOnlyOne(Object onlyOne) { this.onlyOne = onlyOne; }

  接下来咱们修改一下main方法,把本来用来分页的List<Page> page = DataByPage.byPage(list) 注释掉。而后新加一句List<Page> page = individual(list)

public static void main(String[] args) throws Exception { // 模板位置,输出流
        String templatePath = "E:/template5.xls"; OutputStream os = new FileOutputStream("E:/out5.xls"); List<Student> list = generateData(); // 模拟数据库获取数据 //List<Page> page = DataByPage.byPage(list); // 把获取的数据进行分页转换
        List<Page> page = individual(list); // 一页一我的
 Map<String, Object> model = new HashMap<String, Object>(); model.put("pages", page); model.put("sheetNames", getSheetName(page)); model.put("className", "六年三班"); model.put("teacherComment", "已核实"); model.put("directorComment", "已核实"); JxlsUtils.exportExcel(templatePath, os, model); os.close(); // 删除多出来的sheet
        DelSheet.deleteSheet("E:/out5.xls", "template"); System.out.println("完成"); }

  Individual() 方法的代码以下:

    /** * 将数据获取的数据封装成一页一我的的List */
    public static List<Page> individual(List<Student> list){ List<Page> pages = new ArrayList<Page>(); for(int i = 0; i < list.size(); i++){ Page p = new Page(); p.setOnlyOne(list.get(i)); p.setSheetName(list.get(i).getName()); pages.add(p); } return pages; }

  接收传进来的List<Student> list链表数据,而后遍历该链表,将其封装Page对象中,而且别忘了设置sheetName。

  模板是这样的:

 

  模板中不须要两层循环了,直接在页面中取page的onlyOne属性(装的是Student对象)的值就行了。例如:${page.onlyOne.id}、${page.onlyOne.chinese}。

  行了,执行代码看一下效果:

  到这里就真的是结束了,这篇文章内容有点多,主要讲了分sheet导出的方法,本质上只是一种的,我先讲了比较复杂的,带有分页效果的分sheet。而后再讲了单纯的分sheet。介于代码比较多,我就把源码发上了让你们一块儿研究。源码里的jar包我给删掉了,要使用就在第一篇文章先下载jar包吧!

顺便说一句,前几篇教程的源码是不存在的,由于我每一篇都是在原来代码基础上改的,其实全部代码我都发上来了。这篇我是专门开新工程弄的例子。哦,还有一句,家里eclipse过久不用了,编码竟然是GBK而不是通用的UTF-8(写完了才发现),你们看着改吧。

  jar包下载地址(内有官方2.4.0版本,2.4依赖的jar包,klguang demo这里下载

  本文源码下载(内有模板,无jar包,请配合上边jar包使用): 源码下载

相关文章
相关标签/搜索