easyExcle单元格合并

自定义单元格合并策略:

/**
 * 自定义单元格合并策略
 *
 * @create: 2023-11-15 13:41
 **/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Slf4j
public class EasyExcelCustomMergeStrategy implements RowWriteHandler {

    /**
     * 总数
     */
    private Integer totalNum;

    //合并行计数
    private int count;

    //要合并的列 从0开始
    private int[] mergeColumnIndex;

    //已合并单元数
    private int mergedTotalCount = 0;

    public EasyExcelCustomMergeStrategy(int[] mergeColumnIndex) {
        this.mergeColumnIndex = mergeColumnIndex;
    }
    
    // 每写入一行会执行一次afterRowDispose,存在合并行时进行合并方法mergeSameRow,否则什么也不做
    @Override
    public void afterRowDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Integer relativeRowIndex, Boolean isHead) {
        //当前行索引
        int curRowNum = row.getRowNum();

        if (mergeColumnIndex != null && mergeColumnIndex.length > 0 && !isHead) {
            //当前行第一列单元格
            Cell curA1Cell = row.getCell(0);
            Object curA1Data = curA1Cell.getCellTypeEnum() == CellType.STRING ? curA1Cell.getStringCellValue() : curA1Cell.getNumericCellValue();
            //上一行第一列单元格
            Cell preA1Cell = row.getSheet().getRow(curRowNum - 1).getCell(0);
            Object preA1Data = preA1Cell.getCellTypeEnum() == CellType.STRING ? preA1Cell.getStringCellValue() : preA1Cell.getNumericCellValue();

            if (curA1Data.equals(preA1Data)) {
                count++;
            } else {
                if (count > 0) {
                    for (int columnIndex : mergeColumnIndex) {
                        mergeSameRow(writeSheetHolder, curRowNum, count, columnIndex);
                    }
                    //for (int i = 0; i < mergeColumnIndex.length; i++) {
                    //
                    //}
                    count = 0;
                }
            }
            // 最后一行存在合并时,需要单独调用合并,否则不会执行mergeSameRow
            if (curRowNum == totalNum && count > 0) {
                for (int columnIndex : mergeColumnIndex) {
                    mergeSameRow(writeSheetHolder, curRowNum + 1, count, columnIndex);
                }
            }
        }
    }

    /**
     * 按列合并单元格
     *
     * @param writeSheetHolder 写出处理
     * @param curRowIndex      当前行索引,有n行固定行就加n
     * @param needMergeNum     需要合并的行
     * @param curColIndex      需要合并的列
     */
    private void mergeSameRow(WriteSheetHolder writeSheetHolder, int curRowIndex, int needMergeNum, int curColIndex) {
        Sheet sheet = writeSheetHolder.getSheet();
        try {
            Field sh = sheet.getClass().getDeclaredField("_sh");
            sh.setAccessible(true);
            XSSFSheet shSheet = (XSSFSheet) sh.get(sheet);
            CTWorksheet worksheet = shSheet.getCTWorksheet();
            CTMergeCells ctMergeCells = mergedTotalCount > 0 ? worksheet.getMergeCells() : worksheet.addNewMergeCells();
            CTMergeCell ctMergeCell = ctMergeCells.addNewMergeCell();

            CellRangeAddress cellAddresses = new CellRangeAddress(curRowIndex - needMergeNum - 1, curRowIndex - 1, curColIndex, curColIndex);
            ctMergeCell.setRef(cellAddresses.formatAsString());
            mergedTotalCount++;
        } catch (Exception e) {
            log.error("导出出错!", e);
            throw new BusinessException("导出出错!\n");
        }
    }
}

easyExcel提供了两种合并单元格方法:addMergedRegion和addMergedRegionUnsafe,在使用中发现会遇到不同的问题:

如果将需要合并的数据一次性传入afterRowDispose方法进行合并,在合并过程中使用addMergedRegionUnsafe方法合并单元格可能会导致工作簿损坏,而使用addMergedRegion会进行单元格是否重复合并的校验,会直接抛出异常。

使用addMergedRegionUnsafe方法合并单元格导致的问题:

使用addMergedRegion进行的校验:

/**
 * adds a merged region of cells (hence those cells form one)
 *
 * @param region (rowfrom/colfrom-rowto/colto) to merge
 * @param validate whether to validate merged region
 * @return index of this region
 * @throws IllegalArgumentException if region contains fewer than 2 cells
 * @throws IllegalStateException if region intersects with an existing merged region
 * or multi-cell array formula on this sheet
 */
private int addMergedRegion(CellRangeAddress region, boolean validate) {
    if (region.getNumberOfCells() < 2) {
        // 区域包含少于 2 个单元格
        throw new IllegalArgumentException("Merged region " + region.formatAsString() + " must contain 2 or more cells");
    }
    region.validate(SpreadsheetVersion.EXCEL97);

    if (validate) {
        // throw IllegalStateException if the argument CellRangeAddress intersects with
        // a multi-cell array formula defined in this sheet
        // 此工作表上的多单元格数组公式
        validateArrayFormulas(region);
    
        // Throw IllegalStateException if the argument CellRangeAddress intersects with
        // a merged region already in this sheet
        // 区域与现有合并区域相交
        validateMergedRegions(region);
    }

    return _sheet.addMergedRegion(region.getFirstRow(),
            region.getFirstColumn(),
            region.getLastRow(),
            region.getLastColumn());
}

导致错误产生的代码:

public class EasyExcelCustomMergeStrategy implements RowWriteHandler {

    //合并坐标集合
    private List<CellRangeAddress> cellRangeAddress;
    //从哪行开始
    private int beginRow;
    //合并行数
    private int mergeRows;

    public PiFillMergeStrategy(List<CellRangeAddress> cellRangeAddress, int beginRow, int mergeRows) {
        this.cellRangeAddress = cellRangeAddress;
        this.beginRow = beginRow;
        this.mergeRows = mergeRows;
    }

    @Override
    public void afterRowDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Integer relativeRowIndex, Boolean isHead) {
        if (CollectionUtil.isNotEmpty(cellRangeAddress)) {
            if (row.getRowNum() >= beginRow && row.getRowNum() <= beginRow + mergeRows) {
                for (CellRangeAddress item : cellRangeAddress) {
                    writeSheetHolder.getSheet().addMergedRegionUnsafe(item);
                }
            }
        }
    }

}

因此合并时循环每一条数据判断是否与上一条数据是否相同,相同时再逐条进行合并,写法使用XSSFSheet写法或使用easyExcel自带的两种合并方法:

private void mergeSameRow(WriteSheetHolder writeSheetHolder, int curRowIndex, int needMergeNum, int curColIndex) {
    Sheet sheet = writeSheetHolder.getSheet();
    CellRangeAddress cellAddresses = new CellRangeAddress(curRowIndex - needMergeNum - 1, curRowIndex - 1, curColIndex, curColIndex);
    sheet.addMergedRegionUnsafe(cellAddresses);
    // 或使用
    // sheet.addMergedRegion(cellAddresses);
}

合并策略的使用:

