1. 程式人生 > >Java EasyPoi簡單報表+複合表頭報表的匯出

Java EasyPoi簡單報表+複合表頭報表的匯出

前言:用Java做報表匯出的功能,基本上都會用到Apache的Poi,Poi的功能很完善,能滿足專案的所有需求,但Poi的基礎API太多,不容易上手,且需要編寫大量程式碼來構建基礎Excel表格;因此,這裡推薦一個易上手的Excel匯出工具EasyPoi,採用的是註解配置的方法,開發人員只需要在匯出的實體類上加上對應的標籤,即可輕鬆編寫表頭的內容以及樣式等。下面就介紹一下簡單的EasyPoi匯出以及複雜表頭的匯出。

按規矩先呈上EasyPoi的文件:EasyPoi,如何引入以及各功能的使用說明都在文件裡,這裡就不贅述了,下面直接上EasyPoi在專案中的應用。

簡單報表匯出

匯入上圖這種簡單報表,後臺的程式碼很簡單,只需要寫兩個類,一個用來存放匯出模板的Template類,另一個則是將得到的List匯出的輸出類,springmvc中會放在實現層去處理,Template程式碼如下:

public class TestTemplate {
    /**
    * 省略Get、Set方法
    * 此處欄位名與實體類欄位名需保持一致
    * 欄位順序儘量與匯出報表的表頭順序一致,這樣可以少寫orderNum
    */

    @Excel(name = "月份", height = 20, width = 20)
    private String monthRecord;

    @Excel(name = "樓宇名稱", height = 20, width = 20)
    private String buildingName;

    @Excel(name = "房間號", height = 20, width = 20)
    private String detailHouseNumber;

    @Excel(name = "電錶ID", height = 20, width = 20)
    private String eleMeterId;

    @Excel(name = "租戶姓名", height = 20, width = 20)
    private String renterName;

    @Excel(name = "租戶狀態", replace = {"已搬出_0", "正在租住_1", "退租辦理中_2", "入住待召測_3"}, height = 20, width = 20)
    private Short renterRent;

    @Excel(name = "本月已用電量(度)", height = 20, width = 40)
    private BigDecimal elePower;

    @Excel(name = "本月剩餘電量", height = 20, width = 40)
    private BigDecimal eleResPrePower;

    @Excel(name = "最後抄表時間", format = "yyyy-MM-dd HH:mm:ss", height = 20, width = 40)
    private Date lastReportTime;
}

實現層程式碼:

