使用 EasyPOI 優雅導出Excel模板數據(含圖片)

2021-01-14 程序猿DD

點擊上方藍色「程序猿DD」,選擇「設為星標」

回復「資源」獲取獨家整理的學習資料!

作者 | 星懸月

來源 | blog.csdn.net/u012441819/article/details/96828044

EasyPOI功能如同名字Easy,主打的功能就是容易,讓一個沒接觸過POI的人員可以方便的寫出Excel導出,Excel模板導出,Excel導入,Word模板導出。通過簡單的註解和模板語言(熟悉的表達式語法),完成以前複雜的寫法。

本文主要通過簡單的分析讓讀者知道Excel模板該如何編寫,EasyPOI要如何使用才能導出滿足自己需要的Excel數據,從而簡化編碼。同時本文還會對一些不常見的功能如圖片導出功能進行說明,讓讀者少踩坑。

版本及依賴說明

EasyPOI4.0.0及以後的版本依賴於Apache POI的4.0.0及以後版本。所以在maven的配置中,兩者的版本號一定要匹配。

需要注意的是,Apache POI的4.0.0相對之前的版本有很大的變更,如果之前代碼中Excel操作部分依賴於舊的版本,那麼不建議使用4.0.0及之後的版本。當然,如果之前代碼不涉及或很少涉及WorkBook的創建細節,使用新版也沒有問題。

筆者需要改寫的項目基於JEECG 3.7版本,依賴的是3.9版本的Apache POI,而JEECG維護的jeasypoi版本最高只有2.2.0,而該版本並不支持模板導出圖片功能。說到這裡又要吐槽以下JEECG團隊,既然自己不打算維護jeasypoi,那項目中直接使用官方的EasyPOI不就好了,2.2.0版本的jeasypoi給開發者挖了多少坑啊!

為了和舊版本兼容,又想使用EasyPOI帶來的圖片導出功能,所以筆者最終採用的EasyPOI版本是3.3.0,對應的Apache POI依賴是3.15。

Maven配置如下所示:

<properties>
    <poi.version>3.15</poi.version>
    <easypoi.version>3.3.0</easypoi.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi</artifactId>
        <version>${poi.version}</version>
    </dependency>
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi-ooxml</artifactId>
        <version>${poi.version}</version>
    </dependency>
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi-ooxml-schemas</artifactId>
        <version>${poi.version}</version>
    </dependency>
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi-scratchpad</artifactId>
        <version>${poi.version}</version>
    </dependency>
    <dependency>
        <groupId>cn.afterturn</groupId>
        <artifactId>easypoi-web</artifactId>
        <version>${easypoi.version}</version>
    </dependency>
</dependencies>

Excel模板的設計

我們使用EasyPOI的模板導出功能就是不想通過編碼的方式來設計Excel報表的樣式,所以工作的第一步就是設計Excel模板,分清楚哪些部分是固定的,哪些是需要循環填充的。EasyPOI有自己的表達式語言,每種表達式的詳細介紹請參考後文的參考連結。

一個簡單的Excel報表模板

一些簡單的模板就不在這裡詳細解釋了,只放一下效果圖和模板配置內容。等讀者明白了複雜的模板如何製作並如何填值的時候,簡單的很快就能明白了。

先看看報表效果圖:

再看看實際的模板:

看了上述兩張圖,是不是已經感受到模板導出功能的強大了呢?

一個複雜的Excel報表模板

下面要介紹的這個模板比較複雜,不像是常見的那種一行是一條記錄的情況,所以將詳細介紹該模板的配置,順帶對EasyPOI的部分表達式進行簡單介紹。

還是先看效果圖:

再看看模板:

這兩張圖一對比,是不是有種知識改變命運的感覺?

複雜模板設計剖析

從貨品信息的模板圖及效果圖中我們發現,整個模板實際上分為上下兩部分。其中上部分為不變的抬頭信息,下部分為循環插入的貨品明細信息。所以我們關注的重點是下半部分的語法。

