EasyExcel复杂Excel导出

news2025/6/4 1:47:55

效果图展示

在这里插入图片描述

1、引入依赖

<!-- easyExcel -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>4.0.2</version>
</dependency>

2、实体类

import com.alibaba.excel.annotation.ExcelProperty;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

/**
 * @Author: Harris
 * @Date: 2025/5/29
 * @Description:
 **/
@Data
public class AbroadDeptCommBo {

    @ApiModelProperty(value = "单位名称")
    @ExcelProperty(value = {"单位名称", "单位名称", "单位名称"})
    private String unitName;

    @ApiModelProperty(value = "线路类型-国际")
    @ExcelProperty(value = {"通信情况", "线路情况", "线路类型"})
    private String lineTypeInternational;

    @ApiModelProperty(value = "速率-国际")
    @ExcelProperty(value = {"通信情况", "线路情况", "速率"})
    private String rateInternational;

    @ApiModelProperty(value = "数量-国际")
    @ExcelProperty(value = {"通信情况", "线路情况", "数量"})
    private String quantityInternational;

    @ApiModelProperty(value = "备份方式-国际")
    @ExcelProperty(value = {"通信情况", "线路情况", "备份方式"})
    private String backupTypeInternational;

    @ApiModelProperty(value = "数量-国际")
    @ExcelProperty(value = {"通信情况", "站数", "站数"})
    private String cmacastReceiveNumInternational;

    @ApiModelProperty(value = "线路类型-国内")
    @ExcelProperty(value = {"通信情况", "线路情况", "线路类型"})
    private String lineTypeDomestic;

    @ApiModelProperty(value = "速率-国内")
    @ExcelProperty(value = {"通信情况", "线路情况", "速率"})
    private String rateDomestic;

    @ApiModelProperty(value = "数量-国内")
    @ExcelProperty(value = {"通信情况", "线路情况", "数量"})
    private String quantityDomestic;

    @ApiModelProperty(value = "备份方式-国内")
    @ExcelProperty(value = {"通信情况", "线路情况", "备份方式"})
    private String backupTypeDomestic;

    @ApiModelProperty(value = "站数量-国内")
    @ExcelProperty(value = {"通信情况", "站数", "站数"})
    private String cmacastReceiveNumDomestic;
}

3、excel 生成

