Easyexcel 导出数据 一对多关系导出数据集合

news2024/11/25 3:07:39

客户要求

要求导出的表格如图
在这里插入图片描述
实现这样表格 很多人会想到动态表头,easypoi可以直接实现,但是我用的是easyexcel,而easyexcel自身并没有提供自动合并的功能所以还是需要自己来合并。

代码如下

首先我们来看下将嵌套数据平铺,不进行合并导出的Excel

在这里插入图片描述

实现思路

首先我们得把原来嵌套的订单商品信息给平铺了,创建一个专门的导出对象OrderData,包含订单和商品信息,二级表头可以通过设置@ExcelProperty的value为数组来实现

@Data
@AllArgsConstructor
@NoArgsConstructor
public class OrderData {


    @ExcelProperty(value = "订单编码")
    @ColumnWidth(20)
    @CollectCustomMerge(needMerge = true, isPk = true)
    private String orderSn;
    @ExcelProperty(value = "创建时间")
    @ColumnWidth(20)
    @DateTimeFormat("yyyy-MM-dd")
    @CollectCustomMerge(needMerge = true)
    private Date createTime;
    @ExcelProperty(value = "收货地址")
    @CollectCustomMerge(needMerge = true)
    @ColumnWidth(20)
    private String receiverAddress;
    @ExcelProperty(value = {"商品信息", "商品编码"})
    @ColumnWidth(20)
    private String productSn;
    @ExcelProperty(value = {"商品信息", "商品名称"})
    @ColumnWidth(20)
    private String name;
    @ExcelProperty(value = {"商品信息", "商品标题"})
    @ColumnWidth(30)
    private String subTitle;

}

创建一个自定义注解CollectCustomMerge,用于标记哪些属性需要合并,哪个是主键

/**
 * 自定义注解,用于判断是否需要合并以及合并的主键
 * 标记哪些属性需要合并,哪个是主键
 */
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface CollectCustomMerge {

    /**
     * 是否需要合并单元格
     */
    boolean needMerge() default false;

    /**
     * 是否是主键,即该字段相同的行合并
     */
    boolean isPk() default false;
}

创建自定义单元格合并策略类CustomMergeStrategy,当Excel中两列主键相同时,合并被标记需要合并的列


import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.write.handler.RowWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteTableHolder;
import com.easyexcle.demo.easyExcel.annotion.CollectCustomMerge;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.util.CellRangeAddress;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

/**
 * @Author: lkz
 * @Title: CustomMergeStrategy
 * @Description: 自定义单元格合并策略类CustomMergeStrategy,当Excel中两列主键相同时,合并被标记需要合并的列
 * @Date: 2023/7/5 9:16
 */
public class CustomMergeStrategy implements RowWriteHandler {
    /**
     * 主键下标
     */
    private Integer pkIndex;

    /**
     * 需要合并的列的下标集合
     */
    private List<Integer> needMergeColumnIndex = new ArrayList<>();

    /**
     * DTO数据类型
     */
    private Class<?> elementType;

    public CustomMergeStrategy(Class<?> elementType) {
        this.elementType = elementType;
    }

