使用 EasyPOI 優雅匯出Excel模板資料(含圖片)
前言
最近有讀者在問easypoi的問題,抽空整理了一篇文章。
正文
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
list這個名字很容易理解,就是一個佔位符,可以隨便取。 EasyPOI解析到list就知道Map
搜尋Java知音公眾號,回覆“後端面試”,送你一份Java面試題寶典.pdf
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
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,那么图片高度的设置也会失效,最终导致导出的图片非常小。
搜索Java知音公众号,回复“后端面试”,送你一份Java面试题宝典.pdf
个人认为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 優雅匯出Excel模板資料(含圖片)的詳細內容。更多資訊請關注PHP中文網其他相關文章!

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

AI Hentai Generator
免費產生 AI 無盡。

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

熱門話題

本文將指導您完成為Excel Pivot表和圖表創建時間表的過程,並演示如何使用它以動態和引人入勝的方式與數據進行交互。 您的數據在Pivo中組織了

本教程演示瞭如何在數據集中有效地定位頂部N值並使用Excel公式檢索關聯的數據。 無論您需要最高,最低還是符合特定標準的人,本指南都提供解決方案。 Findi

掌握Google表格分類:綜合指南 在Google表中對數據進行排序不需要復雜。本指南涵蓋了各種技術,從整個床單到特定範圍,按顏色,日期和多個列。 無論你是諾維

在本教程中,您將學習如何在Excel中使用正則表達式來查找和提取與給定模式相匹配的子字符串。 Microsoft Excel提供了許多功能,可以從單元格中提取文本。這些功能可以與大多數

本教程向您展示瞭如何將下拉列表添加到Outlook電子郵件模板中,包括多個選擇和數據庫總體。 雖然Outlook並未直接支持下拉列表,但本指南提供了創造性的解決方法。 電子郵件模板SAV

如果您現在可以撰寫一封電子郵件並在以後的更合適的時間發送它,是否會很方便?有了Outlook的調度功能,您就可以做到這一點! 想像一下您在深夜工作,靈感來自輝煌的IDE

本指南向您顯示了啟用Gmail中的電子郵件模板的兩種簡單方法:使用Gmail的內置設置或安裝Gmail Chrome擴展名的共享電子郵件模板。 Gmail模板是經常發送電子郵件的大量節省時間,消除了

本教程展示了使用內置功能和自定義VBA函數在Excel單元格中分離文本和數字的幾種方法。 您將在刪除文本時學習如何提取數字,隔離文本時丟棄數字