@Test
    public void exportExcel() {
        String fileName = "./data/tmp/out.xlsx";

        WriteCellStyle writeCellStyle = getWriteCellStyle();
        //头策略使用默认
        WriteCellStyle headWriteCellStyle = new WriteCellStyle();

        List<AbroadDeptCommBo> dataList = new ArrayList<>(10);
        dataList.add(initData("中心"));
        dataList.add(initData("中心"));
        dataList.add(initData("中心"));
        dataList.add(initData("集团"));
        dataList.add(initData("集团"));
        dataList.add(initData("集团"));

        try (ExcelWriter build = EasyExcel.write(fileName).build()) {
            WriteSheet sheet0 = EasyExcel.writerSheet(0, "sheet0")
                    .head(AbroadDeptCommBo.class)
                    //设置拦截器或自定义样式
                    .registerWriteHandler(new HorizontalCellStyleStrategy(headWriteCellStyle, writeCellStyle))
                    .registerWriteHandler(new ExcelMergeHandler(3, new int[]{0}))
                    .useDefaultStyle(true)
                    .build();
            build.write(dataList, sheet0);
            WriteSheet sheet1 = EasyExcel.writerSheet(1, "sheet1")
                    .head(AbroadDeptCommBo.class)
                    //设置拦截器或自定义样式
                    .registerWriteHandler(new HorizontalCellStyleStrategy(headWriteCellStyle, writeCellStyle))
                    .registerWriteHandler(new ExcelMergeHandler(3, new int[]{0}))
                    .useDefaultStyle(true)
                    .build();
            build.write(dataList, sheet1);
            build.finish();
        } catch (Exception e) {
            // TODO catch block
        }
    }

    /**
     * 单元格样式设置
     * 
     * @return WriteCellStyle
     */
    private static WriteCellStyle getWriteCellStyle() {
        WriteCellStyle writeCellStyle = new WriteCellStyle();
        writeCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
        writeCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
        writeCellStyle.setBorderLeft(BorderStyle.THIN);
        writeCellStyle.setBorderTop(BorderStyle.THIN);
        writeCellStyle.setBorderRight(BorderStyle.THIN);
        writeCellStyle.setBorderBottom(BorderStyle.THIN);
        //设置 自动换行
        writeCellStyle.setWrapped(true);
        // 字体策略
        WriteFont contentWriteFont = new WriteFont();
        // 字体大小
        contentWriteFont.setFontHeightInPoints((short) 12);
        writeCellStyle.setWriteFont(contentWriteFont);
        return writeCellStyle;
    }

    /**
     * 初始化数据
     * 
     * @param unitName 单位名称
     * @return AbroadDeptCommBo
     */
    private static AbroadDeptCommBo initData(String unitName) {
        AbroadDeptCommBo abroadDeptCommBo = new AbroadDeptCommBo();
        abroadDeptCommBo.setUnitName(unitName);
        abroadDeptCommBo.setLineTypeInternational("1");
        abroadDeptCommBo.setRateInternational("1");
        abroadDeptCommBo.setQuantityInternational("1");
        abroadDeptCommBo.setBackupTypeInternational("1");
        abroadDeptCommBo.setCmacastReceiveNumInternational("1");
        abroadDeptCommBo.setLineTypeDomestic("1");
        abroadDeptCommBo.setRateDomestic("1");
        abroadDeptCommBo.setQuantityDomestic("1");
        abroadDeptCommBo.setBackupTypeDomestic("1");
        abroadDeptCommBo.setCmacastReceiveNumDomestic("1");
        return abroadDeptCommBo;
    }

合并单元格拦截器(纵向合并)


import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.write.handler.CellWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteTableHolder;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.util.CellRangeAddress;

import java.util.List;

/**
 * @Author: Harris
 * @Date: 2025/5/30
 * @Description:
 **/
public class ExcelMergeHandler implements CellWriteHandler {

    // 要合并的列索引数组
    private final int[] mergeColumnIndex;
    // 合并开始的行索引
    private final int mergeRowIndex;

    /**
     * 构造函数
     *
     * @param mergeRowIndex     合并开始的行索引
     * @param mergeColumnIndex  要合并的列索引数组
     */
    public ExcelMergeHandler(int mergeRowIndex, int[] mergeColumnIndex) {
        this.mergeRowIndex = mergeRowIndex;
        this.mergeColumnIndex = mergeColumnIndex;
    }

