EasyExcel 合并单元格最佳实践:基于注解的自动合并与样式控制

EasyExcel 合并单元格最佳实践:基于注解的自动合并与样式控制

前言

在日常开发中,我们经常需要导出 Excel 报表,而合并单元格是提升报表可读性的常见需求。本文将介绍如何基于 EasyExcel 实现智能的单元格合并功能,通过自定义注解 @ExcelMerge 标记需要合并的字段,并确保合并后的内容完美居中对齐。

核心功能

  1. 注解驱动:通过 @ExcelMerge 注解标记需要合并的字段
  2. 自动合并:相邻行相同值的单元格自动合并
  3. 样式控制:合并后的单元格内容水平和垂直居中
  4. 兼容性:支持 EasyExcel 原生功能(自动列宽、下拉框等)

实现代码

easyexcel 版本

<dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>3.3.4</version></dependency>

1. 定义合并注解

import java.lang.annotation.*;/*** 标记需要合并的 Excel 列*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelMerge {/*** 是否启用合并(默认 true)*/boolean enable() default true;
}

2. Excel 合并工具类

package cn.iocoder.yudao.framework.excel.core.util;import cn.iocoder.yudao.framework.excel.core.handler.SelectSheetWriteHandler;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.converters.longconverter.LongStringConverter;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.write.merge.AbstractMergeStrategy;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;/*** Excel 合并单元格工具类(支持注解驱动)*/
public class ExcelMergeUtils {/*** 导出 Excel 并自动合并标记字段** @param outputStream  响应* @param filename  文件名* @param sheetName Sheet 名称* @param head      表头类* @param data      数据列表* @param <T>       数据类型* @throws IOException 写入异常*/public static <T> void write(OutputStream outputStream, String filename, String sheetName,Class<T> head, List<T> data) throws IOException {// 内容样式:水平 + 垂直居中WriteCellStyle contentStyle = new WriteCellStyle();contentStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);contentStyle.setVerticalAlignment(VerticalAlignment.CENTER);// 注册样式策略HorizontalCellStyleStrategy styleStrategy = new HorizontalCellStyleStrategy(null, contentStyle);// 自动合并策略(基于注解)AbstractMergeStrategy mergeStrategy = new AnnotationBasedMergeStrategy<>(data, head);// 输出 ExcelEasyExcel.write(outputStream, head).autoCloseStream(false).registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) // 自动列宽.registerWriteHandler(new SelectSheetWriteHandler(head))         // 下拉框支持.registerWriteHandler(mergeStrategy)                           // 自动合并.registerWriteHandler(styleStrategy)                           // 居中对齐.registerConverter(new LongStringConverter())                  // Long 转 String.sheet(sheetName).doWrite(data);}public static <T> void write(HttpServletResponse response, String filename, String sheetName,Class<T> head, List<T> data) throws IOException {write(response.getOutputStream(), filename, sheetName, head, data);// 设置响应头response.addHeader("Content-Disposition", "attachment;filename=" +URLEncoder.encode(filename, StandardCharsets.UTF_8.name()));response.setContentType("application/vnd.ms-excel;charset=UTF-8");}/*** 基于注解的合并策略*/private static class AnnotationBasedMergeStrategy<T> extends AbstractMergeStrategy {private final List<T> dataList;private final Class<T> clazz;public AnnotationBasedMergeStrategy(List<T> dataList, Class<T> clazz) {this.dataList = dataList != null ? dataList : new ArrayList<>();this.clazz = clazz;}@Overrideprotected void merge(Sheet sheet, Cell cell, Head head, Integer relativeRowIndex) {if (relativeRowIndex != 0) return; // 只在第一行处理int columnIndex = cell.getColumnIndex();Field field = clazz.getDeclaredFields()[columnIndex];if (field.isAnnotationPresent(ExcelMerge.class)) {mergeColumn(sheet, columnIndex);}}private void mergeColumn(Sheet sheet, int columnIndex) {List<CellRangeAddress> ranges = new ArrayList<>();if (dataList.isEmpty()) return;try {Field field = clazz.getDeclaredFields()[columnIndex];field.setAccessible(true);Object currentValue = field.get(dataList.get(0));int startRow = 1; // 从第2行开始(第1行是标题)for (int i = 1; i < dataList.size(); i++) {Object value = field.get(dataList.get(i));if (!value.equals(currentValue)) {if (startRow < i) {ranges.add(new CellRangeAddress(startRow, i, columnIndex, columnIndex));}currentValue = value;startRow = i + 1;}}// 处理最后一段if (startRow < dataList.size()) {ranges.add(new CellRangeAddress(startRow, dataList.size(), columnIndex, columnIndex));}// 应用合并for (CellRangeAddress range : ranges) {sheet.addMergedRegion(range);}} catch (IllegalAccessException e) {throw new RuntimeException("反射获取字段值失败", e);}}}
}

使用示例

1. 定义实体类

public class UserVO {@ExcelMerge // 此字段相同值会自动合并private String username;@ExcelMerge(enable = false) // 不合并private Integer age;@ExcelMerge // 此字段相同值会自动合并private String department;// 省略构造方法、getter/setter
}

2. 导出 Excel

List<UserVO> users = Arrays.asList(new UserVO("张三", 25, "研发部"),new UserVO("张三", 30, "研发部"), // username 和 department 相同,会自动合并new UserVO("李四", 28, "市场部")
);// HTTP 响应方式
ExcelMergeUtils.write(response, "users.xlsx", "用户列表", UserVO.class, users);// 或者输出流方式
try (OutputStream out = new FileOutputStream("users.xlsx")) {ExcelMergeUtils.write(out, "users.xlsx", "用户列表", UserVO.class, users);
}

技术要点解析

