Spring Boot 使用FastExcel实现多级表头动态数据填充导出

1:依赖(Maven)

XML 复制代码
<!-- FastExcel 官网最新版 -->
<dependency>
    <groupId>cn.idev.excel</groupId>
    <artifactId>fastexcel</artifactId>
    <version>1.0.0</version>
</dependency>

2:动态表头实体类

java 复制代码
package com.fantaibao.module.vo.target;

import cn.idev.excel.annotation.ExcelProperty;
import cn.idev.excel.annotation.write.style.ColumnWidth;
import cn.idev.excel.annotation.write.style.ContentStyle;
import cn.idev.excel.annotation.write.style.HeadFontStyle;
import cn.idev.excel.annotation.write.style.HeadStyle;
import cn.idev.excel.enums.poi.FillPatternTypeEnum;
import cn.idev.excel.enums.poi.HorizontalAlignmentEnum;
import cn.idev.excel.enums.poi.VerticalAlignmentEnum;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@HeadFontStyle(fontName = "黑体", fontHeightInPoints = 12)
@HeadStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER, verticalAlignment = VerticalAlignmentEnum.CENTER,
        fillPatternType = FillPatternTypeEnum.SOLID_FOREGROUND, fillForegroundColor = 44)
@ContentStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER, verticalAlignment = VerticalAlignmentEnum.CENTER)
public class TargetIndexExportStoreStaVo {
    /**
     * 年份(2025)
     */
    @ColumnWidth(15)
    @ExcelProperty({"${indexName}", "年度"})
    private Integer year;

    /**
     * 门店名称
     */
    @ColumnWidth(20)
    @ExcelProperty({"${indexName}", "门店名称"})
    private String storeName;
    /**
     * 年目标
     */
    @ColumnWidth(15)
    @ExcelProperty({"${indexName}", "年目标"})
    private String totalTarget;
    /**
     * 当前进度
     */
    @ColumnWidth(20)
    @ExcelProperty({"${indexName}", "当前进度"})
    private String progress;
    /**
     * 目标完成率
     */
    @ColumnWidth(20)
    @ExcelProperty({"${indexName}", "目标完成率"})
    private String finishRate;
}
java 复制代码
package com.fantaibao.module.vo.target;

import cn.idev.excel.annotation.ExcelProperty;
import cn.idev.excel.annotation.write.style.*;
import cn.idev.excel.enums.poi.FillPatternTypeEnum;
import cn.idev.excel.enums.poi.HorizontalAlignmentEnum;
import cn.idev.excel.enums.poi.VerticalAlignmentEnum;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@HeadFontStyle(fontName = "黑体", fontHeightInPoints = 12)
@HeadStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER, verticalAlignment = VerticalAlignmentEnum.CENTER,
        fillPatternType = FillPatternTypeEnum.SOLID_FOREGROUND, fillForegroundColor = 44)
@ContentStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER, verticalAlignment = VerticalAlignmentEnum.CENTER)
@ContentFontStyle(fontName = "黑体", fontHeightInPoints = 10)
public class TargetIndexExportStoreMonthStaVo {
    /**
     * 年份(2025)
     */
    @ColumnWidth(15)
    @ExcelProperty({"${indexName}", "年度"})
    private Integer year;

    /**
     * 门店名称
     */
    @ColumnWidth(25)
    @ExcelProperty({"${indexName}", "门店名称"})
    private String storeName;

    /**
     * 月目标
     */
    @ColumnWidth(15)
    @ExcelProperty({"${indexName}", "${month}月目标"})
    private String totalTarget;

    /**
     * 当前进度
     */
    @ColumnWidth(20)
    @ExcelProperty({"${indexName}", "当前进度"})
    private String progress;

    /**
     * 目标完成率
     */
    @ColumnWidth(20)
    @ExcelProperty({"${indexName}", "目标完成率"})
    private String finishRate;
}

3:动态表头数据填充策略

java 复制代码
package com.fantaibao.handler;

import cn.hutool.core.collection.CollUtil;
import cn.idev.excel.metadata.Head;
import cn.idev.excel.write.handler.CellWriteHandler;
import cn.idev.excel.write.metadata.holder.WriteSheetHolder;
import cn.idev.excel.write.metadata.holder.WriteTableHolder;
import org.apache.poi.ss.usermodel.Cell;

public class CustomHeaderWriteHandler implements CellWriteHandler {

    private final String indexName;

    public CustomHeaderWriteHandler(String indexName) {
        this.indexName = indexName;
    }

    @Override
    public void afterCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder,
                                Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
        // 处理表头
        if (CollUtil.isNotEmpty(head.getHeadNameList())) {
            // 处理一级表头
            if (CollUtil.isNotEmpty(head.getHeadNameList())) {
                String replace = head.getHeadNameList().get(0).replace("${indexName}", indexName);
                head.getHeadNameList().set(0, replace);
            }
        }
    }

}

