目录
3. 自动合并单元格 行合并为主,列合并为辅 (内容相同就合并)
简言
EasyExcel是一个基于Java的简单、省内存的读写Excel的开源项目。在尽可能节约内存的情况下支持读写百M的Excel。 之前开发也做过excel导出,但是都是公司前人已经封装好的工具类调用进行导出(公司用的Apache poi)。便在自己写的项目中添加 EasyExcel 进行练习,以备不时之需。
EasyExcel 版本
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.0.5</version>
</dependency>
POI合并单元格--CellRangeAddress
CellRangeAddress有4个参数:起始行号,终止行号, 起始列号,终止列号
CellRangeAddress(2, 3, 4, 5); 代表合并的区域范围 E3 ,F4 第2行起 第3行终止 第4列开始 第5列结束。
注意:
1.起始行号 不可能大于 终止行号(错误:new CellRangeAddress(1, 0, 0, 0))。
2.起始列号 不可能大于 终止列号(错误:new CellRangeAddress(0, 0, 1, 0))。
3.execl的行列都是从0开始,而不是从1开始。
一个sheet页的单元格合并展示样式,是由一个sheet页中的CellRangeAddress集合所控制。
EasyExcel 自定义合并单元格 也就是对每个sheet页中的CellRangeAddress集合进行操作!
自定义策略
下面是合并单元格策略(代码仅供参考理解,具体策略需要根据自身实际业务进行自定义!)
自定义单元格样式
CellStyle:
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.metadata.style.WriteFont;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.IndexedColors;
import org.apache.poi.ss.usermodel.VerticalAlignment;
public class CellStyle {
public static HorizontalCellStyleStrategy getCellCenterStyle(){
WriteCellStyle headWriteCellStyle = new WriteCellStyle();
//设置背景颜色
headWriteCellStyle.setFillForegroundColor(IndexedColors.WHITE.getIndex());
//设置头字体
WriteFont headWriteFont = new WriteFont();
headWriteFont.setFontHeightInPoints((short)13);
headWriteFont.setBold(true);
headWriteCellStyle.setWriteFont(headWriteFont);
//设置头居中
headWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
//内容策略
WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
//设置 水平居中
contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
//设置 垂直居中
contentWriteCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
HorizontalCellStyleStrategy horizontalCellStyleStrategy = new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle);
return horizontalCellStyleStrategy;
}
}
自定义单元格合并Handler
1. 根据指定的列进行合并 , 向上合并相同内容单元格
ExcelFillCellLineMergeHandler:
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.metadata.data.WriteCellData;
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.Cell;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.util.CellRangeAddress;
import java.util.List;
/**
* @author Inori
* @version 1.0.0-SNAPSHOT
* @Description : 自定义合并单元格策略Handler 列合并 向上合并相同单元格
* @create 2022-01-27 9:52
*/
public class ExcelFillCellLineMergeHandler implements CellWriteHandler {
private int[] mergeColumnIndex; //自定义合并单元格的列 例: int[] mergeColumeIndex = {0, 1, 11};
private int mergeRowIndex; //自定义合并单元格的行 一般来说填表头行高 例如行高位为3则 int mergeRowIndex = 3;
public ExcelFillCellLineMergeHandler() {
}
public ExcelFillCellLineMergeHandler(int mergeRowIndex, int[] mergeColumnIndex) {
this.mergeRowIndex = mergeRowIndex;
this.mergeColumnIndex = mergeColumnIndex;
}
//单元格创造之前的操作
@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) {
}
//afterCellDispose方法是在单元格创建后销毁前的一个时机。这时候我们可以改变单元格内容。
//mergeColumnIndex 自定义合并单元格的列 例: int[] mergeColumeIndex = {0, 1, 11};
//mergeRowIndex 自定义合并单元格的行 //一般来说填表头行高 如表头行高位为3则 int mergeRowIndex = 3;
@Override
public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List<WriteCellData<?>> list, Cell cell, Head head, Integer integer, Boolean aBoolean) {
int curRowIndex = cell.getRowIndex(); //当前单元格的行数
int curColIndex = cell.getColumnIndex(); // 当前单元格的列数
if (curRowIndex > mergeRowIndex) {
for (int i = 0; i < mergeColumnIndex.length; i++) {
if (curColIndex == mergeColumnIndex[i]) {
//单元格数据处理
mergeWithPrevRow(writeSheetHolder, cell, curRowIndex, curColIndex);
break;
}
}
}
}
/**
* @description 当前单元格向上合并
* cell 当前单元格
* curRowIndex 当前行
* curColIndex 当前列
* @author Inori
* @date 2022/1/26 15:50
* @params [writeSheetHolder, cell, curRowIndex, curColIndex]
* @return void
* @throws
*/
private void mergeWithPrevRow(WriteSheetHolder writeSheetHolder, Cell cell, int curRowIndex, int curColIndex) {
//当前单元格中数据
Object curData = cell.getCellType() == CellType.STRING ? cell.getStringCellValue() : cell.getNumericCellValue();
//获取当前单元格的上面一个单元格
Cell preCell = cell.getSheet().getRow(curRowIndex - 1).getCell(curColIndex);
//获取当前单元格的上面一个单元格中的数据
Object preData = preCell.getCellType() == CellType.STRING ? preCell.getStringCellValue() : preCell.getNumericCellValue();
// 将当前单元格数据与上一个单元格数据比较
if (preData.equals(curData)) {
//获取当前sheet页
Sheet sheet = writeSheetHolder.getSheet();
//得到所有的合并单元格
List<CellRangeAddress> mergeRegions = sheet.getMergedRegions();
//是否合并
boolean isMerged = false;
for (int i = 0; i < mergeRegions.size() && !isMerged; i++) {
//CellRangeAddress POI合并单元格
//CellRangeAddress(int firstRow, int lastRow, int firstCol, int lastCol)
//例子:CellRangeAddress(2, 6000, 3, 3);
//第2行起 第6000行终止 第3列开始 第3列结束。
CellRangeAddress cellRangeAddr = mergeRegions.get(i);
// cellRangeAddr.isInRange(int rowInd, int colInd)确定给定坐标是否在此范围的范围内。
// 若上一个单元格已经被合并,则先移出原有的合并单元,再重新添加合并单元
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);
}
}
}
}
代码调用(数据就图个乐呵实在不会编!)
//Excel相同表头自动合并 ,Excel表格内容数据 指定列进行合并,从指定行后开始!列内容相同进行合并!
@PostMapping("/tableDataLineMerge")
public void tableDataLineMerge(HttpServletResponse response) throws IOException {
//Excel表头数据
List<List<String>> headers=new ArrayList<>();
//基础信息
//序号
List<String> baseSerialHead=new ArrayList<>();
baseSerialHead.add("基础信息");
baseSerialHead.add("基础信息");
baseSerialHead.add("序号");
//商家
List<String> baseShopHead=new ArrayList<>();
baseShopHead.add("基础信息");
baseShopHead.add("基础信息");
baseShopHead.add("商家");
//年份
List<String> baseYearHead=new ArrayList<>();
baseYearHead.add("基础信息");
baseYearHead.add("基础信息");
baseYearHead.add("年份");
//商品列表(饮品)
//矿泉水
//娃哈哈
List<String> DrinkWahahaHead=new ArrayList<>();
DrinkWahahaHead.add("饮品");
DrinkWahahaHead.add("矿泉水");
DrinkWahahaHead.add("娃哈哈");
//农夫山泉
List<String> DrinkNongfuHead=new ArrayList<>();
DrinkNongfuHead.add("饮品");
DrinkNongfuHead.add("矿泉水");
DrinkNongfuHead.add("农夫山泉");
//饮料
//北冰洋
List<String> DrinkArcticHead=new ArrayList<>();
DrinkArcticHead.add("饮品");
DrinkArcticHead.add("饮料");
DrinkArcticHead.add("北冰洋");
//宏宝莱
List<String> DrinkHBLHead=new ArrayList<>();
DrinkHBLHead.add("饮品");
DrinkHBLHead.add("饮料");
DrinkHBLHead.add("宏宝莱");
//Excel表头数据 填充
headers.add(baseSerialHead);
headers.add(baseShopHead);
headers.add(baseYearHead);
headers.add(DrinkWahahaHead);
headers.add(DrinkNongfuHead);
headers.add(DrinkArcticHead);
headers.add(DrinkHBLHead);
//Excel表格内容数据
List<List<String>> datas=new ArrayList<>();
//第一行数据
List<String> dataFirst=new ArrayList<>();
dataFirst.add("1");
dataFirst.add("A");
dataFirst.add("2010");
dataFirst.add("1.0");
dataFirst.add("1.5");
dataFirst.add("5.0");
dataFirst.add("1.5");
//第二行数据
List<String> dataSecond=new ArrayList<>();
dataSecond.add("2");
dataSecond.add("A");
dataSecond.add("2020");
dataSecond.add("2.0");
dataSecond.add("2.5");
dataSecond.add("8.0");
dataSecond.add("2.0");
//第三行数据
List<String> dataThird=new ArrayList<>();
dataThird.add("3");
dataThird.add("B");
dataThird.add("2010");
dataThird.add("0.9");
dataThird.add("1.4");
dataThird.add("4.5");
dataThird.add("2.0");
//第四行数据
List<String> dataFourth=new ArrayList<>();
dataFourth.add("4");
dataFourth.add("B");
dataFourth.add("2020");
dataFourth.add("2.0");
dataFourth.add("2.5");
dataFourth.add("8.0");
dataFourth.add("2.0");
//Excel表格内容数据填充
datas.add(dataFirst);
datas.add(dataSecond);
datas.add(dataThird);
datas.add(dataFourth);
//excel生成
// 这里注意 有同学反应使用swagger 会导致各种问题,请直接用浏览器或者用postman
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
// 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
String fileName = URLEncoder.encode("指定列合并", "UTF-8").replaceAll("\\+", "%20");
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
//自定义合并单元格的列 (根据自身数据情况指定合并的列):如我们想合并 商家
int[] mergeColumeIndex = {1};
//自定义合并单元格的行
int mergeRowIndex = 3;//一般设置为Excel表头数据高
//所有配置都是继承的,Workbook的配置会被Sheet继承,所以在用EasyExcel设置参数的时候,在EasyExcel...sheet()方法之前作用域是整个sheet,之后针对单个sheet
EasyExcel.write(response.getOutputStream()).registerWriteHandler(new ExcelFillCellLineMergeHandler(mergeRowIndex, mergeColumeIndex)).head(headers).sheet("sheet1").doWrite(datas);
}
调用展示:
合并前:
合并后
2. 根据指定的列进行合并 , 向上合并相同内容单元格
ExcelFillCellRowMergeHandler:
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.write.handler.CellWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteTableHolder;
import com.wty.model.CellLineRange;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.util.CellRangeAddress;
import java.util.List;
/**
* @author Inori
* @version 1.0.0-SNAPSHOT
* @Description : 自定义合并单元格策略Handler 行合并 向左合并相同单元格
* @create 2022-01-27 9:52
*/
public class ExcelFillCellRowMergeHandler implements CellWriteHandler {
private List<CellLineRange> cellLineRangeList; //自定义合并单元格的列 如果想合并 第4列和第5例 、第6列和第7例: [CellLineRange(firstCol=3, lastCol=4), CellLineRange(firstCol=5, lastCol=6)]
private int mergeRowIndex; //自定义合并单元格的开始的行 一般来说填表头行高-1 表示从表头下每列开始合并 :如表头行高位为3则 int mergeRowIndex = 2 ;
public ExcelFillCellRowMergeHandler() {
}
public ExcelFillCellRowMergeHandler(List<CellLineRange> cellLineRangeList, int mergeRowIndex) {
this.cellLineRangeList=cellLineRangeList;
this.mergeRowIndex=mergeRowIndex;
}
//单元格创造之前的操作
@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) {
}
//afterCellDispose方法是在单元格创建后销毁前的一个时机。这时候我们可以改变单元格内容。
//cellLineRangeList 自定义合并单元格的列 如果想合并 第4列和第5例 、第6列和第7例: [CellLineRange(firstCol=3, lastCol=4), CellLineRange(firstCol=5, lastCol=6)]
//mergeRowIndex 自定义合并单元格的行 一般来说填表头行高-1 表示从表头下每列开始合并 :如表头行高位为3则 int mergeRowIndex = 2 ;
@Override
public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List<WriteCellData<?>> list, Cell cell, Head head, Integer integer, Boolean aBoolean) {
int curRowIndex = cell.getRowIndex(); //当前单元格的行数
int curColIndex = cell.getColumnIndex(); // 当前单元格的列数
if (curRowIndex > mergeRowIndex) {
for (int i = 0; i < cellLineRangeList.size(); i++) {
if (curColIndex > cellLineRangeList.get(i).getFirstCol()&&curColIndex<=cellLineRangeList.get(i).getLastCol()) {
//单元格数据处理
mergeWithLeftLine(writeSheetHolder, cell, curRowIndex, curColIndex);
break;
}
}
}
}
/**
* @description 当前单元格向左合并
* @author Inori
* @date 2022/1/27 15:39
* @params [writeSheetHolder, cell, curRowIndex, curColIndex]
* @return void
* @throws
*/
private void mergeWithLeftLine(WriteSheetHolder writeSheetHolder, Cell cell, int curRowIndex, int curColIndex) {
//当前单元格中数据
Object curData = cell.getCellType() == CellType.STRING ? cell.getStringCellValue() : cell.getNumericCellValue();
//获取当前单元格的左面一个单元格
Cell leftCell = cell.getSheet().getRow(curRowIndex).getCell(curColIndex - 1);
//获取当前单元格的左面一个单元格中的数据
Object leftData = leftCell.getCellType() == CellType.STRING ? leftCell.getStringCellValue() : leftCell.getNumericCellValue();
// 将当前单元格数据与左侧一个单元格数据比较
if (leftData.equals(curData)) {
//获取当前sheet页
Sheet sheet = writeSheetHolder.getSheet();
//得到所有的合并单元格
List<CellRangeAddress> mergeRegions = sheet.getMergedRegions();
//是否合并
boolean isMerged = false;
for (int i = 0; i < mergeRegions.size() && !isMerged; i++) {
//CellRangeAddress POI合并单元格
//CellRangeAddress(int firstRow, int lastRow, int firstCol, int lastCol)
//例子:CellRangeAddress(2, 6000, 3, 3);
//第2行起 第6000行终止 第3列开始 第3列结束。
CellRangeAddress cellRangeAddr = mergeRegions.get(i);
// cellRangeAddr.isInRange(int rowInd, int colInd)确定给定坐标是否在此范围的范围内。
// 若左侧一个单元格已经被合并,则先移出原有的合并单元,再重新添加合并单元
if (cellRangeAddr.isInRange(curRowIndex, curColIndex - 1)) {
sheet.removeMergedRegion(i);
cellRangeAddr.setLastColumn(curColIndex);
sheet.addMergedRegion(cellRangeAddr);
isMerged = true;
}
}
// 若左侧一个单元格未被合并,则新增合并单元
if (!isMerged) {
CellRangeAddress cellRangeAddress = new CellRangeAddress(curRowIndex , curRowIndex, curColIndex- 1, curColIndex);
sheet.addMergedRegion(cellRangeAddress);
}
}
}
}
CellLineRange:
import lombok.AllArgsConstructor;
import lombok.Data;
/**
* @author Inori
* @version 1.0.0-SNAPSHOT
* @Description :
* @create 2022-01-27 14:07
*/
@Data
@AllArgsConstructor
public class CellLineRange {
private int firstCol; //起始列
private int lastCol; //结束列
}
代码调用(数据就图个乐呵实在不会编!)
//Excel相同表头自动合并,Excel表格内容数据 指定列范围进行合并,从指定行后开始! 行内容相同进行合并!
@PostMapping("/tableDataRowMerge")
public void tableDataRowMerge(HttpServletResponse response) throws IOException {
//Excel表头数据
List<List<String>> headers=new ArrayList<>();
//基础信息
//序号
List<String> baseSerialHead=new ArrayList<>();
baseSerialHead.add("基础信息");
baseSerialHead.add("基础信息");
baseSerialHead.add("序号");
//商家
List<String> baseShopHead=new ArrayList<>();
baseShopHead.add("基础信息");
baseShopHead.add("基础信息");
baseShopHead.add("商家");
//年份
List<String> baseYearHead=new ArrayList<>();
baseYearHead.add("基础信息");
baseYearHead.add("基础信息");
baseYearHead.add("年份");
//商品列表(饮品)
//矿泉水
//娃哈哈
List<String> DrinkWahahaHead=new ArrayList<>();
DrinkWahahaHead.add("饮品");
DrinkWahahaHead.add("矿泉水");
DrinkWahahaHead.add("娃哈哈");
//农夫山泉
List<String> DrinkNongfuHead=new ArrayList<>();
DrinkNongfuHead.add("饮品");
DrinkNongfuHead.add("矿泉水");
DrinkNongfuHead.add("农夫山泉");
//饮料
//北冰洋
List<String> DrinkArcticHead=new ArrayList<>();
DrinkArcticHead.add("饮品");
DrinkArcticHead.add("饮料");
DrinkArcticHead.add("北冰洋");
//宏宝莱
List<String> DrinkHBLHead=new ArrayList<>();
DrinkHBLHead.add("饮品");
DrinkHBLHead.add("饮料");
DrinkHBLHead.add("宏宝莱");
//Excel表头数据 填充
headers.add(baseSerialHead);
headers.add(baseShopHead);
headers.add(baseYearHead);
headers.add(DrinkWahahaHead);
headers.add(DrinkNongfuHead);
headers.add(DrinkArcticHead);
headers.add(DrinkHBLHead);
//Excel表格内容数据
List<List<String>> datas=new ArrayList<>();
//第一行数据
List<String> dataFirst=new ArrayList<>();
dataFirst.add("1");
dataFirst.add("A");
dataFirst.add("2010");
dataFirst.add("1.5");
dataFirst.add("1.5");
dataFirst.add("5.0");
dataFirst.add("1.5");
//第二行数据
List<String> dataSecond=new ArrayList<>();
dataSecond.add("2");
dataSecond.add("A");
dataSecond.add("2020");
dataSecond.add("2.0");
dataSecond.add("2.0");
dataSecond.add("2.0");
dataSecond.add("2.0");
//第三行数据
List<String> dataThird=new ArrayList<>();
dataThird.add("3");
dataThird.add("B");
dataThird.add("2010");
dataThird.add("0.9");
dataThird.add("1.4");
dataThird.add("4.5");
dataThird.add("2.0");
//第四行数据
List<String> dataFourth=new ArrayList<>();
dataFourth.add("4");
dataFourth.add("B");
dataFourth.add("2020");
dataFourth.add("2.0");
dataFourth.add("2.5");
dataFourth.add("8.0");
dataFourth.add("8.0");
//Excel表格内容数据填充
datas.add(dataFirst);
datas.add(dataSecond);
datas.add(dataThird);
datas.add(dataFourth);
//excel生成
// 这里注意 有同学反应使用swagger 会导致各种问题,请直接用浏览器或者用postman
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
// 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
String fileName = URLEncoder.encode("指定列合并", "UTF-8").replaceAll("\\+", "%20");
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
//自定义合并单元格的列
ArrayList<CellLineRange> cellLineRanges=new ArrayList<>();
cellLineRanges.add(new CellLineRange(3,4));
cellLineRanges.add(new CellLineRange(5,6));
//自定义开始合并单元格的行
int mergeRowIndex = 2;//一般设置为Excel表头数据高 高位3的话就为2 例如:0,1,2
//所有配置都是继承的,Workbook的配置会被Sheet继承,所以在用EasyExcel设置参数的时候,在EasyExcel...sheet()方法之前作用域是整个sheet,之后针对单个sheet
EasyExcel.write(response.getOutputStream()).registerWriteHandler(getCellCenterStyle()).registerWriteHandler(new ExcelFillCellRowMergeHandler(cellLineRanges,mergeRowIndex)).head(headers).sheet("sheet1").doWrite(datas);
}
调用展示:
合并前:
合并后:
3. 自动合并单元格 行合并为主,列合并为辅 (内容相同就合并)
(这个我就是随便合并了,并没有对单元格的合并的范围进行控制!后续如果想要用的话可以定义生效的单元格区域范围!)
ExcelFillCellMergeHandler:
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.metadata.data.WriteCellData;
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.Cell;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.util.CellRangeAddress;
import java.util.List;
/**
* @author Inori
* @version 1.0.0-SNAPSHOT
* @Description : 自动合并单元格 行合并为主,列合并为辅
* @create 2022-01-27 15:28
*/
public class ExcelFillCellMergeHandler implements CellWriteHandler {
private int mergeColumnIndex; // 自定义合并单元格的列 列如从第一列开始 int mergeColumnIndex=0;
private int mergeRowIndex; //自定义合并单元格的行 默认填行高例如行高位为3则 int mergeRowIndex = 2 ;
public ExcelFillCellMergeHandler(){
}
public ExcelFillCellMergeHandler(int mergeColumnIndex,int mergeRowIndex){
this.mergeColumnIndex=mergeColumnIndex;
this.mergeRowIndex=mergeRowIndex;
}
//单元格创造之前的操作
@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) {
}
//afterCellDispose方法是在单元格创建后销毁前的一个时机。这时候我们可以改变单元格内容。
@Override
public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List<WriteCellData<?>> list, Cell cell, Head head, Integer integer, Boolean aBoolean) {
int curRowIndex = cell.getRowIndex(); //当前单元格的行数
int curColIndex = cell.getColumnIndex(); // 当前单元格的列数
if (curRowIndex>mergeRowIndex&&curColIndex>mergeColumnIndex){
mergeCell(writeSheetHolder, cell, curRowIndex, curColIndex);
}
}
/**
* @return void
* @throws
* @description 当前单元格向上合并
* cell 当前单元格
* curRowIndex 当前行
* curColIndex 当前列
* @author Inori
* @date 2022/1/26 15:50
* @params [writeSheetHolder, cell, curRowIndex, curColIndex]
*/
private void mergeCell(WriteSheetHolder writeSheetHolder, Cell cell, int curRowIndex, int curColIndex) {
//当前单元格中数据
Object curData = cell.getCellType() == CellType.STRING ? cell.getStringCellValue() : cell.getNumericCellValue();
//获取当前单元格的上面一个单元格
Cell preCell = cell.getSheet().getRow(curRowIndex - 1).getCell(curColIndex);
//获取当前单元格的上面一个单元格中的数据
Object preData = preCell.getCellType() == CellType.STRING ? preCell.getStringCellValue() : preCell.getNumericCellValue();
//获取当前单元格的左面一个单元格
Cell leftCell = cell.getSheet().getRow(curRowIndex).getCell(curColIndex - 1);
//获取当前单元格的左面一个单元格中的数据
Object leftData = leftCell.getCellType() == CellType.STRING ? leftCell.getStringCellValue() : leftCell.getNumericCellValue();
//当前单元格的左面一个单元格数据是否与当前单元格相同
boolean left = leftData.equals(curData);
//当前单元格的上面一个单元格数据是否与当前单元格相同
boolean pre = preData.equals(curData);
if (left) {
//获取当前sheet页
Sheet sheet = writeSheetHolder.getSheet();
//得到所有的合并单元格
List<CellRangeAddress> mergeRegions = sheet.getMergedRegions();
//判断左侧单元格是否参与合并 true 参与了 false没参与
boolean leftIsMerged = false;
//左侧合并单元格
CellRangeAddress cellRangeLeftAddr = null;
for (int i = 0; i < mergeRegions.size() && !leftIsMerged; i++) {
//CellRangeAddress 说明:POI合并单元格
//CellRangeAddress(int firstRow, int lastRow, int firstCol, int lastCol)
//例子:CellRangeAddress(2, 6000, 3, 3);
//第2行起 第6000行终止 第3列开始 第3列结束。
//左侧合并单元格
cellRangeLeftAddr = mergeRegions.get(i);
// cellRangeAddr.isInRange(int rowInd, int colInd)确定给定坐标是否在此范围的范围内。
// 若左侧单元格已经被合并,则先移出原有的合并单元格,并接下来进行数据处理
if (cellRangeLeftAddr.isInRange(curRowIndex, curColIndex - 1)) {
sheet.removeMergedRegion(i);
// cellRangeLeftAddr.setLastColumn(curColIndex);
leftIsMerged = true;
}
}
//左侧单元格是否参与了合并 true 参与了 false没参与
if (leftIsMerged) {
if (pre) {
//能否与上面合并单元格合并 true 能 false 不能
boolean preIsMerged = false;
for (int i = 0; i < mergeRegions.size() && !preIsMerged; i++) {
//CellRangeAddress POI合并单元格
//CellRangeAddress(int firstRow, int lastRow, int firstCol, int lastCol)
//例子:CellRangeAddress(2, 6000, 3, 3);
//第2行起 第6000行终止 第3列开始 第3列结束。
//上面合并单元格
CellRangeAddress cellRangePreAddr = mergeRegions.get(i);
// cellRangeAddr.isInRange(int rowInd, int colInd)确定给定坐标是否在此范围的范围内。
// 若上一个单元格已经被合并
if (cellRangePreAddr.isInRange(curRowIndex - 1, curColIndex)) {
//判断能否与上面合并单元格进行合并
//判断合并区域起始列否于左侧合并单元格起始列相同,并且最后一列是否与当前单元格列相同!
if (cellRangePreAddr.getFirstColumn() == cellRangeLeftAddr.getFirstColumn()
&&
cellRangePreAddr.getLastColumn() == curColIndex
) {
//当前单元格与上面合并单元格进行列合并
sheet.removeMergedRegion(i);
cellRangePreAddr.setLastRow(curRowIndex);
sheet.addMergedRegion(cellRangePreAddr);
preIsMerged = true;
}
//跳出当前循环
break;
}
}
if (!preIsMerged) {
//将当前单元格行合并
cellRangeLeftAddr.setLastColumn(curColIndex);
sheet.addMergedRegion(cellRangeLeftAddr);
}
} else {
//如果左侧合并单元格的起始行等于当前行!
if (cellRangeLeftAddr.getFirstRow()==curRowIndex){
//将当前单元格行合并
cellRangeLeftAddr.setLastColumn(curColIndex);
sheet.addMergedRegion(cellRangeLeftAddr);
}else {
//将左侧合并单元格减去一行
cellRangeLeftAddr.setLastRow(curRowIndex-1);
//从新生成当前单元格行合并
CellRangeAddress cellRangeAddress = new CellRangeAddress(curRowIndex , curRowIndex, cellRangeLeftAddr.getFirstColumn(), curColIndex);
sheet.addMergedRegion(cellRangeLeftAddr);
sheet.addMergedRegion(cellRangeAddress);
}
}
} else {
//生成新的行合并单元格!
CellRangeAddress cellRangeAddress = new CellRangeAddress(curRowIndex, curRowIndex, curColIndex - 1, curColIndex);
if (pre) {
//能否与上面合并单元格合并 true 能 false 不能
boolean preIsMerged = false;
for (int i = 0; i < mergeRegions.size() && !preIsMerged; i++) {
//CellRangeAddress POI合并单元格
//CellRangeAddress(int firstRow, int lastRow, int firstCol, int lastCol)
//例子:CellRangeAddress(2, 6000, 3, 3);
//第2行起 第6000行终止 第3列开始 第3列结束。
//上面合并单元格
CellRangeAddress cellRangePreAddr = mergeRegions.get(i);
// cellRangeAddr.isInRange(int rowInd, int colInd)确定给定坐标是否在此范围的范围内。
// 若上一个单元格已经被合并
if (cellRangePreAddr.isInRange(curRowIndex - 1, curColIndex)) {
//判断能否与上面合并单元格进行合并
//判断合并区域起始列否与新合并单元格起始列相同,并且最后一列是否与当前单元格列相同!
if (cellRangePreAddr.getFirstColumn() == cellRangeAddress.getFirstColumn()
&&
cellRangePreAddr.getLastColumn() == cellRangeAddress.getLastColumn()
) {
sheet.removeMergedRegion(i);
cellRangePreAddr.setLastRow(curRowIndex);
sheet.addMergedRegion(cellRangePreAddr);
preIsMerged = true;
}
//跳出当前循环
break;
}
}
if (!preIsMerged) {
//新增合并单元格
sheet.addMergedRegion(cellRangeAddress);
}
}else {
//新增合并单元格
sheet.addMergedRegion(cellRangeAddress);
}
}
} else {
if (pre) {
//获取当前sheet页
Sheet sheet = writeSheetHolder.getSheet();
//得到所有的合并单元格
List<CellRangeAddress> mergeRegions = sheet.getMergedRegions();
//判断能否与上面合并单元格合并 false能 true不能
boolean preIsMerged = false;
for (int i = 0; i < mergeRegions.size() && !preIsMerged; i++) {
//CellRangeAddress POI合并单元格
//CellRangeAddress(int firstRow, int lastRow, int firstCol, int lastCol)
//例子:CellRangeAddress(2, 6000, 3, 3);
//第2行起 第6000行终止 第3列开始 第3列结束。
CellRangeAddress cellRangeAddr = mergeRegions.get(i);
// cellRangeAddr.isInRange(int rowInd, int colInd)确定给定坐标是否在此范围的范围内。
// 若上一个单元格已经被合并,则先移出原有的合并单元,再重新添加合并单元
if (cellRangeAddr.isInRange(curRowIndex - 1, curColIndex)) {
//是否为当前列合并 是的话则合并
if (cellRangeAddr.getFirstColumn()==curColIndex&&cellRangeAddr.getLastColumn()==curColIndex){
sheet.removeMergedRegion(i);
cellRangeAddr.setLastRow(curRowIndex);
sheet.addMergedRegion(cellRangeAddr);
}
preIsMerged = true;
break;
}
}
// 若上一个单元格未被合并,则新增合并单元
if (!preIsMerged) {
CellRangeAddress cellRangeAddress = new CellRangeAddress(curRowIndex - 1, curRowIndex, curColIndex, curColIndex);
sheet.addMergedRegion(cellRangeAddress);
}
}
}
}
}
代码调用(数据就图个乐呵实在不会编!)
//自动合并单元格 行合并为主,列合并为辅 (内容相同就合并)
@PostMapping("/tableDataMerge")
public void tableDataMerge(HttpServletResponse response) throws IOException {
//Excel表头数据
List<List<String>> headers=new ArrayList<>();
//基础信息
//序号
List<String> baseSerialHead=new ArrayList<>();
baseSerialHead.add("基础信息");
baseSerialHead.add("基础信息");
baseSerialHead.add("序号");
//商家
List<String> baseShopHead=new ArrayList<>();
baseShopHead.add("基础信息");
baseShopHead.add("基础信息");
baseShopHead.add("商家");
//年份
List<String> baseYearHead=new ArrayList<>();
baseYearHead.add("基础信息");
baseYearHead.add("基础信息");
baseYearHead.add("年份");
//商品列表(饮品)
//矿泉水
//娃哈哈
List<String> DrinkWahahaHead=new ArrayList<>();
DrinkWahahaHead.add("饮品");
DrinkWahahaHead.add("矿泉水");
DrinkWahahaHead.add("娃哈哈");
//农夫山泉
List<String> DrinkNongfuHead=new ArrayList<>();
DrinkNongfuHead.add("饮品");
DrinkNongfuHead.add("矿泉水");
DrinkNongfuHead.add("农夫山泉");
//饮料
//北冰洋
List<String> DrinkArcticHead=new ArrayList<>();
DrinkArcticHead.add("饮品");
DrinkArcticHead.add("饮料");
DrinkArcticHead.add("北冰洋");
//宏宝莱
List<String> DrinkHBLHead=new ArrayList<>();
DrinkHBLHead.add("饮品");
DrinkHBLHead.add("饮料");
DrinkHBLHead.add("宏宝莱");
//酒水
//青岛啤酒
List<String> DrinkQDHead=new ArrayList<>();
DrinkQDHead.add("饮品");
DrinkQDHead.add("酒水");
DrinkQDHead.add("青岛啤酒");
//Excel表头数据 填充
headers.add(baseSerialHead);
headers.add(baseShopHead);
headers.add(baseYearHead);
headers.add(DrinkWahahaHead);
headers.add(DrinkNongfuHead);
headers.add(DrinkArcticHead);
headers.add(DrinkHBLHead);
headers.add(DrinkQDHead);
//Excel表格内容数据
List<List<String>> datas=new ArrayList<>();
//第一行数据
List<String> dataFirst=new ArrayList<>();
dataFirst.add("1");
dataFirst.add("A");
dataFirst.add("2010");
dataFirst.add("1.5");
dataFirst.add("1.5");
dataFirst.add("5.0");
dataFirst.add("1.5");
dataFirst.add("6.0");
//第二行数据
List<String> dataSecond=new ArrayList<>();
dataSecond.add("2");
dataSecond.add("A");
dataSecond.add("2020");
dataSecond.add("2.0");
dataSecond.add("2.0");
dataSecond.add("2.0");
dataSecond.add("2.0");
dataSecond.add("8.0");
//第三行数据
List<String> dataThird=new ArrayList<>();
dataThird.add("3");
dataThird.add("B");
dataThird.add("2010");
dataThird.add("0.9");
dataThird.add("2.5");
dataThird.add("8.0");
dataThird.add("8.0");
dataThird.add("8.0");
//第四行数据
List<String> dataFourth=new ArrayList<>();
dataFourth.add("4");
dataFourth.add("B");
dataFourth.add("2020");
dataFourth.add("2.0");
dataFourth.add("2.5");
dataFourth.add("8.0");
dataFourth.add("8.0");
dataFourth.add("8.1");
//第五行数据
List<String> dataFifth=new ArrayList<>();
dataFifth.add("5");
dataFifth.add("c");
dataFifth.add("2010");
dataFifth.add("2.0");
dataFifth.add("2.5");
dataFifth.add("8.0");
dataFifth.add("8.0");
dataFifth.add("8.1");
//第六行数据
List<String> dataSixth=new ArrayList<>();
dataSixth.add("6");
dataSixth.add("c");
dataSixth.add("2020");
dataSixth.add("2.0");
dataSixth.add("2.5");
dataSixth.add("7.0");
dataSixth.add("8.0");
dataSixth.add("8.0");
//Excel表格内容数据填充
datas.add(dataFirst);
datas.add(dataSecond);
datas.add(dataThird);
datas.add(dataFourth);
datas.add(dataFifth);
datas.add(dataSixth);
//excel生成
// 这里注意 有同学反应使用swagger 会导致各种问题,请直接用浏览器或者用postman
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
// 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
String fileName = URLEncoder.encode("行列自动合并,行合并为主,列合并为辅", "UTF-8").replaceAll("\\+", "%20");
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
int mergeColumnIndex =0;
int mergeRowIndex = 2;//一般设置为Excel表头数据高 高位3的话就为2 例如:0,1,2
//所有配置都是继承的,Workbook的配置会被Sheet继承,所以在用EasyExcel设置参数的时候,在EasyExcel...sheet()方法之前作用域是整个sheet,之后针对单个sheet
EasyExcel.write(response.getOutputStream()).registerWriteHandler(getCellCenterStyle()).registerWriteHandler(new ExcelFillCellMergeHandler(mergeColumnIndex,mergeRowIndex)).head(headers).sheet("sheet1").doWrite(datas);
}
调用展示:
合并前:
合并后