1:準備一個單元格合并策略類代碼:
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;public class ExcelMergeHandler implements CellWriteHandler {// 要合并的列索引數組private final int[] mergeColumnIndex;// 合并開始的行索引private final int mergeRowIndex;/*** 構造函數** @param mergeRowIndex 合并開始的行索引* @param mergeColumnIndex 要合并的列索引數組*/public ExcelMergeHandler(int mergeRowIndex, int[] mergeColumnIndex) {this.mergeRowIndex = mergeRowIndex;this.mergeColumnIndex = mergeColumnIndex;}@Overridepublic void beforeCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Head head, Integer columnIndex, Integer relativeRowIndex, Boolean isHead) {// 單元格創建前的處理(這里不需要處理)}@Overridepublic void afterCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {// 單元格創建后的處理(這里不需要處理)}@Overridepublic void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List<WriteCellData<?>> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {// 當前行索引int curRowIndex = cell.getRowIndex();// 當前列索引int curColIndex = cell.getColumnIndex();// 如果當前行大于合并開始行且當前列在需要合并的列中if (curRowIndex > mergeRowIndex && isMergeColumn(curColIndex)) {// 進行合并操作mergeWithPrevRow(writeSheetHolder, cell, curRowIndex, curColIndex);}}/*** 檢查當前列是否在需要合并的列中** @param curColIndex 當前列索引* @return 如果是需要合并的列返回true,否則返回false*/private boolean isMergeColumn(int curColIndex) {for (int columnIndex : mergeColumnIndex) {if (curColIndex == columnIndex) {return true;}}return false;}/*** 當前單元格向上合并** @param writeSheetHolder 當前工作表持有者* @param cell 當前單元格* @param curRowIndex 當前行索引* @param curColIndex 當前列索引*/private void mergeWithPrevRow(WriteSheetHolder writeSheetHolder, Cell cell, int curRowIndex, int curColIndex) {// 獲取當前單元格的數據Object curData = getCellData(cell);// 獲取前一個單元格的數據Cell preCell = cell.getSheet().getRow(curRowIndex - 1).getCell(curColIndex);Object preData = getCellData(preCell);// 判斷當前單元格和前一個單元格的數據以及主鍵是否相同if (curData.equals(preData) && isSamePrimaryKey(cell, curRowIndex)) {// 獲取工作表Sheet sheet = writeSheetHolder.getSheet();// 合并單元格mergeCells(sheet, curRowIndex, curColIndex);}}/*** 獲取單元格的數據** @param cell 單元格* @return 單元格數據*/private Object getCellData(Cell cell) {return cell.getCellType() == CellType.STRING ? cell.getStringCellValue() : cell.getNumericCellValue();}/*** 判斷當前單元格和前一個單元格的主鍵是否相同** @param cell 當前單元格* @param curRowIndex 當前行索引* @return 如果主鍵相同返回true,否則返回false*/private boolean isSamePrimaryKey(Cell cell, int curRowIndex) {String currentPrimaryKey = cell.getRow().getCell(0).getStringCellValue();String previousPrimaryKey = cell.getSheet().getRow(curRowIndex - 1).getCell(0).getStringCellValue();return currentPrimaryKey.equals(previousPrimaryKey);}/*** 合并單元格** @param sheet 工作表* @param curRowIndex 當前行索引* @param curColIndex 當前列索引*/private void mergeCells(Sheet sheet, int curRowIndex, int curColIndex) {// 獲取已合并的區域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);}}
}
2:業務類實現代碼:
?
//從第2行開始合并private static final int mergeRowIndex=1;//需要合并的單元格列private static final int[] mergeCols={0,1,2,3,4,5,6,7,8,9,10,11,12};@Overridepublic void exportBillOfQuantity(BillOfQuantityExportDto exportDto, HttpServletResponse response) {// 設置響應參數setResponseHeader(response, "export-bill-quantities.xlsx_");// 整理需要導出的數據List<ExportBillQuantitiesVo> exportBillQuantitiesVoList = getExportBillQuantitiesVoList(exportDto);try {// 獲取模板文件輸入流InputStream templateStream = new ClassPathResource(GlobalConstants.TEMPLATE_PATH + File.separator + GlobalConstants.EXPORT_BILL_QUANTITIES_FILE_NAME).getInputStream();// 使用EasyExcel寫入數據到HttpServletResponseEasyExcel.write(response.getOutputStream()).registerWriteHandler(setStyle()).registerWriteHandler(new ExcelMergeHandler(mergeRowIndex, mergeCols)).withTemplate(templateStream).sheet().doWrite(exportBillQuantitiesVoList);} catch (IOException e) {log.error("export productCoreParamList data is filed", e);throw new BusinessException(StatusCode.FAILED);}}// 設置響應頭通用方法private void setResponseHeader(HttpServletResponse response, String fileName) {try {String fileNameStr = fileName + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")) + ".xls";String encodedFileName = URLEncoder.encode(fileNameStr, StandardCharsets.UTF_8.toString());response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");response.setCharacterEncoding("UTF-8");response.setHeader("Content-Disposition", "attachment;filename=" + encodedFileName);} catch (Exception e) {log.error("set response header error", e);throw new BusinessException(StatusCode.SYSTEM_ERROR);}}// 設置導出excel文件部分內容樣式private HorizontalCellStyleStrategy setStyle() {// 定義樣式:自動換行WriteCellStyle contentWriteCellStyle = new WriteCellStyle();contentWriteCellStyle.setWrapped(true); // 關鍵:開啟自動換行WriteFont writeFont = new WriteFont();writeFont.setFontName("Microsoft YaHei"); // 字體writeFont.setFontHeightInPoints((short) 12); // 字體大小contentWriteCellStyle.setWriteFont(writeFont);// 注冊樣式策略(全局生效)HorizontalCellStyleStrategy styleStrategy = new HorizontalCellStyleStrategy(null, // 頭樣式(默認)contentWriteCellStyle // 內容樣式(自動換行));return styleStrategy;}