    @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 relativeRowIndex, Boolean isHead) {

    }

    @Override
    public void afterRowDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Integer relativeRowIndex, Boolean isHead) {
        // 如果是标题,则直接返回
        if (isHead) {
            return;
        }

        // 获取当前sheet
        Sheet sheet = writeSheetHolder.getSheet();

        // 获取标题行
        Row titleRow = sheet.getRow(0);

        if (null == pkIndex) {
            this.lazyInit(writeSheetHolder);
        }

        // 判断是否需要和上一行进行合并
        // 不能和标题合并,只能数据行之间合并
        if (row.getRowNum() <= 1) {
            return;
        }
        // 获取上一行数据
        Row lastRow = sheet.getRow(row.getRowNum() - 1);
        // 将本行和上一行是同一类型的数据(通过主键字段进行判断),则需要合并
        if (lastRow.getCell(pkIndex).getStringCellValue().equalsIgnoreCase(row.getCell(pkIndex).getStringCellValue())) {
            for (Integer needMerIndex : needMergeColumnIndex) {
                CellRangeAddress cellRangeAddress = new CellRangeAddress(row.getRowNum() - 1, row.getRowNum(),
                        needMerIndex, needMerIndex);
                sheet.addMergedRegionUnsafe(cellRangeAddress);
            }
        }
    }

    /**
     * 初始化主键下标和需要合并字段的下标
     */
    private void lazyInit(WriteSheetHolder writeSheetHolder) {

        // 获取当前sheet
        Sheet sheet = writeSheetHolder.getSheet();

        // 获取标题行
        Row titleRow = sheet.getRow(0);
        // 获取DTO的类型
        Class<?> eleType = this.elementType;

        // 获取DTO所有的属性
        Field[] fields = eleType.getDeclaredFields();

        // 遍历所有的字段,因为是基于DTO的字段来构建excel,所以字段数 >= excel的列数
        for (Field theField : fields) {
            // 获取@ExcelProperty注解,用于获取该字段对应在excel中的列的下标
            ExcelProperty easyExcelAnno = theField.getAnnotation(ExcelProperty.class);
            // 为空,则表示该字段不需要导入到excel,直接处理下一个字段
            if (null == easyExcelAnno) {
                continue;
            }
            // 获取自定义的注解,用于合并单元格
            CollectCustomMerge collectCustomMerge = theField.getAnnotation(CollectCustomMerge.class);

            // 没有@CustomMerge注解的默认不合并
            if (null == collectCustomMerge) {
                continue;
            }

            for (int index = 0; index < fields.length; index++) {
                Cell theCell = titleRow.getCell(index);
                // 当配置为不需要导出时,返回的为null,这里作一下判断,防止NPE
                if (null == theCell) {
                    continue;
                }
                // 将字段和excel的表头匹配上
                if (easyExcelAnno.value()[0].equalsIgnoreCase(theCell.getStringCellValue())) {
                    if (collectCustomMerge.isPk()) {
                        pkIndex = index;
                    }

                    if (collectCustomMerge.needMerge()) {
                        needMergeColumnIndex.add(index);
                    }
                }
            }
        }

        // 没有指定主键,则异常
        if (null == this.pkIndex) {
            throw new IllegalStateException("使用@CustomMerge注解必须指定主键");
        }

    }
}

测试最终效果

public class CollectTest {


    public static void main(String[] args) {
    
        List<OrderData> orderDataList=new ArrayList<>();
        orderDataList.add(new OrderData("1",new Date(),"南京","001","商品名称1","标题1"));
        orderDataList.add(new OrderData("1",new Date(),"南京","001","商品名称2","标题2"));
        orderDataList.add(new OrderData("2",new Date(),"北京","002","商品名称02","标题02"));
        orderDataList.add(new OrderData("2",new Date(),"北京","003","商品名称03","标题1"));
        orderDataList.add(new OrderData("1",new Date(),"南京","001","商品名称3","标题3"));
        orderDataList.add(new OrderData("3",new Date(),"上海","001","商品名称3","标题3"));
        orderDataList.add(new OrderData("3",new Date(),"天津","001","商品名称302","标题302"));
        orderDataList.add(new OrderData("3",new Date(),"上海","001","商品名称303","标题303"));
        orderDataList.add(new OrderData("4",new Date(),"杭州","001","商品名称4","标题4"));
        orderDataList.add(new OrderData("5",new Date(),"杭州","001","商品名称5","标题5"));


        EasyExcel.write("D:/pdf/CollectExport.xlsx").head(OrderData.class)
                .registerWriteHandler(new CustomMergeStrategy(OrderData.class))
                .sheet().doWrite(orderDataList);

    }

}

注意事项

在这里插入图片描述

