【excel】easy excel如何导出动态列
动态也有多重含义:本文将描述两种动态场景下的解决方案
场景一:例如表头第一列固定为动物,且必定有第二列,第二列的表头可能为猫 也可能为狗;这是列数固定,列名不固定的场景;
场景二:更复杂的场景则为 第二列可能为猫 可能为狗,第三列可能为熊,也可能没有第三列,甚至可能会有第四列;这是表头数和列数都不固定的场景;
文章目录
- 场景一:表头名不固定
- 场景二:表头名和列数都不固定
场景一:表头名不固定
如下dto所示,content字段对应的表头可能会变换,我们只需要在ExcelProperty注解中使用占位符替代
@HeadRowHeight(30)
@ContentRowHeight(20)
@Data
public class EasyExcelDTO {@ColumnWidth(30)@ExcelProperty("标题")private String title;@ColumnWidth(30)@ExcelProperty("${content}")private String content;}
测试代码:
(其中IoUtil是hutool包的工具类)
public static void main(String[] args) throws FileNotFoundException {File file = new File(("C:\\Users\\10057\\Desktop\\easy_excel.xls"));List<EasyExcelDTO> res = new ArrayList<>();EasyExcelDTO easyExcelDTO = new EasyExcelDTO();Map<String, String> map = new HashMap<>();// DTO里面有title字段
// map.put("title","1");// 相当于bean拷贝 (下面这行是cglib里面的代码)BeanMap.create(easyExcelDTO).putAll(map);System.out.println(easyExcelDTO);easyExcelDTO.setContent("666");res.add(easyExcelDTO);ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();JSONObject jsonObject = new JSONObject();jsonObject.put("content", "测试动态标题111");EasyExcel.write(byteArrayOutputStream, EasyExcelDTO.class).sheet().registerWriteHandler(new ExcelHandler(jsonObject)).doWrite(res);FileOutputStream fos = new FileOutputStream(file);IoUtil.copy(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()), fos);}
导出的excel结果示例:可以看到第二列的表头内容成功被替换

场景二:表头名和列数都不固定
实体类代码:
(实际使用中,将DynamicFieldExcelDTO作为一个基类,而涉及了动态导出的DTO类继承该类 ,例如 AnimalExcelDTO extends DynamicFieldExcelDTO, 这么做的目的 主要是需要设定一个dynamicFields字段,用于处理器里面映射并替换值)
dynamicFields的key值则为表头,value为内容值
import com.alibaba.excel.annotation.ExcelIgnore;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.alibaba.excel.annotation.write.style.ContentRowHeight;
import com.alibaba.excel.annotation.write.style.HeadRowHeight;
import lombok.Data;import java.util.Map;@HeadRowHeight(30)
@ContentRowHeight(20)
@Data
public class DynamicFieldExcelDTO {@ColumnWidth(20)@ExcelProperty("订单编号")private String orderNo;/*** K: 表头 V:值 (注意要加上@ExcelIgnore)*/@ExcelIgnoreprivate Map<String,Object> dynamicFields;
}
测试代码:
/*** 动态字段导出方法 (不需要调整之前的固定字段)* usage: exportDTO类 <b><u>已有固定字段</u></b> ,需要新增动态字段(即需要追加动态表头及对应数据)* 模拟数据: orderNo为固定字段,dynamicFields map中的key为动态表头* {@link DynamicColumnWriteHandler}*/@Testpublic void dynamicHeadMixWrite() {String fileName = "D:\\test\\out.xlsx";List<String> header = Arrays.asList("动态头部1","动态头部2","动态头部3");List<DynamicFieldExcelDTO> dataList = new ArrayList<>();DynamicFieldExcelDTO d1 = new DynamicFieldExcelDTO();d1.setOrderNo("订单111");Map<String, Object> map = new HashMap<>();map.put("动态头部1","aaa111");map.put("动态头部2","aaa222");map.put("动态头部3","aaa333");d1.setDynamicFields(map);DynamicFieldExcelDTO d2 = new DynamicFieldExcelDTO();d2.setOrderNo("订单222");Map<String, Object> map2 = new HashMap<>();map2.put("动态头部1","bbb111");map2.put("动态头部2","bbb222");map2.put("动态头部3","bbb333");d2.setDynamicFields(map2);DynamicFieldExcelDTO d3 = new DynamicFieldExcelDTO();d3.setOrderNo("订单333");Map<String, Object> map3 = new HashMap<>();map3.put("动态头部1","ccc111");map3.put("动态头部2","ccc222");map3.put("动态头部3","ccc333");d3.setDynamicFields(map3);dataList.add(d1);dataList.add(d2);dataList.add(d3);EasyExcel.write(fileName,DynamicFieldExcelDTO.class).registerWriteHandler(new DynamicColumnWriteHandler<>(header,dataList)).sheet().doWrite(dataList);}/*** 如果全部为动态字段 将动态数据传参即可*/@Testpublic void dynamicHeadWrite() {String fileName = "D:\\test\\out.xlsx";EasyExcel.write(fileName)// 这里放入动态头.head(new ArrayList<>()).sheet("模板")// dataList.doWrite(new ArrayList());}
处理器代码:
主要逻辑是在afterRowDispose方法里面追加cell,并通过判断列数、是否创建过表头,避免重复添加
/*** 动态导出* @author csdn:孟秋与你*/
public class DynamicColumnWriteHandler<T extends DynamicFieldExcelDTO> implements RowWriteHandler {private final List<String> dynamicHeaders;private final List<T> dataList;private ConcurrentHashMap<String,Boolean> isHeaderCreated = new ConcurrentHashMap<>();/*** 用于记录表头列数*/private AtomicInteger totalHeaderColumns = new AtomicInteger(-1);public DynamicColumnWriteHandler(List<String> dynamicHeaders, List<T> dataList) {this.dynamicHeaders = dynamicHeaders;this.dataList = dataList;}@Overridepublic void beforeRowCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Integer rowIndex, Integer relativeRowIndex, Boolean isHead) { }@Overridepublic void afterRowCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Integer rowIndex, Boolean isHead) {}@Overridepublic void afterRowDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Integer rowIndex, Boolean isHead) {if (isHead && rowIndex == 0) {// 获取默认的头部样式CellStyle headerCellStyle = row.getCell(0).getCellStyle();// 创建动态列头部int lastCellIndex = row.getLastCellNum() == -1 ? 0 : row.getLastCellNum();// 记录表头的总列数totalHeaderColumns.set(lastCellIndex + dynamicHeaders.size());for (int i = 0; i < dynamicHeaders.size(); i++) {if (Objects.equals(isHeaderCreated.get(dynamicHeaders.get(i)), Boolean.TRUE)) {continue;}Cell cell = row.createCell(lastCellIndex + i);cell.setCellValue(dynamicHeaders.get(i));// 设置样式cell.setCellStyle(headerCellStyle);isHeaderCreated.put(dynamicHeaders.get(i),Boolean.TRUE);}} else if (!isHead) {// 检查数据行是否超出表头的列数if (row.getLastCellNum() + dynamicHeaders.size() > totalHeaderColumns.get()) {// 如果超出,不写入该行数据return;}// 数据行的写入逻辑if (rowIndex < dataList.size()) {T dto = dataList.get(rowIndex);// k:与表头内容对应Map<String, Object> dynamicFields = dto.getDynamicFields();int lastCellIndex = row.getLastCellNum() == -1 ? 0 : row.getLastCellNum();for (int i = 0; i < dynamicHeaders.size(); i++) {Cell cell = row.createCell(lastCellIndex + i);Object value = dynamicFields.get(dynamicHeaders.get(i));cell.setCellValue(value == null ? "" : value.toString());}}}}}
导出的excel结果示例:可以看到订单编号固定字段,以及其它动态字段 都准确的导出。

其中处理器写的比较仓促,网上用处理器实现动态列的案例很少,博主自己测试通过,如果发现有问题或者更好的建议 欢迎各位在评论区指出;
如果追求稳妥的话,直接在head里面传动态参数即可
(dynamicHeadWrite 方法,可以参考github中easy excel给出的官方demo ),
缺点就是需要将所有的表头都放到headers里面。
假设有个业务场景:你们项目中已有导出近百个字段的报表功能,这些字段之前都是固定的,突然加了个需求 需要导出数个动态字段; 为了这几个动态字段 把之前所有固定字段都写一遍 存入list中 代码可能会很丑陋, 如:
List headers = new ArrayList();
headers.add("第一列");
headers.add("第二列");
// ...
headers.add("第一百列");
// 动态列
List dynamicColumn = xxx;
headers.addAll(dynamicColumn);String fileName = "D:\\test\\out.xlsx";
EasyExcel.write(fileName)// 这里放入动态头.head(headers).sheet("模板")// dataList 你的数据.doWrite(dataList);相关文章:
【excel】easy excel如何导出动态列
动态也有多重含义:本文将描述两种动态场景下的解决方案 场景一:例如表头第一列固定为动物,且必定有第二列,第二列的表头可能为猫 也可能为狗;这是列数固定,列名不固定的场景; 场景二࿱…...
[Linux] 进程间通信
进程间通信(Inter-Process Communication, IPC)是指不同进程之间的数据交换与协作。在Linux中,进程间通信有多种方式,每种方式都有其适用的场景。本文将介绍Linux中常见的几种进程间通信方法:管道(Pipe&…...
【蓝桥等考C++真题】蓝桥杯等级考试C++组第13级L13真题原题(含答案)-最大的数
CL13 最大的数(20 分) 输入一个有 n 个无重复元素的整数数组 a,输出数组中最大的数。提示:如使用排序库函数 sort(),需要包含头文件#include 。输入: 第一行是一个正整数 n(2<n<20); 第二行包含 n 个不重复的整…...
【Linux】sudo make install 命令往系统中安装了什么 指定目录进行安装
前情提要 假如我们通过源码安装的方式,安装一个动态库,风格往往是这样的: # 克隆仓库 git clone https://github.com/xxx.git# 进入仓库目录 cd xxx编译 # ... 可能有一些校验代码完整性的sh命令# 构建 mkdir build cd build cmake ..# 编…...
RT-DETR融合CVPR[2020]轻量化卷积模块Ghost Module模块
RT-DETR使用教程: RT-DETR使用教程 RT-DETR改进汇总贴:RT-DETR更新汇总贴 《GhostNet: More Features from Cheap Operations》 一、 模块介绍 论文链接:https://arxiv.org/abs/1911.11907 代码链接:GitHub - huawei-noah/Effici…...
发布rust crate
文章目录 一、cargo构建的配置类型:dev与release两种1.编译级别2.将 crate 发布到 Crates.io对整个库的注释pub use再导出功能发布crates.io 参考 一、cargo构建的配置类型:dev与release两种 $ cargo buildFinished dev [unoptimized debuginfo] targe…...
Sequelize+Sqlite3使用示例
以下是一个简单的示例,展示了如何在Node.js中使用Express框架、Sequelize ORM以及SQLite数据库来构建一个支持RESTful API的Web应用程序。 一,安装必要的npm包: npm install express sequelize sqlite3 body-parser 二,创建Jav…...
MyBatisPlus 用法详解
MyBatisPlus 用法详解 MyBatis-Plus(简称MP)是一个MyBatis的增强工具,在MyBatis的基础上只做增强不做改变,为简化开发、提高效率而生。它提供了丰富的功能,包括强大的CRUD操作、条件构造器、自动填充、分页插件等&…...
强化学习入门笔记(Reinforcement Learning,RL) 强推!
由于本人的近期研究方向涉及到强化学习,本科时已经学习过了,但是感觉还是有些概念和算法没有学懂学透,所以想重新系统性的学习一下,记录了整个学习过程,而且对当时没有理解不是特别深刻的内容有了一些更加深刻的理解&a…...
C++ QT 工具日志异步分批保存
C QT 工具软件一般可以如此实现日志保存: #define THREAD_ID (reinterpret_cast<qulonglong>(QThread::currentThreadId()) & 0x0FFF) #define TIME (QDateTime::currentDateTime().toString("yyyy_MM_dd_hh_mm_ss_zzz"))#define LOGD(msg) qD…...
win32com库基于wps对Word文档的基础操作
win32com库基于wps对Word文档的基础操作 文章目录 win32com库基于wps对Word文档的基础操作新建/打开文档段落操作(Paragraph)字体设置(Font)图表操作(Shape) 参考链接: WAS API手册 新建/打开文档 import win32com import win32com.client as win32 # 启动WPS进程 word_obj …...
Kubernetes 网络之深度探索:网络模型与 CNI 插件
Kubernetes 网络之深度探索:网络模型与 CNI 插件 在 Kubernetes 中,网络是一个至关重要的组成部分。它不仅决定了容器之间如何通信,还影响着整个集群的可扩展性和稳定性。本节课将深入剖析 Kubernetes 的网络模型以及 CNI(Container Network Interface)网络插件。 一、K…...
Go 模块管理教程:go.mod 与依赖版本控制
Go 模块管理教程:go.mod 与依赖版本控制 Go 从版本 1.11 开始引入了 Go Modules,通过 go.mod 文件来管理项目的依赖关系和版本。Go 模块系统大大简化了 Go 项目的依赖管理,解决了之前 GOPATH 模式的许多问题。本教程将介绍如何使用 Go 模块管…...
大数据 ETL + Flume 数据清洗 — 详细教程及实例(附常见问题及解决方案)
大数据 ETL Flume 数据清洗 — 详细教程及实例 1. ETL 和 Flume 概述1.1 ETL(Extract, Transform, Load)1.2 Flume 概述 2. Flume 环境搭建2.1 下载并安装 Flume2.2 启动 Flume 3. Flume 配置和常见 Source、Sink、Channel3.1 Flume Source3.2 Flume Si…...
鸿蒙next版开发:订阅应用事件(ArkTS)
在HarmonyOS 5.0中,ArkTS提供了强大的应用事件订阅机制,允许开发者订阅和处理系统或应用级别的事件。这一功能对于监控应用行为、优化用户体验和进行性能分析至关重要。本文将详细介绍如何在ArkTS中订阅应用事件,并提供示例代码进行说明。 应…...
F litter 开发之flutter_local_notifications
flutter_local_notifications 消息通知 flutter_local_notifications地址 flutter_local_notifications: ^18.0.1class NotificationHelper {//工厂模式调用该类时,默认调用此方法,将实例对象返回出去static NotificationHelper? _instance null;sta…...
springboot参数校验
springboot 参数校验 Validated 以及 Valid - 唏嘘- - 博客园 SpringBoot参数校验Validated、Valid_springboot validate-CSDN博客...
Spring生态学习路径与源码深度探讨
引言 Spring框架作为Java企业级开发中的核心框架,其丰富的生态系统和强大的功能吸引了无数开发者的关注。学习Spring生态不仅仅是掌握Spring Framework本身,更需要深入理解其周边组件和工具,以及源码的底层实现逻辑。本文将从Spring生态的学…...
C++:set详解
文章目录 前言一、set概念介绍二、set的使用1. 插入删除相关2. 查找相关1)find2)count3)lower_bound与upper_bound4)equal_range 三、set的值是不能修改的原理四、基于哈希表的set总结 前言 根据应用场景的不同,STL总…...
(一)- DRM架构
一,DRM简介 linux内核中包含两类图形显示设备驱动框架: FB设备:Framebuffer图形显示框架; DRM:直接渲染管理器(Direct Rendering Manager),是linux目前主流的图形显示框架; 1&am…...
前端导出带有合并单元格的列表
// 导出async function exportExcel(fileName "共识调整.xlsx") {// 所有数据const exportData await getAllMainData();// 表头内容let fitstTitleList [];const secondTitleList [];allColumns.value.forEach(column > {if (!column.children) {fitstTitleL…...
P3 QT项目----记事本(3.8)
3.8 记事本项目总结 项目源码 1.main.cpp #include "widget.h" #include <QApplication> int main(int argc, char *argv[]) {QApplication a(argc, argv);Widget w;w.show();return a.exec(); } 2.widget.cpp #include "widget.h" #include &q…...
【git】把本地更改提交远程新分支feature_g
创建并切换新分支 git checkout -b feature_g 添加并提交更改 git add . git commit -m “实现图片上传功能” 推送到远程 git push -u origin feature_g...
什么是EULA和DPA
文章目录 EULA(End User License Agreement)DPA(Data Protection Agreement)一、定义与背景二、核心内容三、法律效力与责任四、实际应用与意义 EULA(End User License Agreement) 定义: EULA即…...
中医有效性探讨
文章目录 西医是如何发展到以生物化学为药理基础的现代医学?传统医学奠基期(远古 - 17 世纪)近代医学转型期(17 世纪 - 19 世纪末)现代医学成熟期(20世纪至今) 中医的源远流长和一脉相承远古至…...
深度剖析 DeepSeek 开源模型部署与应用:策略、权衡与未来走向
在人工智能技术呈指数级发展的当下,大模型已然成为推动各行业变革的核心驱动力。DeepSeek 开源模型以其卓越的性能和灵活的开源特性,吸引了众多企业与开发者的目光。如何高效且合理地部署与运用 DeepSeek 模型,成为释放其巨大潜力的关键所在&…...
Python 高效图像帧提取与视频编码:实战指南
Python 高效图像帧提取与视频编码:实战指南 在音视频处理领域,图像帧提取与视频编码是基础但极具挑战性的任务。Python 结合强大的第三方库(如 OpenCV、FFmpeg、PyAV),可以高效处理视频流,实现快速帧提取、压缩编码等关键功能。本文将深入介绍如何优化这些流程,提高处理…...
2025年- H71-Lc179--39.组合总和(回溯,组合)--Java版
1.题目描述 2.思路 当前的元素可以重复使用。 (1)确定回溯算法函数的参数和返回值(一般是void类型) (2)因为是用递归实现的,所以我们要确定终止条件 (3)单层搜索逻辑 二…...
高端性能封装正在突破性能壁垒,其芯片集成技术助力人工智能革命。
2024 年,高端封装市场规模为 80 亿美元,预计到 2030 年将超过 280 亿美元,2024-2030 年复合年增长率为 23%。 细分到各个终端市场,最大的高端性能封装市场是“电信和基础设施”,2024 年该市场创造了超过 67% 的收入。…...
SQL注入篇-sqlmap的配置和使用
在之前的皮卡丘靶场第五期SQL注入的内容中我们谈到了sqlmap,但是由于很多朋友看不了解命令行格式,所以是纯手动获取数据库信息的 接下来我们就用sqlmap来进行皮卡丘靶场的sql注入学习,链接:https://wwhc.lanzoue.com/ifJY32ybh6vc…...
