谈到新技术,每个人都会有点恐惧,怕处理不好。确实,第一次使用新技术会遇到很多坑,这次使用 EasyExcel 这个新技术去做 excel 导出,还要给表格加样式,遇到不同的版本问题,遇到颜色加错了地方,反正各种效果都打不到自己想要的那种,幸好最终看文档解决了,特此写下这篇博客。
EasyExcel导出自定义表格
- 一、导入依赖
- 二、原理分析
- 三、上代码
一、导入依赖
<dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>3.3.2</version>
</dependency>
二、原理分析
-
对于 read 函数主要通过流操作获取
对于 EasyExcel.read 方法中常用的一个 read 函数:java">EasyExcel.read(fileName, head, readListener).sheet().doRead();
三个参数如下:
- fileName:Excel 文件的路径或输入流。
- head:Excel 表头对应的实体类,定义了 Excel 表的结构。
- readListener:数据读取的监听器,定义了读取数据的逻辑。
-
Excel 表头的实体类
在读取 Excel 文件时,需要定义一个实体类来映射 Excel 表头,每个字段对应一个表头列。这个实体类用于指定数据在 Java 对象中的存储结构。
java">public class ExcelData {private String name;private Integer age;// 其他字段...// 省略 getter 和 setter 方法 }
-
数据读取监听器
EasyExcel 提供了 AnalysisEventListener 类来处理 Excel 数据的读取。
需要集成该类,并实现 invoke 方法来处理每一行数据的读取逻辑,以及 doAfterAllAnalysed 方法来处理所有数据解析完成后的逻辑。
public class ExcelDataListener extends AnalysisEventListener<ExcelData> {@Overridepublic void invoke(ExcelData data, AnalysisContext context) {// 处理每一行数据的逻辑System.out.println("Read data: " + data);}@Overridepublic void doAfterAllAnalysed(AnalysisContext context) {// 所有数据解析完成后的逻辑} }
-
Excel 写入
EasyExcel 也提供了写入 Excel 文件的功能。可以使用 EasyExcel.write 方法来配置写入参数,然后调用 sheet 方法指定写入的 sheet,最后调用 doWrite 方法执行写入操作。
EasyExcel.write(fileName, head).sheet("Sheet1").doWrite(dataList);
三个参数如下:
- fileName:写入的 Excel 文件路径。
- head:Excel 表头对应的实体类。
- dataList:要写入的数据列表。dataList 是一个 List 集合,其中的元素是实体类的对象。
-
Excel 写入监听器
写入 Excel 文件时进行一些额外的处理,可以使用写入的监听器 WritHandler。
public class ExcelWriteHandler implements WriteHandler {@Overridepublic void sheet(int sheetNo, Sheet sheet) {// 对每个 sheet 进行处理的逻辑}@Overridepublic void row(int rowNum, Row row) {// 对每一行进行处理的逻辑}@Overridepublic void cell(int cellNum, Cell cell) {// 对每个单元格进行处理的逻辑} }
在写入 Excel 文件时,通过 excelWriter.registerWriterHandler( new ExcelWriterHandler() ) 注册写入监听器即可。
三、上代码
先看要求
其实这里的大部分样式,都可以参考 EasyExcel API 文档
导出
@Overridepublic void importUserSign(ImportUserSignReq req, HttpServletResponse response) {String projectName = req.getProjectName();String time = req.getTime();// 查询第一页数据List<SignTemplate1> data1 = new ArrayList<>();data1.add(new SignTemplate1().setE1("序号").setE2("成员姓名").setE3("签到次数").setE4("补签次数").setE5("签到总工时").setE6("最后签到时间"));data1.addAll(getData1(req));// 查询第二页数据List<SignTemplate2> data2 = getData2(req, getData1(req));Integer maxRow = data2.stream().map(SignTemplate2::getE2).max(Integer::compare).orElse(0); //获取最大行try {// 指定文件名
// response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
// response.setCharacterEncoding("utf-8");
// String fileName = URLEncoder.encode("签到模板导出.xlsx", "UTF-8");
// response.setHeader("Content-disposition", "attachment;filename*=" + fileName);String fileName = "E:\\excel\\" + "签到模板导出" + System.currentTimeMillis() + ".xlsx";ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream()).build();// 第一页// 自定义头部样式WriteCellStyle headWriteCellStyle1 = new WriteCellStyle();headWriteCellStyle1.setFillForegroundColor(IndexedColors.GOLD.getIndex()); //背景颜色-黄色headWriteCellStyle1.setHorizontalAlignment(HorizontalAlignment.LEFT); //左对齐// 自定义内容样式WriteCellStyle contentWriteCellStyle1 = new WriteCellStyle();// 这个策略是 头是头的样式 内容是内容的样式 其他的策略可以自己实现HorizontalCellStyleStrategy style1 = new HorizontalCellStyleStrategy(headWriteCellStyle1, contentWriteCellStyle1);// sheet命名WriteSheet writeSheet1 = EasyExcel.writerSheet(1, "项目名称").registerWriteHandler(style1) //自定义策略.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) //自动列宽.head(getHead("【"+projectName+"】", time, 6)) //动态表头.head(SignTemplate1.class).build();// 写入第一页excelWriter.write(data1, writeSheet1);// 第二页// 自定义头部样式WriteCellStyle headWriteCellStyle2 = new WriteCellStyle();headWriteCellStyle2.setFillForegroundColor(IndexedColors.GOLD.getIndex()); //背景颜色-黄色headWriteCellStyle2.setHorizontalAlignment(HorizontalAlignment.LEFT); //左对齐// 自定义内容样式WriteCellStyle contentWriteCellStyle2 = new WriteCellStyle();contentWriteCellStyle2.setHorizontalAlignment(HorizontalAlignment.RIGHT); //右对齐// 这个策略是 头是头的样式 内容是内容的样式 其他的策略可以自己实现HorizontalCellStyleStrategy style2 = new HorizontalCellStyleStrategy(headWriteCellStyle2, contentWriteCellStyle2);// sheet命名WriteSheet writeSheet2 = EasyExcel.writerSheet(2, "签到明细").registerWriteHandler(style2) //自定义策略.registerWriteHandler(new CustomCellWriteHandler(maxRow, (data2.size()+1))) //自定义动态行/列背景颜色.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) //自动列宽.head(getHead("【"+projectName+"】", time, (data2.size()+1))) //动态表头.build();// 写入第二页excelWriter.write(dataList(data2, maxRow), writeSheet2).close();}catch (Exception e){e.printStackTrace();throw new CustomException("导出失败");}}
动态标题头
private List<List<String>> getHead(String projectName, String time, Integer num) {List<List<String>> list = new ArrayList<List<String>>();for (int i = 0; i < num; i++) {list.add(Arrays.asList(projectName, time));}return list;
}
动态填充数据
private List<List<Object>> dataList(List<SignTemplate2> data2, Integer maxRow) {List<List<Object>> list = new ArrayList<>();List<Object> row1 = ListUtils.newArrayList(); //第一行List<Object> row2 = ListUtils.newArrayList(); //第二行row1.add("成员名称");row2.add("签到次数");for (int i = 0; i <data2.size(); i++) { //行内每一列数据row1.add(data2.get(i).getE1());row2.add(data2.get(i).getE2());}list.add(row1);list.add(row2);for (int i = 0; i < maxRow; i++) {List<Object> row3 = ListUtils.newArrayList(); //第三行-多条List<Object> row4 = ListUtils.newArrayList(); //第四行-多条row3.add(null);row4.add(null);for (int j = 0; j <data2.size(); j++) { //行内每一列数据List<SignTemplate3> eList = data2.get(j).getEList();//当前列的签到集合if (i < eList.size()) {row3.add(eList.get(i).getE2()+" "+eList.get(i).getE1());row4.add(ObjectUtil.isNotNull(eList.get(i).getE3())?eList.get(i).getE3():"暂无");}else {row3.add(null);row4.add(null);}}list.add(row3);list.add(row4);}return list;
}
自定义动态行/列背景颜色
package com.glbTech.business.dto.req.stat;import cn.hutool.core.util.ObjectUtil;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.write.handler.AbstractCellWriteHandler;
import com.alibaba.excel.write.handler.SheetWriteHandler;
import com.alibaba.excel.write.handler.context.CellWriteHandlerContext;
import lombok.Data;
import org.apache.commons.lang.BooleanUtils;
import org.apache.poi.hssf.usermodel.HSSFPalette;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.hssf.util.HSSFColor;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddressList;import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;@Data
public class CustomCellWriteHandler extends AbstractCellWriteHandler {private Integer maxRow;private Integer maxCol;private final short colorL = IndexedColors.LIME.getIndex(); //绿色private final short colorH = IndexedColors.GREY_25_PERCENT.getIndex(); //灰色public CustomCellWriteHandler(Integer maxRow, Integer maxCol) {this.maxRow = maxRow;this.maxCol = maxCol;}@Overridepublic void afterCellDispose(CellWriteHandlerContext context) {// 自定义样式处理// 当前事件会在 数据设置到poi的cell里面才会回调int x = 1;for (int i = 4; i < (maxRow+2)*2; i=(x*2)) {Cell cell = context.getCell();int rowIndex = cell.getRowIndex(); //行int cellIndex = cell.getColumnIndex(); //行的列// 判断不是头的情况 如果是fill 的情况 这里会==null 所以用not trueif (BooleanUtils.isNotTrue(context.getHead())) {if (cellIndex > 0 && (rowIndex==i || rowIndex==i+1)) {// 拿到poi的workbookWorkbook workbook = context.getWriteWorkbookHolder().getWorkbook();// 这里千万记住 想办法能复用的地方把他缓存起来 一个表格最多创建6W个样式// 不同单元格尽量传同一个 cellStyleCellStyle cellStyle = workbook.createCellStyle();//设置颜色if (x%2==0) {cellStyle.setFillForegroundColor(colorL); //绿色}else {cellStyle.setFillForegroundColor(colorH); //灰色}cellStyle.setAlignment(HorizontalAlignment.RIGHT); //右对齐// 这里需要指定 FillPatternType 为FillPatternType.SOLID_FOREGROUNDcellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);cell.setCellStyle(cellStyle);// 由于这里没有指定dataformat 最后展示的数据 格式可能会不太正确// 这里要把 WriteCellData的样式清空, 不然后面还有一个拦截器 FillStyleCellWriteHandler 默认会将 WriteCellStyle 设置到// cell里面去 会导致自己设置的不一样(很关键)context.getFirstCellData().setWriteCellStyle(null);}}x++;}}
}
好事定律:每件事最后都会是好事,如果不是好事,说明还没到最后。