效果

在这里插入图片描述

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

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

相关文章

解决github打不开的方法(亲测有效)

网上提供了很多针对github打开慢的解决方案&#xff0c;什么又是改host文件&#xff0c;又是下载杂七杂八的加速器等等&#xff0c;其实效果并不好&#xff0c;微软商城已有对应的软件可以解决该问题&#xff0c;获取路径更为安全。 目录 安装Watt Toolkit找不到Microsoft Stor…

Aduc7126的PLA模块

PLA 一、PLA结构讲解 PLA是Aduc7126内部的可编辑逻辑阵列&#xff0c;Aduc7126总共有16个element&#xff0c;分为两组&#xff0c;如下图所示。 下图是PLA的其中一个element结构图&#xff0c;按照由左至右进行讲解&#xff1a; 左边MUX0、MUX1、MUX2、MUX3都是选择器&#…

将 InputStream 流转成 MultipartFile

MultipartFile是一个接口, 有一个MockMultipartFile实现类,里面有构造方法可以直接将输入流转为MutipartFile对象: MultipartFile File new MockMultipartFile(filename, file.getName(), file.getContentType(), fileStream); 使用MockMultipartFile类, 项目需要导入org.sp…

maven配置问题

maven配置问题 Error running ‘项目名 [install]’: No valid Maven installation No valid Maven installation found. Either set the home directory in the configuration dialog or set the M2_HOME environment variable on your system. 解决方法&#xff1a; 依次检…

Echarts柱状图循环配色多色彩

话不多说&#xff0c;直接上配置案例&#xff0c;欢迎留言分享交流 option {xAxis: {type: category,data: [Mon, Tue, Wed, Thu, Fri, Sat, Sun,"y","q","y","u"]},yAxis: {type: value},series: [{itemStyle: {normal: {// barBorde…

华为OD机试真题 Python 实现【数组的中心位置】【2023Q1 100分】,附详细解题思路

目录 一、题目描述二、输入描述三、输出描述四、解题思路五、Python算法源码六、效果展示1、输入2、输出 一、题目描述 给你一个整数数组nums&#xff0c;请计算数组的中心位置&#xff0c;数组的中心位置是数组的一个下标&#xff0c;其左侧所有元素相乘的积等于右侧所有元素…

Fiddler 工具的使用

文章目录 01 Fiddler 工具介绍1. 下载与安装2. Fiddler 工具界面介绍3. Fiddler 工具的工作原理 02 手工调用 HTTP 接口1. 发送HTTP请求2. 查看返回数据包 03 获取 PC 端的网络数据包04 获取手机端的网络数据包05 截包与改包场景一&#xff1a;截断请求数据&#xff0c;然后篡改…

一键安装和卸载docker及docker-compose

代码&#xff1a; #!/bin/bashSYSTEMD_PATH/usr/lib/systemd/system/docker.service DOCKER_FILEdocker-20.10.23.tgz DOCKER_COMPOSE_FILEdocker-compose-plugin-2.15.1-3.el8.x86_64.rpm RED\E[1;31m GREEN\E[1;32m YELOW\E[1;33m SHAN\E[1;31;5m RES\E[0mfunction install_…

项目中期检查会议和进度对接

1.召开中期项目检查会议&#xff0c;与团队成员和博士王锟对接进度。对整体项目表示满意接受&#xff0c;指出重点需要修改提升和进一步开发完善的部分&#xff0c;以增强系统的完整度、功能亮点和界面数量点。具体为 ①注重“highlight”&#xff0c;即布局凸显主题功能&…

spring cloud 之 ribbon

Ribbon概念 Spring Cloud Ribbon 是基于 Netflix Ribbon 实现的一套客户端负载均衡的工具&#xff0c;主要功能是提供客户端负载均衡算法和服务调用。简单的说&#xff0c;就是在配置文件中列出 Load Balance&#xff08; LB&#xff1a;将用户的请求平摊的分配到多个服务上&a…

