单元格合并的思想:
通过检测上下文中是否有相同的内容来决定是否合并单元格,当多个相邻的单元格内容相同时,则进行合并
单元格合并的实现:
实体类
java
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Employee {
@ExcelProperty("部门")
private String department;
@ExcelProperty("姓名")
private String name;
@ExcelProperty("职位")
private String position;
}
合并的实现
行合并
多行内容相同时进行合并
定义配置类
java
import com.alibaba.excel.metadata.CellData;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.write.handler.CellWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteTableHolder;
import org.apache.poi.ss.util.CellRangeAddress;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
/**
* 真正能用的合并策略
*/
public class RowMerge implements CellWriteHandler {
private final Set<Integer> mergeColumnIndexes;
public RowMerge(Integer... columns) {
this.mergeColumnIndexes = new HashSet<>(Arrays.asList(columns));
}
@Override
public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder,
List<CellData> list, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
if (isHead || cell == null) {
return;
}
int currentRowIndex = cell.getRowIndex();
int currentColIndex = cell.getColumnIndex();
// 只处理指定的合并列
if (!mergeColumnIndexes.contains(currentColIndex)) {
return;
}
Sheet sheet = writeSheetHolder.getSheet();
// 直接处理当前单元格的合并
String currentValue = getCellValue(sheet, currentRowIndex, currentColIndex);
if (currentValue == null || currentValue.isEmpty()) {
return;
}
// 向上查找连续相同值的起始行
int startRow = currentRowIndex;
for (int row = currentRowIndex - 1; row >= 0; row--) {
String value = getCellValue(sheet, row, currentColIndex);
if (currentValue.equals(value)) {
startRow = row;
} else {
break;
}
}
// 向下查找连续相同值的结束行
int endRow = currentRowIndex;
int lastRowNum = sheet.getLastRowNum();
for (int row = currentRowIndex + 1; row <= lastRowNum; row++) {
String value = getCellValue(sheet, row, currentColIndex);
if (currentValue.equals(value)) {
endRow = row;
} else {
break;
}
}
// 如果找到需要合并的区域(至少2行)
if (startRow < endRow) {
// 直接合并,不检查是否已存在
mergeCells(sheet, startRow, endRow, currentColIndex, currentValue);
}
}
/**
* 执行合并
*/
private void mergeCells(Sheet sheet, int startRow, int endRow, int colIndex, String value) {
try {
// 先检查并移除可能重叠的合并区域
removeOverlappingRegions(sheet, startRow, endRow, colIndex);
CellRangeAddress mergeRegion = new CellRangeAddress(startRow, endRow, colIndex, colIndex);
sheet.addMergedRegion(mergeRegion);
System.out.println("成功合并: 行[" + startRow + "-" + endRow + "], 列[" + colIndex + "], 值: " + value);
} catch (Exception e) {
System.err.println("合并失败: " + startRow + "-" + endRow + " 列:" + colIndex + " 错误: " + e.getMessage());
}
}
/**
* 移除重叠的合并区域
*/
private void removeOverlappingRegions(Sheet sheet, int startRow, int endRow, int colIndex) {
CellRangeAddress targetRegion = new CellRangeAddress(startRow, endRow, colIndex, colIndex);
// 从后往前遍历,避免索引问题
for (int i = sheet.getNumMergedRegions() - 1; i >= 0; i--) {
CellRangeAddress existingRegion = sheet.getMergedRegion(i);
// 只移除同列的重叠区域
if (existingRegion.getFirstColumn() == colIndex &&
existingRegion.getLastColumn() == colIndex &&
targetRegion.intersects(existingRegion)) {
sheet.removeMergedRegion(i);
}
}
}
/**
* 获取单元格值
*/
private String getCellValue(Sheet sheet, int rowIndex, int colIndex) {
Row row = sheet.getRow(rowIndex);
if (row == null) {
return null;
}
Cell cell = row.getCell(colIndex);
return getCellValue(cell);
}
private String getCellValue(Cell cell) {
if (cell == null) {
return null;
}
switch (cell.getCellType()) {
case STRING:
return cell.getStringCellValue();
case NUMERIC:
double numValue = cell.getNumericCellValue();
if (numValue == (int) numValue) {
return String.valueOf((int) numValue);
} else {
return String.valueOf(numValue);
}
case BOOLEAN:
return String.valueOf(cell.getBooleanCellValue());
case FORMULA:
try {
return cell.getStringCellValue();
} catch (Exception e) {
return String.valueOf(cell.getNumericCellValue());
}
default:
return "";
}
}
// 其他需要实现的方法
@Override
public void beforeCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder,
Row row, Head head, Integer columnIndex, Integer relativeRowIndex, Boolean isHead) {
}
@Override
public void afterCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder,
Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
}
@Override
public void afterCellDataConverted(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder,
CellData cellData, Cell cell, Head head, Integer integer, Boolean aBoolean) {
}
}
导出实现
java
public static void main(String[] args) {
String projectRoot = System.getProperty("user.dir");
// 构建完整文件路径
String urlString = "C:\\Users\\Administrator\\Desktop\\789\\456.xlsx";
File fileUrl = new File(urlString);
List<Employee> employees = Arrays.asList(
new Employee("技术部", "张三", "工程师"),
new Employee("技术部", "李四", "工程师"),
new Employee("技术部", "王五", "架构师"),
new Employee("市场部", "市场部", "经理"),
new Employee("市场部", "钱七", "经理")
);
EasyExcel.write(fileUrl, Employee.class)
.registerWriteHandler(new RowMerge(0,1,2)) //用来控制合并哪几列,这里指的0,1,2列的合并
.sheet("员工信息")
.doWrite(employees);
}
列合并
多列内容相同时进行合并
定义配置类
java
import com.alibaba.excel.metadata.CellData;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.write.handler.CellWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteTableHolder;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* 列合并策略 - 横向合并相同值的单元格并居中显示
*/
public class ColumnMerge implements CellWriteHandler {
private final Set<Integer> mergeRowIndexes;
public ColumnMerge(Integer... rows) {
this.mergeRowIndexes = new HashSet<>(Arrays.asList(rows));
}
@Override
public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder,
List<CellData> list, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
if (isHead || cell == null) {
return;
}
int currentRowIndex = cell.getRowIndex();
int currentColIndex = cell.getColumnIndex();
// 只处理指定的合并行
if (!mergeRowIndexes.contains(currentRowIndex)) {
return;
}
Sheet sheet = writeSheetHolder.getSheet();
Workbook workbook = sheet.getWorkbook();
// 直接处理当前单元格的合并
String currentValue = getCellValue(sheet, currentRowIndex, currentColIndex);
if (currentValue == null || currentValue.isEmpty()) {
return;
}
// 向左查找连续相同值的起始列
int startCol = currentColIndex;
for (int col = currentColIndex - 1; col >= 0; col--) {
String value = getCellValue(sheet, currentRowIndex, col);
if (currentValue.equals(value)) {
startCol = col;
} else {
break;
}
}
// 向右查找连续相同值的结束列
int endCol = currentColIndex;
Row currentRow = sheet.getRow(currentRowIndex);
int lastColNum = currentRow != null ? currentRow.getLastCellNum() - 1 : 0;
for (int col = currentColIndex + 1; col <= lastColNum; col++) {
String value = getCellValue(sheet, currentRowIndex, col);
if (currentValue.equals(value)) {
endCol = col;
} else {
break;
}
}
// 如果找到需要合并的区域(至少2列)
if (startCol < endCol) {
// 合并并设置居中
mergeAndCenterCells(sheet, workbook, currentRowIndex, startCol, endCol, currentValue);
}
}
/**
* 执行合并并设置居中
*/
private void mergeAndCenterCells(Sheet sheet, Workbook workbook, int rowIndex, int startCol, int endCol, String value) {
try {
// 先检查并移除可能重叠的合并区域
removeOverlappingRegions(sheet, rowIndex, startCol, endCol);
// 创建合并区域
CellRangeAddress mergeRegion = new CellRangeAddress(rowIndex, rowIndex, startCol, endCol);
sheet.addMergedRegion(mergeRegion);
// 获取合并区域的第一个单元格(合并后只有第一个单元格有数据)
Row row = sheet.getRow(rowIndex);
if (row == null) {
row = sheet.createRow(rowIndex);
}
Cell firstCell = row.getCell(startCol);
if (firstCell == null) {
firstCell = row.createCell(startCol);
}
// 创建居中样式
CellStyle centerStyle = createCenterStyle(workbook);
firstCell.setCellStyle(centerStyle);
System.out.println("成功列合并并居中: 行[" + rowIndex + "], 列[" + startCol + "-" + endCol + "], 值: " + value);
} catch (Exception e) {
System.err.println("列合并失败: 行[" + rowIndex + "], 列[" + startCol + "-" + endCol + "] 错误: " + e.getMessage());
}
}
/**
* 创建居中样式
*/
private CellStyle createCenterStyle(Workbook workbook) {
CellStyle style = workbook.createCellStyle();
// 设置水平居中
style.setAlignment(HorizontalAlignment.CENTER);
// 设置垂直居中
style.setVerticalAlignment(VerticalAlignment.CENTER);
// 设置边框(可选)
style.setBorderTop(BorderStyle.THIN);
style.setBorderBottom(BorderStyle.THIN);
style.setBorderLeft(BorderStyle.THIN);
style.setBorderRight(BorderStyle.THIN);
return style;
}
/**
* 移除重叠的合并区域
*/
private void removeOverlappingRegions(Sheet sheet, int rowIndex, int startCol, int endCol) {
CellRangeAddress targetRegion = new CellRangeAddress(rowIndex, rowIndex, startCol, endCol);
// 从后往前遍历,避免索引问题
for (int i = sheet.getNumMergedRegions() - 1; i >= 0; i--) {
CellRangeAddress existingRegion = sheet.getMergedRegion(i);
// 只移除同行的重叠区域
if (existingRegion.getFirstRow() == rowIndex &&
existingRegion.getLastRow() == rowIndex &&
targetRegion.intersects(existingRegion)) {
sheet.removeMergedRegion(i);
}
}
}
/**
* 获取单元格值
*/
private String getCellValue(Sheet sheet, int rowIndex, int colIndex) {
Row row = sheet.getRow(rowIndex);
if (row == null) {
return null;
}
Cell cell = row.getCell(colIndex);
return getCellValue(cell);
}
private String getCellValue(Cell cell) {
if (cell == null) {
return null;
}
switch (cell.getCellType()) {
case STRING:
return cell.getStringCellValue();
case NUMERIC:
double numValue = cell.getNumericCellValue();
if (numValue == (int) numValue) {
return String.valueOf((int) numValue);
} else {
return String.valueOf(numValue);
}
case BOOLEAN:
return String.valueOf(cell.getBooleanCellValue());
case FORMULA:
try {
return cell.getStringCellValue();
} catch (Exception e) {
return String.valueOf(cell.getNumericCellValue());
}
default:
return "";
}
}
// 其他需要实现的方法
@Override
public void beforeCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder,
Row row, Head head, Integer columnIndex, Integer relativeRowIndex, Boolean isHead) {
}
@Override
public void afterCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder,
Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
}
@Override
public void afterCellDataConverted(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder,
CellData cellData, Cell cell, Head head, Integer integer, Boolean aBoolean) {
}
}
导出实现
java
public static void main(String[] args) {
String projectRoot = System.getProperty("user.dir");
// 构建完整文件路径
String urlString = "C:\\Users\\Administrator\\Desktop\\789\\456.xlsx";
File fileUrl = new File(urlString);
List<Employee> employees = Arrays.asList(
new Employee("技术部", "张三", "工程师"),
new Employee("技术部", "李四", "工程师"),
new Employee("技术部", "王五", "架构师"),
new Employee("市场部", "市场部", "经理"),
new Employee("市场部", "钱七", "经理")
);
EasyExcel.write(fileUrl, Employee.class)
.registerWriteHandler(new ColumnMerge(0,1,2)) //用来控制合并哪几行,这里指的0,1,2列的合并
.sheet("员工信息")
.doWrite(employees);
}
列和行都合并
列和行有重复的就合并
这个要注意,如果列和行有冲突(比如:多列和后面一行一摸一样,那么就有问题了),我这的解决办法是优先合并行,忽略列
定义配置类
java
import com.alibaba.excel.metadata.CellData;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.write.handler.CellWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteTableHolder;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* 统一合并策略 - 修复列合并版本
*/
public class RowColumnMerge implements CellWriteHandler {
private final Set<Integer> rowMergeColumns; // 需要行合并的列
private final Set<Integer> columnMergeRows; // 需要列合并的行
public UnifiedMergeStrategy(Integer[] rowMergeCols, Integer[] columnMergeRows) {
this.rowMergeColumns = new HashSet<>(Arrays.asList(rowMergeCols));
this.columnMergeRows = new HashSet<>(Arrays.asList(columnMergeRows));
}
@Override
public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder,
List<CellData> list, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
if (isHead || cell == null) {
return;
}
Sheet sheet = writeSheetHolder.getSheet();
Workbook workbook = sheet.getWorkbook();
int currentRow = cell.getRowIndex();
int currentCol = cell.getColumnIndex();
// 处理行合并(纵向合并)
if (rowMergeColumns.contains(currentCol)) {
processRowMerge(sheet, workbook, currentRow, currentCol);
}
// 处理列合并(横向合并)
if (columnMergeRows.contains(currentRow)) {
processColumnMerge(sheet, workbook, currentRow, currentCol);
}
}
/**
* 处理行合并(纵向)
*/
private void processRowMerge(Sheet sheet, Workbook workbook, int currentRow, int currentCol) {
String currentValue = getCellValue(sheet, currentRow, currentCol);
if (currentValue == null || currentValue.isEmpty()) return;
// 向上查找起始行
int startRow = currentRow;
for (int row = currentRow - 1; row >= 0; row--) {
String value = getCellValue(sheet, row, currentCol);
if (isSameValue(currentValue, value)) {
startRow = row;
} else {
break;
}
}
// 向下查找结束行
int endRow = currentRow;
for (int row = currentRow + 1; row <= sheet.getLastRowNum(); row++) {
String value = getCellValue(sheet, row, currentCol);
if (isSameValue(currentValue, value)) {
endRow = row;
} else {
break;
}
}
// 需要合并(至少2行)
if (startRow < endRow) {
mergeCells(sheet, workbook, startRow, endRow, currentCol, currentCol, currentValue, "行合并");
}
}
/**
* 处理列合并(横向)- 简化版本
*/
private void processColumnMerge(Sheet sheet, Workbook workbook, int currentRow, int currentCol) {
String currentValue = getCellValue(sheet, currentRow, currentCol);
if (currentValue == null || currentValue.isEmpty()) return;
// 直接向右查找连续相同值的列
int startCol = currentCol;
int endCol = currentCol;
// 向左查找
for (int col = currentCol - 1; col >= 0; col--) {
String value = getCellValue(sheet, currentRow, col);
if (isSameValue(currentValue, value)) {
startCol = col;
} else {
break;
}
}
// 向右查找
for (int col = currentCol + 1; col <= 100; col++) { // 设置一个足够大的列数
String value = getCellValue(sheet, currentRow, col);
if (isSameValue(currentValue, value)) {
endCol = col;
} else {
break;
}
}
// 需要合并(至少2列)
if (startCol < endCol) {
mergeCells(sheet, workbook, currentRow, currentRow, startCol, endCol, currentValue, "列合并");
}
}
/**
* 执行合并
*/
private void mergeCells(Sheet sheet, Workbook workbook, int startRow, int endRow,
int startCol, int endCol, String value, String mergeType) {
try {
// 创建合并区域
CellRangeAddress mergeRegion = new CellRangeAddress(startRow, endRow, startCol, endCol);
sheet.addMergedRegion(mergeRegion);
// 设置样式
setMergedRegionStyle(sheet, workbook, mergeRegion);
System.out.println("成功" + mergeType + ": 行[" + startRow + "-" + endRow +
"], 列[" + startCol + "-" + endCol + "], 值: " + value);
} catch (Exception e) {
// 如果合并失败,可能是因为重叠,尝试移除重叠区域后重新合并
if (e.getMessage().contains("overlaps")) {
try {
removeOverlappingRegions(sheet, startRow, endRow, startCol, endCol);
CellRangeAddress mergeRegion = new CellRangeAddress(startRow, endRow, startCol, endCol);
sheet.addMergedRegion(mergeRegion);
setMergedRegionStyle(sheet, workbook, mergeRegion);
System.out.println("重试成功" + mergeType + ": 行[" + startRow + "-" + endRow +
"], 列[" + startCol + "-" + endCol + "], 值: " + value);
} catch (Exception ex) {
System.err.println(mergeType + "最终失败: 行[" + startRow + "-" + endRow +
"], 列[" + startCol + "-" + endCol + "] - " + ex.getMessage());
}
} else {
System.err.println(mergeType + "失败: 行[" + startRow + "-" + endRow +
"], 列[" + startCol + "-" + endCol + "] - " + e.getMessage());
}
}
}
/**
* 移除重叠区域
*/
private void removeOverlappingRegions(Sheet sheet, int startRow, int endRow, int startCol, int endCol) {
CellRangeAddress target = new CellRangeAddress(startRow, endRow, startCol, endCol);
for (int i = sheet.getNumMergedRegions() - 1; i >= 0; i--) {
CellRangeAddress existing = sheet.getMergedRegion(i);
if (target.intersects(existing)) {
sheet.removeMergedRegion(i);
System.out.println("移除重叠区域: 行[" + existing.getFirstRow() + "-" + existing.getLastRow() +
"], 列[" + existing.getFirstColumn() + "-" + existing.getLastColumn() + "]");
}
}
}
/**
* 设置合并区域样式
*/
private void setMergedRegionStyle(Sheet sheet, Workbook workbook, CellRangeAddress region) {
CellStyle centerStyle = createCenterStyle(workbook);
// 只设置第一个单元格的样式(合并后只有第一个单元格显示内容)
Row row = sheet.getRow(region.getFirstRow());
if (row != null) {
Cell cell = row.getCell(region.getFirstColumn());
if (cell != null) {
cell.setCellStyle(centerStyle);
}
}
}
/**
* 创建居中样式(无边框)
*/
private CellStyle createCenterStyle(Workbook workbook) {
CellStyle style = workbook.createCellStyle();
style.setAlignment(HorizontalAlignment.CENTER);
style.setVerticalAlignment(VerticalAlignment.CENTER);
// 去掉边框设置
return style;
}
private boolean isSameValue(String value1, String value2) {
if (value1 == null && value2 == null) return true;
if (value1 == null || value2 == null) return false;
return value1.equals(value2);
}
private String getCellValue(Sheet sheet, int rowIndex, int colIndex) {
Row row = sheet.getRow(rowIndex);
if (row == null) return null;
Cell cell = row.getCell(colIndex);
return getCellValue(cell);
}
private String getCellValue(Cell cell) {
if (cell == null) return null;
switch (cell.getCellType()) {
case STRING: return cell.getStringCellValue();
case NUMERIC:
double numValue = cell.getNumericCellValue();
return numValue == (int) numValue ? String.valueOf((int) numValue) : String.valueOf(numValue);
case BOOLEAN: return String.valueOf(cell.getBooleanCellValue());
case FORMULA:
try { return cell.getStringCellValue(); }
catch (Exception e) { return String.valueOf(cell.getNumericCellValue()); }
default: return "";
}
}
@Override
public void beforeCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder,
Row row, Head head, Integer columnIndex, Integer relativeRowIndex, Boolean isHead) {
}
@Override
public void afterCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder,
Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
}
@Override
public void afterCellDataConverted(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder,
CellData cellData, Cell cell, Head head, Integer integer, Boolean aBoolean) {
}
}
导出的实现
java
public static void main(String[] args) {
String projectRoot = System.getProperty("user.dir");
// 构建完整文件路径
String urlString = "C:\\Users\\Administrator\\Desktop\\789\\456.xlsx";
File fileUrl = new File(urlString);
List<Employee> employees = Arrays.asList(
new Employee("技术部", "张三", "工程师"),
new Employee("技术部", "李四", "工程师"),
new Employee("技术部", "王五", "架构师"),
new Employee("市场部", "市场部", "经理"),
new Employee("市场部", "钱七", "经理")
);
// 使用统一合并策略
EasyExcel.write(fileUrl, Employee.class)
.registerWriteHandler(new UnifiedMergeStrategy(
new Integer[]{0, 1, 2}, // 需要行合并的列(指定的才会合并)
new Integer[]{0, 1, 2,3,4,5} // 需要列合并的行(指定的才会合并)
))
.sheet("员工信息")
.doWrite(employees);
}