下半部分的第一列圖中沒有顯示完整,實際上是{{!fe: list t.id。

注意,這裡 沒有 }}符號!根據EasyPOI的官方文檔,{{}}代表的是表達式,根據表達式取裡邊的值。仔細看圖可以發現,表達式的閉合符號{{}}出現在圖中的右下角。也就是說,從第一列{{開始至右下角}}結束,這中間的所有內容都是表達式的一部分。

因為整個模板信息都是表達式的一部分,所以即使是普通字符串也需要專門標明。下面對表達式中的子表達式進行逐個說明。

!fe: 遍歷數據不創建row。

官方文檔中的這句話大家理解起來可能有點費解,什麼叫不創建row?實際上,不創建row是相對於創建row而言的,創建row的表達式是fe:。

就像是資料庫中每條記錄對應著一個實體對象,創建row表示每行就是一個實體對象Entity,這個實體對象的屬性用{{}}表達式包裹起來。

不創建row表示整個表達式中只有一個實體對象Object,只不過這個Object比較特別,它是由list中N個Entity拼接起來的。每一個Entity不僅僅是指模型本身,也包含了Excel的樣式,比如佔用了幾個單元格,單元格的坐標、排布順序等。

list 自定義的名稱,表示表達式中的數據集合,由代碼以list為鍵,從Map<String, Object>中獲取值的集合。

list這個名字容易理解,就是一個佔位符,可以隨便取。EasyPOI解析到list就知道Map<String, Object>中存在著該鍵的值的集合,後邊解析到數據就從該集合中取即可。


t 預定義值,表示集合中的任意對象。

從模板中我們大致能感覺到,list中每個對象叫做t,t.name就代表t的name屬性,所以t這個名字就可以隨便叫,反正它和list一樣,作用是佔位符。

但實際上這是一個大坑!如果你把t換成了其他值比如g,模板中其他地方寫g.name g.code等等,最終是解析不到的!官方文檔對這一點並沒有強調,而是作者實際踩了坑之後才發現的!

]] 換行符 多行遍歷導出。

對於這個符號的官方解釋也是莫名其妙,什麼叫換行符,多行遍歷導出?實際上它的意思就是,當解析到表達式中含有這個符號,該行後邊的內容就不解析了,管你後邊有沒有其他內容或者樣式。

該符號一定要寫在每行的最後一列,不然會出現每行列數不一樣的情況,EasyPOI內部做賦值的時候就會報空指針異常了。

『』 單引號表示常量值 『』 比如』1』 那麼輸出的就是 1

官方文檔中這裡的介紹也有坑。''是表示常量值,但實際上Excel中只是這麼些是不對的,因為Excel的單元格中遇到'後會認為後面都是字符串,所以得在單元格中寫''庫別:',這樣顯示出來的才是'庫別:',而不是字符串庫別:'。

經過上述分析,圖中的模板實際上就類似以下內容:

{{!fe: list t.id 『庫別:』 t.bin 換行 『商品名稱:』 t.name 換行 『商品編號:』 t.code t.barcode 換行 『生產日期:』 t.proDate 換行 『進貨日期:』 t.recvDate}}

如果list中有多條記錄,上述字符串就再循環拼接一些內容,最終都在一個{{}}表達式中。

至此,模板的設計已剖析完畢,讀者可根據自己的需求結合官方文檔自行設計模板。下面將對模板賦值進行介紹。

準備模板數據

從上節的描述中可知,只需要準備一個Map<String, Object>的對象即可,其中鍵為list,值為一個List數組,數組中元素類型為Map<String, Object>。代碼如下:

Map<String, Object> total = new HashMap<>();
List<Map<String, Object>> mapList = new ArrayList<>();
for (int i = 1; i <= 5; i++) {
    Map<String, Object> map = new HashMap<>();
    map.put("id", i + "");
    map.put("bin", "001 1000千克");
    map.put("name", "商品" + i);
    map.put("code", "goods" + i);
    map.put("proDate", "2019-05-30");
    map.put("recvDate", "2019-07-07");

    // 插入圖片
    ByteArrayOutputStream byteArrayOut = new ByteArrayOutputStream();
    BufferedImage bufferImg = ImageIO.read(BarcodeUtil.generateToStream("001"));
    ImageIO.write(bufferImg, "jpg", byteArrayOut);
    ImageEntity imageEntity = new ImageEntity(byteArrayOut.toByteArray(), 200, 1000);
    map.put("barcode", imageEntity);

    mapList.add(map);
}
total.put("list", mapList);

