easyExcel单元格动态合并示例

复制代码
1.先按照mergekey排序,然后相同的key分组,只保留第一个对象,其他对象属性都设置为null.

2.
复制代码
ClassPathResource resource = new ClassPathResource("template/template.xlsx");
ExcelWriter excelWriter = EasyExcel.write(outputStream).withTemplate(resource.getInputStream()).build();
WriteSheet writeSheet = EasyExcel.writerSheet().registerWriteHandler(new PartBoxMergeStrategyNew(16, 27, 11, details.size())).build();

FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
excelWriter.fill(header, writeSheet);
excelWriter.fill(details, fillConfig, writeSheet);
excelWriter.finish();
复制代码
3.

public class PartBoxMergeStrategyNew implements CellWriteHandler {

    // 需要合并的起始列索引(从0开始)
    private final int startMergeColumns;
    // 需要合并的结束列索引(从0开始)
    private final int endMergeColumns;
    // rowIndex 从0 开始
    private final int startMergeRowIndex;
    // rowIndex 从0 开始s
    private final int lastRowIndex;

    public PartBoxMergeStrategyNew(int startMergeColumns, int endMergeColumns, int startMergeRowIndex, int rowSize) {
        this.startMergeColumns = startMergeColumns;
        this.endMergeColumns = endMergeColumns;
        this.startMergeRowIndex = startMergeRowIndex;
        this.lastRowIndex = rowSize-1+startMergeRowIndex;
    }

    @Override
    public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List<WriteCellData<?>> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {

        // 跳过表头和空值单元格
        if (isHead) return;

        Sheet sheet = writeSheetHolder.getSheet();
        int col = cell.getColumnIndex();
        int rowIndex = cell.getRowIndex();
        boolean lastFlag = rowIndex == lastRowIndex;

        if(rowIndex <= startMergeRowIndex) return;

        if (col >= startMergeColumns && col <= endMergeColumns) {
            mergeAdjacentCells(sheet, cell, rowIndex, col, lastFlag);
        }
    }
    //row是从0开始的
    private void mergeAdjacentCells(Sheet sheet, Cell cell, int currentRowIndex, int currentColIndex, boolean lastFlag) {
        int firstRow = currentRowIndex;
        int lastRow = currentRowIndex;
        // 向上查找合并起始行(跳过空值)
        // 这里是要等到最后一个空值才进行合并
        Cell curr = cell.getSheet().getRow(currentRowIndex).getCell(startMergeColumns);
        while (firstRow -1 >= startMergeRowIndex) {
            Cell prev = cell.getSheet().getRow(firstRow -1).getCell(startMergeColumns);
            if(lastFlag) {
                if(getCellValue(curr)==null&&getCellValue(prev)==null) {
                    //最后一行,当前为空,前一个为空,则继续找,说明还没有找到最前面的一行不为空的,需要继续
                    firstRow--;
                }else if(getCellValue(curr)==null&&getCellValue(prev)!=null){
                    //最后一行,当前为空,前一个不为空,说明已经找到了需要合并的最前面的一行,同时当前也是空的,所以lastRow不用-1
                    firstRow--;
                     break;
                }else if(getCellValue(curr)!=null&&getCellValue(prev)!=null){
                    //最后一行,当前不为空,前一个也不为空,相邻的两行都不是空的,无需合并
                         lastRow--;                     
                        break;
                }else{
                    //最后一行,当前不为空,前一个为空,当前不为空,则说明当前行不是一伙的,所以要-1,前一行为空,说明这一行的领头羊在前面
                    firstRow--;
                   
                }
            }else{
                if(getCellValue(curr)==null) {//不是最后一行,当前为空,则需要等到不为空的行再往上面找,不需要触发合并,
                    break;
                }else{
                    if(getCellValue(prev)==null){//不是最后一行,当前不为空,前一个为空,则继续找
                        firstRow--;
                    }else{//不是最后一行,当前不为空,前一个不为空,则不用找,当前不为空,说明跟前面不是一伙的,
                         // 前一个不为空,则说明已经找到了最前面的领头的
                        firstRow--;
                        lastRow--;
                        break;
                    }
                }
            }
        }
        // cur=17 16
        // 执行合并(如果跨越多行)
        if (lastRow > firstRow) {
            sheet.addMergedRegionUnsafe(new CellRangeAddress(
                    firstRow , lastRow , currentColIndex, currentColIndex
            ));
        }
    }

    private Object getCellValue(Cell cell) {
        switch (cell.getCellType()) {
            case STRING:
                return cell.getStringCellValue();
            case NUMERIC:
                return cell.getNumericCellValue();
            case BOOLEAN:
                return cell.getBooleanCellValue();
            default:
                return null;
        }
    }

}
相关推荐
evans在进步25 分钟前
LeetCode 17:电话号码的字母组合——Java DFS 回溯法详解
java·leetcode·深度优先
2401_8504811728 分钟前
UVA10391
java
UQR35 分钟前
一、Java基础高频面试题
java·开发语言
AC赳赳老秦36 分钟前
网页公开附件自动采集:OpenClaw 批量下载页面内嵌 Word/Excel 附件,统一格式后结构化入库
java·python·word·php·excel·deepseek·openclaw
上海云盾商务经理杨杨42 分钟前
Tomcat 弱配置漏洞渗透实战!批量 getshell 高频漏洞
java·web安全·tomcat
10mAh1 小时前
【Linux】error while loading shared libraries 怎么解决?——ldd、RPATH 与动态链接排错
java·linux·前端
深念Y1 小时前
stable-diffusion.cpp 的 FLUX.2 Klein 9B 分步
java·前端·数据库
阿pin1 小时前
Java随笔-红黑树
java·python·算法·红黑树
我命由我123451 小时前
Jetpack Compose - @Preview 注解
android·java·java-ee·android studio·android jetpack·android-studio·android runtime
为美好的生活献上中指1 小时前
Spring AI Advisor 深度实战:构建严谨的 AI Agent 拦截链
java·人工智能·spring·advisor·aiagent