easyExcel 填充写时,动态合并单元格

1、定义合并单元格策略获取方法
/** 
     * @description: 获取第二个表格的合并策略
     * @param secondTablelist  
     * @return: java.util.List<com.alibaba.excel.write.merge.OnceAbsoluteMergeStrategy> 
     * @author 30864
     * @date: 2024/8/24 20:27
     */ 
    public static List<OnceAbsoluteMergeStrategy> getSecondMergeStrategy(List<ProjectContractExpenditureStatisticDto> secondTablelist) {
        int baseRowIndex = 3;

        //合并开始行
        int startRowIndex = 0;
        //合并结束行
        int endRowIndex = 0;
        //合并开始列
        int colStartIndex = 0;
        //合并结束列
        int colEndIndex = 0;
        List<OnceAbsoluteMergeStrategy> strategyList = new ArrayList<>();
        //       取出string列的数据
        List<String> contractNoList = secondTablelist.stream().map(ProjectContractExpenditureStatisticDto::getContractNo).collect(Collectors.toList());

        //       前后添加两个不计入合并的元素 , 类似链表中哨兵,简化索引越界的判断
        contractNoList.add(0, "开始节点不计入合并");
        contractNoList.add(contractNoList.size(), "结束节点不计入合并");

        List<Integer> mergeColIndexList = Arrays.asList(0,1,2,3,4,5,6,13,14,15);

        for (int index = 1; index < contractNoList.size() - 1; index++) {
            // 当前的元素和数组中前一个元素是否相等
            boolean equalsPrevious = Objects.equals(contractNoList.get(index), contractNoList.get(index - 1));
            // 当前的元素和数组中后一个元素是否相等
            boolean equalsAfter = Objects.equals(contractNoList.get(index), contractNoList.get(index + 1));
//            如果找到一个元素和不等于前面,但是等于后面,则这个元素的索引就是一个OnceAbsoluteMergeStrategy的开始行
            if (!equalsPrevious && equalsAfter) {
                startRowIndex = index;
                //            如果找到一个元素和等于前面,但是不等于后面,则这个元素的索引就是一个OnceAbsoluteMergeStrategy的结束结束行
            } else if (equalsPrevious && !equalsAfter) {
                endRowIndex = index;
                // 无合同号,不进行合并
                if (StringUtils.equals(secondTablelist.get(startRowIndex).getContractName(), Constansts.NOT_HAS_CONTRACT_CONTENT) ){
                    continue;
                }
//              创建单个合并策略,并添加到策略列表里面去
                for (int colIndex : mergeColIndexList) {
                    OnceAbsoluteMergeStrategy strategy = new OnceAbsoluteMergeStrategy(baseRowIndex + startRowIndex, baseRowIndex + endRowIndex, colIndex, colIndex);
                    strategyList.add(strategy);
                }
            }

        }

        return strategyList;
    }

解释: 需求:根据合同编码判断是否要合并,相同合同合并单元格

1、根据待写入数据记录的集合list, 获取合同编号集合

2、取当前记录及其前一条记录,判断当前记录的合同号  = 前一条记录合同号 ?

如果不相等,则当然前记录就是待合并单元格的起始行

3、取当前记录及其后一条记录,判断当前记录的合同号 = 后一条记录合同号 ? 

如果不相等,则当前记录就是待合并单元格的结束行

4、起始结尾行号确定了,需要确定起始结尾的列号,由于每列内容不一样,所以合同属性涉及的列号,都要一一合并,(这里涉及的列号 List<Integer> mergeColIndexList=Arrays.asList(0,1,2,3,4,5,6,13,14,15);)所以这里把涉及的列号定义了一个集合,然后其中的每一列都按照 起始行、结束行去合并。

