1. 程式人生 > >JXLS 2.4.0系列教程(四)——多sheet是怎麽做到的

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

while director write 教程 == 模板 phy sheet ack

註:本文代碼在第一篇文章基礎上修改而成,請務必先閱讀第一篇文章。

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

本文也不會過多的講解模板中遍歷表達式的寫法和說明,請先閱讀第二篇文章。

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

  好吧,今天是國慶第二天,大清早起來先把文章給寫了吧!

  這篇內容主要講解一些如何導出多sheet的報表,我將用一個學生成績表作為講解案例。多sheet的導出不單單是簡單分sheet而已,還加入了分頁的功能。效果先看下圖:

  Sheet1

技術分享

  Sheet2

技術分享

  大家可以看到,兩個不同

sheet中,表結構是一樣,但是裏面的學生數據有了個分頁的效果。這是怎麽做到的呢?

  這裏,我們得從模板制作講起,知道了多sheet模板的原理後,寫代碼就輕松多了。

技術分享

  大家看紅框裏的註釋:

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包使用): 源碼下載

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