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的使用)