public void exportTestTemplate(HttpServletResponse response) {
        //新建一個模板List用來接收匯出的實際資料List
        List<TestTemplate> listTemplates  = new ArrayList<>(); 

        //查詢結果集
        List<TestCDO> list = mapper.selectAll();
        
        //自己封裝的List轉換類,內部用了BeanUtils.copyProperties這個方法,第一個引數是        
        sourceList,第二個引數是targetList,第三個引數是targeClass
        TestBeanUtils.transformList(list , listTemplates, TestTemplate.class);

        /**
         * EasyPoi提供的匯出方法模板
         */
        OutputStream out = null;
        String sheetName = "Excel檔名";
        try {
            ExportParams params = new ExportParams(null, sheetName, ExcelType.XSSF);
            params.setTitleHeight((short) 20);
            Workbook workbook = ExcelExportUtil.exportExcel(params, TestTemplate.class, listTemplates);
            response.setContentType("application/octet-stream");
            response.setHeader("Content-disposition", "attachment; filename="
                    + new String((sheetName + ".xlsx").getBytes("GBK"), "ISO-8859-1"));
            out = response.getOutputStream();
            workbook.write(out);
        } catch (Exception e) {
            logger.error("匯出模板Excel,失敗", e);
        } finally {
            try {
                if(null != out){
                    out.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }

這樣一來,只要前端頁面點了匯出按鈕,就可以選擇檔案的存放位置,然後就可以順利匯出了

複雜表頭報表匯出

這是專案中遇到的一個需要匯出複雜表頭的需求,合計上面的那部分是普通的複雜表頭,EasyPoi對縱向的單元格合併具有很方便的註解支援,專案中我用到的是@groupname註解,另外還有@ExcelEntity註解可以實現,詳情見文件;合計那兩行的資料與最上面的表頭的資料來源是不一樣的,因此,必須先新增這兩行資料放到List的頭部,專案中我的處理是對查出來的List使用add方法,這樣總計的兩條資料就插入在了表格的頭部,但是會有一個問題,那就是單元格的橫向合併,下面會提到解決方法,首先是Template:

public class FeeCountRoomTemplate {
    /**
     * 省略了Get、Set方法
     * groupName相同,name不同,即name是groupName的子元素
     * orderNum控制了表頭的排序,數字越大,越在後面,預設值是0
     */
    @Excel(name = "房源名稱", height = 20, width = 20)
    private String buildingName;

    @Excel(name = "房間號", height = 20, width = 20)
    private String detailHouseNumber;

    @Excel(name = "日期", height = 20, width = 20)
    private String dayRecord;

    @Excel(name = "電費", groupName = "收入統計", height = 20, width = 20, orderNum = "0")
    private BigDecimal eleChargeAmount;

    @Excel(name = "市政水費", groupName = "收入統計", height = 20, width = 20, orderNum = "1")
    private BigDecimal municipalWaterChargeAmount;

    @Excel(name = "純淨水費", groupName = "收入統計", height = 20, width = 20, orderNum = "2")
    private BigDecimal pureWaterChargeAmount;

    @Excel(name = "熱能費", groupName = "收入統計", height = 20, width = 20, orderNum = "3")
    private BigDecimal heatChargeAmount;

    @Excel(name = "退款", groupName = "支出統計", height = 20, width = 20)
    private BigDecimal reDepositAmount;

    @Excel(name = "總計", height = 20, width = 20, orderNum = "4")
    private BigDecimal singleTotalAmount;

}

然後是實現層:

public void exportFeeCountByTime(HttpServletResponse response) {
        //與簡單匯出一致
        List<FeeCountTimeTemplate> listTemplates = new ArrayList<>();
        FeeCountSQO feeCountSQO = new FeeCountSQO();

        List<FeeCountCDO> list = feeCountMapper.selectFeeByTimeListAll(); //查詢List
        FeeCountCDO countCDO = feeCountMapper.selectFeeByTimeTotal(); //彙總資料(只有一條)
        //這裡的思路是按照表頭的格式構造兩行資料,在最上方插入,
        //這時候你可能會有疑問,合計是3個單元格的合併,但是表頭卻是3個單元格,
        //其實沒有關係,我們先插入一個合計在最前面(
        //一定要插入在需要合併的單元格的最左邊否則合併時資料會消失),
        //後面的2個用不到的單元格就插入null,插入總計資料的時候同理
        FeeCountCDO copySingleCDO = new FeeCountCDO();
        copySingleCDO.setMonthRecord("合計");
        copySingleCDO.setBuildingName(null);
        copySingleCDO.setDetailHouseNumber(null);
        copySingleCDO.setEleChargeAmount(countCDO.getEleChargeAmountSum());
        copySingleCDO.setMunicipalWaterChargeAmount(
        countCDO.getMunicipalWaterChargeAmountSum());
        copySingleCDO.setPureWaterChargeAmount(countCDO.getPureWaterChargeAmountSum());
        copySingleCDO.setHeatChargeAmount(countCDO.getHeatChargeAmountSum());
        copySingleCDO.setReDepositAmount(countCDO.getReDepositAmountSum());
        copySingleCDO.setRoomTotalAmount(countCDO.getRoomsTotalAmount());
        FeeCountCDO copyTotalCDO = new FeeCountCDO();
        copyTotalCDO.setMonthRecord("合計");
        copyTotalCDO.setBuildingName(null);
        copyTotalCDO.setDetailHouseNumber(null);
        copyTotalCDO.setEleChargeAmount(countCDO.getFeesSum());
        copyTotalCDO.setMunicipalWaterChargeAmount(null);
        copyTotalCDO.setPureWaterChargeAmount(null);
        copyTotalCDO.setHeatChargeAmount(null);
        copyTotalCDO.setReDepositAmount(countCDO.getReDepositAmountSum());
        copyTotalCDO.setRoomTotalAmount(countCDO.getRoomsTotalAmount());

        //插入到結果集的第一個行(注意先插入的實際是第二行)
        list.add(0, copySingleCDO);
        list.add(0, copyTotalCDO);
        IgmsBeanUtils.transformList(list, listTemplates, FeeCountTimeTemplate.class);

        OutputStream out = null;
        String sheetName = "Excel檔名";

        try {
            ExportParams params = new ExportParams(null, sheetName, ExcelType.XSSF);
            params.setTitleHeight((short) 20);
            response.setContentType("application/octet-stream");
            response.setHeader("Content-disposition", "attachment; filename="
                    + new String((sheetName + ".xlsx").getBytes("GBK"), "ISO-8859-1"));
            out = response.getOutputStream();
            workbook.write(out);
        } catch (Exception e) {
            logger.error("匯出模板Excel,失敗", e);
        } finally {
            try {
                if(null != out){
                    out.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

加入了2行自定義的匯出資料之後你會發現你的總計資料確實加上去了,但是表格的單元格沒有合併,如圖:

很明顯發現合計與收入統計的資料沒有合併單元格,這樣肯定是不行的,但是看了很久的文件之後也沒有發現文件對這種問題有便捷的解決方法,因此我就在想如何用Apache Poi的方法實現橫向單元格合併的問題 ,通過查閱資料發現,Poi有一個Region方法可以實現橫向和縱向的任意單元格合併,接著看下去發現它需要對Sheet物件做操作,而得到Sheet又需要Workbook物件,而Workbook物件我們已經得到了,那麼問題就可以解決了,下面獻上程式碼:

public void exportFeeCountByTime(HttpServletResponse response) {
        
        /**
         * 與之前程式碼一樣,不復制了
         */

        try {
            /**
             * 處理橫向單元格合併
             */
            ExportParams params = new ExportParams(null, sheetName, ExcelType.XSSF);
            params.setTitleHeight((short) 20);
            Workbook workbook = ExcelExportUtil.exportExcel(params, FeeCountTimeTemplate.class, listTemplates);
            Sheet sheet = workbook.getSheetAt(0);//取此Excel檔案的第一個Sheet
            //合併第一個‘合計’
            //四個引數依次是:起始行,終止行,起始列,終止列
            CellRangeAddress craOne = new CellRangeAddress(2, 2, 0, 2);//index從0開始
            //合併‘收入統計’
            CellRangeAddress craTwo = new CellRangeAddress(2, 2, 3, 6);
            //合併第二個‘合計’
            CellRangeAddress craThree = new CellRangeAddress(3, 3, 0, 2);
            sheet.addMergedRegion(craOne); //第一次合併
            sheet.addMergedRegion(craTwo); //第二次合併
            sheet.addMergedRegion(craThree); //第三次合併
            response.setContentType("application/octet-stream");
            response.setHeader("Content-disposition", "attachment; filename="
                    + new String((sheetName + ".xlsx").getBytes("GBK"), "ISO-8859-1"));
            out = response.getOutputStream();
            workbook.write(out);
        } catch (Exception e) {
            logger.error("匯出模板Excel,失敗", e);
        } finally {
            try {
                if(null != out){
                    out.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

然後你就會發現表格已經按照需求完美匯出了

之前還擔心自定義的兩行的合併單元格之後不會居中結果也自動居中了