【excel】easy excel如何导出动态列

news2024/12/23 13:22:29

动态也有多重含义:本文将描述两种动态场景下的解决方案

场景一:例如表头第一列固定为动物,且必定有第二列,第二列的表头可能为猫 也可能为狗;这是列数固定,列名不固定的场景;

场景二:更复杂的场景则为 第二列可能为猫 可能为狗,第三列可能为熊,也可能没有第三列,甚至可能会有第四列;这是表头数和列数都不固定的场景;

文章目录

  • 场景一:表头名不固定
  • 场景二:表头名和列数都不固定

场景一:表头名不固定

如下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)
	 */
	@ExcelIgnore
	private Map<String,Object> dynamicFields;
}

测试代码:


	/**
	 *  动态字段导出方法 (不需要调整之前的固定字段)
	 * usage: exportDTO类 <b><u>已有固定字段</u></b> ,需要新增动态字段(即需要追加动态表头及对应数据)
	 * 模拟数据: orderNo为固定字段,dynamicFields map中的key为动态表头
	 * {@link DynamicColumnWriteHandler}
	 */
	@Test
    public 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);
    }
    
	/**
	 * 如果全部为动态字段 将动态数据传参即可
	 */
	 @Test
	public 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;
	}

	@Override
	public void beforeRowCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Integer rowIndex, Integer relativeRowIndex, Boolean isHead) { }

	@Override
	public void afterRowCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Integer rowIndex, Boolean isHead) {}


	@Override
	public 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);

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2240628.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

P10901 [蓝桥杯 2024 省 C] 封闭图形个数

铁子们好呀&#xff0c;今天博主给大家更新一道编程题&#xff01;&#xff01;&#xff01; 题目链接如下&#xff1a; P10901 [蓝桥杯 2024 省 C] 封闭图形个数 好&#xff0c;接下来&#xff0c;我将从三个方面讲解这道例题。分别是 题目解析算法原理代码实现 文章目录 1.题…

【深度学习】神经网络优化方法 正则化方法 价格分类案例

神经网络优化方法 正则化方法 价格分类案例 梯度下降法 ​ 梯度下降法是一种寻找损失函数最小的方法,从数学上的角度来看&#xff0c;梯度的方向是函数增长速度最快的方向&#xff0c;那么梯度的反方向就是函数减少最快的方向&#xff0c;所以有&#xff1a; 其中&#xff0c…

UE5 umg学习(四) 将UI控件显示到关卡中

视频资料 7、将UI控件渲染到关卡_哔哩哔哩_bilibili 在前三节里&#xff0c;创建了用户的控件蓝图Widget_BP 目标是运行的时候&#xff0c;开始运行这个蓝图&#xff0c;因此需要在开始事件触发运行 首先&#xff0c;回到主页&#xff0c;点击关卡蓝图 要从事件开始运行时 …

数字图像处理(c++ opencv):图像复原与重建-常见的滤波方法--自适应滤波器

自适应局部降噪滤波器 自适应局部降噪滤波器&#xff08;Adaptive, Local Noise Reduction Filter&#xff09;原理步骤 步骤 &#xff08;1&#xff09;计算噪声图像的方差 &#xff1b; &#xff08;2&#xff09;计算滤波器窗口内像素的均值 和方差 &#xff1b; &…

websocket身份验证

websocket身份验证 前言 上一集我们就完成了websocket初始化的任务&#xff0c;那么我们完成这个内容之后就应该完成一个任务&#xff0c;当客户端与服务端连接成功之后&#xff0c;客户端应该主动发起一个身份认证的消息。 身份认证proto 我们看一眼proto文件的内容。 我…

鸿蒙HarmonyOS 地图不显示解决方案

基于地图的开发准备已完成的情况下&#xff0c;地图还不显式的问题 首先要获取设备uuid 获取设备uuid 安装DevEco Studio的路径下 有集成好的hdc工具 E:\install_tools\DevEco Studio\sdk\default\openharmony\toolchains 这个路径下打开cmd运行 进入“设置 > 关于手机…

Day44 | 动态规划 :状态机DP 买卖股票的最佳时机IV买卖股票的最佳时机III

Day44 | 动态规划 &#xff1a;状态机DP 买卖股票的最佳时机IV&&买卖股票的最佳时机III&&309.买卖股票的最佳时机含冷冻期 动态规划应该如何学习&#xff1f;-CSDN博客 本次题解参考自灵神的做法&#xff0c;大家也多多支持灵神的题解 买卖股票的最佳时机【…

PySpark——Python与大数据

一、Spark 与 PySpark Apache Spark 是用于大规模数据&#xff08; large-scala data &#xff09;处理的统一&#xff08; unified &#xff09;分析引擎。简单来说&#xff0c; Spark 是一款分布式的计算框架&#xff0c;用于调度成百上千的服务器集群&#xff0c;计算 TB 、…

<项目代码>YOLOv8 番茄识别<目标检测>

