easyexcel通用的行的单元格合并策略

2021-05-20 :01:10:55  因测出bug修改文章

下方代码有个问题:因为合并单元格时机是在需要合并的数据最后一行开始合并,而如何判断是最后一行出现了问题。

具体问题就是:判断是最后一行是根据下一组需要合并的起始行之前。因为把需要合并的单元格的第一个单元格设为true,其余设为false.,比如有如下某一列数据:

1
1
2
处理后
1 true
1 false
2 true
下方代码在合并前两行时是在遇到了 (2 true)这条数据(也就是下一组需要合并的起始行之前)时,合并之前的行。但如果没有(2 true)这条数据时,前两行就无法合并。

即最后一组需要合并的数据无法合并,也就是最后一条数据的值为false时。

修改后的GenericRowMergeStrategy#merge方法如下: 采用了合并单元格时机是在需要合并的数据第一行开始合并,即和elementUI table合并单元格时机一致。这样代码也更加简单易懂

  @Override
    protected void merge(Sheet sheet, Cell cell, GenericMergeBO bo) {
        if (Boolean.TRUE.equals(bo.getStartMergeCell())) {
            CellPoint cellPoint = new CellPoint();
            cellPoint.setStartX(cell.getColumnIndex());
            cellPoint.setStartY(cell.getRowIndex());
            cellPoint.setEndX(cell.getColumnIndex());
            cellPoint.setEndY(cell.getRowIndex() + bo.getRowspan() - 1);
            cellPoint.setText(getCellText(cell));
            if (cellPoint.getStartY() != cellPoint.getEndY()) {
                executorMerge(cellPoint, sheet);
            }
        }
    }

上方说明请仔细阅读,由于没有充分测试造成问题  下面的描述也做了调整


elementUI table单元格合并需要返回如下数据,代表的就是要合并的行数,列数。下面只讨论行的合并。

      objectSpanMethod({ row, column, rowIndex, columnIndex }) {
        if (columnIndex === 0) {
          if (rowIndex % 2 === 0) {
            return {
              rowspan: 2,
              colspan: 1
            };
          } else {
            return {
              rowspan: 0,
              colspan: 0
            };
          }
        }
      }

上述代码的意思是对于第1列每隔2行合并一次,合并的时机是在第一行。也就是当行数是1,3,5...(下标是0,2,4...)时合并(elementUI table 第一行数据rowIndex为0,不考虑表头行),这种是循环合并,而真实的业务大都不是这样的,所以关键点就是条件语句的判断。

这里再以类似1,3,5行开始合并来说,就不再代表循环合并,而是代表合并单元格时第一个单元格行号。比如要合并第一列2,3,4行,那么需要的就是2。合并第一列5,6,7,8行,那么需要的就是5。那么如何知道是2或者5呢,其实只要在第2行数据加个boolean类型参数,值是true的就代表需要合并的单元格的起始行,非起始行的都设置为false。这样就把判断行号2,、5,转换成了判断对应行数据的boolean类型参数。然后row里肯定是有这个参数的,这样就可以做到任意行的合并。(如果不同列行合并不同,可以为不同列对应不同的boolean类型的参数及其需要合并的行数rowspan)。

设置好boolean类型参数,再加上rowspan数值,其实就已经指定了从哪个单元格开始行合并,合并多少行。下面就是java代码实现。

AbstractGenericMergeStrategy是抽象的通用合并策略,GenericRowMergeStrategy是行合并的实现。

public abstract class AbstractGenericMergeStrategy<T extends GenericMerge> extends AbstractMergeStrategy {
    //所有数据 目的就是从所有数据找到当前行数据row
    protected List<T> list;
    //表头行数
    protected int headRowNumber = 1;

    protected AbstractGenericMergeStrategy(List<T> list){
        this.list = list;
    }
    protected AbstractGenericMergeStrategy(List<T> list, int headRowNumber){
        this.list = list;
        this.headRowNumber = headRowNumber;
    }