JavaScript ES5中实现继承

1 对象和函数的原型 2 new、constructor 3 原型链的查找顺序 4 原型链实现的继承 5 借用构造函数继承 6 寄生组合实现继承 function 创建的名称如果开头是大写的&#xff0c;那这个创建的不是函数&#xff0c;是创建了类。 要注意区分&#xff01;本章很多这样子的类。 实…

热烈祝贺! 爱创科技加入中经联溯源技术专业委员会!

6月27日&#xff0c;中国商业股份制企业经济联合会溯源技术专业委员会为北京爱创科技股份有限公司举行入会授牌仪式。授牌仪式由溯源技术专业委员会副秘书长卢要宁同志主持&#xff0c;溯源技术专业委员会主任宁晓鹏及北京爱创科技股份有限公司企业发展规划部总监杜薇出席。 在…

【网络安全带你练爬虫-100练】第3练:遍历获取到的列表中元素

目录 一、前言&#xff1a; 二、分析代码 三、完善代码 一、前言&#xff1a; &#xff08;1&#xff09;本练&#xff0c;我们来完善一下对于数据的处理 &#xff08;2&#xff09;对于同一标签内的内容的遍历爬取 上一段代码的&#xff0c;我们是不是在那个曾用名、高新…

库管理 + 表管理

REVIEW CREATE DROP DATABASE SELECT DATABASE(); SHOW DATABASES ;CREATE DATABASE IF NOT EXISTS GAMEDB ; DROP DATABASE IF EXISTS GAMEDB; CREATE SHOW ALTER TABLE 如何给表头重命名 CREATE TABLE student.studentinfo(name char(10), sex char(10), class char(5…

Redis的介绍和安装教程(配置文件)

1.Redis简单的介绍 redis是一种键值对的NoSql数据库&#xff0c;这里有两个关键字&#xff1a; 键值对 Nosql 其中键值型&#xff0c;是指Redis中存储的数据都是以key.value对的形式多种多样&#xff0c;可以实字符串、数值、甚至json&#xff0c;可以参考HashMap 然后NoSq…

[CSCCTF 2019 Qual] FlaskLight

打开链接&#xff0c;在注释里发现了参数名和请求方式 结合题目flask&#xff0c;检查是否存在SSTI模块注入&#xff1a; ?search{{7*7}} &#xff08;如果对这类题不了解的建议先看我另一篇博客http://t.csdn.cn/4d812&#xff09; 通过回显发现被执行了&#xff0c;存在S…

NacosConfig获取不到配置中心

<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-bootstrap</artifactId><version>3.0.3</version></dependency> 版本说明 alibaba/spring-cloud-alibaba Wiki (github.com) …

【STM32智能车】小车状态

【STM32智能车】小车状态 搭建智能车 65MM轮径小车所选材料安装说明直行测试智能车可能存在的状态 智能车功能丰富&#xff0c;我们从最基础的开始&#xff0c;先来搭建一个智能车吧~。 搭建智能车 我们之前用了一个测试板子去学习调试电机&#xff0c;是时候拼装一个简单的车来…

CSS弹性盒子中弹性子元素文本超出弹性子元素宽或高后导致撑开弹性子元素原本宽或高的解决方法

当使用弹性布局时&#xff0c;设置父容器为弹性容器后&#xff0c;弹性子元素的文本内容超出宽或高后会撑开弹性子元素的原本应该显示的宽高&#xff0c;溢出父容器&#xff0c;只要设置弹性子元素的宽或高&#xff08;由父容器弹性排列方向决定&#xff09;的属性值为0即可解决…

echarts dataZoom.slider is used but not imported.

使用dataZoom时图表报错&#xff1a; resize should not be called during main process. omponent dataZoom.slider is used but not imported. 解决方法&#xff1a; 在使用的页面种单独引入一下dataZoom&#xff1a; import ‘echarts/lib/component/dataZoom’;