1、先看效果圖

2、表頭自適應類
根據自己的業務需求來寫,下面只是個例子
public class RwhzCustemhandler extends AbstractColumnWidthStyleStrategy { private static final int MAX_COLUMN_WIDTH = 255; //the maximum column width in Excel is 255 characters public RwhzCustemhandler() { } @Override protected void setColumnWidth(WriteSheetHolder writeSheetHolder, List<CellData> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) { if (isHead) { int columnWidth = cell.getStringCellValue().getBytes().length; if (columnWidth > MAX_COLUMN_WIDTH) { columnWidth = MAX_COLUMN_WIDTH; } else { if (cell.getColumnIndex() == 1) { columnWidth = columnWidth + 10; } else { columnWidth = columnWidth + 3; } } writeSheetHolder.getSheet().setColumnWidth(cell.getColumnIndex(), columnWidth * 256); } } }
3、根據某些列來合並相同內容的行單元格類
public class RwhzMergeCell implements CellWriteHandler { //需要哪些列的行進行合並 private int[] mergeColumnIndex; //從哪一行開始,表頭如果是只有一行,數據從第二行開始,即索引1開始 private int mergeRowIndex; public RwhzMergeCell() { } public RwhzMergeCell(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) { } @Override public void afterCellDataConverted(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, CellData cellData, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) { } @Override public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List<CellData> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) { //當前行 int curRowIndex = cell.getRowIndex(); //當前列 int curColIndex = cell.getColumnIndex(); if (curRowIndex > mergeRowIndex) { for (int columnIndex : mergeColumnIndex) { if (curColIndex == columnIndex) { //符合條件,調用自定義合並函數 mergeWithPrevRow(writeSheetHolder, cell, curRowIndex, curColIndex); break; } } } } private void mergeWithPrevRow(WriteSheetHolder writeSheetHolder, Cell cell, int curRowIndex, int curColIndex) { //獲取當前行當前列的單元格的值,可能是String類型,也可能是數值 Object curData = cell.getCellTypeEnum() == CellType.STRING ? cell.getStringCellValue() : cell.getNumericCellValue(); //獲取當前列的上一行單元格 Cell preCell = cell.getSheet().getRow(curRowIndex - 1).getCell(curColIndex); //獲取當前列的上一行單元格的值 Object preData = preCell.getCellTypeEnum() == CellType.STRING ? preCell.getStringCellValue() : preCell.getNumericCellValue(); // 將當前單元格數據與上一個單元格數據比較 if (preData.equals(curData)) { Sheet sheet = writeSheetHolder.getSheet(); List<CellRangeAddress> mergeRegions = sheet.getMergedRegions(); boolean isMerged = false; for (int i = 0; i < mergeRegions.size() && !isMerged; i++) { CellRangeAddress cellRangeAddr = mergeRegions.get(i); // 若上一個單元格已經被合並,則先移出原有的合並單元,再重新添加合並單元 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); } } } }
4、API接口方法
@GetMapping("/exportExcel")
public void exportExcel(@RequestParam(value = "ztId") int ztId,
@RequestParam(value = "rwszId") int rwszId,
@RequestParam(value = "bmId", required = false, defaultValue = "") Integer bmId,
@RequestParam(value = "qjszId") int qjszId,
@RequestParam(value = "sbzt", required = false, defaultValue = "") Boolean sbzt,
@RequestParam(value = "wsjbxs") boolean wsjbxs,
@RequestParam(value = "wcqk") boolean wcqk,
HttpServletResponse response) throws IOException {
//獲取內容集合
List<RwhzView> rwhzViews = getRwhzViews(ztId, rwszId, bmId, qjszId, sbzt, wsjbxs, wcqk);
//獲取表頭集合
List<RwhzHead> heads = setRwhzHeads(rwszId);
response.setHeader("Content-Disposition", "attachment; filename=rwhz.xls");
// 響應類型,編碼
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
//因為我業務的表頭是不固定的,而且順序也是按業務規則排序的,可以隨緣改變表頭的位置,我要合並的列是“工作內容”表頭對應的數據,一下是找到“工作內容”所處的位置index
int index = 0;
for (int i = 0; i < heads.size(); i++) {
if ("工作內容".equals(heads.get(i).getXmmc())) {
index = i;
break;
}
}
//因為表頭需要額外加上一列序號、任務、期間,那么此時工作內容的位置應該往后移3,工作內容相同的需要也相同,序號也需要合並相同的
int[] mergeColumnIndex = {0, index + 3};
//自定義獲取需要導出的數據,根據自己的業務需求來
List<List<Object>> exportData = getExportData(heads, rwhzViews, index);
EasyExcel.write(response.getOutputStream())
.registerWriteHandler(new RwhzCustemhandler())
.registerWriteHandler(new RwhzMergeCell(1, mergeColumnIndex))
.sheet("任務匯總導出模板").head(getExcelHeader(heads)).doWrite(exportData);
}
private List<List<String>> getExcelHeader(List<RwhzHead> heads) {//...}
5、重要的是那兩個類
不確定表頭主要是head(List<List<String>>).doWrite( List<List<Object>>)
(1)對於head的List<List<String>>
外面的List的每一個元素List<String>代表每一列表頭,里面的List的每一個元素String代表每一行的表頭,比如
List<List<String>>heads=new ArrayList<>(); List<String> list1 = Arrays.asList("AAA", "aaa"); List<String> list2 = Arrays.asList("AAA", "bbb"); List<String> list3 = Arrays.asList("AAA", "ccc"); heads.add(list1 ); heads.add(list2 ); heads.add(list3 );
形成的表頭如下圖所示

圖片畫的不夠標准,大概是那樣。
(2)對於數據的List<List<Object>>
外面的List的每一個元素List<Object>代表一行數據,里面List的每一個元素Object代表當前行每一列的數據。
合並單元格參考網址:https://blog.csdn.net/weixin_50067580/article/details/111559637
