目录
- 前言
- 一、情景介绍
- 二、文档介绍
- 2.1 读取模板
- 2.2 填充模板
- 三、代码示例
- 3.1 案例一:工资表
- 3.2 案例二:报价单
- 四、我所遇到的问题
前言
Java-easyExcel入门教程:https://blog.csdn.net/xhmico/article/details/134714025
之前有介绍过如何使用 easyExcel
,以及写了两个入门的 demo
,这两个 demo
能应付在开发中大多数的导入和导出需求,不过有时候面对一些复杂的表格,就会有点不够用,该篇是如何使用模板实现导出功能
一、情景介绍
在实际的开发过程中可能会遇到需要导出一些带有复杂表头的表格,比如:工资表和考勤表
或者是类似于发票、报价单这种具有模板性质的表格
如果仅仅通过代码去实现是比较困难的,通常情况下会写一个模板 excel
,模板中的需要变动的数据用占位符替代,导出的文件按照该模板填充数据
二、文档介绍
easyexcel
也提供了根据模板写入的功能,可以用较为简单的方式实现上述功能,其基本步骤为:
- ① 读取模板文件
- ② 填充模板中的占位符
2.1 读取模板
官方文档:根据模板写入
在官方文档中给出了如何使用 easyexcel
读取模板的代码示例:
官网代码示例:
/*** 根据模板写入* <p>1. 创建excel对应的实体对象 参照{@link IndexData}* <p>2. 使用{@link ExcelProperty}注解指定写入的列* <p>3. 使用withTemplate 写取模板* <p>4. 直接写即可*/@Testpublic void templateWrite() {String templateFileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";String fileName = TestFileUtil.getPath() + "templateWrite" + System.currentTimeMillis() + ".xlsx";// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭// 这里要注意 withTemplate 的模板文件会全量存储在内存里面,所以尽量不要用于追加文件,如果文件模板文件过大会OOM// 如果要再文件中追加(无法在一个线程里面处理,可以在一个线程的建议参照多次写入的demo) 建议临时存储到数据库 或者 磁盘缓存(ehcache) 然后再一次性写入EasyExcel.write(fileName, DemoData.class).withTemplate(templateFileName).sheet().doWrite(data());}
从上述示例代码中可以看到读取模板文件的方法为:withTemplate()
public ExcelWriterBuilder withTemplate(InputStream templateInputStream) {this.writeWorkbook.setTemplateInputStream(templateInputStream);return this;}public ExcelWriterBuilder withTemplate(File templateFile) {this.writeWorkbook.setTemplateFile(templateFile);return this;}public ExcelWriterBuilder withTemplate(String pathName) {return this.withTemplate(new File(pathName));}
2.2 填充模板
官方文档:填充Excel
注意:这里建议先去看下官方文档中给的示例,会比较好理解
- 在模板中占位符用
{变量}
表示,比如{name}
、{age}
- 如果占位符为列表,则使用
{.变量}
表示,比如:{.name}
、{.age}
进行填充的方法为:fill()
public ExcelWriter fill(Object data, WriteSheet writeSheet) {return this.fill((Object)data, (FillConfig)null, writeSheet);}public ExcelWriter fill(Object data, FillConfig fillConfig, WriteSheet writeSheet) {this.excelBuilder.fill(data, fillConfig, writeSheet);return this;}public ExcelWriter fill(Supplier<Object> supplier, WriteSheet writeSheet) {return this.fill((Object)supplier.get(), (FillConfig)null, writeSheet);}public ExcelWriter fill(Supplier<Object> supplier, FillConfig fillConfig, WriteSheet writeSheet) {this.excelBuilder.fill(supplier.get(), fillConfig, writeSheet);return this;}
在 EasyExcel
中,FillConfig
类用于配置单元格填充相关的设置。
以下是一些 FillConfig
类中常用的配置项:
- forceNewRow:是否强制写入到新行。当设置为
true
时,每次写入都会创建新的行,而不是在现有行上进行覆盖 - direction:填充方向,可以指定是水平填充还是垂直填充
- 属性:
- WriteDirectionEnum.VERTICAL:垂直填充
- WriteDirectionEnum.HORIZONTAL:水平填充
- 属性:
如果有多列组合填充的情况,通过 {前缀.变量}
的形式区分不同的列表,例如
使用 FillWrapper
构建 Excel
写入时的填充模板
...excelWriter.fill(new FillWrapper("data1", data()), writeSheet);excelWriter.fill(new FillWrapper("data2", data()), writeSheet);excelWriter.fill(new FillWrapper("data3", data()), writeSheet);...
三、代码示例
简单介绍了 easyexcel
如何使用模板进行导出,接下来就通过几个简单的案例进行演示
以下案例使用的模板文件均可在文章顶部下载
3.1 案例一:工资表
编写 excel
模板:
实体类:
StaffSalaryEntity.java
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class StaffSalaryEntity {@ApiModelProperty(value = "姓名")private String name;@ApiModelProperty(value = "职称")private String post;@ApiModelProperty(value = "月薪")private BigDecimal mouthSalary;@ApiModelProperty(value = "时薪")private BigDecimal hourSalary;@ApiModelProperty(value = "应出勤天数")private Double shouldAttend;@ApiModelProperty(value = "出勤天数")private Double actualAttend;@ApiModelProperty(value = "平时加班时数")private Double overtime;@ApiModelProperty(value = "周末加班时数")private Double weekOvertime;@ApiModelProperty(value = "国假天数")private Double holiday;@ApiModelProperty(value = "正班薪资")private BigDecimal normalSalary;@ApiModelProperty(value = "平时加班薪资")private BigDecimal overtimeSalary;@ApiModelProperty(value = "周末加班薪资")private BigDecimal weekOvertimeSalary;@ApiModelProperty(value = "国假薪资")private BigDecimal holidaySalary;@ApiModelProperty(value = "岗位津贴")private BigDecimal postSubsidy;@ApiModelProperty(value = "全勤补贴")private BigDecimal fullAttendSubsidy;@ApiModelProperty(value = "全勤奖")private BigDecimal award;@ApiModelProperty(value = "事假缺勤扣款")private BigDecimal deduction;@ApiModelProperty(value = "社保费用")private BigDecimal social;@ApiModelProperty(value = "应得薪资")private BigDecimal shouldSalary;@ApiModelProperty(value = "个人扣税")private BigDecimal selfTax;@ApiModelProperty(value = "实发薪资")private BigDecimal actualSalary;@ApiModelProperty(value = "员工签名")private BigDecimal sign;
}
实体类中的字段要跟模板中占位符中的变量对应
代码示例:
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.fill.FillConfig;
import com.mike.server.system.domain.excel.ProductOfferExcelEntity;
import com.mike.server.system.entity.StaffSalaryEntity;
import org.junit.jupiter.api.Test;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;public class TestDemo {/*** 案例一:工资表*/@Testpublic void salaryList() {// 模板文件路径String templateFilePath = "D:\\excel-files\\gzb-template.xlsx";// 输出文件路径String outFilePath = "D:\\excel-files\\gzb.xlsx";// 创建 ExcelWriter 实例ExcelWriter writer = EasyExcel// 写入到.write(outFilePath)// 指定模板.withTemplate(templateFilePath).build();WriteSheet sheet = EasyExcel.writerSheet().build();// 获取员工工资数据List<StaffSalaryEntity> staffSalaryEntities = getStaffSalaryEntities();FillConfig fillConfig = FillConfig.builder()// 开启填充换行.forceNewRow(true).build();// 执行填充操作writer.fill(staffSalaryEntities, fillConfig, sheet);// 结束writer.finish();}public List<StaffSalaryEntity> getStaffSalaryEntities() {List<StaffSalaryEntity> list = new ArrayList<>();list.add(StaffSalaryEntity.builder().name("米大傻").post("开发").mouthSalary(new BigDecimal(1320)).hourSalary(new BigDecimal("7.59")).shouldAttend(21.0).actualAttend(21.0).overtime(21.0).weekOvertime(8.0).holiday(0.0).normalSalary(new BigDecimal(1320)).overtimeSalary(new BigDecimal("238.97")).weekOvertimeSalary(new BigDecimal("242.76")).holidaySalary(new BigDecimal(0)).postSubsidy(new BigDecimal(0)).award(new BigDecimal(20)).deduction(new BigDecimal(0)).social(new BigDecimal("113.6")).shouldSalary(new BigDecimal("1688.12")).selfTax(new BigDecimal(0)).actualSalary(new BigDecimal("1688.1")).build());list.add(StaffSalaryEntity.builder().name("曹大力").post("店长").mouthSalary(new BigDecimal(13200)).hourSalary(new BigDecimal("7.59")).shouldAttend(21.0).actualAttend(21.0).overtime(21.0).weekOvertime(8.0).holiday(0.0).normalSalary(new BigDecimal(1320)).overtimeSalary(new BigDecimal("238.97")).weekOvertimeSalary(new BigDecimal("242.76")).holidaySalary(new BigDecimal(0)).postSubsidy(new BigDecimal(0)).award(new BigDecimal(20)).deduction(new BigDecimal(0)).social(new BigDecimal("113.6")).shouldSalary(new BigDecimal("13200.12")).selfTax(new BigDecimal(0)).actualSalary(new BigDecimal("13200.1")).build());list.add(StaffSalaryEntity.builder().name("张大仙").post("经理").mouthSalary(new BigDecimal(13200)).hourSalary(new BigDecimal("7.59")).shouldAttend(21.0).actualAttend(21.0).overtime(21.0).weekOvertime(8.0).holiday(0.0).normalSalary(new BigDecimal(1320)).overtimeSalary(new BigDecimal("238.97")).weekOvertimeSalary(new BigDecimal("242.76")).holidaySalary(new BigDecimal(0)).postSubsidy(new BigDecimal(0)).deduction(new BigDecimal(0)).social(new BigDecimal("113.6")).shouldSalary(new BigDecimal("13200.12")).selfTax(new BigDecimal(0)).actualSalary(new BigDecimal("13200.1")).build());return list;}
}
展示:
3.2 案例二:报价单
编写模板:
模板文件放在项目的 resources
目录下的 templates/excel
文件夹下
实体类:
OfferDetailEntity.java
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class OfferDetailEntity {@ApiModelProperty(value = "公司名称")private String companyName;@ApiModelProperty(value = "报价日期")private String offerDate;@ApiModelProperty(value = "报价有效期")private String offerValidDate;@ApiModelProperty(value = "客户名称")private String customerName;@ApiModelProperty(value = "客户地址")private String customerAddress;@ApiModelProperty(value = "联系方式")private String contact;@ApiModelProperty(value = "合计数量")private Integer totalQty;@ApiModelProperty(value = "合计金额")private BigDecimal totalAmount;
}
ProductOfferEntity.java
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;import java.math.BigDecimal;@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ProductOfferEntity {@ApiModelProperty(value = "产品名称")private String productName;@ApiModelProperty(value = "型号及规格")private String typeSpec;@ApiModelProperty(value = "数量")private Integer quantity;@ApiModelProperty(value = "单价")private BigDecimal price;@ApiModelProperty(value = "金额")private BigDecimal amount;@ApiModelProperty(value = "备注")private String remark;}
代码示例:
TestController.java
@GetMapping("/export/by-template")@ApiOperation(value = "导出-按模板格式", produces = "application/octet-stream")public ResponseBean<String> exportByTemplate(HttpServletResponse response) {testService.exportByTemplate(response);return ResponseBean.success();}
TestService.java
/*** 导出-按模板格式*/void exportByTemplate(HttpServletResponse response);
TestServiceImpl.java
@SneakyThrows@Overridepublic void exportByTemplate(HttpServletResponse response) {ServletOutputStream out = response.getOutputStream();EasyExcelUtil.initResponse(response, "报价单");// 文件模板输入流,将 excel 模板放到 resources 目录下InputStream templateFile = new ClassPathResource("templates/excel/bjd.xlsx").getInputStream();ExcelWriter writer = EasyExcel.write(out).withTemplate(templateFile).build();WriteSheet sheet = EasyExcel.writerSheet().build();/*HashMap<String, Object> offerDetail = new HashMap<>();offerDetail.put("companyName", "多加辣科技");offerDetail.put("offerDate", "2024-04-07");offerDetail.put("offerValidDate", "2024-04-11");offerDetail.put("customerName", "米大傻");offerDetail.put("customerAddress", "广东省广州市");offerDetail.put("contact", "078-182****4568");offerDetail.put("totalQty", 5);offerDetail.put("totalAmount", new BigDecimal(10300));*/OfferDetailEntity offerDetail = OfferDetailEntity.builder().companyName("多加辣科技").offerDate("2024-04-07").offerValidDate("2024-04-11").customerName("米大傻").customerAddress("广东省广州市").contact("078-182****4568").totalQty(5).totalAmount(new BigDecimal(10300)).build();// 填充普通占位符// 这里 data 使用对象或者 Map 都可以writer.fill(offerDetail, sheet);List<ProductOfferEntity> list = new ArrayList<>();list.add(ProductOfferEntity.builder().productName("电脑").typeSpec("联想").price(new BigDecimal(5000)).quantity(2).amount(new BigDecimal(10000)).build());list.add(ProductOfferEntity.builder().productName("鼠标").typeSpec("联想").price(new BigDecimal(100)).quantity(3).amount(new BigDecimal(300)).build());// 填充配置,开启组合填充换行FillConfig fillConfig = FillConfig.builder().forceNewRow(true).build();// 填充列表占位符writer.fill(list, fillConfig, sheet);//填充完成writer.finish();}
相关工具类:
EasyExcelUtil.java
package com.mike.common.core.utils.excel;import com.mike.common.core.constant.DateFormatConstant;
import com.mike.common.core.utils.StringUtils;
import javax.servlet.http.HttpServletResponse;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;public class EasyExcelUtil {/*** 初始化响应体* @param response 请求头* @param fileName 导出名称*/public static void initResponse(HttpServletResponse response, String fileName) {// 最终文件名:文件名_(截止yyyy-MM-dd) --> 这块地方得根据你们自己项目做更改了String finalFileName = fileName + "_(截止"+ StringUtils.getNowTimeStr(DateFormatConstant.Y0M0D)+")";// 设置content—type 响应类型response.setContentType("application/vnd.ms-excel");response.setCharacterEncoding("utf-8");try {// 这里URLEncoder.encode可以防止中文乱码finalFileName = URLEncoder.encode(finalFileName, "UTF-8");} catch (UnsupportedEncodingException e) {e.printStackTrace();}response.setHeader("Content-disposition", "attachment;filename=" + finalFileName + ".xlsx");}
}
测试:
四、我所遇到的问题
(1)驼峰命名导致填充数据失败
我在编写工资条案例时,最开始的模板如下:
导出效果:
数据都有,但是导出的表格中有些字段填充失败
这个我也不知道具体原因是为什么,不过测了几组数据之后,得出以下结论:
如果占位符中的变量采用的是驼峰命名的方式,且组成变量的 “单词” 存在过于简单的时就会出现该状况,比如说:
月薪的占位符 {.mSalary}
中的 mSalary
是由 m
和 salary
组成,m
只有一个字母, {.mSalary}
填充不上,但是如果单独使用 {.m}
或者 {.salary}
却可以填充上去,或者把每个组成的单词写复杂一点,比如写成 {.mouthSalary}
也是可以填充上的
解决方案:要么就不要用驼峰命名,要么驼峰命名就写规范点,不要简写
(3)模板单元格样式消失
如果你使用模板导出发现有些单元格的样式消失了,比如:
这是因为在 write()
方法中添加了表格头的 class
对象
通过模板导出的方式不需要设置这个类对象,去掉即可
ExcelWriter writer = EasyExcel.write(out).withTemplate(templateFile).build();
参考文章:
easyexcel导出excel表格:https://blog.csdn.net/qq_57732418/article/details/136944211
SpringBoot集成阿里EasyExcel导出excel高级实战:https://blog.csdn.net/Blueeyedboy521/article/details/128257388
springboot 使用 EasyExcel 通过模板导出EXCEL 带多个动态列表:https://www.cnblogs.com/guanxiaohe/p/17719954.html