使用springboot+easyexcel实现导出excel并合并指定单元格

1:准备一个单元格合并策略类代码:

java 复制代码
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;
    }

    @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 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:业务类实现代码:

java 复制代码
    //从第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};

    @Override
    public 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写入数据到HttpServletResponse
            EasyExcel.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;
    }
相关推荐
Asthenia04127 分钟前
数据库分区、Upsert 语义与用户表逻辑删除问题分析及事务逻辑解析
后端
Sirius Wu19 分钟前
Channel如何安全地尝试发送数据
后端·golang
代码续发20 分钟前
SpringBoot实现的后端开发
spring boot
二狗哈22 分钟前
制作一款打飞机游戏23:编辑器ui
ui·编辑器·excel
liyongqiangcc25 分钟前
微服务之间有哪些调用方式?
后端
苍煜42 分钟前
MinIO 教程:从入门到Spring Boot集成
java·spring boot·后端·minio
嘻嘻嘻嘻嘻嘻ys44 分钟前
《Vue 3全栈架构实战:Vite工程化、Pinia状态管理与Nuxt 3深度解析》
前端·后端
chaowwwww1 小时前
代码的圈复杂度和认知复杂度
后端
uhakadotcom1 小时前
如何用AI打造高效招聘系统,HR效率提升100%!
后端·算法·面试