easyExcle单元格合并

news2025/1/18 3:17:24

自定义单元格合并策略:

/**
 * 自定义单元格合并策略
 *
 * @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);
    }
}

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

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

相关文章

【ARM Trace32(劳特巴赫) 使用介绍 5 -- Trace32 scan dump 详细介绍】

文章目录 1.1 JTAG 测试逻辑架构1.2 D型扫描触发器1.2.1 全扫描介绍1.3 IR 寄存器1.4 TDR(Test data registers)1.4.1 TDR 的实现1.4.1.1 Bypass Register1.4.1.2 Boundary-scan register1.5 Scan Dump1.5.1 soft fusion1.1 JTAG 测试逻辑架构 图 1-1 片上测试逻辑概念图 如前面…

我这些年对于自动化测试的理解

&#x1f4e2;专注于分享软件测试干货内容&#xff0c;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01;&#x1f4e2;交流讨论&#xff1a;欢迎加入我们一起学习&#xff01;&#x1f4e2;资源分享&#xff1a;耗时200小时精选的「软件测试」资…

DTW(Dynamic Time Warping)算法学习应用实践与效率对比分析

DTW&#xff08;Dynamic Time Warping&#xff09;算法是一种用于度量两个时间序列之间的相似性的方法。它的构建原理如下&#xff1a; 基本思想&#xff1a;DTW算法通过计算两个时间序列之间的最小距离&#xff0c;来度量它们的相似性。与欧氏距离等传统距离度量方法不同&…

MariaDB安装配置、使用、授权、增删改查以及数据库备份与恢复

目录 1 MariaDB安装 1.1 MariaDB源配置 1.2 清空缓存 1.3 安装MariaDB 2 MariaDB的基本配置 2.1 启动MariaDB 2.2 MariaDB进程查看 2.3 MariaDB数据库初始化 2.3.1 数据库初始化 2.3.2 初始化测试登录 3 MariaDB的使用 3.1 查看数据库 3.2 修改密码 3.3 创建数据库test 3…

conda环境下Tesseract:Failed loading language ‘eng‘问题解决

1 问题描述 使用Tesseract进行ocr文字识别&#xff0c;运行识别代码&#xff0c;报错如下&#xff1a; C:\Users\lishu\anaconda3\envs\pt2\python.exe D:/code/ptcontainer/opencv/car_reg.py Traceback (most recent call last): File "D:\code\ptcontainer\opencv\…

C#WPF文本转语音实例

本文介绍C#WPF文本转语音实例 实现方法:使用类库(SpeechSynthesizer )实现的。 一、首先是安装程序包。 二、创建项目 需要添加引用using System.Speech.Synthesis; UI界面 <Windowx:Class="TextToSpeechDemo.MainWindow"xmlns="http://schemas.micr…

著名的《NP问题》是个啥概念?

一、说明 关于复杂问题&#xff0c;始终是计算机科学挡在路前的一块巨石。所谓一个问题有解&#xff0c;但需要秒完成&#xff0c;这相当于说&#xff0c;此类问题无解。还有一类问题是说不清楚到底有没有一个具体解法&#xff0c;该解法能在多项式时间复杂函数上完成&#xff…

mybatis-plus3.5.3.1 支持不同数据源sql适配

mybatis-plus3.5.3.1 支持不同数据源sql适配 背景 最近公司要求支持国产数据库达梦&#xff0c;人大金仓&#xff0c;高斯等数据库&#xff0c;这些数据库与mysql的语法有一些差异&#xff0c;需要做一些兼容操作。 解决问题 1.不同数据库分页不同 2.支持通过参数控制执行…

JQuery ajax 提交数据提示:Uncaught TypeError:Illegal invocation

JQuery ajax 提交数据提示&#xff1a;Uncaught TypeError:Illegal invocation 1 问题描述 用jQuery Ajax向DRF接口提交数据的时候&#xff0c;console提示&#xff1a;Uncaught TypeError:Illegal invocation(未捕获的异常&#xff1a;非法调用)。 这个问题可能有两种原因导…

MatrixOne 实战系列回顾 | 建模与多租户

本次分享主要介绍MatrixOne建模与多租户相关内容。 1 建模 #1 与MySQL的区别 使用create table语句建表和MySQL建表语句基本相同&#xff0c;也有几点要注意。 MatrixOne暂不支持空间数据类型&#xff0c;其他数据类型在保持与 MySQL 命名一致的情况下&#xff0c;在精度与…

android studio导入eclipse项目

网上下载一个老工程&#xff0c;.project文件里有eclipse。 android studio导入eclipse项目 eclipse项目结构 Android studio文件结构 下面是导入步骤&#xff1a; 第一步&#xff0c;打开一个项目。 选择File->New->Import Project 第二步&#xff0c;选择Eclipse项目根…

保护数据库防止数据泄露

为了避免金钱损失、声誉损害、机密性损失、不遵守政府法规等&#xff0c;保护组织的数据至关重要&#xff0c;保护数据库可以保护您的企业免受无数安全威胁&#xff0c;包括权限滥用、数据泄露、数据库注入和其他网络攻击。 选择工具保护数据库 Log360 是一站式 SIEM 解决方案…

element-plus使用el-date-picker组件时,如何禁止用户选择当前时间之后的日时分秒

element-plus使用el-date-picker组件时&#xff0c;如何禁止用户选择当前时间之后的日时分秒 例&#xff1a; 当前时间为2023-11-15 14.24&#xff0c;不能选择这之后的时分秒。&#xff08;禁止用户选择2023-11-15 14.28&#xff09; <el-date-pickerv-model"form.s…

MM MSTA-STATM数据丢失问题

2001工厂的采购视图已经维护了&#xff0c;但是在MSTA里面找不到对应的记录 解决方案&#xff1a; 1、se38 执行程序 RMMMVERW 参考 data were lost in table msta | SAP Community 2、取数逻辑换位置&#xff0c;从marc 取数 附加&#xff1a;RMMMVERW 执行界面 执行后…

nvm安装详细教程(卸载旧的nodejs,安装nvm、node、npm、cnpm、yarn及环境变量配置)

文章目录 一、完全卸载旧的nodejs1、打开系统的控制面板&#xff0c;点击卸载程序&#xff0c;卸载nodejs&#xff08;1&#xff09;打开系统的控制面板&#xff0c;点击程序下的卸载程序&#xff08;2&#xff09;找到node.js&#xff0c;鼠标右击出现下拉框&#xff0c;点卸载…

【Python语言】字典的使用方法总结

目录 1、基本知识 1.1 定义 1.2 定义字典变量 1.3 定义空字典 2、字典的常用方法介绍 2.1 基于key获取value 2.2 嵌套字典 2.3 新增元素 2.4 更新元素 2.5 删除元素 2.6 清空字典 2.7 获取全部的key 2.8 遍历字典 2.9 统计字典内的元素数量 3、 字典常用操作总结…

如何快速搭建Spring Boot接口调试环境并实现公网访问

文章目录 前言1. 本地环境搭建1.1 环境参数1.2 搭建springboot服务项目 2. 内网穿透2.1 安装配置cpolar内网穿透2.1.1 windows系统2.1.2 linux系统 2.2 创建隧道映射本地端口2.3 测试公网地址 3. 固定公网地址3.1 保留一个二级子域名3.2 配置二级子域名3.2 测试使用固定公网地址…

Figma语言设置教程:简易切换至中文,提高操作便捷性!

Figma是世界领先的在线协作UI设计工具。它摆脱了Sketch等传统设计软件对设备的依赖&#xff0c;使设计师可以随时随地使用任何设备打开网页浏览器&#xff0c;轻松实现跨平台、跨时空的设计合作。那么&#xff0c;Figma如何改变中文&#xff0c;以提高国内设计师的使用体验呢&a…

ZOC8 for Mac:最佳终端仿真器,助力您的工作效率飞升!

在现代的工作环境中&#xff0c;终端仿真器扮演着不可或缺的角色。无论是开发人员、系统管理员还是网络工程师&#xff0c;都需要一个功能强大、易于使用的终端仿真器来处理各种任务。而ZOC8 for Mac正是为这些专业人士而打造的最佳选择。 作为一款全功能的终端仿真软件&#…

聊天即绘画,我来给好说 AI 当「甲方」

很多人说 “AI 能解放生产力”&#xff0c;放几个月前&#xff0c;小编并不这么想。用过 SD 画图的朋友都知道&#xff0c;拟提示词、调参数那都是脑力活&#xff0c;反复试错的过程就像在操纵机器、给 AI 打下手。 但最近&#xff0c;好说上线了 “聊天即绘画” 的 AI 绘画 2…