  1. 合并策略实现

    • 继承 AbstractMergeStrategy 实现自定义合并逻辑
    • 通过反射获取标记了 @ExcelMerge 的字段值
    • 计算需要合并的单元格区域(CellRangeAddress
  2. 样式控制

    • 使用 HorizontalCellStyleStrategy 设置内容居中对齐
    • 表头使用默认样式,内容使用自定义样式
  3. 性能优化

    • 只在第一行数据时执行合并操作(relativeRowIndex == 0
    • 按列处理,避免重复计算

常见问题解决

1. 合并区域重叠问题

错误信息:

Cannot add merged region A2:A6 to sheet because it overlaps with an existing merged region

解决方案:

  • 确保每个合并操作只执行一次
  • 可以使用 Set 记录已处理的列,避免重复合并

2. 字段顺序问题

确保实体类字段顺序与 Excel 列顺序一致:

  1. 保持字段声明顺序
  2. 或使用 @ExcelProperty 注解指定顺序

3. 大数据量性能优化

当数据量较大时:

  1. 考虑分批处理
  2. 缓存字段信息,减少反射调用

总结

本文实现的 Excel 合并工具具有以下优势:

  1. 简单易用:通过注解标记即可实现自动合并
  2. 灵活可控:可以单独控制每个字段是否合并
  3. 样式美观:合并后的单元格自动居中对齐
  4. 功能完善:兼容 EasyExcel 的各项特性

通过这种方式,我们可以轻松实现专业级的 Excel 导出功能,提升报表的可读性和美观度。

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

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

相关文章

Unity设置UI显示区域

系列文章目录 untiy工具 文章目录 系列文章目录 👉前言 👉一、效果图 👉二、制作过程(检测中心点位置) 👉2-1、代码实现 👉三、优化为检测整个UI四个角点 👉四、性能优化建议 👉壁纸分享 👉总结 👉前言 思路: 获取屏幕的宽度和高度,定义中间区域的范围…

Qt中用于图像缩放的核⼼⽅法QPixmap::scaled

QPixmap::scaled是Qt中用于图像缩放的核⼼⽅法&#xff0c;其作⽤和⽤法如下&#xff1a;‌一、核心作用‌‌图像尺寸调整‌根据指定尺寸对图像进⾏等⽐例或⾮等⽐例缩放&#xff0c;⽀持放⼤和缩⼩操作。‌保持宽高比‌通过AspectRatioMode参数控制是否保持原始图像的宽⾼⽐。…

SQL Workbench/J:一款免费开源、跨平台的通用SQL查询工具

SQL Workbench/J 是一款基于 Java 开发的免费开源、跨平台的通用 SQL 查询工具。 SQL Workbench/J 主要专注于 SQL 脚本开发和数据导入导出功能&#xff0c;不提供各种数据库管理功能。 功能特性 跨平台&#xff1a;可以在任何安装了 Java 运行时环境的操作系统上运行&#xf…

DOLO 上涨:Berachain 生态爆发的前奏?

在 Berachain 生态逐渐进入公众视野之际&#xff0c;Dolomite&#xff08;简称 Dolomite&#xff0c;代币 DOLO&#xff09;成为链上表现最为突出的明星协议。其代币价格在短短两个月内&#xff0c;从 $0.03 飙升至 $0.3&#xff0c;涨幅接近 10 倍。市场不仅将其视作 Berachai…

吉利汽车与芯鼎微成立联合创新实验室共谱车规级LCoS显示新篇章

2025年8月20日&#xff0c;吉利汽车研究院技术规划中心副主任李莉、光学实验室负责人李金桦博士等一行四人莅临芯鼎微&#xff0c;双方共同为"吉利汽车-芯鼎微联合创新实验室"揭牌&#xff0c;标志着两家企业在车载先进显示技术领域迈入深度协同创新的新阶段。 在这汽…

NPM组件 @angular_devkit/core 等窃取主机敏感信息

【高危】NPM组件 angular_devkit/core 等窃取主机敏感信息 漏洞描述 当用户安装受影响版本的 angular_devkit/core 等NPM组件包时会窃取用户的主机名、用户名、IP地址信息并发送到攻击者可控的服务器地址。 MPS编号MPS-1jf5-s6ix处置建议强烈建议修复发现时间2025-08-14投毒…

docker cuda版安装 dockercuda版安装

目录 1.一键安装docker 测试ok 2.安装cuda支持 通用的应该没问题 安装工具包 配置 runtime&#xff1a; 3.检查 Docker 是否支持 NVIDIA 运行时 1.一键安装docker 测试ok curl -fsSL https://get.docker.com | sh 2.安装cuda支持 通用的应该没问题 也可以搜索安装 cuda版d…

Spring发布订阅模式详解

Spring 的发布订阅模式&#xff08;Publish-Subscribe Pattern&#xff09;是一种基于事件驱动的设计模式&#xff0c;通过 "事件" 作为中间载体实现组件间的解耦。在这种模式中&#xff0c;"发布者"&#xff08;Publisher&#xff09;负责产生事件并发布&…

服务器硬件中的磁盘SSD与HDD性能区别,以及分别适用于什么业务?

SSD&#xff08;固态硬盘&#xff09;和 HDD&#xff08;机械硬盘&#xff09;是服务器中常见的存储设备类型&#xff0c;两者在性能、可靠性、成本等方面存在显著差异。根据这些特性&#xff0c;它们适用于不同的业务需求。以下是详细的对比与应用场景分析&#xff1a;1. SSD …

AI驱动的SEO关键词优化秘籍

内容概要人工智能技术的飞速发展正重塑SEO关键词优化领域&#xff0c;为从业者带来全新机遇与挑战。本文将系统解析AI如何革新关键词策略&#xff0c;覆盖从语义搜索深度解析到长尾词智能挖掘的核心环节。通过工具驱动的内容优化路径&#xff0c;读者将掌握提升流量转化率的关键…

自然语言处理(NLP)技术的发展历史

自然语言处理&#xff08;NLP&#xff09;作为人工智能的重要分支&#xff0c;其发展历程跨越了大半个世纪&#xff0c;从早期的规则式尝试到如今的大模型时代&#xff0c;技术路径不断迭代&#xff0c;核心目标始终是实现人机间的自然语言交互。以下从关键阶段、技术突破和标志…

Swift 解法详解 LeetCode 361:轰炸敌人,用动态规划轻松拿下

文章目录摘要描述题解答案题解代码分析代码解析示例测试及结果时间复杂度空间复杂度总结摘要 “轰炸敌人”这道题名字听起来就很带感&#xff0c;它其实是一个二维网格搜索问题。我们要找到一个能放置炸弹的位置&#xff0c;让炸掉的敌人最多。虽然题目看起来复杂&#xff0c;…

如何高效推进将科技创新成果转化为标准?

2024年10月26日&#xff0c;全国标准信息公共服务平台正式发布了国家标准《科技成果评估规范》&#xff08;GB/T 44731-2024 &#xff09;&#xff0c;并从发布之日起正式实施。这一标准的正式推出&#xff0c;标志着政府在推进科技成果转化、提升科技服务能力方面迈出了重要一…

CMake 快速开始

CMake 快速开始 CMake 安装 编辑环境&#xff1a;VS Code 编译环境&#xff1a;VS Code Remote SSH模式 Ubuntu 24.04 CMake 官⽅源代码下载地址&#xff1a;https://cmake.org/download/ CMake 官⽅英⽂ 档地址&#xff1a;https://cmake.org/cmake/help/latest/index.html S…

STM32F1 EXTI介绍及应用

第三章 EXTI介绍及应用 1. EXTI介绍 EXTI&#xff08;External interrupt/event controller&#xff09;—外部中断/事件控制器&#xff0c;管理了控制器的 20 个中断/事件线。每个中断/事件线都对应有一个边沿检测器&#xff0c;可以实现输入信号的上升沿检测和下降沿的检测。…

Oracle SYS用户无法登录数据库-ORA-12162

错误详情 [Oracleorcl bin]$ ./sqlplus / as sysdba SQL*Plus: Release 11.2.0.4.0 Production on Mon Aug 18 08:12:04 2025 Copyright (c) 1982, 2013, Oracle. All rights reserved. ERROR: ORA-12162: TNS:net service name is incorrectly specifiedOS登录解析 注意&…

【计算机视觉与深度学习实战】06基于光流算法的实时运动检测系统设计与实现——以蚊子轨迹追踪为例(有完整代码)

第一章 引言 计算机视觉作为人工智能领域的重要分支,近年来在目标检测、运动分析、行为识别等方面取得了显著进展。其中,运动检测技术作为视频分析的基础技术之一,在安防监控、交通管理、体感交互、生物行为研究等领域发挥着越来越重要的作用。光流算法作为运动检测的经典方…

国产CANFD芯片技术特性与应用前景综述:以ASM1042系列为例

摘要本文综述了国科安芯推出的国产CANFD芯片ASM1042系列的技术特性与应用前景。ASM1042系列作为一款高性能的CANFD收发器&#xff0c;支持5Mbps的高速通信和高达70V的总线耐压&#xff0c;广泛应用于汽车电子、工业控制和航空航天等领域。文中详细分析了其高速率设计、高耐压设…

偶现型Bug处理方法---用系统方法对抗随机性

在软件开发中&#xff0c;Bug是影响产品质量的核心问题&#xff0c;而偶现型Bug&#xff08;Intermittent Bug&#xff09;因其“时隐时现、难以复现”的特性&#xff0c;成为最头疼的挑战之一。这类Bug不像必现Bug那样有稳定的触发路径&#xff0c;可能在特定环境、特定操作序…

一分钟docker部署onlyoffice 在线预览word pdf excel...

目录 效果 1.执行命令 2.访问 3.测试 3.1执行下面的命令 3.2测试效果 3.3预览效果 3.4转换 效果 1.执行命令 sudo docker run -i -t -d -p 80:80 onlyoffice/documentserver 稍等片刻 2.访问 浏览器打开ip:80即可访问 3.测试 3.1执行下面的命令 sudo docker exec 7…