<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          使用 EasyPOI 優(yōu)雅導(dǎo)出Excel模板數(shù)據(jù)(含圖片)

          共 4954字,需瀏覽 10分鐘

           ·

          2020-09-24 10:08

          點(diǎn)擊上方藍(lán)色“程序猿DD”,選擇“設(shè)為星標(biāo)”

          回復(fù)“資源”獲取獨(dú)家整理的學(xué)習(xí)資料!

          作者 |?星懸月

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

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

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

          版本及依賴說明

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

          需要注意的是,Apache POI的4.0.0相對之前的版本有很大的變更,如果之前代碼中Excel操作部分依賴于舊的版本,那么不建議使用4.0.0及之后的版本。當(dāng)然,如果之前代碼不涉及或很少涉及WorkBook的創(chuàng)建細(xì)節(jié),使用新版也沒有問題。

          筆者需要改寫的項(xiàng)目基于JEECG 3.7版本,依賴的是3.9版本的Apache POI,而JEECG維護(hù)的jeasypoi版本最高只有2.2.0,而該版本并不支持模板導(dǎo)出圖片功能。說到這里又要吐槽以下JEECG團(tuán)隊(duì),既然自己不打算維護(hù)jeasypoi,那項(xiàng)目中直接使用官方的EasyPOI不就好了,2.2.0版本的jeasypoi給開發(fā)者挖了多少坑啊!

          為了和舊版本兼容,又想使用EasyPOI帶來的圖片導(dǎo)出功能,所以筆者最終采用的EasyPOI版本是3.3.0,對應(yīng)的Apache POI依賴是3.15。

          Maven配置如下所示:

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

          <dependencies>
          ????<dependency>
          ????????<groupId>org.apache.poigroupId>
          ????????<artifactId>poiartifactId>
          ????????<version>${poi.version}version>
          ????dependency>
          ????<dependency>
          ????????<groupId>org.apache.poigroupId>
          ????????<artifactId>poi-ooxmlartifactId>
          ????????<version>${poi.version}version>
          ????dependency>
          ????<dependency>
          ????????<groupId>org.apache.poigroupId>
          ????????<artifactId>poi-ooxml-schemasartifactId>
          ????????<version>${poi.version}version>
          ????dependency>
          ????<dependency>
          ????????<groupId>org.apache.poigroupId>
          ????????<artifactId>poi-scratchpadartifactId>
          ????????<version>${poi.version}version>
          ????dependency>
          ????<dependency>
          ????????<groupId>cn.afterturngroupId>
          ????????<artifactId>easypoi-webartifactId>
          ????????<version>${easypoi.version}version>
          ????dependency>
          dependencies>

          Excel模板的設(shè)計(jì)

          我們使用EasyPOI的模板導(dǎo)出功能就是不想通過編碼的方式來設(shè)計(jì)Excel報(bào)表的樣式,所以工作的第一步就是設(shè)計(jì)Excel模板,分清楚哪些部分是固定的,哪些是需要循環(huán)填充的。EasyPOI有自己的表達(dá)式語言,每種表達(dá)式的詳細(xì)介紹請參考后文的參考鏈接。

          一個(gè)簡單的Excel報(bào)表模板

          一些簡單的模板就不在這里詳細(xì)解釋了,只放一下效果圖和模板配置內(nèi)容。等讀者明白了復(fù)雜的模板如何制作并如何填值的時(shí)候,簡單的很快就能明白了。

          先看看報(bào)表效果圖:

          再看看實(shí)際的模板:

          看了上述兩張圖,是不是已經(jīng)感受到模板導(dǎo)出功能的強(qiáng)大了呢?

          一個(gè)復(fù)雜的Excel報(bào)表模板

          下面要介紹的這個(gè)模板比較復(fù)雜,不像是常見的那種一行是一條記錄的情況,所以將詳細(xì)介紹該模板的配置,順帶對EasyPOI的部分表達(dá)式進(jìn)行簡單介紹。

          還是先看效果圖:

          再看看模板:

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

          復(fù)雜模板設(shè)計(jì)剖析

          從貨品信息的模板圖及效果圖中我們發(fā)現(xiàn),整個(gè)模板實(shí)際上分為上下兩部分。其中上部分為不變的抬頭信息,下部分為循環(huán)插入的貨品明細(xì)信息。所以我們關(guān)注的重點(diǎn)是下半部分的語法。

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

          注意,這里 沒有 }}符號!根據(jù)EasyPOI的官方文檔,{{}}代表的是表達(dá)式,根據(jù)表達(dá)式取里邊的值。仔細(xì)看圖可以發(fā)現(xiàn),表達(dá)式的閉合符號{{}}出現(xiàn)在圖中的右下角。也就是說,從第一列{{開始至右下角}}結(jié)束,這中間的所有內(nèi)容都是表達(dá)式的一部分。

          因?yàn)檎麄€(gè)模板信息都是表達(dá)式的一部分,所以即使是普通字符串也需要專門標(biāo)明。下面對表達(dá)式中的子表達(dá)式進(jìn)行逐個(gè)說明。

          !fe: 遍歷數(shù)據(jù)不創(chuàng)建row。

          官方文檔中的這句話大家理解起來可能有點(diǎn)費(fèi)解,什么叫不創(chuàng)建row?實(shí)際上,不創(chuàng)建row是相對于創(chuàng)建row而言的,創(chuàng)建row的表達(dá)式是fe:。

          就像是數(shù)據(jù)庫中每條記錄對應(yīng)著一個(gè)實(shí)體對象,創(chuàng)建row表示每行就是一個(gè)實(shí)體對象Entity,這個(gè)實(shí)體對象的屬性用{{}}表達(dá)式包裹起來。

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

          list 自定義的名稱,表示表達(dá)式中的數(shù)據(jù)集合,由代碼以list為鍵,從Map中獲取值的集合。

          list這個(gè)名字容易理解,就是一個(gè)占位符,可以隨便取。EasyPOI解析到list就知道Map中存在著該鍵的值的集合,后邊解析到數(shù)據(jù)就從該集合中取即可。


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

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

          但實(shí)際上這是一個(gè)大坑!如果你把t換成了其他值比如g,模板中其他地方寫g.name g.code等等,最終是解析不到的!官方文檔對這一點(diǎn)并沒有強(qiáng)調(diào),而是作者實(shí)際踩了坑之后才發(fā)現(xiàn)的!

          ]] 換行符 多行遍歷導(dǎo)出。

          對于這個(gè)符號的官方解釋也是莫名其妙,什么叫換行符,多行遍歷導(dǎo)出?實(shí)際上它的意思就是,當(dāng)解析到表達(dá)式中含有這個(gè)符號,該行后邊的內(nèi)容就不解析了,管你后邊有沒有其他內(nèi)容或者樣式。

          該符號一定要寫在每行的最后一列,不然會出現(xiàn)每行列數(shù)不一樣的情況,EasyPOI內(nèi)部做賦值的時(shí)候就會報(bào)空指針異常了。

          ‘’ 單引號表示常量值 ‘’ 比如’1’ 那么輸出的就是 1

          官方文檔中這里的介紹也有坑。''是表示常量值,但實(shí)際上Excel中只是這么些是不對的,因?yàn)镋xcel的單元格中遇到'后會認(rèn)為后面都是字符串,所以得在單元格中寫''庫別:',這樣顯示出來的才是'庫別:',而不是字符串庫別:'。

          經(jīng)過上述分析,圖中的模板實(shí)際上就類似以下內(nèi)容:

          {{!fe: list t.id ‘庫別:’ t.bin 換行 ‘商品名稱:’ t.name 換行 ‘商品編號:’ t.code t.barcode 換行 ‘生產(chǎn)日期:’ t.proDate 換行 ‘進(jìn)貨日期:’ t.recvDate}}

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

          至此,模板的設(shè)計(jì)已剖析完畢,讀者可根據(jù)自己的需求結(jié)合官方文檔自行設(shè)計(jì)模板。下面將對模板賦值進(jìn)行介紹。

          準(zhǔn)備模板數(shù)據(jù)

          從上節(jié)的描述中可知,只需要準(zhǔn)備一個(gè)Map的對象即可,其中鍵為list,值為一個(gè)List數(shù)組,數(shù)組中元素類型為Map。代碼如下:

          Map?total?=?new?HashMap<>();
          List>?mapList?=?new?ArrayList<>();
          for?(int?i?=?1;?i?<=?5;?i++)?{
          ????Map?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);

          圖片數(shù)據(jù)導(dǎo)出

          上述代碼中需要特殊關(guān)注的是圖片導(dǎo)出部分。EasyPOI導(dǎo)出圖片有兩種方式,一種是通過圖片的Url,還有一種是獲取圖片的byte[],畢竟圖片的本質(zhì)就是byte[]。因?yàn)楣P者的項(xiàng)目中圖片不是存放在數(shù)據(jù)庫之中,而是需要根據(jù)查詢結(jié)果動態(tài)生成條碼,所以通過byte[]導(dǎo)出圖片。

          ImageEntity是EasyPOI內(nèi)置的一個(gè)JavaBean,用于設(shè)定圖片的寬度和高度、導(dǎo)出方式、RowSpan和ColumnSpan等。調(diào)試EasyPOI的源碼可知,當(dāng)設(shè)置了RowSpan或者ColumnSpan之后,圖片的高度設(shè)置就失效了,圖片大小會自動填充圖片所在的單元格。

          圖片導(dǎo)出的坑點(diǎn)在于導(dǎo)出圖片的大小。假設(shè)我們將四個(gè)單元格合成為一個(gè),希望導(dǎo)出的圖片能填充合并之后的單元格,但是對不起,EasyPOI暫時(shí)做不到,它只會填充合并之前左上角的單元格,具體原因如下源碼所示:

          //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);
          }

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


          個(gè)人認(rèn)為ImageEntity提供的RowSpan或者ColumnSpan的set方法并沒有什么用,因?yàn)槲覀儎討B(tài)創(chuàng)建的合并單元格并不能被賦值。所以,導(dǎo)出圖片的最好方式就是直接指定它的高度,因?yàn)閷挾葧詣犹畛鋯卧瘢0逯袉卧竦膶挾纫线m。

          //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);
          }

          將數(shù)據(jù)導(dǎo)出至模板

          以上準(zhǔn)備工作全部完成后就可以將模板和數(shù)據(jù)進(jìn)行組裝了,或者說是渲染,代碼如下所示:

          public?static?void?exportByTemplate(String?templateName,?Map?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);
          ????}
          }

          總結(jié)

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

          如果想詳細(xì)了解EasyPOI的各種功能,參考鏈接中的文檔說明及測試項(xiàng)目源碼就是最好的學(xué)習(xí)資料。希望大家都能得心應(yīng)手地使用EasyPOI,大大提升開發(fā)效率!

          參考鏈接

          EasyPOI官方文檔

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

          EasyPOI測試項(xiàng)目

          • https://gitee.com/lemur/easypoi-test

          一些坑

          近日有網(wǎng)友求助我解決EasyPOI的復(fù)雜模板配置問題,通過解決該網(wǎng)友的問題發(fā)現(xiàn)了EasyPOI中的幾個(gè)坑點(diǎn),補(bǔ)充說明幾個(gè)問題。

          在復(fù)雜模板設(shè)計(jì)剖析一節(jié)中已經(jīng)描述了EasyPOI支持的復(fù)雜的模板該如何配置。該模板的配置是絕對正確的,但是有3個(gè)點(diǎn)沒有說清楚,大家在照葫蘆畫瓢時(shí)容易出錯(cuò):

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


          往期推薦

          贈書:一本書帶你吃透Nginx應(yīng)用與運(yùn)維

          超全的 Linux Shell 文本處理工具集錦,快收藏

          今年2月的微盟“刪庫”主角被判 6 年有期徒刑

          MySQL中的InnoDB是怎么解決幻讀的?

          Redis為什么變慢了?常見延遲問題定位與分析

          為什么 GROUP BY 之后不能直接引用原表中的列?

          Java開發(fā)中Websocket的技術(shù)選型參考


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

          我的星球是否適合你?

          點(diǎn)擊閱讀原文看看我們都聊過啥?

          瀏覽 93
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日日夜夜影音 | 欧.美干逼逼 | 青青操久操视频 | 欧美日韩在线一级黄色电影 | 一本色道久久综合无码 |