EasyExcel导出自定义表格

谈到新技术,每个人都会有点恐惧,怕处理不好。确实,第一次使用新技术会遇到很多坑,这次使用 EasyExcel 这个新技术去做 excel 导出,还要给表格加样式,遇到不同的版本问题,遇到颜色加错了地方,反正各种效果都打不到自己想要的那种,幸好最终看文档解决了,特此写下这篇博客。

一、导入依赖

<dependency>
	<groupId>com.alibaba</groupId>
	<artifactId>easyexcel</artifactId>
	<version>3.3.2</version>
</dependency>

二、原理分析

  1. 对于 read 函数主要通过流操作获取
    在这里插入图片描述
    对于 EasyExcel.read 方法中常用的一个 read 函数:

    java">EasyExcel.read(fileName, head, readListener).sheet().doRead();
    

    三个参数如下:

    • fileName:Excel 文件的路径或输入流。
    • head:Excel 表头对应的实体类,定义了 Excel 表的结构。
    • readListener:数据读取的监听器,定义了读取数据的逻辑。
  2. Excel 表头的实体类

    在读取 Excel 文件时,需要定义一个实体类来映射 Excel 表头,每个字段对应一个表头列。这个实体类用于指定数据在 Java 对象中的存储结构。

    java">public class ExcelData {
        private String name;
        private Integer age;
        // 其他字段...
    
        // 省略 getter 和 setter 方法
    }
    
  3. 数据读取监听器

    EasyExcel 提供了 AnalysisEventListener 类来处理 Excel 数据的读取。

    需要集成该类,并实现 invoke 方法来处理每一行数据的读取逻辑,以及 doAfterAllAnalysed 方法来处理所有数据解析完成后的逻辑。

    public class ExcelDataListener extends AnalysisEventListener<ExcelData> {
    
        @Override
        public void invoke(ExcelData data, AnalysisContext context) {
            // 处理每一行数据的逻辑
            System.out.println("Read data: " + data);
        }
    
        @Override
        public void doAfterAllAnalysed(AnalysisContext context) {
            // 所有数据解析完成后的逻辑
        }
    }
    
  4. Excel 写入

    EasyExcel 也提供了写入 Excel 文件的功能。可以使用 EasyExcel.write 方法来配置写入参数,然后调用 sheet 方法指定写入的 sheet,最后调用 doWrite 方法执行写入操作。

    EasyExcel.write(fileName, head).sheet("Sheet1").doWrite(dataList);
    

    三个参数如下:

    • fileName:写入的 Excel 文件路径。
    • head:Excel 表头对应的实体类。
    • dataList:要写入的数据列表。dataList 是一个 List 集合,其中的元素是实体类的对象。
  5. Excel 写入监听器

    写入 Excel 文件时进行一些额外的处理,可以使用写入的监听器 WritHandler。

    public class ExcelWriteHandler implements WriteHandler {
    
        @Override
        public void sheet(int sheetNo, Sheet sheet) {
            // 对每个 sheet 进行处理的逻辑
        }
    
        @Override
        public void row(int rowNum, Row row) {
            // 对每一行进行处理的逻辑
        }
    
        @Override
        public void cell(int cellNum, Cell cell) {
            // 对每个单元格进行处理的逻辑
        }
    }
    

在写入 Excel 文件时,通过 excelWriter.registerWriterHandler( new ExcelWriterHandler() ) 注册写入监听器即可。

三、上代码

先看要求
在这里插入图片描述
其实这里的大部分样式,都可以参考 EasyExcel API 文档

导出

@Override
    public 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;
    }

    @Override
    public 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 true
            if (BooleanUtils.isNotTrue(context.getHead())) {
                if (cellIndex > 0 && (rowIndex==i || rowIndex==i+1)) {
                    // 拿到poi的workbook
                    Workbook workbook = context.getWriteWorkbookHolder().getWorkbook();
                    // 这里千万记住 想办法能复用的地方把他缓存起来 一个表格最多创建6W个样式
                    // 不同单元格尽量传同一个 cellStyle
                    CellStyle cellStyle = workbook.createCellStyle();
                    //设置颜色
                    if (x%2==0) {
                        cellStyle.setFillForegroundColor(colorL); //绿色
                    }else {
                        cellStyle.setFillForegroundColor(colorH); //灰色
                    }
                    cellStyle.setAlignment(HorizontalAlignment.RIGHT); //右对齐
                    // 这里需要指定 FillPatternType 为FillPatternType.SOLID_FOREGROUND
                    cellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
                    cell.setCellStyle(cellStyle);
                    // 由于这里没有指定dataformat 最后展示的数据 格式可能会不太正确
                    // 这里要把 WriteCellData的样式清空, 不然后面还有一个拦截器 FillStyleCellWriteHandler 默认会将 WriteCellStyle 设置到
                    // cell里面去 会导致自己设置的不一样(很关键)
                    context.getFirstCellData().setWriteCellStyle(null);
                }
            }
            x++;
        }
    }
}

好事定律:每件事最后都会是好事,如果不是好事,说明还没到最后。

  • 25
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值