easyExcel 单元格合并

news2024/9/20 22:56:52

需求

现在有一张员工表,需要将员工信息导出为excel,同一个部门放在一起,同一个工资段放在一起。

case

在这里插入图片描述

员工表

package com.tx.test.testeasyexcel.excel;

import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.alibaba.excel.annotation.write.style.ContentRowHeight;
import com.alibaba.excel.annotation.write.style.HeadRowHeight;
import com.tx.test.testeasyexcel.excel.converter.LocalDateTimeConverter;
import lombok.Builder;
import lombok.Data;

import java.io.Serializable;
import java.time.LocalDateTime;

/**
 * @description: 用户excel
 * @author: tx
 * @date: 2024/8/27 14:38
 * @version: 1.0
 */
@Data
@Builder
@ColumnWidth(25)
@HeadRowHeight(20)
@ContentRowHeight(18)
public class UserExcel implements Serializable {

    private static final long serialVersionUID = -5905730761779621234L;
    @ExcelProperty(value = {"用户信息", "部门"})
    private String dept;

    @ExcelProperty(value = {"用户信息", "部门描述"})
    private String deptDescription;

    @ExcelProperty(value = {"用户信息", "工资范围"})
    private String salaryRange;

    @ExcelProperty(value = {"用户信息", "工资"})
    private Double salary;

    @ExcelProperty(value = {"用户信息", "id"})
    private Long id;

    @ExcelProperty(value = {"用户信息", "姓名"})
    private String name;

    @ExcelProperty(value = {"用户信息", "生日"},converter = LocalDateTimeConverter.class)
    private LocalDateTime birthday;
}

处理

需要实现 easyExcel 的 CellWriteHandler 接口的 afterCellDispose 方法。
根据自定义的合并条件 添加或修改 Sheet 单元格范围地址信息(MergedRegions)

  • 我们先定义一个接口,用于自定义合并条件。接口有2个参数,一个是当前行,一个是上一行;返回结果是一个boolean值,true表示这两行的指定列需要合并需要,false表示不需要合并。
package com.tx.test.testeasyexcel.excel.handler;

import org.apache.poi.ss.usermodel.Row;

/**
 * @description: 合并条件接口
 * @author: tx
 * @date: 2024/8/27 16:49
 * @version: 1.0
 */
public interface MergeCondition {
    /**
     * 判断是否需要合并
     * @param cur 当前行
     * @param pre 前一行
     * @return 是否需要合并
     */
    boolean merged(Row cur, Row pre);
}

  • 实现easyexcel的CellWriteHandler 接口,实现其中的afterCellDispose 方法。

为了方便使用以及自定义条件合并,该类定义了4个成员变量,在使用该类进行处理时需要通过构造函数传入这4个变量。4个变量分别是:
private int optLastColumnIdx;// 合并列的条件判断中需要使用到的最后一列,避免在进行判断时无法获取到该列的数据
private int[] mergeColumns; // 需要合并的列的下标数组
private MergeCondition mc; // 合并条件
private int startIdx; // 数据的起始行
大致流程如下

Created with Raphaël 2.3.0 开始 获取当前单元格的坐标 rowIndex 和 columnIndex rowIndex <= startIdx 或者 optLastColumnIdx != columnIndex boolean merged 合并 no yes

值得注意的是单元格合并如果合并的上一行已经被合并了,需要先移除上一行单元格所在的合并范围地址信息,然后最后一行加1再添加进去。

package com.tx.test.testeasyexcel.excel.handler;

import com.alibaba.excel.metadata.CellData;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.write.handler.CellWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteTableHolder;
import lombok.extern.slf4j.Slf4j;
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.util.List;

/**
 * @description: 自定义单元格合并处理
 * @author: tx
 * @date: 2024/8/27 15:04
 * @version: 1.0
 */
@Slf4j
public class customCellWriteHandler implements CellWriteHandler {
    private int optLastColumnIdx;
    // 需要合并的列
    private int[] mergeColumns;
    // 合并条件
    private MergeCondition mc;
    // 数据起始行
    private int startIdx;

    /**
     * @param optLastColumnIdx 操作需要使用的最后一列,
     *                         主要用于将合并操作定位到这一列,避免一些使用的到数据还未被填充到excel中,导致无法正常判断。
     * @param startIdx         数据的起始行,从0开始计数
     * @param mergeColumns     需要合并的列
     * @param mc               合并条件
     */
    public customCellWriteHandler(int optLastColumnIdx, int startIdx, int[] mergeColumns, MergeCondition mc) {
        this.optLastColumnIdx = optLastColumnIdx;
        this.startIdx = startIdx;
        this.mergeColumns = mergeColumns;
        this.mc = mc;
    }