YOLOv8是一种单阶段&#xff08;one-stage&#xff09;检测算法&#xff0c;它将目标检测问题转化为一个回归问题&#xff0c;能够在一次前向传播过程中同时完成目标的分类和定位任务。相较于两阶段检测算法&#xff08;如Faster R-CNN&#xff09;&#xff0c;YOLOv8具有更高的…

MySQL技巧之跨服务器数据查询:基础篇-动态参数

MySQL技巧之跨服务器数据查询&#xff1a;基础篇-动态参数 上一篇已经描述&#xff1a;借用微软的SQL Server ODBC 即可实现MySQL跨服务器间的数据查询。 而且还介绍了如何获得一个在MS SQL Server 可以连接指定实例的MySQL数据库的连接名: MY_ODBC_MYSQL 以及用同样的方法&a…

三天精通一种算法之螺旋矩阵(设计思路),长度最小子数组(滑动窗口)

这题主要考察思维 我来一一解释这串代码 var generateMatrix function(n) { const matrix Array.from({ length: n }, () > Array(n).fill(0)); let top 0, bottom n - 1; let left 0, right n - 1; var num 1; while (num < n * n) { …

2024-11-13 Unity Addressables1——概述与导入

文章目录 1 概述1.1 介绍1.2 主要作用1.3 Addressables 与 AssetBundle 的区别 2 导入3 配置3.1 方法一3.2 方法二 1 概述 1.1 介绍 ​ Addressables 是可寻址资源管理系统。 ​ Unity 从 2018.2 版本开始&#xff0c;建议用于替代 AssetBundle 的高阶资源管理系统。在 Unit…

python爬虫实战案例——爬取A站视频,m3u8格式视频抓取(内含完整代码!)

1、任务目标 目标网站&#xff1a;A站视频&#xff08;https://www.acfun.cn/v/ac40795151&#xff09; 要求&#xff1a;抓取该网址下的视频&#xff0c;将其存入本地&#xff0c;视频如下&#xff1a; 2、网页分析 进入目标网站&#xff0c;打开开发者模式&#xff0c;我们发…

web实验3:虚拟主机基于不同端口、目录、IP、域名访问不同页面

创建配置文件&#xff1a; 创建那几个目录及文件&#xff0c;并且写内容&#xff1a; 为网卡ens160添加一个 IPv4 地址192.168.234.199/24: 再重新激活一下网卡ens160&#xff1a; 重启服务&#xff1a; 关闭防火墙、改宽松模式&#xff1a; 查看nginx端口监听情况&#xff1a;…

在tiktok开店,商家可以享受到多少显著的优势?

短视频带货正在蓬勃发展&#xff0c;因此&#xff0c;许多人开始利用自媒体平台进行商品销售。越来越多的商家选择在TikTok上开设店铺。那么&#xff0c;在TikTok上开店&#xff0c;商家可以享受到哪些显著的优势呢&#xff1f; 1. 庞大的用户基础 TikTok拥有海量的用户群体&…

【系统设计】理解带宽延迟积(BDP)、吞吐量、延时(RTT)与TCP发送窗口的关系:优化网络性能的关键

在设计和优化网络性能时&#xff0c;理解 带宽延迟积&#xff08;BDP&#xff09;、吞吐量、延时&#xff08;RTT&#xff09; 和 TCP发送窗口 之间的关系至关重要。这些概念相互影响&#xff0c;决定了网络连接的性能上限&#xff0c;尤其是在高带宽、高延迟的环境中&#xff…

Flutter:使用Future发送网络请求

pubspec.yaml配置http的SDK cupertino_icons: ^1.0.8 http: ^1.2.2请求数据的格式转换 // Map 转 json final chat {name: 张三,message: 吃饭了吗, }; final chatJson json.encode(chat); print(chatJson);// json转Map final newChat json.decode(chatJson); print(newCha…

IOT物联网低代码可视化大屏解决方案汇总

目录 参考来源云服务商阿里云物联网平台产品主页产品文档 开源项目DGIOT | 轻量级工业物联网开源平台项目特点项目地址开源许可 IoTGateway | 基于.NET6的跨平台工业物联网网关项目特点项目地址开源许可 IoTSharp | 基于.Net Core开源的物联网基础平台项目特点项目地址开源许可…

redis 原理篇 26 网络模型 Redis是单线程的吗?为什么使用单线程

都是学cs的&#xff0c;有人月薪几万&#xff0c;有人月薪几千&#xff0c;哎&#xff0c; 相信 边际效用&#xff0c; 也就是说&#xff0c; 随着技术提升的越来越多&#xff0c;薪资的提升比例会更大 一个月几万&#xff0c;那肯定是高级开发了&#xff0c; 一个月几千&…

前端中的 File 和 Blob两个对象到底有什么不同

JavaScript 在处理文件、二进制数据和数据转换时&#xff0c;提供了一系列的 API 和对象&#xff0c;比如 File、Blob、FileReader、ArrayBuffer、Base64、Object URL 和 DataURL。每个概念在不同场景中都有重要作用。下面的内容我们将会详细学习每个概念及其在实际应用中的用法…