基于 easyExcel 3.1.5依赖的包 实现动态表头 动态表格内容

ops/2025/3/18 17:12:03/

1.需求:需要导出的EXCEL示例:

2.依赖:

java"><dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>3.1.5</version></dependency>

3.工具类:

java">package com.minex.web.device.utils;import com.minex.web.device.entity.vo.ReadRecordsVO;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.stereotype.Component;import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.*;@Component
public class ExcelExporter {private static final String DEFAULT_SHEET_NAME = "Sheet1";public void export(HttpServletResponse response, Map<String, List<ReadRecordsVO>> sheetDataMap,String fileName) throws IOException {try (Workbook workbook = new XSSFWorkbook()) {// 创建并写入多个Sheet页
//            Map<String, List<ReadRecordsVO>> sheetDataMap = splitDataIntoSheets(data);for (String sheetName : sheetDataMap.keySet()) {Sheet sheet = createSheet(workbook, sheetName);writeDataToSheet(sheet, sheetDataMap.get(sheetName));}// 设置响应头以触发浏览器下载String utf8EncodedFilename = URLEncoder.encode(fileName, StandardCharsets.UTF_8);response.setHeader("Content-Disposition","attachment; filename*=utf-8''" + utf8EncodedFilename+".xlsx");// 响应类型,编码response.setContentType("application/vnd.ms-excel;charset=utf-8");response.setCharacterEncoding("utf-8");// 将工作簿写入响应输出流try (OutputStream os = response.getOutputStream()) {workbook.write(os);}}}private Sheet createSheet(Workbook workbook, String sheetName) {return workbook.createSheet(sheetName);}private void writeDataToSheet(Sheet sheet, List<ReadRecordsVO> data) {// 获取所有列信息和它们对应的父级表头Map<String, String> headerToParentHeaderMap = getHeadersWithParent(data);// 写入表头Row headerRow = sheet.createRow(0);Row subHeaderRow = sheet.createRow(1);int columnIndex = 0;Map<String, Integer> startColumnIndexMap = new HashMap<>();Map<String, Integer> endColumnIndexMap = new HashMap<>();for (String header : headerToParentHeaderMap.keySet()) {String parentHeader = headerToParentHeaderMap.get(header);// 记录每个父级表头的起始列索引startColumnIndexMap.putIfAbsent(parentHeader, columnIndex);Cell cell = subHeaderRow.createCell(columnIndex++);cell.setCellValue(header);// 更新每个父级表头的最后一列索引endColumnIndexMap.put(parentHeader, columnIndex - 1);}// 合并第一行相同父级表头的单元格mergeCells(sheet, startColumnIndexMap, endColumnIndexMap);// 写入父级表头(第一行)columnIndex = 0;for (String parentHeader : startColumnIndexMap.keySet()) {Cell cell = headerRow.createCell(startColumnIndexMap.get(parentHeader));cell.setCellValue(parentHeader);}// 写入数据行writeDataRow(sheet, data, headerToParentHeaderMap.keySet(), 2);// 自动调整列宽autoSizeColumns(sheet, headerToParentHeaderMap.size());}private void writeDataRow(Sheet sheet, List<ReadRecordsVO> data, Set<String> allHeaders, int rowIndex) {for (ReadRecordsVO record : data) {Row row = sheet.createRow(rowIndex++);int columnIndex = 0;for (String header : allHeaders) {Cell cell = row.createCell(columnIndex++);record.getRecordItems().stream().filter(item -> item.getHeader().equals(header)).findFirst().ifPresentOrElse(item -> cell.setCellValue(item.getValue()),() -> cell.setCellValue(""));}}}private void autoSizeColumns(Sheet sheet, int columnCount) {for (int i = 0; i < columnCount; i++) {sheet.autoSizeColumn(i);}}private Map<String, String> getHeadersWithParent(List<ReadRecordsVO> data) {Map<String, String> headerToParentHeaderMap = new LinkedHashMap<>();for (ReadRecordsVO record : data) {for (ReadRecordsVO.RecordItem item : record.getRecordItems()) {headerToParentHeaderMap.putIfAbsent(item.getHeader(), item.getParentHeader());}}return headerToParentHeaderMap;}private void mergeCells(Sheet sheet, Map<String, Integer> startColumnIndexMap, Map<String, Integer> endColumnIndexMap) {Set<String> mergedParents = new HashSet<>();for (String parentHeader : startColumnIndexMap.keySet()) {if (!mergedParents.contains(parentHeader)) {int startColumnIndex = startColumnIndexMap.get(parentHeader);int endColumnIndex = endColumnIndexMap.getOrDefault(parentHeader, startColumnIndex);if (startColumnIndex != endColumnIndex) {sheet.addMergedRegion(new CellRangeAddress(0, 0, startColumnIndex, endColumnIndex));}mergedParents.add(parentHeader);}}}private Map<String, List<ReadRecordsVO>> splitDataIntoSheets(List<ReadRecordsVO> data) {// 根据业务逻辑分割数据到不同的Sheet页中// 这里简单地将所有数据放入一个名为DEFAULT_SHEET_NAME的Sheet页中Map<String, List<ReadRecordsVO>> result = new HashMap<>();result.put(DEFAULT_SHEET_NAME, data);return result;}
}

4.controller调用工具类导出

java">@PostMapping("/export")public void export(@RequestBody SubstationMeterReadRecordListRO query,HttpServletResponse response,@ApiIgnore CurrentUser user) throws IOException {List<ReadRecordsVO> dataList = substationMeterReadRecordService.readRecords(query, user);if (dataList.isEmpty()) {throw ExceptionFactory.warnBizException("没有数据可导出");}Map<String, List<ReadRecordsVO>> listMap = dataList.stream().collect(Collectors.groupingBy(ReadRecordsVO::getRecordDate));excelExporter.export(response, listMap, dataList.get(0).getSubstationName() + "抄表记录");}

5.数据类

java">package com.minex.web.device.entity.vo;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;import java.util.Date;
import java.util.List;@Data
public class ReadRecordsVO {private String substationName;private String recordDate;private List<RecordItem> recordItems;@Data@AllArgsConstructor@NoArgsConstructorpublic static class RecordItem {private Integer order = 0;private String parentHeader;private String header;private String value;}
}

6.运行效果


http://www.ppmy.cn/ops/166821.html

相关文章

ZooKeeper的五大核心作用及其在分布式系统中的关键价值

引言 在分布式系统的复杂架构中&#xff0c;协调多个节点的一致性、可靠性和高可用性始终是技术挑战的核心。​Apache ZooKeeper作为业界广泛采用的分布式协调服务&#xff0c;凭借其简洁的树形数据模型&#xff08;ZNode&#xff09;和高效的原子广播协议&#xff08;ZAB&…

【多线程】线程不安全问题

文章目录 多线程不安全的原因大的层面->多线程是随机调度的容易产生死锁 小的层面->内存不可见性引入volatile关键字 指令重排序不是原子性带来的隐患 synchronized锁的互斥性及作用可重入性——解决死锁 wait()和notify()两个突然迸发出的疑问 多线程不安全的原因 大的…

QVariant:Qt中万能类型的使用与理解

目录 1.引言 2.QVariant的用法 2.1.包含头文件 2.2.基本类型的存储与获取 2.3.自定义类型的存储与获取 2.4.枚举类型的存储与获取 2.5.类型检查与转换 2.6.容器类型的存储与获取 3.枚举的问题 4.信号槽中使用自定义结构体 4.1.使用QVariant转换 4.2.直接传递自定义…

走进Java:Integer128陷阱

❀❀❀ 大佬求个关注~祝您开心每一天 ❀❀❀ 目录 一、Integer和int的联系 1.1 Integer和int的区别 1.2 Integer和int的相互转换 二、装箱 三、拆箱 今天在学习Java的时候遇到了下面几个问题。 public static void main(String[] args) {Integer num1 127;Integer nu…

34个适合机械工程及自动化专业【论文选题】

论文选题具有极其重要的意义&#xff0c;它直接关系到论文的质量、价值以及研究的可行性和顺利程度。选题明确了研究的具体领域和核心问题&#xff0c;就像给研究旅程设定了方向和目的地。例如&#xff0c;选择 “人工智能在医疗影像诊断中的应用” 这一选题&#xff0c;就确定…

算法竞赛-基础算法-位运算

目录 1.快速幂 2.快速乘 3.lowbit(n) 4.其他 5.相关题目 6.小结 引言&#xff1a;位运算的主要特点之一是在二进制表示下不进位&#xff0c;一下为一些基础的位运算操作&#xff1a; 与或非异或and,&or,|not,~xor 1.快速幂 快速幂的计算原理就是基于位运算&#x…

C# Type类中Name、FullName、Namespace、AssemblyQualifiedName的区别

总目录 前言 在C#中&#xff0c;Type 类提供了多种属性来获取类型的相关信息。以下是 Name、FullName、Namespace 和 AssemblyQualifiedName 这几个属性的区别和具体用途。 一、获取各名称属性示例 namespace ReflectionDemo {public class User { }internal class Program{s…

使用yolov8+flask实现精美登录界面+图片视频摄像头检测系统

这个是使用flask实现好看登录界面和友好的检测界面实现yolov8推理和展示&#xff0c;代码仅仅有2个html文件和一个python文件&#xff0c;真正做到了用最简洁的代码实现复杂功能。 测试通过环境&#xff1a; windows x64 anaconda3python3.8 ultralytics8.3.81 flask1.1.2…