    @Override
    public void beforeCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Head head, Integer integer, Integer integer1, Boolean aBoolean) {

    }

    @Override
    public void afterCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Cell cell, Head head, Integer integer, Boolean aBoolean) {

    }

    @Override
    public void afterCellDataConverted(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, CellData cellData, Cell cell, Head head, Integer integer, Boolean aBoolean) {

    }

    @Override
    public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List<CellData> list, Cell cell, Head head, Integer integer, Boolean aBoolean) {
        // 获取行号
        int rowIndex = cell.getRowIndex();
        // 获取列号
        int columnIndex = cell.getColumnIndex();
        // 行号 <= 数据所在的行,不做处理,直接返回,行号为数据行的第二行时才继续。 列号不等于需要使用的最后一列,直接返回
        if (rowIndex <= startIdx || optLastColumnIdx != columnIndex) return;
        // 获取处理结果
        boolean merged = mc.merged(cell.getSheet().getRow(rowIndex), cell.getSheet().getRow(rowIndex - 1));
        // 需要合并
        if (merged) {
            // 遍历需要合并的列
            for (int mergeColumn : mergeColumns) {
                // 标志位
                boolean isMerged = false;
                // 获取所有合并配置
                Sheet sheet = writeSheetHolder.getSheet();
                List<CellRangeAddress> mergeRegions = sheet.getMergedRegions();
                // 遍历合并配置
                for (int i = 0; i < mergeRegions.size(); i++) {
                    CellRangeAddress cellRangeAddr = mergeRegions.get(i);
                    // 上一行的单元格在配置中,移除这个配置,修改末尾行下标后重新加入一下。
                    if (cellRangeAddr.isInRange(rowIndex - 1, mergeColumn)) {
                        sheet.removeMergedRegion(i);
                        cellRangeAddr.setLastRow(rowIndex);
                        sheet.addMergedRegion(cellRangeAddr);
                        // 修改标志位,表示在已经存在的合并配置中存在当前需要合并的信息。
                        isMerged = true;
                        break;
                    }
                }
                // 新增合并单元
                if (!isMerged) {
                    CellRangeAddress cellRangeAddress = new CellRangeAddress(rowIndex - 1, rowIndex, mergeColumn,
                            mergeColumn);
                    sheet.addMergedRegion(cellRangeAddress);
                }
            }
        }
    }
}

简单测试

  • 构造数据

这里设置了四个部门,每条数据随机选择。
2个工资范围段,分别是5000-10000,10000-15000。
名字是tx + 循环增加 i 。
数据创建完成后需要根据部门 和 工资范围排序。

        // 构造部门
        String dept[] = {"部门1", "部门2", "部门3", "bumen4"};
        // 工资范围
        int salaryRange[] = {5000, 10000, 15000};
        // 构造list
        List<UserExcel> excel = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            Instant instant = LocalDateTime.now().minusYears(Math.round(Math.random() * 20)).toInstant(ZoneOffset.ofHours(8));
            String randomDept = dept[(int) (Math.random() * 4)];
            String randomDeptDescription = randomDept + "的描述信息";
            Double salary = 5000 + BigDecimal.valueOf(Math.random() * 10000).setScale(2, BigDecimal.ROUND_UP).doubleValue();
            String salaryRangeString = "未知";
            for (int i1 = 0; i1 < salaryRange.length; i1++) {
                int end = salaryRange[i1];
                if (salary < end) {
                    salaryRangeString = salaryRange[i1 - 1] + "-" + end;
                    break;
                }
            }
            UserExcel user = UserExcel.builder().dept(randomDept).deptDescription(randomDeptDescription).id((long) i).name("tx-" + i).birthday(LocalDateTime.ofInstant(instant, ZoneId.systemDefault())).salary(salary).salaryRange(salaryRangeString).build();
            excel.add(user);
        }
        // 按照部门、工资范围排序
        excel.sort((one, two) -> {
            int i = one.getDept().compareTo(two.getDept());
            if (i == 0) {
                return one.getSalaryRange().compareTo(two.getSalaryRange());
            }
            return i;
        });
  • 设置文件保存路径
        // 文件保存路径
        String baseDir = System.getProperty("user.dir");
        String fileName = Paths.get(baseDir, "testExcel.xlsx").toString();
  • 合并单元格的excel导出
        EasyExcel.write(fileName, UserExcel.class).registerWriteHandler(new customCellWriteHandler(2, 2, new int[]{
                // 需要合并部门列、部门描述列、工资范围列
                0, 1, 2}, ((cur, pre) -> {
            // 部门名称相同 && 工资范围相同才需要合并
            String curDept = cur.getCell(0).getStringCellValue();
            String preDept = pre.getCell(0).getStringCellValue();
            String curSalaryRange = cur.getCell(2).getStringCellValue();
            String preSalaryRange = pre.getCell(2).getStringCellValue();
            return curDept.equals(preDept) && curSalaryRange.equals(preSalaryRange) ? true : false;
        }))).sheet("用户信息").doWrite(excel);