public void download(HttpServletResponse response) {
    /**
    *  此处为业务数据
    *
    *
    *
    */
    String subject = "XXX";
    String fileName = subject.concat(".xlsx");
    //定义多级表头
    List<List<String>> headList = new ArrayList<>();
    //定义数据体
    List<List<Object>> dataList = new ArrayList<>();
    this.getHeadAndData(headList, dataList);

    // 定义合并单元格 (根据业务需求自定义)
    List<Integer> mergeColumnIndex = new ArrayList<>();
    int mergeCol = headList.size() - offerVOList.size() - 3;
    for (int i = 0; i < mergeCol; i++) {
        mergeColumnIndex.add(i);
    }
    for (int j = mergeCol + 3; j < headList.size(); j++) {
        mergeColumnIndex.add(j);
    }
    // 生成自定义策略
    EasyExcelCustomMergeStrategy customMergeStrategy = new EasyExcelCustomMergeStrategy(mergeColumnIndex.stream().filter(Objects::nonNull).mapToInt(i->i).toArray());
    customMergeStrategy.setTotalNum(dataList.size());

    try {
        response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
        EasyExcel.write(response.getOutputStream())
                .excelType(ExcelTypeEnum.XLSX)
                .head(headList)
                .sheet(subject)
                .registerWriteHandler(customMergeStrategy)//调用策略
                .doWrite(dataList);
    } catch (Exception e) {
        throw new Exception(String.format("生成文件(%s)失败", fileName), e);
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

填充每个节点的下一个右侧节点指针

题目链接 填充每个节点的下一个右侧节点指针 题目描述 注意点 给定一个 完美二叉树 解答思路 广度优先遍历一层层的遍历二叉树&#xff0c;将每一层节点的next指针都指向右侧节点 代码 class Solution {public Node connect(Node root) {if (root null) {return null;}…

[nlp] 损失缩放(Loss Scaling)loss sacle

在深度学习中,由于浮点数的精度限制,当模型参数非常大时,会出现数值溢出的问题,这可能会导致模型训练不稳定。为了解决这个问题,损失缩放(Loss Scaling)技术被引入,它通过缩放损失值来解决这个问题。 在深度学习中,损失缩放技术通常是通过将梯度进行缩放来实现的。具…

【ES6标准入门】JavaScript中的模块Module语法的使用细节:export命令和imprt命令详细使用,超级详细!!!

&#x1f601; 作者简介&#xff1a;一名大四的学生&#xff0c;致力学习前端开发技术 ⭐️个人主页&#xff1a;夜宵饽饽的主页 ❔ 系列专栏&#xff1a;JavaScript进阶指南 &#x1f450;学习格言&#xff1a;成功不是终点&#xff0c;失败也并非末日&#xff0c;最重要的是继…

Google codelab WebGPU入门教程源码<5> - 使用Storage类型对象给着色器传数据(源码)

对应的教程文章: https://codelabs.developers.google.com/your-first-webgpu-app?hlzh-cn#5 对应的源码执行效果: 对应的教程源码: 此处源码和教程本身提供的部分代码可能存在一点差异。运行的时候&#xff0c;点击画面可以切换效果。 class Color4 {r: number;g: numb…

Java面向对象(高级)-- static关键字的使用

文章目录 一、static关键字&#xff08;1&#xff09;类属性、类方法的设计思想&#xff08;2&#xff09; static关键字的说明&#xff08;3&#xff09;static修饰属性1. 复习变量的分类2. 静态变量2.1 语法格式2.2 静态变量的特点2.3 举例2.3.1 举例12.3.2 举例22.3.3 举例3…

linux套接字-Socket

1.概念 局域网和广域网 局域网&#xff1a;局域网将一定区域内的各种计算机、外部设备和数据库连接起来形成计算机通信的私有网络。广域网&#xff1a;又称广域网、外网、公网。是连接不同地区局域网或城域网计算机通信的远程公共网络。IPInternet Protocol&#xff09;&#…

无需云盘,不限流量实现Zotero跨平台同步:内网穿透+私有WebDAV服务器

&#x1f525;博客主页&#xff1a; 小羊失眠啦. &#x1f3a5;系列专栏&#xff1a;《C语言》 《数据结构》 《Linux》《Cpolar》 ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 无需云盘&#xff0c;不限流量实现Zotero跨平台同步&#xff1a;内网穿透私有WebDAV服务器 文章目…

系列八、堆(Heap)

一、概述 一个JVM实例只存在一个堆内存&#xff0c;堆内存的大小是可以手动调节的。类加载器读取了类文件后&#xff0c;需要把类、方法、常变量放到堆内存中&#xff0c;保存所有引用类型的真实信息&#xff0c;以方便执行器执行&#xff0c;堆内存分为三个部分&#xff0c;即…

高压开关柜实现无线测温监控关键点在哪里?

近年来&#xff0c;电力系统已发生多起因设备过热而发生火灾和大面积停电事故。据统计分析&#xff0c;我国每年发生的电力事故&#xff0c;有40&#xff05;是由高压电气设备过热所致&#xff1b;而在采用高压开关柜和电力电缆的供电系统中&#xff0c;有70&#xff05;以上的…

36、Flink 的 Formats 之Parquet 和 Orc Format

Flink 系列文章 1、Flink 部署、概念介绍、source、transformation、sink使用示例、四大基石介绍和示例等系列综合文章链接 13、Flink 的table api与sql的基本概念、通用api介绍及入门示例 14、Flink 的table api与sql之数据类型: 内置数据类型以及它们的属性 15、Flink 的ta…

教育案例分享 | 安全狗云安全体系为高校提升立体化纵深防御能力

一、客户情况 某高校有服务器500台&#xff0c;对外站点200个&#xff0c;核心交换流量20G。 二、客户痛点 校园网系统分类较多&#xff0c;并且每类网站中安全级重要程度又各不相同&#xff0c;同时有多个网络出口(如&#xff1a;教育网、电信网、移动网等)&#xff0c;二级学…

你不懂API接口是什么?怎么和程序员做朋友

说到开发平台就一定离不开接口&#xff0c;作为PM&#xff0c;我们不需要对接口了解的特别细。只需要知道接口是什么&#xff0c;有什么用&#xff0c;有哪些要素就行。 1. 接口是什么 (1) 硬件接口 生活中我们经常会接触接口&#xff0c;最常见的就是HDMI接口和USB接口&…

软件测试/测试开发丨人工智能产品质量保障:挑战与创新

点此领取人工智能课程 人工智能产品的质量保障与测试是当前软件开发领域最具挑战性的任务之一。随着人工智能技术的迅猛发展&#xff0c;产品日益复杂&#xff0c;传统测试方法逐渐显得力不从心。在这个背景下&#xff0c;我们需要创新性地思考并采用新的策略&#xff0c;以确…

使用 Java 枚举和自定义数据类型

介绍 在 Java 编程领域&#xff0c;理解并有效利用枚举和自定义数据类型对于编写健壮、可维护且高效的代码至关重要。这篇文章旨在深入研究 Java 枚举和自定义数据类型的概念&#xff0c;提供见解和示例&#xff0c;以增强您的编码技能和知识。 理解 Java 中的枚举 枚举是枚…

十秒钟学会Mac系统和Linux之间的文件传输

前言 在我们的mac系统上&#xff0c;大家应该要先学会用我们的终端远程连接Linux的虚拟机或者云服务器&#xff0c;教程在这篇博客&#xff1a;http://t.csdnimg.cn/KQzgc 大家如果想安装iterm2和on-my-zsh&#xff08;非常推荐&#xff0c;很好用&#xff09;的话&#xff0c;…

CentOS中安装常用环境

一、CentOS安装 redis ①&#xff1a;更新yum sudo yum update②&#xff1a;安装 EPEL 存储库 Redis 通常位于 EPEL 存储库中。运行以下命令安装 EPEL 存储库 sudo yum install epel-release③&#xff1a;安装 Redis sudo yum install redis④&#xff1a;启动 Redis 服…

java 批量更改

直接上代码 void batchUpdateSpecificationId(Param("infos") List<GoodsInfo> infos);<update id"batchUpdateSpecificationId">update goods_infoset specification_id <foreach collection"infos" item"info" open&…

打开PDF文件之后不能编辑,有哪些原因?

打开PDF文件之后发现没有办法编辑PDF文件&#xff0c;都有哪些原因呢&#xff1f; 首先我们可以考虑一下&#xff0c;PDF文件中的内容是否是图片&#xff0c;如果确认是图片文件&#xff0c;那么我们想要编辑&#xff0c;就可以先使用PDF编辑器中的OCR扫描功能&#xff0c;将图…

利用jquery对HTML中的名字进行替代

想法&#xff1a;将网页中经常要修改的名字放在一个以jquery编写的js文件中&#xff0c;如果需要修改名字&#xff0c;直接修改js文件中的名字即可。 新建name_07.html文件&#xff0c;写入下面的代码&#xff1a; <!DOCTYPE html> <html> <head><meta …

如何选择一款车规级电源开关/驱动器芯片?NCV8405ASTT3G自保护低压侧驱动器

关于车规级芯片&#xff08;Automotive Grade Chip&#xff09;&#xff0c;车规级芯片是专门用于汽车行业的芯片&#xff0c;具有高可靠性、高稳定性和低功耗等特点&#xff0c;以满足汽车电子系统的严格要求。这些芯片通常用于车载电子控制单元&#xff08;ECU&#xff09;和…