圖片數據導出

上述代碼中需要特殊關注的是圖片導出部分。EasyPOI導出圖片有兩種方式,一種是通過圖片的Url,還有一種是獲取圖片的byte[],畢竟圖片的本質就是byte[]。因為筆者的項目中圖片不是存放在資料庫之中,而是需要根據查詢結果動態生成條碼,所以通過byte[]導出圖片。

ImageEntity是EasyPOI內置的一個JavaBean,用於設定圖片的寬度和高度、導出方式、RowSpan和ColumnSpan等。調試EasyPOI的源碼可知,當設置了RowSpan或者ColumnSpan之後,圖片的高度設置就失效了,圖片大小會自動填充圖片所在的單元格。

圖片導出的坑點在於導出圖片的大小。假設我們將四個單元格合成為一個,希望導出的圖片能填充合併之後的單元格,但是對不起,EasyPOI暫時做不到,它只會填充合併之前左上角的單元格,具體原因如下源碼所示:

//BaseExportService.java
ClientAnchor anchor;
if (type.equals(ExcelType.HSSF)) {
    anchor = new HSSFClientAnchor(0, 0, 0, 0, (short) cell.getColumnIndex(), cell.getRow().getRowNum(), (short) (cell.getColumnIndex() + 1),
            cell.getRow().getRowNum() + 1);
} else {
    anchor = new XSSFClientAnchor(0, 0, 0, 0, (short) cell.getColumnIndex(), cell.getRow().getRowNum(), (short) (cell.getColumnIndex() + 1),
            cell.getRow().getRowNum() + 1);
}

可以看到,在創建圖片插入位置的時候已經指定了圖片的跨度為1行1列,即左上角的單元格。如果之前又設置了RowSpan或者ColumnSpan,那麼圖片高度的設置也會失效,最終導致導出的圖片非常小。


個人認為ImageEntity提供的RowSpan或者ColumnSpan的set方法並沒有什麼用,因為我們動態創建的合併單元格並不能被賦值。所以,導出圖片的最好方式就是直接指定它的高度,因為寬度會自動填充單元格,模板中單元格的寬度要合適。

//ExcelExportOfTemplateUtil.java
if (img.getRowspan()>1 || img.getColspan() > 1){
    img.setHeight(0);
    PoiMergeCellUtil.addMergedRegion(cell.getSheet(),cell.getRowIndex(),
            cell.getRowIndex() + img.getRowspan() - 1, cell.getColumnIndex(), cell.getColumnIndex() + img.getColspan() -1);
}

將數據導出至模板

以上準備工作全部完成後就可以將模板和數據進行組裝了,或者說是渲染,代碼如下所示:

public static void exportByTemplate(String templateName, Map<String, Object> data, OutputStream fileOut) {
    TemplateExportParams params = new TemplateExportParams("export/template/" + templateName, true);
    try {
        Workbook workbook = ExcelExportUtil.exportExcel(params, data);
        workbook.write(fileOut);
    } catch (Exception e) {
        LogUtil.error("", e);
    }
}

總結

網上針對EasyPOI的介紹多限於最基本的行插入功能,但實際上Excel模板的需求可能各式各樣。本文只是拋磚引玉,對EasyPOI中的部分概念做了詳細介紹,希望幫助大家少踩坑。

如果想詳細了解EasyPOI的各種功能,參考連結中的文檔說明及測試項目源碼就是最好的學習資料。希望大家都能得心應手地使用EasyPOI,大大提升開發效率!

參考連結

EasyPOI官方文檔

https://opensource.afterturn.cn/doc/easypoi.html

EasyPOI測試項目

https://gitee.com/lemur/easypoi-test一些坑

近日有網友求助我解決EasyPOI的複雜模板配置問題,通過解決該網友的問題發現了EasyPOI中的幾個坑點,補充說明幾個問題。

在複雜模板設計剖析一節中已經描述了EasyPOI支持的複雜的模板該如何配置。該模板的配置是絕對正確的,但是有3個點沒有說清楚,大家在照葫蘆畫瓢時容易出錯:

