【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…...
CANoe Trace中的Time列:从基础定义到高级时序分析实战
1. CANoe Trace中的Time列基础解析 第一次打开CANoe的Trace窗口时,那排密密麻麻的数据确实让人头皮发麻。但别担心,咱们先来搞定最左边那个看似简单却至关重要的Time列。这个时间戳就像车载网络的"心电图"记录仪,精确到微秒级别地记…...
深入解读XDMA驱动:从/dev节点看透RK3588与FPGA的PCIe数据流(H2C/C2H通道详解)
深入解读XDMA驱动:从/dev节点看透RK3588与FPGA的PCIe数据流(H2C/C2H通道详解) 当你在RK3588开发板上执行ls /dev/xdma0_*命令时,那些神秘的字符设备节点背后隐藏着一套精密的PCIe通信体系。作为连接ARM SoC与FPGA的高速数据通道&…...
Pixel Script Temple 企业知识库图解:将文档内容自动转化为像素示意图
Pixel Script Temple 企业知识库图解:将文档内容自动转化为像素示意图 1. 企业知识管理的痛点与机遇 技术文档和操作手册是企业知识管理的重要组成部分,但传统文档形式存在明显的可读性问题。密密麻麻的文字说明、复杂的流程图和晦涩的专业术语&#x…...
Wan2.1-umt5多轮对话效果展示:复杂任务分解与执行跟踪
Wan2.1-umt5多轮对话效果展示:复杂任务分解与执行跟踪 最近在测试各种对话模型时,我遇到了一个挺有意思的挑战:让AI帮忙规划一次完整的旅行。这可不是简单的一问一答,它涉及到理解模糊需求、主动追问细节、分解多个子任务&#x…...
Wan2.2-I2V-A14B开源大模型部署:PyTorch 2.4+CUDA 12.4兼容性验证
Wan2.2-I2V-A14B开源大模型部署:PyTorch 2.4CUDA 12.4兼容性验证 1. 镜像概述与核心价值 Wan2.2-I2V-A14B是一款专注于文本到视频生成的开源大模型,其私有部署镜像经过深度优化,能够充分发挥RTX 4090D显卡的性能优势。这个镜像最大的特点在…...
GNSS数据处理避坑指南:从CDDIS、IGS等官网下载BSX、DCB文件的保姆级教程
GNSS数据处理避坑指南:从CDDIS、IGS等官网下载BSX、DCB文件的保姆级教程 第一次接触GNSS数据处理时,面对各种数据中心的复杂目录和神秘的文件命名规则,我完全懵了。记得当时为了找一个.BSX文件,整整花了两天时间在不同网站间来回切…...
Keras图像分割模型训练完整指南:从参数配置到性能评估
Keras图像分割模型训练完整指南:从参数配置到性能评估 【免费下载链接】image-segmentation-keras Implementation of Segnet, FCN, UNet , PSPNet and other models in Keras. 项目地址: https://gitcode.com/gh_mirrors/im/image-segmentation-keras 图像分…...
Qwen3-14B应用案例:智能客服与内容生成,企业落地实操
Qwen3-14B应用案例:智能客服与内容生成,企业落地实操 1. 为什么选择Qwen3-14B作为企业AI解决方案 在当今企业数字化转型浪潮中,AI技术正从实验室走向实际业务场景。Qwen3-14B作为140亿参数的大型语言模型,在能力与资源消耗之间取…...
新手入门指南:在快马平台用AI生成代码理解云桌面基础概念
今天想和大家分享一个特别适合新手理解云桌面基础概念的实践方法。作为一个刚接触云计算的小白,我最初对"一台主机创建多个云桌面"这个概念也是一头雾水,直到在InsCode(快马)平台上尝试用AI生成代码来模拟这个过程,才真正搞明白其中…...
基于python的演唱会门票演出购票系统的设计与实现
目录同行可拿货,招校园代理 ,本人源头供货商用户管理模块演出信息管理购票与选座功能支付系统集成订单与票务管理数据分析与报表高并发优化项目技术支持源码获取详细视频演示 :文章底部获取博主联系方式!同行可合作同行可拿货,招校园代理 ,本人源头供货商…...
