使用springboot+easyexcel实现导出excel并合并指定单元格
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;}