    @Override
    protected void merge(Sheet sheet, Cell cell, Head head, int relativeRowIndex) {
        int columnIndex = cell.getColumnIndex();
        int rowIndex = cell.getRowIndex();
        //找到cell对应的当前行数据row
        T t = list.get(rowIndex - headRowNumber);
        GenericMergeBO bo = t.merge(columnIndex);
        if (bo != null){
            merge(sheet,cell,bo);
        }
    }

    protected abstract void merge(Sheet sheet, Cell cell,GenericMergeBO bo);

    protected void executorMerge(CellPoint cellPoint, Sheet sheet){
            CellRangeAddress cellRangeAddress = new CellRangeAddress(
                    cellPoint.getStartY(),
                    cellPoint.getEndY(),
                    cellPoint.getStartX(),
                    cellPoint.getEndX());
            sheet.addMergedRegionUnsafe(cellRangeAddress);
    }

    String getCellText(Cell cell){
        String text = "";
        CellType cellType = cell.getCellTypeEnum();
        if(CellType.STRING.equals(cellType)){
            text = cell.getStringCellValue();
        }else if(CellType.NUMERIC.equals(cellType)){
            text = Double.toString(cell.getNumericCellValue());
        }else if(CellType.BOOLEAN.equals(cellType)){
            text = Boolean.toString(cell.getBooleanCellValue());
        }
        return text;
    }
}

 下方GenericRowMergeStrategy#merge方法已废弃,请使用文章开头的merge方法

public class GenericRowMergeStrategy<T extends GenericMerge> extends AbstractGenericMergeStrategy<T> {
    private Map<String, CellPoint> col = new HashMap<>();

    public GenericRowMergeStrategy(List<T> list) {
        super(list);
    }

    public GenericRowMergeStrategy(List<T> list, int headRowNumber) {
        super(list, headRowNumber);
    }

    @Override
    protected void merge(Sheet sheet, Cell cell, GenericMergeBO bo) {
        //使用文章开头代码
    }
}
public interface GenericMerge {
    GenericMergeBO merge(int columnIndex);
}
public class CellPoint {
    /**
     * 开始单元格x坐标
     */
    private int startX;
    /**
     * 结束单元格x坐标
     */
    private int endX;
    /**
     * 开始单元格y坐标
     */
    private int startY;
    /**
     * 结束单元格y坐标
     */
    private int endY;
    /**
     * 单元格内容,文本
     */
    private String text;
}
@Data
public class GenericMergeBO {
    /**
     * 是否是开始合并的单元格
     */
    private Boolean startMergeCell;
    /**
     * 合并的行数
     */
    private Integer rowspan;
    /**
     * 合并的列数
     */
    private Integer colspan;
}

使用

easyexcel导出时传入GenericRowMergeStrategy策略类,需要导出的数据类实现GenericMerge 接口,方法体就是将elementUI 合并单元格的方法翻译成java代码即可。返回参数就多了一个startMergeCell是否是开始合并的单元格,为了好判断。不需要合并的返回null即可。

缺点

当不同列合并策略不同时,构造数据比较麻烦。因为需要为不同的策略对应不同 boolean参数(是否是开始合并的单元格startMergeCell)和合并的行数(rowspan)。

js/java方法对比分析

方法

js: objectSpanMethod({ row, column, rowIndex, columnIndex }) {}

java: GenericMergeBO merge(int columnIndex);

对比

js  row-> java this(当前对象)

js columnIndex->java 方法参数columnIndex

由于只是行合并,所以column和rowIndex并未使用到,如果要做其它限制,可以丰富GenericMerge 接口方法参数

示例

如上图所示,0,1,2,8为一种合并策略  3,4为另一种合并策略  5,6,7为不需要合并的列

