Java使用EasyExcel模版導(dǎo)出詳細(xì)操作教程
一、官方提供方法
easyexcel官方文檔 填充Excel | Easy Excel
官方demo是利用本地模版文件填充并下載到本地
/**
* 復(fù)雜的填充
*
* @since 2.1.1
*/
@Test
public void complexFill() {
// 模板注意 用{} 來表示你要用的變量 如果本來就有"{","}" 特殊字符 用"\{","\}"代替
// {} 代表普通變量 {.} 代表是list的變量
String templateFileName =
TestFileUtil.getPath() + "demo" + File.separator + "fill" + File.separator + "complex.xlsx";
String fileName = TestFileUtil.getPath() + "complexFill" + System.currentTimeMillis() + ".xlsx";
// 方案1
try (ExcelWriter excelWriter = EasyExcel.write(fileName).withTemplate(templateFileName).build()) {
WriteSheet writeSheet = EasyExcel.writerSheet().build();
// 這里注意 入?yún)⒂昧薴orceNewRow 代表在寫入list的時(shí)候不管list下面有沒有空行 都會(huì)創(chuàng)建一行,然后下面的數(shù)據(jù)往后移動(dòng)。默認(rèn) 是false,會(huì)直接使用下一行,如果沒有則創(chuàng)建。
// forceNewRow 如果設(shè)置了true,有個(gè)缺點(diǎn) 就是他會(huì)把所有的數(shù)據(jù)都放到內(nèi)存了,所以慎用
// 簡單的說 如果你的模板有l(wèi)ist,且list不是最后一行,下面還有數(shù)據(jù)需要填充 就必須設(shè)置 forceNewRow=true 但是這個(gè)就會(huì)把所有數(shù)據(jù)放到內(nèi)存 會(huì)很耗內(nèi)存
// 如果數(shù)據(jù)量大 list不是最后一行 參照下一個(gè)
FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
excelWriter.fill(data(), fillConfig, writeSheet);
excelWriter.fill(data(), fillConfig, writeSheet);
Map<String, Object> map = MapUtils.newHashMap();
map.put("date", "2019年10月9日13:28:28");
map.put("total", 1000);
excelWriter.fill(map, writeSheet);
}
}二、根據(jù)業(yè)務(wù)場景的方法
我用的是web項(xiàng)目,將輸出改為了輸出流 OutputStream
/**
* 導(dǎo)出質(zhì)檢任務(wù)詳情
*
* @param id 任務(wù)主鍵,用于獲取數(shù)據(jù)
*/
@Override
public void exportTask(Long id, HttpServletResponse response) {
ExcelWriter excelWriter = null;
try {
// outputStream:要導(dǎo)出的文件的輸出流
OutputStream outputStream = response.getOutputStream();
// 模版文件
ClassPathResource classPathResource = new ClassPathResource("template/taskTemplate.xlsx");
// 使用模版文件的兩種方式:
// 1、文件路徑:.withTemplate(templateFileName)
// 2、輸入流:.withTemplate(inputStream)
String templateFileName = classPathResource.getFile().getPath();
InputStream inputStream = classPathResource.getInputStream();
excelWriter = EasyExcel.write(outputStream).withTemplate(inputStream).excelType(ExcelTypeEnum.XLSX).autoCloseStream(Boolean.FALSE).build();
WriteSheet writeSheet = EasyExcel.writerSheet().build();
// 獲取數(shù)據(jù) dataList columnList formData
Map<String, Object> mapDetail = qmsQcTaskDetailService.getDataListByTaskId(id);
// 調(diào)用微服務(wù)獲取字典 determine 用來翻譯判定結(jié)果
List<SysDictItem> determineItem = remoteDictService.getDictByType("determine").getData();
// 調(diào)用微服務(wù)獲取字典 product_unit 用來翻譯零件單位
List<SysDictItem> unitItem = remoteDictService.getDictByType("product_unit").getData();
/* 1、List以外的數(shù)據(jù) */
QmsQcTask formData = (QmsQcTask) mapDetail.get("formData");
determineItem.stream().filter(item -> StrUtil.equals(formData.getResult(), item.getItemValue())).findFirst().ifPresent(sysDictItem -> formData.setResult(sysDictItem.getLabel()));
unitItem.stream().filter(item -> StrUtil.equals(formData.getMaterialUnit(), item.getItemValue())).findFirst().ifPresent(sysDictItem -> formData.setMaterialUnit(sysDictItem.getLabel()));
excelWriter.fill(formData, writeSheet);
/* 2、List cols數(shù)據(jù) */
List<Map<String, Object>> cols = new ArrayList<>();
// 查找抽檢數(shù)是多少,即有幾列
int num = formData.getSampleNum();
for (int i = 1; i <= num; i++) {
Map<String, Object> map = new HashMap<>();
// 列名
map.put("label", "" + i);
cols.add(map);
}
// 橫向填充
FillConfig fillConfigCols = FillConfig.builder().direction(WriteDirectionEnum.HORIZONTAL).build();
excelWriter.fill(new FillWrapper("cols", cols), fillConfigCols, writeSheet);
/* 3、List details數(shù)據(jù) */
// 這里注意 入?yún)⒂昧薴orceNewRow 代表在寫入list的時(shí)候不管list下面有沒有空行 都會(huì)創(chuàng)建一行,然后下面的數(shù)據(jù)往后移動(dòng)。默認(rèn) 是false,會(huì)直接使用下一行,如果沒有則創(chuàng)建。
// forceNewRow 如果設(shè)置了true,有個(gè)缺點(diǎn) 就是他會(huì)把所有的數(shù)據(jù)都放到內(nèi)存了,所以慎用
// 簡單的說 如果你的模板有l(wèi)ist,且list不是最后一行,下面還有數(shù)據(jù)需要填充 就必須設(shè)置 forceNewRow=true 但是這個(gè)就會(huì)把所有數(shù)據(jù)放到內(nèi)存 會(huì)很耗內(nèi)存
FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
List<Map<String, Object>> dataList = (List<Map<String, Object>>) mapDetail.get("dataList");
for (int i = 0; i < dataList.size(); i++) {
Map<String, Object> data = dataList.get(i);
// 翻譯判定合不合格(因?yàn)闃I(yè)務(wù)和字典不在一個(gè)數(shù)據(jù)庫,所以放在代碼里處理)
String determine = (String) data.get("determine");
determineItem.stream().filter(item -> StrUtil.equals(determine, item.getItemValue())).findFirst().ifPresent(sysDictItem -> data.put("determine", sysDictItem.getLabel()));
// 如果沒有質(zhì)檢標(biāo)準(zhǔn),每一個(gè)質(zhì)檢值都要轉(zhuǎn)為合不合格
if (StrUtil.isBlank((String) data.get("inspection_standard"))){
int samplingNum = Optional.ofNullable((Integer) data.get("sampling_num")).orElse(0);
for (int j = 1; j <= samplingNum; j++) {
String val = (String) data.get(""+j);
int finalJ = j;
determineItem.stream().filter(item -> StrUtil.equals(val, item.getItemValue())).findFirst().ifPresent(sysDictItem -> data.put(""+ finalJ, sysDictItem.getLabel()));
}
}
// 給數(shù)據(jù)加上序號(hào)
data.put("index", i + 1);
}
excelWriter.fill(new FillWrapper("details", dataList), fillConfig, writeSheet);
// 設(shè)置輸出流格式以及文件名:
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
String fileName = URLEncoder.encode("質(zhì)檢任務(wù)", "UTF-8").replaceAll("\\+", "%20");
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
// 千萬別忘記close關(guān)閉流
if (excelWriter != null) {
excelWriter.close();
}
}
}模版:

效果:

三、說明
1、獲取輸出流
// outputStream:要導(dǎo)出的文件的輸出流 OutputStream outputStream = response.getOutputStream();
2、獲取模版文件
// 模版文件
ClassPathResource classPathResource = new ClassPathResource("template/taskTemplate.xlsx");
// 方式一:路徑
String templateFileName = classPathResource.getFile().getPath();
// 方式二:輸入流
InputStream inputStream = classPathResource.getInputStream();3、創(chuàng)建 ExcelWriter 和 WriteSheet
excelWriter = EasyExcel.write(outputStream).withTemplate(inputStream).excelType(ExcelTypeEnum.XLSX).autoCloseStream(Boolean.FALSE).build(); WriteSheet writeSheet = EasyExcel.writerSheet().build();
使用模版文件有兩種方式,使用其中一種就可以:
1.文件路徑:.withTemplate(templateFileName)
2.輸入流:.withTemplate(inputStream)
4、獲取數(shù)據(jù)
Map<String, Object> mapDetail = qmsQcTaskDetailService.getDataListByTaskId(id);
我這里直接調(diào)用了方法,map包含
dataList主要列表數(shù)據(jù) formData 列表外的數(shù)據(jù)
5、填充數(shù)據(jù)
1.獲取字典數(shù)據(jù)
// 調(diào)用微服務(wù)獲取字典 determine 用來翻譯判定結(jié)果
List<SysDictItem> determineItem = remoteDictService.getDictByType("determine").getData();
// 調(diào)用微服務(wù)獲取字典 product_unit 用來翻譯零件單位
List<SysDictItem> unitItem = remoteDictService.getDictByType("product_unit").getData();我的業(yè)務(wù)數(shù)據(jù)和字典不在同一個(gè)數(shù)據(jù)庫內(nèi),sql無法翻譯,所以需要在代碼里翻譯
2.填充列表外的數(shù)據(jù)
/* 1、List以外的數(shù)據(jù) */
QmsQcTask formData = (QmsQcTask) mapDetail.get("formData");
determineItem.stream().filter(item -> StrUtil.equals(formData.getResult(), item.getItemValue())).findFirst().ifPresent(sysDictItem -> formData.setResult(sysDictItem.getLabel()));
unitItem.stream().filter(item -> StrUtil.equals(formData.getMaterialUnit(), item.getItemValue())).findFirst().ifPresent(sysDictItem -> formData.setMaterialUnit(sysDictItem.getLabel()));
excelWriter.fill(formData, writeSheet);只要對(duì)象屬性名和模版里寫的標(biāo)記已知就可以了
3.填充缺少的列名
模版:

效果:

因?yàn)槲液蟀氩糠至忻婕暗叫修D(zhuǎn)列,且不固定列數(shù),所以需要橫著填充上列名。注意列表的名字“cols”要對(duì)應(yīng)起來。
使用.direction(WriteDirectionEnum.HORIZONTAL) 設(shè)置為橫向填充。
/* 2、List cols數(shù)據(jù) */
List<Map<String, Object>> cols = new ArrayList<>();
// 查找抽檢數(shù)是多少,即有幾列
int num = formData.getSampleNum();
for (int i = 1; i <= num; i++) {
Map<String, Object> map = new HashMap<>();
// 列名
map.put("label", "" + i);
cols.add(map);
}
// 橫向填充
FillConfig fillConfigCols = FillConfig.builder().direction(WriteDirectionEnum.HORIZONTAL).build();
excelWriter.fill(new FillWrapper("cols", cols), fillConfigCols, writeSheet);4.填充主列表數(shù)據(jù)
/* 3、List details數(shù)據(jù) */
// 這里注意 入?yún)⒂昧薴orceNewRow 代表在寫入list的時(shí)候不管list下面有沒有空行 都會(huì)創(chuàng)建一行,然后下面的數(shù)據(jù)往后移動(dòng)。默認(rèn) 是false,會(huì)直接使用下一行,如果沒有則創(chuàng)建。
// forceNewRow 如果設(shè)置了true,有個(gè)缺點(diǎn) 就是他會(huì)把所有的數(shù)據(jù)都放到內(nèi)存了,所以慎用
// 簡單的說 如果你的模板有l(wèi)ist,且list不是最后一行,下面還有數(shù)據(jù)需要填充 就必須設(shè)置 forceNewRow=true 但是這個(gè)就會(huì)把所有數(shù)據(jù)放到內(nèi)存 會(huì)很耗內(nèi)存
FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
List<Map<String, Object>> dataList = (List<Map<String, Object>>) mapDetail.get("dataList");
/*以下為個(gè)人業(yè)務(wù)處理*/
for (int i = 0; i < dataList.size(); i++) {
Map<String, Object> data = dataList.get(i);
// 翻譯判定合不合格(因?yàn)闃I(yè)務(wù)和字典不在一個(gè)數(shù)據(jù)庫,所以放在代碼里處理)
String determine = (String) data.get("determine");
determineItem.stream().filter(item -> StrUtil.equals(determine, item.getItemValue())).findFirst().ifPresent(sysDictItem -> data.put("determine", sysDictItem.getLabel()));
// 如果沒有質(zhì)檢標(biāo)準(zhǔn),每一個(gè)質(zhì)檢值都要轉(zhuǎn)為合不合格
if (StrUtil.isBlank((String) data.get("inspection_standard"))){
int samplingNum = Optional.ofNullable((Integer) data.get("sampling_num")).orElse(0);
for (int j = 1; j <= samplingNum; j++) {
String val = (String) data.get(""+j);
int finalJ = j;
determineItem.stream().filter(item -> StrUtil.equals(val, item.getItemValue())).findFirst().ifPresent(sysDictItem -> data.put(""+ finalJ, sysDictItem.getLabel()));
}
}
// 給數(shù)據(jù)加上序號(hào)
data.put("index", i + 1);
}
/*以上為個(gè)人業(yè)務(wù)處理*/
excelWriter.fill(new FillWrapper("details", dataList), fillConfig, writeSheet);最重要的三句話:
FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
List<Map<String, Object>> dataList = (List<Map<String, Object>>) mapDetail.get("dataList");
excelWriter.fill(new FillWrapper("details", dataList), fillConfig, writeSheet);這里注意 入?yún)⒂昧薴orceNewRow 代表在寫入list的時(shí)候不管list下面有沒有空行 都會(huì)創(chuàng)建一行,然后下面的數(shù)據(jù)往后移動(dòng)。默認(rèn) 是false,會(huì)直接使用下一行,如果沒有則創(chuàng)建。forceNewRow 如果設(shè)置了true,有個(gè)缺點(diǎn) 就是他會(huì)把所有的數(shù)據(jù)都放到內(nèi)存了,所以慎用簡單的說 如果你的模板有l(wèi)ist,且list不是最后一行,下面還有數(shù)據(jù)需要填充 就必須設(shè)置 forceNewRow=true 但是這個(gè)就會(huì)把所有數(shù)據(jù)放到內(nèi)存 會(huì)很耗內(nèi)存
5.設(shè)置輸出流格式以及文件名
// 設(shè)置輸出流格式以及文件名:
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
String fileName = URLEncoder.encode("質(zhì)檢任務(wù)", "UTF-8").replaceAll("\\+", "%20");
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");四、思想
我的模版使用了動(dòng)態(tài)的列,不確定有幾列,所以我就寫了一百多個(gè)。

