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.mzph.cn/news/803930.shtml

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

相关文章

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

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

Python代码识别minist手写数字【附pdf】

一、概述 对于人类而言&#xff0c;要识别图片中的数字是一件很容易的事情&#xff0c;但是&#xff0c;如何让机器学会理解图片上的数字&#xff0c;这似乎并不容易。那么&#xff0c;能否找出一个函数&#xff08;模型&#xff09;&#xff0c;通过输入相关的信息&#xff0…

网络基础三——IP协议补充和Mac帧协议

全球网络及网段划分的理解 ​ 根据国家组织地区人口综合评估进行IP地址范围的划分&#xff1b; ​ 假设前8位用来区分不同的国家&#xff0c;国际路由器负责全球数据传输&#xff0c;子网掩码为IP/8&#xff1b;次6位区分不同的省份&#xff0c;国内路由器负责全国数据的传输…

jvm调优案例分析-window通过jstack查找死锁的进程

我们经常会遇到java程序遇死锁的问题&#xff0c;也会经常遇到。 案例 以下是案例代码&#xff1a; package com.dzend.mall.order;public class JstackLockDemo {public static final int initData 666;public static User user new User();public int compute(){int a1;i…

Window安装PostgresSQL

PostgreSQL 安装参考&#xff1a;Windows下安装PostgreSQL_window 安装postgresql-CSDN博客 安装好后打开pgAdmin4 配置Navicat连接PostgresSQL 找到安装目录文件 pg_hba.conf 修改配置增加&#xff1a; 修改前&#xff1a; # TYPE DATABASE USER ADDRES…

登录压力测试

目录 一、准备测试数据 1.1数据库存储过程添加数据 1.2导出为csv作为测试数据&#xff08;账号、密码&#xff09; 二、使用fiddler抓包查看接口 2.1.抓到相关接口信息 2.2添加线程组和http请求 2.3将前面接口需要的参数去json格式化 ​2.4填写相关信息 ​ 2.5添加http…

vue canvas绘制信令图,动态显示标题、宽度、高度

需求: 1、 根据后端返回的数据&#xff0c;动态绘制出信令图 2、根据 dataStatus 返回值&#xff1a; 0 和 1&#xff0c; 判断 文字内容的颜色&#xff0c;0&#xff1a;#000&#xff0c;1&#xff1a;red 3.、根据 lineType 返回值&#xff1a; 0 和 1&#xff0c; 判断 箭…

20240309web前端_第三周作业_教务系统页面

作业&#xff1a;教务系统页面 成果展示&#xff1a; 完整代码&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1…

Verilog实现手表计时

实现手表的计时功能&#xff1a; 1.具有start启动信号、pause暂停信号&#xff0c;可以自定义其触发机制。 2.具有时间更改接口&#xff0c;可以更改时、分、秒。 3.输出时、分、秒。 Verilog设计 模块端口定义&#xff1a; module watch1(input wire clk …

STC89C52学习笔记(七)

STC89C52学习笔记&#xff08;七&#xff09; 综述&#xff1a;本文介绍了串口以及讲述了串口相关寄存器如何配置并给予相关代码。 一、修改代码注意事项 在修改代码时不要一次性加入一堆代码&#xff0c;不利于定位错误。可以先注释一些代码&#xff0c;待解决完毕问题后再…

Angular 使用DomSanitizer

跨站脚本Cross-site scripting 简称XSS&#xff0c;是代码注入的一种&#xff0c;是一种网站应用程序的安全漏洞攻击。它允许恶意用户将代码注入到网页上&#xff0c;其他用户在使用网页时就会收到影响&#xff0c;这类攻击通常包含了HTML和用户端脚本语言&#xff08;JS&…

ES6基础(JavaScript基础)

本文用于检验学习效果&#xff0c;忘记知识就去文末的链接复习 1. ECMAScript介绍 ECMAScript是一种由Ecma国际&#xff08;前身为欧洲计算机制造商协会&#xff0c;英文名称是European Computer Manufacturers Association&#xff09;通过ECMA-262标准化的脚本程序设计语言…

基于拉格朗日分布算法的电动汽车充放电调度MATLAB程序

微❤关注“电气仔推送”获得资料&#xff08;专享优惠&#xff09; 程序简介 该模型主要做的是基于拉格朗日分布算法的电动汽车充放电调度模型。利用蒙特卡洛模拟法模拟出电动汽车负荷曲线&#xff0c;并求解出无序充电功率曲线和有序充电曲线&#xff0c;该模型在电动汽车个…

逆向案例十六——简单webpack逆向,财联社信息

网址链接&#xff1a;财联社A股24小时电报-上市公司动态-今日股市行情报道 数据包sign参数为加密&#xff0c;可以直接搜索找参数的位置&#xff0c;搜索不到的情况下&#xff0c;在断点跟栈&#xff1a; 确定js文件所在位置&#xff0c;并打上断点。 点击加载刷新页面。可以发…

REST API实战演练之JavaScript使用Rest API

咱们前面讲了一下如何创建REST API 假期别闲着&#xff1a;REST API实战演练之创建Rest API-CSDN博客 又讲了java客户端如何使用REST API 假期别闲着&#xff1a;REST API实战演练之客户端使用Rest API-CSDN博客 接下来咱们看看JavaScript怎么使用REST API。 一、新建一个…

Log4J2漏洞(CVE-2024-44228)原理_log4j2漏洞原理,网络安全面试项目

Apache log4j2-RCE 漏洞 一、漏洞简介 二、漏洞原理 三、靶场漏洞复现 四、总结 Apache log4j2-RCE 漏洞 一、漏洞简介 Apache Log4j2是一个基于Java的日志记录工具&#xff0c;当前被广泛应用于业务系统开发&#xff0c;开发 者可以利用该工具将程序的输入输出信息进行日…

Feature Pyramid Networks for object detection

FPN 总述1.引言2.相关工作3. Feature Pyramid NetworksBottom-up pathwayTop-down pathway and lateral connections 4. 应用用于 RPN用于 Fast R-CNN 核心代码复现FPN网络结构ResNet Bottleneck完整代码 总述 下图中&#xff0c;蓝色边框表示的是特征图&#xff0c;边框越粗表…

在Ubuntu Linux中安装boost库详细步骤

下载boost安装包 在Linux浏览器 Boost C Libraries 下载boost的最新版安装包 安装包解压缩 在安装目录中找到压缩包&#xff0c;右键点击压缩包&#xff0c;点击Extract to...解压缩至选择的目录 设置编译器 在解压缩后的目录中进入终端&#xff0c;运行命令&#xff1a; 如…

LeetCode 2529. 正整数和负整数的最大计数——每日一题

上一篇博客&#xff1a;LeetCode 993. 二叉树的堂兄弟节点——每日一题 写在前面&#xff1a;大家好&#xff01;我是晴空๓。如果博客中有不足或者的错误的地方欢迎在评论区或者私信我指正&#xff0c;感谢大家的不吝赐教。我的唯一博客更新地址是&#xff1a;https://ac-fun.…

力扣121. 买卖股票的最佳时机

Problem: 121. 买卖股票的最佳时机 文章目录 题目描述思路复杂度Code 题目描述 思路 1.定义一个int数组max大小同prices&#xff1b;定义int变量curMax初始化为0&#xff1b; 2.从后往前遍历数组&#xff0c;若当前元素prices[i] > curMax时&#xff0c;则使将其赋值给curMa…