代码地址

https://gitee.com/tian_xiong/test-easy-excel

参考

https://easyexcel.opensource.alibaba.com/docs/current/quickstart/write#%E5%90%88%E5%B9%B6%E5%8D%95%E5%85%83%E6%A0%BC
https://sensationg.github.io/blog/%E4%BD%BF%E7%94%A8EasyExcel%E8%87%AA%E5%AE%9A%E4%B9%89%E5%AF%BC%E5%87%BA/

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

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

相关文章

人工智能开发实战TensorFlow机器学习框架解析

内容导读 TensorFlow概述 TensorFlow环境搭建 TensorFlow计算机加速 一、TensorFlow概述 1、TensorFlow简介 ‌‌TensorFlow是由‌Google开发的用于‌机器学习和人工智能的开源软件库&#xff0c;特别适用于‌深度神经网络的训练和推理。‌ 它是一个基于‌数据流图的符号…

08 - debugfs

---- 整理自 王利涛老师 课程 实验环境&#xff1a;宅学部落 www.zhaixue.cc 文章目录 0. 什么是 debugfs1. debugfs 配置编译和注册运行2. 第一个 debugfs 编程示例3. 通过 debugfs 导出整型数据4. 通过 debugfs 导出 16 进制数据5. 通过 debugfs 到处数组6. 通过 debugfs 导出…

【SQL】换座位

目录 题目 分析 代码 题目 表: Seat ---------------------- | Column Name | Type | ---------------------- | id | int | | student | varchar | ---------------------- id 是该表的主键&#xff08;唯一值&#xff09;列。 该表的每一行都表示学…

ComfyUI SDXL Prompt Styler 简介

SDXL Prompt Styler 来自于 comfyui-art-venture 节点 style 已经更新 旧版本的 sai-line art 变更为 line art log_prompt 已经更新 旧版本的 false 变更为 Yes 或 No style_name 已经更新 旧版本的 true &#xff08;不再适用&#xff09;&#xff08;可以尝试对应style中…

【学习笔记 及 课后题练习】 陈强-机器学习-Python-Ch12 随机森林(Random Forest)

系列文章目录 监督学习&#xff1a;参数方法 【学习笔记】 陈强-机器学习-Python-Ch4 线性回归 【学习笔记】 陈强-机器学习-Python-Ch5 逻辑回归 【课后题练习】 陈强-机器学习-Python-Ch5 逻辑回归&#xff08;SAheart.csv&#xff09; 【学习笔记】 陈强-机器学习-Python-…

dht11 + Sc32440驱动

一、DHT11概述 1、简介 DHT11数字温湿度传感器是一款含有已校准数字信号输出的温湿度复合传感器&#xff0c;它应用专用的数字模块采集技术和温湿度传感技术&#xff0c;确保产品具有极高的可靠性和卓越的长期稳定性。传感器包括一个电阻式感湿元件和一个NTC测温元件&#xff…

【专题】2024年中国AI人工智能基础数据服务研究报告合集PDF分享(附原数据表)

原文链接&#xff1a;https://tecdat.cn/?p37516 随着人工智能技术的迅猛发展&#xff0c;AI 基础数据服务行业迎来了前所未有的发展机遇。报告合集显示&#xff0c;2023 年中国 AI 基础数据服务市场规模达到 45 亿元&#xff0c;且未来五年复合增长率有望达到 30.4%。多模态大…

如何在不同设备上检查IP 地址?

IP 地址&#xff08;Internet 协议地址&#xff09;是网络上设备的唯一标识符。了解如何查找 IP 地址对于解决网络问题、设置网络设备和维护网络安全非常重要。本文将详细介绍如何在不同设备上检查 IP 地址&#xff0c;包括 Windows 计算机、Mac 计算机、智能手机&#xff08;A…