    @Override
    public void beforeCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Head head, Integer columnIndex, Integer relativeRowIndex, Boolean isHead) {
        // 单元格创建前的处理(这里不需要处理)
    }

    @Override
    public void afterCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
        // 单元格创建后的处理(这里不需要处理)
    }

    @Override
    public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List<WriteCellData<?>> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
        // 当前行索引
        int curRowIndex = cell.getRowIndex();
        // 当前列索引
        int curColIndex = cell.getColumnIndex();

        // 如果当前行大于合并开始行且当前列在需要合并的列中
        if (curRowIndex > mergeRowIndex && isMergeColumn(curColIndex)) {
            // 进行合并操作
            mergeWithPrevRow(writeSheetHolder, cell, curRowIndex, curColIndex);
        }
    }

    /**
     * 检查当前列是否在需要合并的列中
     *
     * @param curColIndex 当前列索引
     * @return 如果是需要合并的列返回true,否则返回false
     */
    private boolean isMergeColumn(int curColIndex) {
        for (int columnIndex : mergeColumnIndex) {
            if (curColIndex == columnIndex) {
                return true;
            }
        }
        return false;
    }

    /**
     * 当前单元格向上合并
     *
     * @param writeSheetHolder 当前工作表持有者
     * @param cell             当前单元格
     * @param curRowIndex      当前行索引
     * @param curColIndex      当前列索引
     */
    private void mergeWithPrevRow(WriteSheetHolder writeSheetHolder, Cell cell, int curRowIndex, int curColIndex) {
        // 获取当前单元格的数据
        Object curData = getCellData(cell);
        // 获取前一个单元格的数据
        Cell preCell = cell.getSheet().getRow(curRowIndex - 1).getCell(curColIndex);
        Object preData = getCellData(preCell);

        // 判断当前单元格和前一个单元格的数据以及主键是否相同
        if (curData.equals(preData) && isSamePrimaryKey(cell, curRowIndex)) {
            // 获取工作表
            Sheet sheet = writeSheetHolder.getSheet();
            // 合并单元格
            mergeCells(sheet, curRowIndex, curColIndex);
        }
    }

    /**
     * 获取单元格的数据
     *
     * @param cell 单元格
     * @return 单元格数据
     */
    private Object getCellData(Cell cell) {
        return cell.getCellType() == CellType.STRING ? cell.getStringCellValue() : cell.getNumericCellValue();
    }

    /**
     * 判断当前单元格和前一个单元格的主键是否相同
     *
     * @param cell         当前单元格
     * @param curRowIndex  当前行索引
     * @return 如果主键相同返回true,否则返回false
     */
    private boolean isSamePrimaryKey(Cell cell, int curRowIndex) {
        String currentPrimaryKey = cell.getRow().getCell(0).getStringCellValue();
        String previousPrimaryKey = cell.getSheet().getRow(curRowIndex - 1).getCell(0).getStringCellValue();
        return currentPrimaryKey.equals(previousPrimaryKey);
    }

    /**
     * 合并单元格
     *
     * @param sheet        工作表
     * @param curRowIndex  当前行索引
     * @param curColIndex  当前列索引
     */
    private void mergeCells(Sheet sheet, int curRowIndex, int curColIndex) {
        // 获取已合并的区域
        List<CellRangeAddress> mergeRegions = sheet.getMergedRegions();
        boolean isMerged = false;

        // 检查前一个单元格是否已经被合并
        for (int i = 0; i < mergeRegions.size() && !isMerged; i++) {
            CellRangeAddress cellRangeAddr = mergeRegions.get(i);
            if (cellRangeAddr.isInRange(curRowIndex - 1, curColIndex)) {
                sheet.removeMergedRegion(i);
                cellRangeAddr.setLastRow(curRowIndex);
                sheet.addMergedRegion(cellRangeAddr);
                isMerged = true;
            }
        }

        // 如果前一个单元格未被合并,则新增合并区域
        if (!isMerged) {
            CellRangeAddress cellRangeAddress = new CellRangeAddress(curRowIndex - 1, curRowIndex, curColIndex, curColIndex);
            sheet.addMergedRegion(cellRangeAddress);
        }
    }
}

最主要的方法其实就是下面这段代码,可以通过这段合并任意单元格

CellRangeAddress cellRangeAddress = new CellRangeAddress(curRowIndex - 1, curRowIndex, curColIndex, curColIndex);
sheet.addMergedRegion(cellRangeAddress);

参考文档:
https://www.cnblogs.com/better-farther-world2099/articles/16106085.html
https://blog.csdn.net/ManGooo0/article/details/128094925

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

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

相关文章

1,QT的编译教程

目录 整体流程: 1,新建project文件 2,编写源代码 3,打开QT的命令行窗口 4,生成工程文件(QT_demo.pro) 5,生成Make file 6,编译工程 7,运行编译好的可执行文件 整体流程: 1,新建project文件 新建文本文件,后缀改为.cpp 2,编写源代码

【笔记】在 MSYS2(MINGW64)中安装 Python 工具链的记录