public class CustomMonthHeaderWriteHandler implements CellWriteHandler {

    private final String indexName;
    private final int month;

    public CustomMonthHeaderWriteHandler(String indexName, int month) {
        this.indexName = indexName;
        this.month = month;
    }


    @Override
    public void afterCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder,
                                Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
        if (isHead && head != null) {
            // 处理表头
            if (CollUtil.isNotEmpty(head.getHeadNameList())) {
                // 处理一级表头
                String indexName = head.getHeadNameList().get(0).replace("${indexName}", this.indexName);
                head.getHeadNameList().set(0, indexName);
                // 处理二级表头
                String monthStr = head.getHeadNameList().get(1);
                if (!monthStr.contains("${month}")) {
                    return;
                }
                String month = monthStr.replace("${month}", String.valueOf(this.month));
                head.getHeadNameList().set(1, month);
            }
        }
    }
}

4:导出工具类

java 复制代码
/**
     * 按单个指标生成年度-月度汇总数据
     *
     * @param zipOut               ZIP输出流
     * @param fileNamePrefix        文件名前缀
     * @param storeTargetYearList  门店年目标集合
     * @param storeMonthTargetList 门店月目标集合
     * @param enums                指标
     * @param storeMap             门店集合
     * @throws IOException 抛出的异常
     */
    public static void generateSingleIndexStaExcelFile(ZipOutputStream zipOut, String fileNamePrefix, List<StoreIndexYearStaVo> storeTargetYearList,
                                                       List<StoreIndexMonthStaVo> storeMonthTargetList, IndicatorsTypeEnums enums,
                                                       Map<String, StoreInfoListVO> storeMap) throws IOException {
        ZipEntry zipEntry = new ZipEntry(enums.getName() + "门店" + fileNamePrefix + "目标汇总.xlsx");
        zipOut.putNextEntry(zipEntry);
        try (ExcelWriter excelWriter = EasyExcel.write(zipOut).autoCloseStream(false).build()) {
            // Sheet1: 门店指标年目标汇总
            WriteSheet yearSheet = EasyExcel.writerSheet(0, enums.getName() + "门店年目标")
                    .head(TargetIndexExportStoreStaVo.class)
                    .registerWriteHandler(new CustomHeaderWriteHandler(StrUtil.isNotBlank(enums.getUnit()) ? enums.getName() + "(" + enums.getUnit() + ")" : enums.getName()))
                    .build();
            excelWriter.write(getYearSheetData(enums, storeTargetYearList, storeMap), yearSheet);
            // Sheet2: 门店指标月目标汇总
            for (int month = 1; month <= 12; month++) {
                WriteSheet monthSheet = EasyExcel.writerSheet(month, "门店" + month + "月目标")
                        .head(TargetIndexExportStoreMonthStaVo.class)
                        .registerWriteHandler(new CustomMonthHeaderWriteHandler(StrUtil.isNotBlank(enums.getUnit()) ? enums.getName() + "(" + enums.getUnit() + ")" : enums.getName(), month))
                        .build();
                excelWriter.write(getMonthSheetDataByMonth(enums, storeMonthTargetList, month, storeMap), monthSheet);
            }
            excelWriter.finish();
        } catch (Exception e) {
            try {
                zipOut.closeEntry();
            } catch (IOException ignored) {
            }
            throw e;
        }
        zipOut.closeEntry();
    }

5:样例

相关推荐
微尘寒风6 小时前
【Git】的安装和使用
java·git
y = xⁿ7 小时前
DeepSeek Harness 学习日记:关于Agent接口,工具调用的底层实现
android·java·学习
侧耳倾听1117 小时前
jwt使用简介
java·jwt
顶点多余8 小时前
那些在算法中适合巩固的知识点---1
java·前端·算法
AI人工智能+电脑小能手8 小时前
大白话说Java设计模式-46-责任链模式(业务实战篇)
java·设计模式·责任链模式·风控校验·servlet filter·spring interceptor·链式处理
jay神8 小时前
【计算机毕业设计】基于SpringBoot的程序教学辅助系统
java·前端·vue.js·spring boot·后端·毕业设计·课程设计
2601_962066499 小时前
【Sql Server】Update中的From语句,以及常见更新操作方式
android·java·数据库
yaoxin52112310 小时前
507. Java 反射 - 在 BeanFactory 中实现依赖注入
java·开发语言
OPEN-F10 小时前
C++模板教程:变参模板、折叠表达式与SFINAE
java·开发语言·c++
2601_9620722111 小时前
SpringMVC进阶(自定义拦截器以及异常处理)
java