EasyExcel按模板导出(动态合并单元格问题处理)

参考网上的链接:

EasyExcel按模板导出与下载(自定义合并单元格)
https://blog.csdn.net/weixin_44511845/article/details/120290264
EasyExcel(根据条件动态合并单元格的重复数据))
https://blog.csdn.net/Violet_201903027/article/details/105724907

 

编写模板导出时,某一列单元格合并功能

上面的资料使用的EasyExcel版本是:2.1.7

我使用的版本是:2.2.0-beta2

 

使用资料代码进行模板导出同时,动态单元格合并

github官方提供了 LoopMergeStrategy 合并策略 在模板导出不生效。因为LoopMergeStrategy extends AbstractRowWriteHandler ,使用 afterRowDispose() 行操作完成后执行合并操作。

但是在模板导出过程中afterRowDispose() 方法没有被触发。

合并策略类:ExcelFillCellMergeStrategy 

 1 public class ExcelFillCellMergeStrategy implements CellWriteHandler {
 2 
 3     /** 需要进行单元格合并的列数组 **/
 4     private int[] mergeColumnIndex;
 5     /** 单元格合并从第几行开始 **/
 6     private int mergeRowIndex;
 7 
 8     public ExcelFillCellMergeStrategy() {}
 9 
10     public ExcelFillCellMergeStrategy(int mergeRowIndex, int[] mergeColumnIndex) {
11         this.mergeRowIndex = mergeRowIndex;
12         this.mergeColumnIndex = mergeColumnIndex;
13     }
14  。。。
15  。。。
16 
17     @Override
18     public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder,
19         List<CellData> list, Cell cell, Head head, Integer integer, Boolean isHead) {
20         int curRowIndex = cell.getRowIndex();
21         int curColIndex = cell.getColumnIndex();
22         if (curRowIndex > mergeRowIndex) {
23             for (int i = 0; i < mergeColumnIndex.length; i++) {
24                 if (curColIndex == mergeColumnIndex[i]) {
25                     mergeWithPrevRow(writeSheetHolder, cell, curRowIndex, curColIndex);
26                     break;
27                 }
28             }
29         }
30     }
31 
32     /**
33      * 当前单元格向上合并
34      *
35      * @param writeSheetHolder
36      * @param cell
37      *            当前单元格
38      * @param curRowIndex
39      *            当前行
40      * @param curColIndex
41      *            当前列
42      */
43     private void mergeWithPrevRow(WriteSheetHolder writeSheetHolder, Cell cell, int curRowIndex, int curColIndex) {
44         Object curData =
45             cell.getCellTypeEnum() == CellType.STRING ? cell.getStringCellValue() : cell.getNumericCellValue();
46         Cell preCell = cell.getSheet().getRow(curRowIndex - 1).getCell(curColIndex);
47         Object preData =
48             preCell.getCellTypeEnum() == CellType.STRING ? preCell.getStringCellValue() : preCell.getNumericCellValue();
49         // 将当前单元格数据与上一个单元格数据比较
50         Boolean dataBool = preData.equals(curData);
51         if (dataBool) {
52             Sheet sheet = writeSheetHolder.getSheet();
53             List<CellRangeAddress> mergeRegions = sheet.getMergedRegions();
54             boolean isMerged = false;
55             for (int i = 0; i < mergeRegions.size() && !isMerged; i++) {
56                 CellRangeAddress cellRangeAddr = mergeRegions.get(i);
57                 // 若上一个单元格已经被合并,则先移出原有的合并单元,再重新添加合并单元
58                 if (cellRangeAddr.isInRange(curRowIndex - 1, curColIndex)) {
59                     sheet.removeMergedRegion(i);
60                     cellRangeAddr.setLastRow(curRowIndex);
61                     sheet.addMergedRegion(cellRangeAddr);
62                     isMerged = true;
63                 }
64             }
65             // 若上一个单元格未被合并,则新增合并单元
66             if (!isMerged) {
67                 CellRangeAddress cellRangeAddress =
68                     new CellRangeAddress(curRowIndex - 1, curRowIndex, curColIndex, curColIndex);
69                 sheet.addMergedRegion(cellRangeAddress);
70             }
71         }
72     }
73 }

 

