easyExcel - 动态复杂表头的编写

目录

  • 前言
  • 一、情景介绍
  • 二、问题分析
  • 三、代码实现
    • 方式一:head 设置
    • 方式二:模板导出
    • 方式三:自定义工具类


前言

Java-easyExcel入门教程:https://blog.csdn.net/xhmico/article/details/134714025

之前有介绍过如何使用 easyExcel,以及写了两个入门的 demo ,这两个 demo 能应付在开发中大多数的导入和导出需求,不过有时候面对一些复杂的表格,就会有点不够用,该篇讲述的是如何实现复杂表头编写


一、情景介绍

在实际的开发过程中可能会遇到需要导出一些带有复杂表头的表格,比如以下案例:

在这里插入图片描述

该表头占了两行,其中 橙色 部分的信息是需要动态生成的


二、问题分析

关于如何实现类似于上述复杂表头,有多种方式均可实现,首先这个表头是一个复杂表头,其次还有动态的部分

查看官方文档,对应复杂表头的实现

官方文档:复杂头写入

在这里插入图片描述

从中可以看出,多行表头就是由 多个纵向 的列表组成,并且表头 相同的部分会自动合并居中对齐

再查阅官方文档关于如何实现动态头的写入

官方文档:动态头、实时生成头写入

在这里插入图片描述

官方给了一个 head() 方法允许我们在代码中自定义表头

    public T head(List<List<String>> head) {this.parameter().setHead(head);return this.self();}

三、代码实现


方式一:head 设置

可以将上述表头看作是以下 6 个集合组成的表头,然后使用 head() 方法去设置

在这里插入图片描述