#工作记录 &#x1f4cc; 安装背景 操作系统&#xff1a;MSYS2 MINGW64当前时间&#xff1a;2025年6月1日Python 版本&#xff1a;3.12&#xff08;默认通过 pacman 安装&#xff09;目标工具链&#xff1a; pipxnumpypipsetuptoolswheel &#x1f6e0;️ 安装过程与结果记录…

Linux 学习-模拟实现【简易版bash】

1、bash本质 在模拟实现前&#xff0c;先得了解 bash 的本质 bash 也是一个进程&#xff0c;并且是不断运行中的进程 证明&#xff1a;常显示的命令输入提示符就是 bash 不断打印输出的结果 输入指令后&#xff0c;bash 会创建子进程&#xff0c;并进行程序替换 证明&#x…

【中国・珠海】2025 物联网与边缘计算国际研讨会(IoTEC2025)盛大来袭!

2025 物联网与边缘计算国际研讨会&#xff08;IoTEC2025&#xff09;盛大来袭&#xff01; 科技浪潮奔涌向前&#xff0c;物联网与边缘计算已成为驱动各行业变革的核心力量。在此背景下&#xff0c;2025 物联网与边缘计算国际研讨会&#xff08;IoTEC2025&#xff09;即将震撼…

中国高分辨率高质量地面CO数据集(2013-2023)

时间分辨率&#xff1a;日空间分辨率&#xff1a;1km - 10km共享方式&#xff1a;开放获取数据大小&#xff1a;9.83 GB数据时间范围&#xff1a;2013-01-01 — 2023-12-31元数据更新时间&#xff1a;2024-08-19 数据集摘要 ChinaHighCO数据集是中国高分辨率高质量近地表空气污…

GO——内存逃逸分析

一、可能导致逃逸的5中情况 package mainimport "fmt"func main() {f1()f2()f3()f4()f5() }type animal interface {run() }type dog struct{}func (d *dog) run() {fmt.Println("狗在跑") }// 指针、map、切片为返回值的会发生内存逃逸 func f1() (*int,…

MinVerse 3D触觉鼠标的技术原理与创新解析

MinVerse3D触觉鼠标通过三维交互和触觉反馈技术&#xff0c;彻底颠覆了传统二维鼠标的操作方式。用户在操作虚拟物体时&#xff0c;可以真实感知表面质感、重量和阻力。这种技术不仅为数字环境注入了深度与临场感&#xff0c;还在3D设计、游戏开发和工程仿真等领域展现了广泛潜…

乾元通渠道商中标青海省自然灾害应急能力提升工程基层防灾项目

近日&#xff0c;乾元通渠道商中标青海省自然灾害应急能力提升工程基层防灾项目&#xff0c;乾元通作为设备厂家&#xff0c;为项目提供通信指挥类装备&#xff08;多链路聚合设备&#xff09;QYT-X1。 青岛乾元通数码科技有限公司作为国家应急产业企业&#xff0c;深耕于数据调…

openssl-aes-ctr使用openmp加速

openssl-aes-ctr使用openmp加速 openssl-aes-ctropenmp omp for openssl-aes-ctr 本文采用openssl-1.1.1w进行开发验证开发&#xff1b;因为aes-ctr加解密模式中&#xff0c;不依赖与上一个模块的加/解密的内容&#xff0c;所以对于aes-ctr加解密模式是比较适合进行并行加速的…

PHP+MySQL开发语言 在线下单订水送水小程序源码及搭建指南

随着互联网技术的不断发展&#xff0c;在线下单订水送水服务为人们所需要。分享一款 PHP 和 MySQL 搭建一个功能完善的在线订水送水小程序源码及搭建教程。这个系统将包含用户端和管理端两部分&#xff0c;用户可以在线下单、查询订单状态&#xff0c;管理员可以处理订单、管理…

计算机网络第1章(上):网络组成与三种交换方式全解析