{{!fe: list需要在一個單獨的列中。EasyPOI源碼中是根據該單元格的行、列跨度來決定list中的每個元素需要多少行的。比如上述圖片中,該單元格的跨度是5行1列,也就是說,以後list中的每個元素都會佔用5行。如果覺得該列不符合自定義模板的風格,可以把該列的列寬設置為0,但一定需要有{{!fe: list。在對象的起始和結束符號{{}}之間不能有任何空的單元格!代碼中在解析到該單元格為空時會直接拋異常,如果就希望該單元格為空,得顯示寫入空字符串:』』』。換行符]]必須佔用每行的最後一個單元格!比如說第一行有10個單元格,第二行只用了前5個,那麼不能直接在第5個結束直接寫換行符]],而是需要把6-10個單元格合併,然後寫入]]。參考上述圖片中生產日期所在行的最後一列。這麼設置的原因是EasyPOI要求每行的單元格數目完全一致,因為源碼中判斷了每個單元格的列跨度,如果提前使用了]]換行符,那麼該列的數目就和其他行不同,那麼賦值的時候就亂掉了,會出現索引異常。



我們在星球聊了很多深度話題,你不來看看?

我的星球是否適合你?

點擊閱讀原文看看我們都聊過啥?

相關焦點

  • easypoi 4.2 更新,支持在線預覽,基於 wps 雲服務
    更新內容- 4.2.0 增加wps模塊 模板添加了index常量列 修復橫向迭代的時候,合併問題 添加XLSX水印工具類 修復 SAX
  • Excel 和 Word 簡易工具類 JEasyPoi 2.1.5 發布
    JEasyPoi 功能如同名字easy,主打的功能就是容易,可以讓一個沒接觸過poi的人方便地寫出 Excel 導出、Excel 模板導出、Excel 導入與 Word 模板導出等功能,通過簡單的註解和模板語言(熟悉的表達式語法),完成以往複雜的寫法。
  • php 導出 Excel
    前臺頁面的html代碼<button id="exportExcel"><i>導出Excel表格</i></button>前臺頁面的js代碼//導出$('#exportExcel').on('click', function () {
  • python數據分析——pandas導出數據合集
    header:指定列名,默認True即數據導出時包含列名。index:指定行名稱,默認True即數據導出時包含行索引。index_label:指定索引列列標籤,默認None。chunksize:一次性寫入多行,默認None。encoding:出文件中使用的編碼,默認'utf8'。
  • ArcGIS將Excel經緯度數據轉換為shp點數據
    一、操作準備 1.1 軟體 ArcMap10.4.1 1.2 數據 excel數據(全國機場點數據.xls) ★excel數據中要含有經緯度。
  • PDMan-2.2 發布,沉寂一年後全面開源 | 國產數據建模工具
    支持Windows,Mac,Linux等作業系統,具有上手容易,使用簡單的特點。2018年獲得碼雲GVP (Gitee Most Valuable Projects) - 碼雲最有價值開源項目因為種種原因,開發及更新沉寂了一年多。 通過多方面的努力,終於完成了插件替換以及升級,在生成文檔部分目前已經使用開源的poi+poi-tl,從而使得完全開源成為了現實。
  • SQL Server數據與Excel表的導入導出
    幾乎所有的資料庫管理者或者是與資料庫打過交道的朋友,幾乎都或多或少的了解並使用過數據導入導出的功能以便完成支持諸如數據合併、歸檔和分析等任務,以及開發應用程式或升級資料庫或伺服器,而這一切的便利都已經被SQL Server中的數據轉換服務(DTS)囊括其中,它提供了一套圖形化工具和可編程對象,以幫助管理員和開發人員和需要進行數據維護的人員解決數據移動問題,
  • excel的形狀與圖表——讓數據展示更加有趣
    雖然excel的主要功能是數據的統計與分析,但是也具有word、PPT中的某些圖表形狀功能。使用這些功能,可以使數據與圖形結合,從而更形象化、多樣化地呈現內容。比如形狀圖片的格式變換、smartart圖形、組合圖表以及動態圖表等。現在就一起來看看這些形狀與圖表功能的常用操作吧。
  • 使用pandas和openpyxl處理複雜Excel數據
    關於Excel數據處理,很多同學可能使用過Pyhton的pandas模塊,用它可以輕鬆地讀取和轉換Excel數據。但是實際中Excel表格結構可能比較雜亂,數據會分散不同的工作表中,而且在表格中分布很亂,這種情況下啊直接使用pandas就會非常吃力。本文蟲蟲給大家介紹使用pandas和openpyxl讀取這樣的數據的方法。
  • 使用簡單而強大的Excel來進行數據分析
    使用Excel進行數據分析是使用R或Python進行數據科學的先驅我們應該要學習用於分析數據的基本的Excel函數介紹我一直都很佩服Excel強大的數據分析能力。Excel具有非常廣泛的功能:可視化功能、數組,使你能夠迅速的通過數據產生洞察力,否則這些數據將很難看到價值。當然Excel它也有一些缺點。比如它不能非常有效地處理大型的數據集。相信每個人都已經遇到了這個問題。當你嘗試對大約200,000個條的數據進行數據計算的時候,你會注意到excel開始出現問題。
  • 多年來Excel填報數據的各種不如意,終於現在用這個神器全部都解決了
    Excel,由於其靈活簡單的特性,多年來一直是辦公用的最多的表格數據處理軟體,但是隨著網絡應用越來越深入,人們發現Excel已經不能滿足當前數據收集、統計以及同步的需要。首先,excel對數據收集的使用場景就受到限制,只能通過PC來完成數據收集。
  • 微信聊天記錄圖片保存路徑在哪裡?如何導出?
    一、蘋果手機微信聊天記錄圖片導出方法:1、將需要導出微信圖片的蘋果手機通過數據線連接到電腦。2、安裝並運行iTunes軟體,點擊左上角小手機圖標,再到備份欄目中選擇本電腦進行備份(備份數據時不能加密備份)。
  • 懂Excel就能輕鬆入門Python數據分析包pandas(十六):合併數據
    此系列文章收錄在公眾號中:數據大宇宙 > 數據處理 >E-pd經常聽別人說 Python 在數據領域有多厲害,結果學了很長時間,連數據處理都麻煩得要死。後來才發現,原來不是 Python 數據處理厲害,而是他有數據分析神器—— pandas前言本系列上一節說了拆分數據的案例,這次自然是說下怎麼合併數據。
  • poi!poi!poi!「艦隊collection」艦娘萌萌噠Gif動圖放送
    poi!poi!poi!POI這一口癖更是成為阿宅日常生活的一部分~於是,小編慣例為大家帶來萌作的Gif動圖,賣萌、聊天時都可以使用哦~喜歡的朋友趕緊來下吧!
  • 「Excel技巧」教你如何快速將excel表格轉化為圖片
    今日話題:excel表格如何轉化為圖片!關於excel表格如何轉化為圖片,這個在日常工作中經常會遇到。很多人可能先想到的是QQ截圖,畢竟QQ截圖,大家都用得很順手、很習慣。但是當表格太長了超出屏幕區域,用QQ截圖是沒辦法截完整;若把表格比例縮小,再用QQ截圖,又看不清楚圖片裡的字。怎麼辦?現在教你兩種將excel表格轉化為圖片的方法,不論表格多大,都不成問題。
  • 系統流程圖全新模板!輕鬆設計軟體系統流程圖
    換句話說,就是用圖形符號以黑盒子形式描繪系統每個程序或資料庫等具體部件,表達數據在各個部件之間的流動情況。通過系統流程圖相關工作人員可以做出更好的分析和判斷,而不能對系統進行控制。以下為系統流程圖的通用模板,每一個模板都可以在億圖圖示快速找到並一鍵使用。
  • 掌握這7條excel函數,自動化生成數據周報上篇
    excel的二八原則曾經在面試時候被問到VLOOKUP和HLOOKUP有什麼區別,我回答的是前者是以列匹配,後者是以行匹配。面試完我一個勁兒後悔沒有回答好,對這個函數不熟悉,回答太簡單。諷刺的是已經過去好幾年,我卻一次未用到過HLOOKUP。所以真的沒必要抱著一本excel大全在那挨個學函數,浪費時間。