2、写入时将合并策略注册到ExcelWriterSheetBuilder
/**
     * @param project
     * @description: 导出项目
     * @return: void
     * @author 30864
     * @date: 2024/8/1 9:05
     */
    @Override
    public void export(Project project, HttpServletResponse response) throws IOException {
        Project dbProject = projectMapper.selectProjectByNo(project.getProjectNo());

        String fileName = String.format("%s【%s】报告", dbProject.getProjectName(), dbProject.getProjectNo()) + System.currentTimeMillis() + ".xlsx";
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding("utf-8");
        // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
        fileName = URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20");
        response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName);
        OutputStream os = response.getOutputStream();
        String templateFileName = "template/template-project-report-new.xlsx";
        InputStream templateFileIos = getClass().getClassLoader().getResourceAsStream(templateFileName);
        if (null == templateFileIos) {
            throw new ServiceException("找不到模板文件");
        }
        try (ExcelWriter excelWriter = EasyExcel.write(os).withTemplate(templateFileIos).build()) {
            ExcelWriterSheetBuilder excelWriterSheetBuilder1 =   EasyExcel.writerSheet("项目执行情况-汇总");
            ExcelWriterSheetBuilder excelWriterSheetBuilder2 =   EasyExcel.writerSheet("项目执行情况-明细");
            ExcelWriterSheetBuilder excelWriterSheetBuilder3 =   EasyExcel.writerSheet("项目执行情况-审定数据");

            StatisticFirstTableDataDto firstTableDataDto = firstTableData(project);
            StatisticSecondTableDataDto secondTableDataDto = secondTableData(project);
            StatisticThirdTableDataDto thirdTableDataDto = thirdTableData(project);


            // 第二个表格添加合并单元格策略
            List<OnceAbsoluteMergeStrategy> secondMergeStrategyList = getSecondMergeStrategy(secondTableDataDto.getExpenditureList());
            for (OnceAbsoluteMergeStrategy secondMergeStrategy : secondMergeStrategyList ) {
                excelWriterSheetBuilder2.registerWriteHandler(secondMergeStrategy);
            }
            // 第三个表格添加合并单元格策略
            List<OnceAbsoluteMergeStrategy> thirdMergeStrategyList = getThirdMergeStrategy(thirdTableDataDto.getAuditList());
            for (OnceAbsoluteMergeStrategy thirdMergeStrategy : thirdMergeStrategyList ) {
                excelWriterSheetBuilder3.registerWriteHandler(thirdMergeStrategy);
            }

            WriteSheet writeSheet1 = excelWriterSheetBuilder1.build();
            WriteSheet writeSheet2 = excelWriterSheetBuilder2.build();
            WriteSheet writeSheet3 = excelWriterSheetBuilder3.build();


            /** 这里注意 入参用了forceNewRow 代表在写入list的时候不管list下面有没有空行 都会创建一行,然后下面的数据往后移动。
             * 默认 是false,会直接使用下一行,如果没有则创建。 forceNewRow 如果设置了true,有个缺点 就是他会把所有的数据都放到内存了,所以慎用 */
            // 如果有多个list 模板上必须有{前缀.} 这里的前缀就是 totalData,warnList 然后多个list必须用 FillWrapper包裹
            // HORIZONTAL 横向填充 , VERTICAL 纵向填充
            // 第一个表格合同维度列表统计
            FillConfig contractListConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).direction(WriteDirectionEnum.VERTICAL).build();

            excelWriter.fill(new FillWrapper("firstTableData", firstTableDataDto.getContractList()), contractListConfig, writeSheet1);

            // 第二个表格支出维度,列表统计
            FillConfig secordTableConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).direction(WriteDirectionEnum.VERTICAL).build();


            excelWriter.fill(new FillWrapper("secondTableData", secondTableDataDto.getExpenditureList()), secordTableConfig, writeSheet2);


            // 第三个表格概算类型维度,列表统计
            FillConfig thirdTableConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).direction(WriteDirectionEnum.VERTICAL).build();
            excelWriter.fill(new FillWrapper("thirdTableData", thirdTableDataDto.getAuditList()), thirdTableConfig, writeSheet3);

            // 填充其他非列表数据
            Map<String, Object> data = new HashMap<>();
            // 项目名称
            data.put("projectName", dbProject.getProjectName());
            data.put("projectNo", dbProject.getProjectNo());
            // 第一个表格总计
            data.put("firstTableTotal_totalPayMoney", firstTableDataDto.getTotalPayMoney());
            data.put("firstTableTotal_totalUnpaidAmount", firstTableDataDto.getTotalUnpaidAmount());
            // 第二个表格总计
            data.put("secondTableTotal_totalPayMoney", secondTableDataDto.getTotalPayMoney());
            data.put("secondTableTotal_unpaidAmount", secondTableDataDto.getUnpaidAmount());
            // 第三个表格总计
            data.put("thirdTableTotal_totalPayMoney", thirdTableDataDto.getTotalPayMoney());
            data.put("thirdTableTotal_unpaidAmount", thirdTableDataDto.getUnpaidAmount());
            data.put("thirdTableTotal_estimateAmount", thirdTableDataDto.getEstimateAmount());
            excelWriter.fill(data, writeSheet1);
            excelWriter.fill(data, writeSheet2);
            excelWriter.fill(data, writeSheet3);

            excelWriter.finish();
        }
    }

解释: 这里涉及三个sheet的写入,只需关注 excelWriterSheetBuilder2 即可