【Material-UI】Rating组件:如何使用Basic Rating实现多种评分方式

文章目录 一、Rating 组件概述1. 组件介绍2. Basic rating 的核心功能 二、Basic rating 的详细使用方法1. 受控组件&#xff08;Controlled&#xff09;2. 只读模式&#xff08;Read-only&#xff09;3. 禁用状态&#xff08;Disabled&#xff09;4. 无评分状态&#xff08;No…

嵌入式笔试准备

文件组合 将传输文件切分成多个部分&#xff0c;按照原排列顺序&#xff0c;每部分文件编号为一个正整数。 class Solution { public:vector<vector<int>> fileCombination(int target) {vector<vector<int>> res;int sum 0;for(int i1; i<targe…

VS2017+QT不能正常添加资源文件

is not in a subdirectory of the resource file.You now have the option to copy this file to a valid location. . 该错误原因是项目路径英文导致的&#xff0c;换成全中文路径就没问题了 具体步骤&#xff1a; 双击qrc文件&#xff0c;先添加前缀&#xff0c;然后添加图片…

机器学习之监督学习(一)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 机器学习之监督学习&#xff08;一&#xff09; 1.监督学习定义2.监督学习分类2.1回归 regression2.2 分类 classification 3.线性回归 linear regression3.1 单特征线性回归…

Android 突破边界:解密google Partner机制获取Resource

在 Android 应用开发中&#xff0c;除了可以查找系统中的特定 APK 并获取其信息外&#xff0c;还可以通过 Partner 机制获取 Partner APK 的资源文件&#xff0c;同时这种机制也是一种跨进程的通信方式。本文将进一步探讨这些内容。 1.Partner apk注册特定的action广播 /** M…

Windows上编译GTest并执行测试用例

目录 0 背景1 环境2 生成GTest动态库2.1 下载最新GTest库2.2 编译GTest2.2.1 cmake gtest2.2.1.1 遇到问题&#xff1a;target参数不对2.2.1.2 遇到问题&#xff1a;xxx thread 编译报错 2.2.2 用VS2015编译依赖库 3 依赖库部署3.1 遇到问题 4 编写测试用例5 效果展示 0 背景 …

python中列表的复制亦有区别

python中 对列表的复制可以直接新变量名字等于原有列表名字&#xff0c;或者 创建新的列表从原有列表一个个复制进来。他们的区别在于下面 两个图中&#xff1a;

Codeforces 1304C - Air Conditioner(1500)

Air Conditioner 题面翻译 一个餐馆中有个空调&#xff0c;每分钟可以选择上调 1 1 1 个单位的温度或下调 1 1 1 个单位的温度&#xff0c;当然你也可以选择不变&#xff0c;初始的温度为 m m m 。 有 n n n 个食客&#xff0c;每个食客会在 t i t_i ti​ 时间点到达&am…

【maven】阿里云仓库配置

阿里云公共仓库的配置看起来有多种类型的仓库: 配置指南 我的maven是idea 自带的:D:\Program Files\JetBrains\IntelliJ IDEA 2022.3.1\plugins\maven\lib\maven3\</

突破编程 C++ 设计模式(组合模式)详尽攻略

在软件开发中&#xff0c;设计模式为程序员提供了解决特定问题的最佳实践。设计模式不仅提高了代码的可复用性和可维护性&#xff0c;还能帮助团队更好地进行协作。在这篇文章中&#xff0c;我们将深入探讨组合模式——一种结构型设计模式。 组合模式允许你将对象组合成树形结…

哪里能免费申请IP SSL证书

一、选择可信赖的证书颁发机构 首先&#xff0c;需要选择一个可信赖的证书颁发机构&#xff08;CA&#xff09;。知名的CA机构如JoySSL、Symantec、GlobalSign等提供IP SSL证书服务。这些机构能够提供符合国际标准的SSL证书&#xff0c;确保数据传输的安全性和服务器的身份验证…

Docker 安装 SqlServer

摘要&#xff1a;我们工作当中经常需要拉取多个数据库实例出来做集群&#xff0c;做测试也好&#xff0c;通过 Docker 拉取 SqlServer 镜像&#xff0c;再通过镜像运行多个容器&#xff0c;几分钟就可以创建多个实例&#xff0c;效率是相当的高。 1. docker 拉取镜像 注意&am…