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:样例

相关推荐
毕设源码-朱学姐21 小时前
【开题答辩全过程】以 基于JavaWeb的网上家具商城设计与实现为例,包含答辩的问题和答案
java
C雨后彩虹1 天前
CAS与其他并发方案的对比及面试常见问题
java·面试·cas·同步·异步·
java1234_小锋1 天前
Java高频面试题:SpringBoot为什么要禁止循环依赖?
java·开发语言·面试
2501_944525541 天前
Flutter for OpenHarmony 个人理财管理App实战 - 账户详情页面
android·java·开发语言·前端·javascript·flutter
计算机学姐1 天前
基于SpringBoot的电影点评交流平台【协同过滤推荐算法+数据可视化统计】
java·vue.js·spring boot·spring·信息可视化·echarts·推荐算法
Filotimo_1 天前
Tomcat的概念
java·tomcat
索荣荣1 天前
Java Session 全面指南:原理、应用与实践(含 Spring Boot 实战)
java·spring boot·后端
Amumu121381 天前
Vue Router(二)
java·前端
念越1 天前
数据结构:栈堆
java·开发语言·数据结构