当使用easy Excel导出时,怎么需要根据业务要求去自定义合并单元格?
例子:
当通过人员信息去查询业务信息时,一个人员有多条业务信息时,这个时候直接将查询出的结果直接导出的话会出现如下图一样的情况,该人员对应的业务信息有三条,人员信息模块会有三条一样的人员信息。站在使用者的角度上这样会不太直观,那么就需要程序在导出之前去处理一对多的情况,当人员有多条业务数据时,去合并人员信息模块单元格;
合并前:
合并后:
实现方式
创建一个记录需要合并单元格的DTO
package com.test.model.excel;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* @description: 合并单元格DTO
* @author: 小丁同学
* @create: 2023-01-29
**/
@Data
@Accessors(chain = true)
public class MergeCellDTO {
/**
* 需要合并的单元格列 下标
*/
private int cellIndex;
/**
* 从第几行合并到第几行
* 合并开始行
*/
private int startRow;
/**
* 从第几行合并到第几行
* 合并结束行
*/
private int endRow;
}
实现easyExcel 的 CellWriteHandler(单元格写入处理器)接口
/**
* @description: excel表格单元格合并处理器
* @author: 小丁同学
* @create: 2023-01-29
**/
public class UserCellMergeHandle implements CellWriteHandler {
/**
* 需要合并的单元格参数
*/
private List<MergeCellDTO> mergeCellDtos;
public UserCellMergeHandle (List<MergeCellDTO> mergeCellDtos) {
this.mergeCellDtos = mergeCellDtos;
}
/**
* 该方法会在单元格上的所有操作完成后调用,每一个单元格操作完之后都会进行调用该方法
* Called after all operations on the cell have been completed
*
* @param writeSheetHolder
* @param writeTableHolder Nullable.It is null without using table writes.
* @param cellDataList Nullable.It is null in the case of add header.There may be several when fill the data.
* @param cell
* @param head Nullable.It is null in the case of fill data and without head.
* @param relativeRowIndex Nullable.It is null in the case of fill data.
* @param isHead It will always be false when fill data.
*/
@Override
public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List<WriteCellData<?>> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
//验证是否为表头 为表头则跳过
if(isHead){
//为表头 跳过
return;
}
//获取当前列 下标 以及行 下标
int columnIndex = cell.getColumnIndex();
int rowIndex = cell.getRowIndex();
//验证该行,该单元格是否需要合并 只判断是否为需要合并的列 以及合并的最后一行
//验证是否为空
if(ObjectUtils.isEmpty(mergeCellDtos)){
//直接返回
return;
}
//转换为需要合并的列下标对应的合并信息集合Map
Map<Integer, List<MergeCellDTO>> mergeCellDtoMapByCellIndex = mergeCellDtos.stream().collect(Collectors.groupingBy(e -> e.getCellIndex()));
List<MergeCellDTO> cellDtos = mergeCellDtoMapByCellIndex.get(columnIndex);
//验证该行该单元格是否需要向上合并 true:需要 false:不需要
//验证是否为空
if(ObjectUtils.isEmpty(cellDtos)){
//为空 返回
return;
}
//不为空 说明该列存在需要合并的信息 验证当前行是否为合并信息的最后合并行如果是 则需要向上合并
List<MergeCellDTO> mergeCells = cellDtos
.stream()
.filter(e -> e.getEndRow() == rowIndex)
.collect(Collectors.toList());
//验证是否为空
if(ObjectUtils.isEmpty(mergeCells)){
//为空 返回
return;
}
//不为空 拿到需要合并的信息 该集合中其实只有一个
MergeCellDTO mergeCellDTO = mergeCells.get(0);
//调用合并方法
mergeCell(writeSheetHolder,cell,mergeCellDTO);
}
/**
* 合并单元格
* @param writeSheetHolder sheet 当前sheet页
* @param cell 当前单元格
* @param mergeCellDTO 合并信息
*/
private void mergeCell(WriteSheetHolder writeSheetHolder, Cell cell, MergeCellDTO mergeCellDTO) {
//开始合并
//获取当前sheet对象
Sheet sheet = writeSheetHolder.getSheet();
//添加合并区域 开始行 结束行 开始列 结束列
CellRangeAddress cellAddresses = new CellRangeAddress(mergeCellDTO.getStartRow(), mergeCellDTO.getEndRow()
, mergeCellDTO.getCellIndex(), mergeCellDTO.getCellIndex());
//添加合并区域
sheet.addMergedRegion(cellAddresses);
}
CellWriteHandler(单元格写入处理器)接口有
beforeCellCreate(单元格创建前调用),beforeCellCreate(单元格创建后调用),afterCellDispose(该方法会在单元格上的所有操作完成后调用)
等一系列方法,可根据自己的需求去实现。
我这里只实现了该接口的 afterCellDispose 方法,该方法会在单元格上的所有操作完成后调用,每一个单元格操作完之后都会进行调用该方法。
在该方法中去判断当前 单元格是否需要合并,向当前列的上行合并,还是向当前列的下行合并,又或者是向左边,右边相邻列合并;
然后通过实例化一个合并区域对象:
//开始行 结束行 开始列 结束列
CellRangeAddress cellAddresses = new CellRangeAddress(
mergeCellDTO.getStartRow()
, mergeCellDTO.getEndRow()
, mergeCellDTO.getCellIndex()
, mergeCellDTO.getCellIndex());
再调用方法将合并区域对象放入sheet当中
//将合并区域添加到sheet
sheet.addMergedRegion(cellAddresses);
这样就完成合并了,是不是非常简单呢。
注意事项:
重复合并区域需要单独做处理,否则会报错:
例子:
Cannot add merged region A1:A7 to sheet because it overlaps with an existing merged region (A1:AF1).