代码示例:

    /*** 复杂表头编写:方式一*/@Testpublic void complexHeadDemo01() {// 输出文件路径String outFilePath = "D:\\excel-files\\demo01.xlsx";// 表格数据List<Object> data = new ArrayList<>();EasyExcel.write(outFilePath)// 动态头.head(head()).sheet()// 表格数据.doWrite(data);}private List<List<String>> head() {List<List<String>> list = new ArrayList<List<String>>();List<String> head0 = new ArrayList<String>();head0.add("部门");head0.add("用户名称");List<String> head1 = new ArrayList<String>();head1.add("运营部");head1.add("性别");List<String> head2 = new ArrayList<String>();head2.add("运营部");head2.add("年龄");List<String> head3 = new ArrayList<String>();head3.add("时间");head3.add("出生日期");List<String> head4 = new ArrayList<String>();head4.add("2024-04-09");head4.add("学历");List<String> head5 = new ArrayList<String>();head5.add("2024-04-09");head5.add("电话号码");list.add(head0);list.add(head1);list.add(head2);list.add(head3);list.add(head4);list.add(head5);return list;}

结果展示:

在这里插入图片描述

可以看到是能够实现这种表头的,不过需要自己定义表头的样式


方式二:模板导出

可以使用模板导出的方式,设置一个模板文件,例如:

在这里插入图片描述

实体类:
DeptUserExcelEntity.java

import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class DeptUserExcelEntity {@ApiModelProperty(value = "用户名称")private String realName;@ApiModelProperty(value = "性别")private String gender;@ApiModelProperty(value = "年龄")private Integer age;@ApiModelProperty(value = "出生日期")private String birthdate;@ApiModelProperty(value = "学历")private String education;@ApiModelProperty(value = "电话号码")private String telephone;
}

代码示例:

    /*** 复杂表头编写:方式二*/@Testpublic void complexHeadDemo02() {// 模板文件路径String templateFilePath = "D:\\excel-files\\template.xlsx";// 输出文件路径String outFilePath = "D:\\excel-files\\demo02.xlsx";// 创建 ExcelWriter 实例ExcelWriter writer = EasyExcel// 写入到.write(outFilePath)// 指定模板.withTemplate(templateFilePath).build();WriteSheet sheet = EasyExcel.writerSheet().build();Map<String, String> replaceMap = new HashMap<>();replaceMap.put("deptName", "运营部");replaceMap.put("currentDate", "2024-04-09");// 执行填充普通占位符操作writer.fill(replaceMap, sheet);// 获取员工信息List<DeptUserExcelEntity> data = new ArrayList<>();FillConfig fillConfig = FillConfig.builder()// 开启填充换行.forceNewRow(true).build();// 执行填充列表操作writer.fill(data, fillConfig, sheet);// 结束writer.finish();}

结果展示:

在这里插入图片描述

可以看到效果是比较好的,也不用担心表格样式的问题

关于如何使用 easyexcel 实现按模板导出,可参考:easyExcel - 按模板导出 有较为详细的说明


方式三:自定义工具类

根据官方实现复杂表头的写法,自定义输出对象为

DeptUserExcelEntity.java

import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.*;
import com.alibaba.excel.enums.poi.BorderStyleEnum;
import com.alibaba.excel.enums.poi.FillPatternTypeEnum;
import com.alibaba.excel.enums.poi.HorizontalAlignmentEnum;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
// 头背景设置
@HeadStyle(fillPatternType = FillPatternTypeEnum.SOLID_FOREGROUND, horizontalAlignment = HorizontalAlignmentEnum.CENTER, borderLeft = BorderStyleEnum.THIN, borderTop = BorderStyleEnum.THIN, borderRight = BorderStyleEnum.THIN, borderBottom = BorderStyleEnum.THIN)
//标题高度
@HeadRowHeight(40)
//内容高度
@ContentRowHeight(30)
//内容居中,左、上、右、下的边框显示
@ContentStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER, borderLeft = BorderStyleEnum.THIN, borderTop = BorderStyleEnum.THIN, borderRight = BorderStyleEnum.THIN, borderBottom = BorderStyleEnum.THIN)
public class DeptUserExcelEntity {@ApiModelProperty(value = "用户名称")@ExcelProperty({"部门","用户名称"})@ColumnWidth(15)private String realName;@ApiModelProperty(value = "性别")@ExcelProperty({"deptName","性别"})@ColumnWidth(15)private String gender;@ApiModelProperty(value = "年龄")@ExcelProperty({"deptName","年龄"})@ColumnWidth(15)private Integer age;@ApiModelProperty(value = "出生日期")@ExcelProperty({"时间","出生日期"})@ColumnWidth(15)private String birthdate;@ApiModelProperty(value = "学历")@ExcelProperty({"currentDate","学历"})@ColumnWidth(20)private String education;@ApiModelProperty(value = "电话号码")@ExcelProperty({"currentDate","电话号码"})@ColumnWidth(20)private String telephone;
}

如果依照之前的导出案例,代码如下:

    @Testpublic void complexHeadDemo03_test() {// 输出文件路径String outFilePath = "D:\\excel-files\\demo03.xlsx";List<DeptUserExcelEntity> excelEntities = new ArrayList<>();EasyExcel.write(outFilePath, DeptUserExcelEntity.class).sheet().doWrite(excelEntities);}

最后得到的表格也是根据输出对象定义而来的

在这里插入图片描述

所以如果能让表格在写入的时候,输出对象 DeptUserExcelEntity@ExcelProperty 里面的 deptNamecurrentDate 替换成想要的不就行了,所以我就自定义一个工具类,在需要的时候改变注解的属性值就行了

工具类:

AnnotationUtils.java

import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.usermodel.CellType;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedType;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;/*** 注解工具类*/
public class AnnotationUtils {/*** 变更注解的属性值再处理业务,处理完业务之后恢复类的属性** @param clazz     注解所在的实体类* @param tClass    注解类* @param attrName 要修改的注解属性名* @param attrTypeEnum 要修改的注解属性的类型* @param valueMap  要设置的属性值*/public static <A extends Annotation> void changeAnnotationValueToDealProcess(Class<?> clazz,Class<A> tClass,String attrName,AttrTypeEnum attrTypeEnum,Map<String, String> valueMap,DealProcess dealProcess) {try {Map<String, Object> fieldAnnotationValueMap = new HashMap<>();Field[] fields = clazz.getDeclaredFields();for (Field field : fields) {A annotation = field.getAnnotation(tClass);if (annotation == null) continue;Object value = setAnnotationValue(annotation, attrName, attrTypeEnum, valueMap);String fieldName = field.getName();fieldAnnotationValueMap.put(fieldName, value);}// 处理业务逻辑dealProcess.deal();// 恢复for (Field field : fields) {A annotation = field.getAnnotation(tClass);String fieldName = field.getName();if (annotation == null) continue;Object value = fieldAnnotationValueMap.get(fieldName);InvocationHandler handler = Proxy.getInvocationHandler(annotation);Field memberValuesField = handler.getClass().getDeclaredField("memberValues");memberValuesField.setAccessible(true);@SuppressWarnings("all")Map<String, Object> memberValues = (Map) memberValuesField.get(handler);memberValues.put(attrName, value);}} catch (Exception e) {e.printStackTrace();}}/*** 设置注解中的字段值** @param annotation 要修改的注解实例* @param attrName  要修改的注解属性名* @param attrTypeEnum 要修改的注解属性的类型* @param valueMap   替换属性集的map*/@SuppressWarnings("all")private static Object setAnnotationValue(Annotation annotation, String attrName,AttrTypeEnum attrTypeEnum, Map<String, String> valueMap) throws NoSuchFieldException, IllegalAccessException {InvocationHandler handler = Proxy.getInvocationHandler(annotation);Field field = handler.getClass().getDeclaredField("memberValues");field.setAccessible(true);Map memberValues = (Map) field.get(handler);Object value = memberValues.get(attrName);switch (attrTypeEnum) {case STRING: {String oldValue = (String) value;String newValue = valueMap.get(oldValue);if (StringUtils.isNotBlank(newValue)) {memberValues.put(attrName, newValue);}}break;case STRING_ARR: {String[] oldValue = (String[]) value;String[] newValue = new String[oldValue.length];for (int i = 0; i < oldValue.length; i++) {String replace = valueMap.get(oldValue[i]);newValue[i] = replace != null ? replace : oldValue[i];}memberValues.put(attrName, newValue);}break;}return value;}public enum AttrTypeEnum {STRING,STRING_ARR}public interface DealProcess {void deal() throws Exception;}}

代码示例:

    /*** 复杂表头编写:方式三*/@Testpublic synchronized void complexHeadDemo03() {// 输出文件路径String outFilePath = "D:\\excel-files\\demo03.xlsx";// 替换注解中的属性值为HashMap<String, String> replaceMap = new HashMap<>();replaceMap.put("deptName", "运营部");replaceMap.put("currentDate", "2024-04-09");/** 这里简单的说明一下:*      attrName: 对应的是 @ExcelProperty 中的 value 属性*      attrTypeEnum: 对应的是 @ExcelProperty 中的 value 属性 的类型*/AnnotationUtils.changeAnnotationValueToDealProcess(DeptUserExcelEntity.class, ExcelProperty.class, "value", AnnotationUtils.AttrTypeEnum.STRING_ARR, replaceMap, new AnnotationUtils.DealProcess() {@Overridepublic void deal() {List<DeptUserExcelEntity> excelEntities = new ArrayList<>();EasyExcel.write(outFilePath, DeptUserExcelEntity.class).sheet().doWrite(excelEntities);}});}

结果:

在这里插入图片描述

这里要注意的是因为调用该方法时回去修改对应的 class 对象,所以这里最好加锁

这种方式不仅可以处理 easyexcel 的注解动态表头问题,也可以处理传统的 poi 的注解动态表头,目前也是我用得比较多的一种方式

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

CSS-浮动文字环绕布局、隐藏属性display、overflow、三角形制作、鼠标样式

文字环绕布局 CSS文字环绕布局是指在网页中让文字环绕在图片或其他元素周围的布局方式。这通常通过CSS中的float属性来实现。你可以将图片设置为float: left;或float: right;&#xff0c;然后在文本元素中使用clear属性来清除浮动&#xff0c;以确保文字不会覆盖图片。另外&am…

Jmeter02-2:参数化组件其他方式

0、Jmeter组件&#xff1a;参数化概述 0.1 是什么&#xff1f; 参数化是动态的获取并设置数据 0.2 为什么&#xff1f; 比如执行批量操作时&#xff0c;批量插入或批量删除&#xff0c;之前数据都是手写的&#xff0c;每执行完都要修改一次&#xff0c;效率太低 而参数化就是…

基于javassmJSP的教学质量评价系统

开发语言&#xff1a;Java 框架&#xff1a;ssm 技术&#xff1a;JSP JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09; 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclip…

maxpool long数据类型报错

报错&#xff1a; RuntimeError: “max_pool2d” not implemented for ‘Long’ 源码&#xff1a; import torch from torch import nn from torch.nn import MaxPool2dinput torch.tensor([[1, 2, 0, 3, 1],[0, 1, 2, 3, 1],[1, 2, 1, 0, 0],[5, 2, 3, 1, 1],[2, 1, 0, 1, 1…

重温OKHTTP源码

本文基于OkHttp4.12.0源码分析 官方地址 概括 本篇主要是对okhttp开源库的一个详细解析&#xff0c;包含详细的请求流程分析、各大拦截器的解读等。 使用方法 同步请求&#xff1a;创建一个OKHttpClient对象&#xff0c;一个Request对象&#xff0c;然后利用它们创建一个Ca…

文献速递:深度学习胰腺癌诊断--基于螺旋变换的胰腺癌分割模型驱动深度学习方法

Title 题目 Model-Driven Deep Learning Method forPancreatic Cancer Segmentation Basedon Spiral-Transformation 基于螺旋变换的胰腺癌分割模型驱动深度学习方法 01 文献速递介绍 胰腺癌是最致命的恶性肿瘤之一&#xff0c;其特点是诊断延迟、治疗困难和高死亡率。患者…

采用Flink CDC操作SQL Server数据库获取增量变更数据

采用Flink CDC操作SQL Server数据库获取增量变更数据 Flink CDC 1.12版本引入了对SQL Server的支持&#xff0c;包括SqlServerCatalog和SqlServerTable。在SqlServerCatalog中&#xff0c;你可以根据表名获取对应的字段和字段类型。 SQL Server 2008 开始支持变更数据捕获 (C…

【C语言】简单介绍进制和操作符

&#x1f308;个人主页&#xff1a;是店小二呀 &#x1f308;C语言笔记专栏&#xff1a;C语言笔记 &#x1f308;C笔记专栏&#xff1a; C笔记 &#x1f308;喜欢的诗句:无人扶我青云志 我自踏雪至山巅 本文简要介绍进制和操作符&#xff0c;愿能为您提供帮助&#xff01;文章…

论文解读:吴恩达来信AI Agent技巧—利用自我反馈的迭代细化技术

《自我完善&#xff1a;利用自我反馈的迭代细化技术》 https://arxiv.org/pdf/2303.17651.pdf 摘要 Large language models (LLMs) 经常无法在一次尝试中生成最佳输出。受人类在修改书面文本时所表现出的迭代精炼过程的启发&#xff0c; 我们提出了 SELF-REFINE&#xff0c…

R语言绘图 | 散点小提琴图

原文链接&#xff1a;R语言绘图 | 散点小提琴图 本期教程 写在前面 本期的图形来自发表在Nature期刊中的文章&#xff0c;这样的基础图形在日常分析中使用频率较高。 获得本期教程数据及代码&#xff0c;后台回复关键词&#xff1a;20240405 绘图 设置路径 setwd("You…

CSDN 广告太多,停更通知,转移到博客园

文章目录 前言新博客地址 前言 CSDN的广告实在是太多了&#xff0c;我是真的有点忍不了。直接把广告插在我的文章中间。而且我已经懒得找工作了&#xff0c;我当初写CSDN的目的就是为了找工作&#xff0c;有个博客排名。当时经济环境实在是太差了。我也没必要纠结这个2000粉丝…

因为使用ArrayList.removeAll(List list)导致的机器重启

背景 先说一下背景&#xff0c;博主所在的业务组有一个核心系统&#xff0c;需要同步两个不同数据源给过来的数据到redis中&#xff0c;但是每次同步之前需要过滤掉一部分数据&#xff0c;只存储剩下的数据。每次同步的数据与需要过滤掉的数据量级大概在0-100w的数据不等。 由…

快排序解读

排序算法是计算机科学中不可或缺的一部分&#xff0c;它们在各种数据处理场景中发挥着关键作用。在众多排序算法中&#xff0c;快速排序以其高效的性能和简洁的实现成为了许多程序员的首选。今天&#xff0c;我们就来深入剖析快速排序算法&#xff0c;了解其原理、实现方式以及…

比特币革命:刚刚开始

作者&#xff1a;Marius Farashi Tasooji 编译&#xff1a;秦晋 要充分理解比特币及其含义&#xff0c;首先必须理解什么是价值&#xff0c;什么是货币。以及是什么赋予资产价值&#xff1f; 这个问题看似愚蠢&#xff0c;但实际上非常有趣。我们的生活是由我们消费或出售的物品…

【问题解决】ubuntu安装新版vscode报code-insiders相关错误

问题 目前 vscode官网 最新的包为 insiders_1.89.0-1712297812_amd64.deb &#xff0c;双击或者使用sudo dpkg -i code-insiders_1.89.0-1712297812_amd64.deb安装后报错&#xff0c;执行其他命令也报错。 安装环境&#xff1a;ubuntu18.04 dpkg: 处理软件包 code-insiders (…

Taro框架中的H5 模板基本搭建

1.H5 模板框架的搭建 一个h5 的基本框架的搭建 基础template 阿乐/H5 Taro 的基础模板

在Spring中使用Redis

端口怎么设置&#xff0c;看我前一篇文章 前面使用jedis&#xff0c;通过Jedis对象中各种方法来操作redis的。 此处Spring中则是通过StringRedisTemplate来操作redis。 最原始提供的类是RedisTemplate StringRedisTemplate是RedisTemplate的子类&#xff0c;专门处理文本数据的…

PUBG绝地求生29.1版本延迟高/卡顿/掉帧/丢包的快速解决方法

要想在绝地求生中获得好成绩&#xff0c;咱们需求把握一些根本的游戏技巧。比方&#xff0c;在挑选降落点时&#xff0c;咱们可以运用u标签来着重“安全”二字。挑选一个相对较为安全的降落点可以防止与其他玩家过早触摸&#xff0c;给自己争夺更多时间来搜集资源和配备。接下来…

Vant DropdownMenu 下拉菜单带搜索功能

Vant DropdownMenu 下拉菜单带搜索功能 效果图&#xff1a; 上代码&#xff1a; <van-dropdown-menu active-color"#E33737"><van-dropdown-item ref"dropdownItem"><template #title><span>{{ dropdownItem.text }}</span…

Mysql密码修改问题

docker安装mysql&#xff0c;直接拉取镜像&#xff0c;挂载关键目录即可启动&#xff0c;默认3306端口。此时无法直接连接&#xff0c;需要配置密码。docker进入mysql容器中 docker exec -it mysql bash #mysq是容器名称&#xff0c;也可以用容器id通过修改mysql的配置进行免密…