<think>好的,我现在需要解决用户的问题,即如何使用EasyExcel库读取Excel文件中的合并单元信息及处理方法。用户提到他们在使用EasyExcel读取合并单元遇到了数据不完整的问题,因为合并单元的数据只在第一个单元有值,其他合并的单元为空。我需要根据提供的引用内容来整理出一个解决方案。 首先,根据引用[1],当使用EasyExcel读取包含合并单元的Excel,POI或EasyExcel只会读取合并单元的第一个单元的值,其他被合并的单元会被视为空。因此,用户需要处理这些空单元,给它们赋值,这是核心技术难点。 接下来,引用[2]提到普通的Excel列表读取没有问题,但合并单元会导致数据不完整。例如,合并的单元可能只在第一个位置有数据,其他位置为空。用户需要获取合并单元的信息,并在读取填充这些空值。 然后,引用[3]指出,EasyExcel在处理合并单元主要基于传统原理,数据存储在datas列表里。需要通过对单元进行处理,将合并单元的信息应用到其他被合并的单元上。 综合这些信息,我需要设计一个解决方案,使用EasyExcel的事件监听模式,在读取每个单元检查是否为合并单元,并记录合并信息。然后在填充数据,根据记录的合并信息,将合并区域第一个单元的值复制到其他被合并的单元中。 可能的步骤包括: 1. 创建自定义的AnalysisEventListener,处理每个单元的数据。 2. 在invoke方法中处理每一行的数据,记录合并单元的信息。 3. 在doAfterAllAnalysed方法中进行后续处理,确保所有数据都被正确填充。 4. 使用合并单元的坐标信息,将主单元的值填充到被合并的单元中。 需要注意的是,EasyExcel可能不会直接提供合并单元的信息,因此需要通过监听器的上下文或其他方法来获取合并区域。此外,处理合并单元可能需要维护一个数据结构来存储合并区域的信息,以便后续填充数据使用。 可能遇到的问题包括如何准确获取合并单元的坐标,以及如何处理跨行或跨列的合并。此外,性能也是一个考虑因素,特别是在处理大型Excel文件,需要确保数据结构的高效性。 最后,需要验证解决方案的有效性,确保所有合并单元的数据都被正确读取和填充,避免空值出现。可能还需要参考EasyExcel的官方文档或社区资源,确认是否有更直接的方法来获取合并单元的信息。</think>要使用EasyExcel获取Excel合并单元信息并处理数据,需要通过事件监听模式获取合并区域坐标,并通过数据填充逻辑补全被合并单元的值。具体实现步骤如下: ### 一、基础数据读取结构 ```java // 定义实体类 @Data public class MergeCellModel { @ExcelProperty("字段1") private String field1; @ExcelProperty("字段2") private String field2; } // 创建监听器基类 public class MergeStrategy extends AnalysisEventListener<MergeCellModel> { private final List<MergeCellModel> datas = new ArrayList<>(); @Override public void invoke(MergeCellModel data, AnalysisContext context) { datas.add(data); } @Override public void doAfterAllAnalysed(AnalysisContext context) { // 后续处理入口 } } ``` ### 二、获取合并单元信息 需要重`AnalysisEventListener`的额外方法: ```java @Override public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) { // 获取合并单元信息 ReadSheetHolder readSheetHolder = context.readSheetHolder(); Sheet sheet = readSheetHolder.getSheet(); List<CellRangeAddress> mergedRegions = sheet.getMergedRegions(); // 存储合并区域信息(建议用ConcurrentHashMap) Map<String, CellRangeAddress> mergeMap = new HashMap<>(); for (CellRangeAddress region : mergedRegions) { String key = region.getFirstRow() + "_" + region.getFirstColumn(); mergeMap.put(key, region); } context.readSheetHolder().setExtra(mergeMap); // 存入上下文 } ``` ### 三、空单元数据填充 在`invoke`方法中补充逻辑: ```java @Override public void invoke(MergeCellModel data, AnalysisContext context) { // 获取当前单元坐标 ReadRowHolder readRowHolder = context.readRowHolder(); int rowIndex = readRowHolder.getRowIndex(); Map<Integer, Cell> cellMap = readRowHolder.getCellMap(); // 获取合并区域信息 Map<String, CellRangeAddress> mergeMap = (Map<String, CellRangeAddress>) context.readSheetHolder().getExtra(); // 遍历所有单元列索引 for (Integer columnIndex : cellMap.keySet()) { String key = rowIndex + "_" + columnIndex; if (mergeMap.containsKey(key)) { CellRangeAddress region = mergeMap.get(key); // 获取主单元数据 Object mainValue = cellMap.get(region.getFirstColumn()).getStringValue(); // 填充被合并区域 for (int i = region.getFirstRow(); i <= region.getLastRow(); i++) { for (int j = region.getFirstColumn(); j <= region.getLastColumn(); j++) { if (!(i == rowIndex && j == columnIndex)) { fillData(i, j, mainValue); // 自定义填充方法 } } } } } datas.add(data); } ``` ### 四、最终数据校验 使用如下方式验证结果: ```java // 读取传入自定义策略 MergeStrategy strategy = new MergeStrategy(); EasyExcel.read("input.xlsx", MergeCellModel.class, strategy).sheet().doRead(); // 输出验证 strategy.getDatas().forEach(item -> System.out.println(item.getField1() + " | " + item.getField2())); ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值