前言
原本是想學(xué)習(xí)使用Apache的POI的,但是無意中看到Alibaba的開源工具EastExcel,據(jù)說比POI更加快速高效,關(guān)鍵是使用起來也簡單。
官網(wǎng)地址為:https://alibaba-easyexcel.github.io/index.html,里面講解地非常清楚易懂,我這里就不再贅述了,只是記錄下寫表格時如何通過自定義合并策略來實現(xiàn)動態(tài)地合并單元格。
入門例子
如果我們不合并單元格,那么下載的樣式將是如下這樣的:

那么我們只需要使用如下例子即可:
@Test
public void commonWriteTest() {
String fileName = "mergeWrite" + System.currentTimeMillis() + ".xlsx";
EasyExcel.write(fileName, DownloadDTO.class).sheet("sheet名稱")
.doWrite(getFruitData());
}
// 模擬從數(shù)據(jù)庫讀取需要下載的列表信息
private List<DownloadDTO> getFruitData() {
List<DownloadDTO> returnList = new ArrayList<>();
DownloadDTO d1 = new DownloadDTO();
d1.setCategory("水果");
d1.setFruit("蘋果");
d1.setColor("紅色");
d1.setProduceDate(new Date());
DownloadDTO d2 = new DownloadDTO();
BeanUtils.copyProperties(d1, d2);
d2.setColor("綠色");
DownloadDTO d3 = new DownloadDTO();
BeanUtils.copyProperties(d1, d3);
d2.setColor("白色");
DownloadDTO t1 = new DownloadDTO();
t1.setCategory("水果");
t1.setFruit("香蕉");
t1.setColor("黃色");
t1.setProduceDate(new Date());
DownloadDTO t2 = new DownloadDTO();
BeanUtils.copyProperties(t1, t2);
t2.setColor("青色");
returnList.add(d1);
returnList.add(d2);
returnList.add(d3);
returnList.add(t1);
returnList.add(t2);
return returnList;
}
@Data
public class DownloadDTO {
@ExcelProperty(value = "物品種類", index = 0)
private String category;
@ExcelProperty(value = "水果名稱", index = 1)
private String fruit;
@ExcelProperty(value = "水果顏色", index = 2)
private String color;
@ExcelProperty(value = "水果產(chǎn)期", index = 3)
private Date produceDate;
}
其中,方法getFruitData和DownloadDTO在后面的例子中還會用到,就不再寫出。
如果我們想要合并單元格,那么官方文檔上的例子是這樣的:

與之對應(yīng)的代碼如下:
@Test
public void loopMergeStrategyTest() {
String fileName = "mergeWrite" + System.currentTimeMillis() + ".xlsx";
// 將第一列的數(shù)據(jù)每隔兩行進(jìn)行合并
LoopMergeStrategy loopMergeStrategy = new LoopMergeStrategy(2, 0);
EasyExcel.write(fileName, DownloadDTO.class).registerWriteHandler(loopMergeStrategy)
.sheet("sheet名稱").doWrite(getFruitData());
}
其中LoopMergeStrategy的源碼其實也很簡單,底層還是用到了apache poi的API:
protected void merge(Sheet sheet, Cell cell, Head head, Integer relativeRowIndex) {
if (relativeRowIndex != null) {
Integer currentColumnIndex;
if (head != null) {
currentColumnIndex = head.getColumnIndex();
} else {
currentColumnIndex = cell.getColumnIndex();
}
// 將入?yún)⒅兄付ǖ牧衪his.columnIndex按照每this.eachRow行進(jìn)行合并,this.eachRow也是入?yún)? if (currentColumnIndex == this.columnIndex && relativeRowIndex % this.eachRow == 0) {
CellRangeAddress cellRangeAddress = new CellRangeAddress(cell.getRowIndex(), cell.getRowIndex() + this.eachRow - 1, cell.getColumnIndex(), cell.getColumnIndex() + this.columnCount - 1);
sheet.addMergedRegionUnsafe(cellRangeAddress);
}
}
}
然后,在github的issue中,有這樣一個使用OnceAbsoluteMergeStrategy的例子:

在這個例子中,我們可以指定坐標(biāo)范圍,對固定區(qū)間進(jìn)行合并,其對應(yīng)的代碼如下:
@Test
public void onceMergeStrategyTest() {
String fileName = "mergeWrite" + System.currentTimeMillis() + ".xlsx";
OnceAbsoluteMergeStrategy onceAbsoluteMergeStrategy = new OnceAbsoluteMergeStrategy(1, 3, 1, 1);
EasyExcel.write(fileName, DownloadDTO.class).registerWriteHandler(onceAbsoluteMergeStrategy)
.sheet("sheet名稱").doWrite(getFruitData());
}
當(dāng)然了,我們可以一次使用多個合并策略,比如我們想實現(xiàn)這樣的效果:

用OnceAbsoluteMergeStrategy可以這么做:
@Test
public void onceMergeStrategyTest() {
String fileName = "mergeWrite" + System.currentTimeMillis() + ".xlsx";
OnceAbsoluteMergeStrategy onceAbsoluteMergeStrategy = new OnceAbsoluteMergeStrategy(1, 3, 1, 1);
OnceAbsoluteMergeStrategy onceAbsoluteMergeStrategy2 = new OnceAbsoluteMergeStrategy(4, 5, 1, 1);
EasyExcel.write(fileName, DownloadDTO.class)
.registerWriteHandler(onceAbsoluteMergeStrategy)
.registerWriteHandler(onceAbsoluteMergeStrategy2)
.sheet("sheet名稱").doWrite(getFruitData());
}
自定義實現(xiàn)合并策略
當(dāng)我們需要在寫Excel的時候?qū)崿F(xiàn)更加復(fù)雜乃至動態(tài)地合并單元格時,就需要自己實現(xiàn)一個合并策略。
比如當(dāng)我們想實現(xiàn)這樣的效果時:

在這個例子中,對于第1、4列,我們需要根據(jù)記錄總數(shù),都合并成一個單元格;對于第2列,我們需要根據(jù)每組的記錄個數(shù),分別進(jìn)行單元格的合并;而對于第3列,則不要使用合并策略。
與之對應(yīng)的代碼是:
@Test
public void myMergeStrategyTest() {
String fileName = "mergeWrite" + System.currentTimeMillis() + ".xlsx";
MyMergeStrategy myMergeStrategy = new MyMergeStrategy(getFruitData(), getGroupData());
EasyExcel.write(fileName, DownloadDTO.class).registerWriteHandler(myMergeStrategy)
.sheet("sheet名稱").doWrite(getFruitData());
}
public class MyMergeStrategy extends AbstractMergeStrategy {
private List<DownloadDTO> fruitList;
private List<Integer> fruitGroupCount;
private Sheet sheet;
public MyMergeStrategy(List<DownloadDTO> fruitList, List<Integer> fruitGroupCount) {
this.fruitList = fruitList;
this.fruitGroupCount = fruitGroupCount;
}
// 將該列全部合并成一個單元格
private void mergeCommonColumn(Integer index) {
CellRangeAddress cellRangeAddress = new CellRangeAddress(1, fruitList.size(), index, index);
sheet.addMergedRegionUnsafe(cellRangeAddress);
}
// 按照分組將各種類別分別合并成一個單元格
private void mergeGroupColumn(Integer index) {
Integer rowCnt = 1;
for (Integer count : fruitGroupCount) {
CellRangeAddress cellRangeAddress = new CellRangeAddress(rowCnt, rowCnt + count - 1, index, index);
sheet.addMergedRegionUnsafe(cellRangeAddress);
rowCnt += count;
}
}
@Override
protected void merge(org.apache.poi.ss.usermodel.Sheet sheet, Cell cell, Head head, Integer integer) {
this.sheet = sheet;
if (cell.getRowIndex() == 1) {
switch (cell.getColumnIndex()) {
case 0:
this.mergeCommonColumn(0);
break;
case 1:
this.mergeGroupColumn(1);
break;
case 2:
break;
case 3:
this.mergeCommonColumn(3);
break;
default:
break;
}
}
}
}
為什么不使用多個OnceAbsouluteMergeStrategy?
因為我們在寫代碼的時候并不知道每個分組的數(shù)量究竟有多少個,所以只能自己寫合適的合并策略了。