還有另外一種設(shè)想:
如果先把列表外的數(shù)據(jù)填充,然后將主列表的列填充完畢,再把主列表的標(biāo)記寫上,這些操作完畢之后把文件輸出到另外的輸出流上,再將此輸出流轉(zhuǎn)為輸入流,再把主列表的數(shù)據(jù)填充,輸出到response。
理論上可行,但我沒試。
總結(jié)
到此這篇關(guān)于Java使用EasyExcel模版導(dǎo)出的文章就介紹到這了,更多相關(guān)使用EasyExcel模版導(dǎo)出內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot整合Redis實(shí)現(xiàn)常用功能超詳細(xì)過程
這篇文章主要介紹了SpringBoot整合Redis實(shí)現(xiàn)常用功能,登陸功能是每個(gè)項(xiàng)目必備的功能吧,但是想設(shè)計(jì)好,卻是很難,下面介紹兩種登陸功能的解決方式,需要的朋友可以參考下2022-08-08
IntellIJ?IDEA顯示內(nèi)存不足的完美解決方法
IntelliJ?IDEA是一款廣泛使用的Java集成開發(fā)環(huán)境,以其強(qiáng)大的功能和高效的編碼體驗(yàn)深受開發(fā)者喜愛,然而?在我們導(dǎo)入大項(xiàng)目時(shí),?IntellIJ?Idea?向我們拋出了一個(gè)?OutOfMemoryError?內(nèi)存不足,?所以本文給大家介紹了IntellIJ?Idea顯示內(nèi)存不足的完美解決方法2025-03-03
Java中日期時(shí)間比較的多種方法及實(shí)用代碼
本文介紹了Java中不同日期時(shí)間類的比較方法,包括java.util.Date、java.util.Calendar、java.time?API和java.sql.Timestamp,提供了使用before、after和compareTo等方法進(jìn)行日期比較的示例代碼,展示了各自的特點(diǎn)和使用場景,需要的朋友可以參考下2024-09-09
解決使用@ManyToMany查詢數(shù)據(jù)時(shí)的死循環(huán)問題
這篇文章主要介紹了解決使用@ManyToMany查詢數(shù)據(jù)時(shí)的死循環(huán)問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12
Java實(shí)現(xiàn)大數(shù)運(yùn)算的實(shí)例代碼
這篇文章主要介紹了Java實(shí)現(xiàn)大數(shù)運(yùn)算的實(shí)例代碼,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-06-06
java實(shí)現(xiàn)識(shí)別二維碼圖片功能
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)識(shí)別二維碼圖片功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-04-04
Spring容器擴(kuò)展機(jī)制的實(shí)現(xiàn)原理
這篇文章主要介紹了Spring容器擴(kuò)展機(jī)制的實(shí)現(xiàn)原理,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-11-11