目录 一、计算机网络的概念二、计算机网络的组成和功能2.1 计算机网络的组成2.2 计算机网络的功能 三、电路交换、报文交换、分组交换3.1 电路交换&#xff08;Circuit Switching&#xff09;3.2 报文交换&#xff08;Message Switching&#xff09;3.3 分组交换&#xff08;Pa…

Android studio进阶开发(七)---做一个完整的登录系统(前后端连接)

我们已经讲过了okhttp和登录系统的使用&#xff0c;我们今天做一个完整的登录系统&#xff0c;后端用springmybatis去做 数据库内容 -- 创建学生信息表 CREATE TABLE student_info (id SERIAL PRIMARY KEY, -- 添加自增主键name VARCHAR(255) NOT NULL,number INT NOT NULL,…

计算机网络第1章(下):网络性能指标与分层模型全面解析

目录 一、计算机网络的性能指标1.1 性能指标1&#xff1a;速率1.2 性能指标2&#xff1a;带宽1.3 性能指标3&#xff1a;吞吐量1.4 性能指标4&#xff1a;时延1.5 性能指标5&#xff1a;时延带宽积1.6 性能指标6&#xff1a;往返时延1.7 性能指标7&#xff1a;信道利用率 二、计…

恶意软件清理工具,让Mac电脑安全更简单

​你的Mac最近是不是开始表演"电子迷惑行为"&#xff1f;浏览器主页突然变成澳门赌场&#xff0c;风扇转得比直升机螺旋桨还猛......恭喜你&#xff01;可能中奖获得"恶意软件大礼包"&#xff01;别慌&#xff0c;今天就教你用恶意软件清理工具化身数字特工…

HackMyVM-Jabita

信息搜集 主机发现 ┌──(kali㉿kali)-[~] └─$ nmap -sn 192.168.43.0/24 Starting Nmap 7.95 ( https://nmap.org ) at 2025-06-01 05:20 EDT Nmap scan report for 192.168.43.1 Host is up (0.020s latency). MAC Address: C6:45:66:05:91:88 (Unknown) Nmap scan repo…

112 Gbps 及以上串行链路的有效链路均衡

通道均衡已成为当今高速串行链路的关键机制。目前有许多均衡方案&#xff0c;例如发射机加重均衡、接收机CTLE&#xff08;连续时间线性均衡器&#xff09;、FFE&#xff08;前馈均衡器&#xff09;、DFE&#xff08;判决反馈均衡器&#xff09;和FEC&#xff08;前向纠错&…

Python-13(永久存储)

创建并打开文件 open(file,mode)函数 该函数用于打开一个文件并返回对应的文件对象。 file参数指定的是文件路径和文件名&#xff0c;如果没有添加路径&#xff0c;那么默认将文件创建在python的主文件夹里面。mode参数指定的是打开的模式&#xff0c;r表示读取&#xff08;…

记录一次session安装应用recyclerview更新数据的bug

首先抛出异常日志&#xff0c;在 先说结论&#xff1a;因为session安装监听是在点击事件里面&#xff0c;所以会保留旧的对象数据 直接上代码&#xff0c;原有的逻辑是点击时执行session安装&#xff0c;并注册监听回调 private fun installApk(position: Int) {val packageIns…

大数据-274 Spark MLib - 基础介绍 机器学习算法 剪枝 后剪枝 ID3 C4.5 CART

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 大模型篇章已经开始&#xff01; 目前已经更新到了第 22 篇&#xff1a;大语言模型 22 - MCP 自动操作 FigmaCursor 自动设计原型 Java篇开…

力扣面试150题--二叉树的锯齿形层序遍历

Day 56 题目描述 思路 锯齿形就是一层是从左向右&#xff0c;一层是从右向左&#xff0c;那么我们可以分析样例&#xff0c;对于第奇数层是从左向右&#xff0c;第偶数层是从右向左&#xff0c;于是可以采取一个计数器&#xff0c;采取链表方式&#xff0c;从左向右就是正常插…