测试代码

    @Test
    public void fillTest1() throws IOException {
        // 模板注意 用{} 来表示你要用的变量 如果本来就有"{","}" 特殊字符 用"\{","\}"代替
        // 填充list 的时候还要注意 模板中{.} 多了个点 表示list
        String templatePath = EasyexcelAnnotationFillTest.class.getResource("/").getPath();
        templatePath = templatePath + "fillTemplate.xlsx";
        OutputStream outputStream = new FileOutputStream("E:/测试填充数据.xlsx");

        // 填充列表数据
        List<FillWithAnnotationData> listData = getFillListData();
        // 第一列进行单元格合并
        int[] mergeColumeIndex = {0};
        // 从第4行开始合并
        int mergeRowIndex = 3;

        ExcelWriter excelWriter = EasyExcelFactory.write(outputStream)
                .withTemplate(templatePath)
                //设置合并单元格策略
                .registerWriteHandler(new ExcelFillCellMergeStrategy(mergeRowIndex, mergeColumeIndex))
                .build();
        WriteSheet writeSheet = EasyExcel.writerSheet().build();
        excelWriter.fill(listData, writeSheet);
        excelWriter.finish();
        outputStream.close();
    }

public List<FillWithAnnotationData> getFillListData() {
List<FillWithAnnotationData> listData = new ArrayList<>();
for (int i=0; i < 10; i++) {
FillWithAnnotationData data = new FillWithAnnotationData();
data.setName("名称");
data.setMoney(new BigDecimal(1002.35+i).toPlainString());
data.setNumber(1002.35+i);
listData.add(data);
}
return listData;
}

public class FillWithAnnotationData {

private String name;
@NumberFormat(",##0.00")
@ExcelFillProperty(converter = StringNumberConverter.class)
private String money;
@NumberFormat(",##0.00")
@ExcelFillProperty(converter = DoubleStringConverter.class)
private Double number;
}
 

测试模板如下图

EasyExcel按模板导出(动态合并单元格问题处理)

 

报错位置:ExcelFillCellMergeStrategy合并策略类的 mergeWithPrevRow()方法中

第46行位置

Cell preCell = cell.getSheet().getRow(curRowIndex - 1).getCell(curColIndex);

这一行代码会报空指针异常 java.lang.NullPointerException

 

 

 

原因:

debug发现,cell.getSheet() 行的下标第0到3的数据行,获取的是同一个 sheet 实例

当下标为4时,执行cell.getSheet()获取到的 sheet 实例不一样,而且里面的sheet存在的row数据,只有下标为4以后的

EasyExcel按模板导出(动态合并单元格问题处理)

 

 

 而下标0到3的行数据被存储到 存储sheet中。

writeSheetHolder.getCachedSheet()

EasyExcel按模板导出(动态合并单元格问题处理)

 

 

 

所以改进合并策略类的合并方法:

当获取不到前一行数据时,查找缓存sheet中的行数据

/**
     * 当前单元格向上合并
     *
     * @param writeSheetHolder
     * @param cell
     *            当前单元格
     * @param curRowIndex
     *            当前行
     * @param curColIndex
     *            当前列
     */
    private void mergeWithPrevRow(WriteSheetHolder writeSheetHolder, Cell cell, int curRowIndex, int curColIndex) {
        Object curData =
            cell.getCellTypeEnum() == CellType.STRING ? cell.getStringCellValue() : cell.getNumericCellValue();
        Row preRow = cell.getSheet().getRow(curRowIndex - 1);
        if (preRow == null) {
            // 当获取不到上一行数据时,使用缓存sheet中数据
            preRow = writeSheetHolder.getCachedSheet().getRow(curRowIndex - 1);
        }
        Cell preCell = preRow.getCell(curColIndex);
        Object preData =
            preCell.getCellTypeEnum() == CellType.STRING ? preCell.getStringCellValue() : preCell.getNumericCellValue();
        // 将当前单元格数据与上一个单元格数据比较
        Boolean dataBool = preData.equals(curData);
        if (dataBool) {
            Sheet sheet = writeSheetHolder.getSheet();
            List<CellRangeAddress> mergeRegions = sheet.getMergedRegions();
            boolean isMerged = false;
            for (int i = 0; i < mergeRegions.size() && !isMerged; i++) {
                CellRangeAddress cellRangeAddr = mergeRegions.get(i);
                // 若上一个单元格已经被合并,则先移出原有的合并单元,再重新添加合并单元
                if (cellRangeAddr.isInRange(curRowIndex - 1, curColIndex)) {
                    sheet.removeMergedRegion(i);
                    cellRangeAddr.setLastRow(curRowIndex);
                    sheet.addMergedRegion(cellRangeAddr);
                    isMerged = true;
                }
            }
            // 若上一个单元格未被合并,则新增合并单元
            if (!isMerged) {
                CellRangeAddress cellRangeAddress =
                    new CellRangeAddress(curRowIndex - 1, curRowIndex, curColIndex, curColIndex);
                sheet.addMergedRegion(cellRangeAddress);
            }
        }
    }

 

 

 

 

 

 

 

上一篇:EasyExcel基本应用


下一篇:SpringBoot整合EasyExcel+MyBatis-Plus实现Excel批量导入和导出