构造数据的代码如下:

      List<User> list = new ArrayList<>();
        //根据用户姓名分组
        Map<String, List<User>> nameMap = list.stream().collect(Collectors.groupingBy(User::getName));
        nameMap.forEach((k,v)->{
            int index = v.size();
            for (User dto : v) {
                //给0,1,2,8列设置boolean参数和需要合并的行数
                dto.setIsRowSpan(index == v.size());
                dto.setRowIndexNumber(v.size());
                index--;
            }
            //每一个用户下又根据项目编号分组
            Map<String, List<User>> projectMap = v.stream().collect(Collectors.groupingBy(Project::getProjectNumber));
            projectMap.forEach((k1,v1)->{
                int j = v1.size();
                for (User dto : v1) {
                    //给3,4列设置boolean参数和需要合并的行数
                    dto.setIsProjectRowSpan(j == v1.size());
                    dto.setProjectRowIndexNumber(v1.size());
                    j--;
                }
            });
        });

User类实现GenericMerge接口代码如下:

    @Override
    public GenericMergeBO merge(int columnIndex) {
        GenericMergeBO bo = null;
        if (columnIndex == 0 || columnIndex == 1 || columnIndex == 2 || columnIndex == 3 || columnIndex == 4 || columnIndex == 8) {
            if (columnIndex == 0 || columnIndex == 1 || columnIndex == 2 || columnIndex == 8) {
                bo = new GenericMergeBO();
                bo.setStartMergeCell(this.isRowSpan);
                bo.setRowspan(this.rowIndexNumber);
                bo.setColspan(1);
                return bo;
            } else if(columnIndex == 3 || columnIndex == 4){
                bo = new GenericMergeBO();
                bo.setStartMergeCell(this.isProjectRowSpan);
                bo.setRowspan(this.projectRowIndexNumber);
                bo.setColspan(1);
                return bo; 
            }
        }
        return bo;
    }

js代码如下:

    objectSpanMethod({ row, column, rowIndex, columnIndex }) {
      if (columnIndex === 0 || columnIndex === 1 || columnIndex === 2 || columnIndex === 3 || columnIndex === 4 || columnIndex === 8) {
        if ((columnIndex === 0 || columnIndex === 1 || columnIndex === 2 || columnIndex === 8) && row.isRowSpan) {
          return {
            rowspan: row.rowIndexNumber,
            colspan: 1
          };
        } else if ((columnIndex === 3 || columnIndex === 4) && row.isProjectRowSpan) {
          return {
            rowspan: row.projectRowIndexNumber,
            colspan: 1
          };
        } else {
          return {
            rowspan: 0,
            colspan: 0
          };
        }
      }
    }

注:部分内容使用了新增按照单元格内容相同进行水平,垂直合并单元格的策略 by andotorg · Pull Request #1091 · alibaba/easyexcel · GitHub新增按照单元格内容相同进行水平,垂直合并单元格的策略中的代码(CellPoint ,col map的使用)

  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
EasyExcel中,自定义合并单元格策略可以通过实现`com.alibaba.excel.metadata.CellStrategy`接口来实现。可以参考上面提到的技术斩博主的自定义策略优化。 具体步骤如下: 1. 创建一个类,实现`CellStrategy`接口,并重写`merge(CellRangeAddress cellRangeAddress, Sheet sheet)`方法。 2. 在`merge()`方法中,根据自定义的合并单元格规则,通过`cellRangeAddress`参数来确定需要合并单元格范围,然后通过`sheet`对象进单元格合并操作。 3. 根据需要,在自定义策略中添加其他的处理逻辑,例如设置合并单元格的样式等。 4. 在使用EasyExcel导出时,通过`excelWriter.setCustomCellWriteHandler()`方法来设置自定义的合并单元格策略。 请注意,以上步骤仅是一种实现自定义合并单元格策略的方法,具体的实现方式可能会因项目需求而有所不同。<span class="em">1</span><span class="em">2</span> #### 引用[.reference_title] - *1* [利用easyExcel导出上万条数据,自定义策略合并单元格](https://download.csdn.net/download/qq_32734167/13408705)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [EasyExcel合并单元格,通过注解方式实现自定义合并策略](https://blog.csdn.net/q1468051413/article/details/127832071)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值