Java 使用 Apache 的 poi 动态合并重复单元格数据

以下代码直接复制粘贴就能直接看见效果(使用 EasyExcel 实现 点这里

  • 测试实体类
@Data
public class AttendanceRecordExcelTest {

    @ExcelProperty(value = "用户名")
    @ApiModelProperty(value = "用户名")
    private String userName;

    @ExcelProperty(value = "上班打卡时间")
    @ApiModelProperty(value = "上班打卡时间")
    @DateTimeFormat(value = "yyyy-MM-dd HH:mm:ss")
    private Date morningPunch;

    @ExcelProperty(value = "拜访时间")
    @ApiModelProperty(value = "拜访时间")
    @DateTimeFormat(value = "yyyy-MM-dd HH:mm:ss")
    private Date visitTime;

    @ExcelProperty(value = "拜访客户")
    @ApiModelProperty(value = "拜访客户")
    private String customer;

    @ExcelProperty(value = "拜访备注")
    @ApiModelProperty(value = "拜访备注")
    private String visitNotes;

    @ExcelProperty(value = "下班打卡时间")
    @ApiModelProperty(value = "下班打卡时间")
    @DateTimeFormat(value = "yyyy-MM-dd HH:mm:ss")
    private Date eveningPunch;

    public AttendanceRecordExcelTest(String userName, Date morningPunch, Date visitTime, String customer, String visitNotes, Date eveningPunch) {
        this.userName = userName;
        this.morningPunch = morningPunch;
        this.visitTime = visitTime;
        this.customer = customer;
        this.visitNotes = visitNotes;
        this.eveningPunch = eveningPunch;
    }
}
  • 合并单元格处理器
import io.swagger.annotations.ApiModelProperty;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;

import java.lang.reflect.Field;
import java.util.Date;
import java.util.List;
import java.util.Objects;

public class ApacheMergeColumnHandler {

    /**
     * 合并指定列的单元格。
     *
     * @param sheet          要合并单元格的工作表。
     * @param columnsToMerge 要合并的列的索引列表。
     */
    public static void mergeColumns(Sheet sheet, int... columnsToMerge) {
        for (int columnIndex : columnsToMerge) {
            mergeColumn(sheet, columnIndex);
        }
    }

    /**
     * 合并所有列的单元格。
     *
     * @param sheet 要合并单元格的工作表。
     */
    public static void mergeColumns(Sheet sheet) {
        int columnCount = sheet.getRow(0).getLastCellNum();
        int[] allColumns = new int[columnCount];
        for (int i = 0; i < columnCount; i++) {
            allColumns[i] = i;
        }
        mergeColumns(sheet, allColumns);
    }

    /**
     * 合并单个列的单元格。
     *
     * @param sheet       工作表。
     * @param columnIndex 要合并的列索引。
     */
    private static void mergeColumn(Sheet sheet, int columnIndex) {
        int lastRow = sheet.getLastRowNum();
        if (lastRow < 1) return;

        Object previousValue = getCellValue(sheet.getRow(0).getCell(columnIndex));
        int startRow = 0;

        for (int rowIndex = 1; rowIndex <= lastRow; rowIndex++) {
            Row currentRow = sheet.getRow(rowIndex);
            Object currentValue = getCellValue(currentRow.getCell(columnIndex));

            if (!Objects.equals(previousValue, currentValue)) {
                if (rowIndex - 1 > startRow) {
                    mergeCells(sheet, startRow, rowIndex - 1, columnIndex);
                }
                startRow = rowIndex;
                previousValue = currentValue;
            }
        }

        if (lastRow > startRow) {
            mergeCells(sheet, startRow, lastRow, columnIndex);
        }
    }

    /**
     * 合并单元格。
     *
     * @param sheet    工作表。
     * @param startRow 起始行。
     * @param endRow   结束行。
     * @param column   列索引。
     */
    private static void mergeCells(Sheet sheet, int startRow, int endRow, int column) {
        sheet.addMergedRegion(new CellRangeAddress(startRow, endRow, column, column));
    }

    /**
     * 写入Excel表头。
     * 通过反射获取类的字段信息,并根据字段上的ApiModelProperty注解或字段名写入表头。
     *
     * @param sheet         Excel中的工作表。
     * @param centeredStyle 设置单元格居中样式的CellStyle对象。
     * @param clazz         需要反射获取字段信息的类。
     */
    public static <T> void writeHeader(Sheet sheet, CellStyle centeredStyle, Class<T> clazz) {
        Row headerRow = sheet.createRow(0);
        Field[] fields = clazz.getDeclaredFields();

        for (int i = 0; i < fields.length; i++) {
            Cell cell = headerRow.createCell(i);
            ApiModelProperty annotation = fields[i].getAnnotation(ApiModelProperty.class);
            cell.setCellValue(annotation != null ? annotation.value() : fields[i].getName());
            cell.setCellStyle(centeredStyle);
        }
    }

    /**
     * 写入Excel内容。
     * 通过反射获取类的字段值并写入到Excel单元格中。
     *
     * @param sheet         Excel中的工作表。
     * @param records       需要写入Excel的记录列表。
     * @param centeredStyle 设置单元格居中样式的CellStyle对象。
     * @param dateStyle     设置日期单元格样式的CellStyle对象。
     * @param clazz         需要反射获取字段信息的类。
     */
    public static <T> void writeContent(Sheet sheet, List<T> records, CellStyle centeredStyle, CellStyle dateStyle, Class<T> clazz) {
        Field[] fields = clazz.getDeclaredFields();
        int rowIndex = 1;

        for (T record : records) {
            Row row = sheet.createRow(rowIndex++);
            for (int i = 0; i < fields.length; i++) {
                fields[i].setAccessible(true);
                try {
                    Object value = fields[i].get(record);
                    createCell(row, i, value, value instanceof Date ? dateStyle : centeredStyle);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 创建一个用于表示日期的单元格样式。
     *
     * @param workbook 工作簿对象,用于创建单元格样式和数据格式。
     * @return 创建的单元格样式对象,用于显示日期。
     */
    public static CellStyle getDateStyle(Workbook workbook, String format) {
        CellStyle dateStyle = workbook.createCellStyle();
        DataFormat dateFormat = workbook.createDataFormat();
        dateStyle.setDataFormat(dateFormat.getFormat(format));
        dateStyle.setAlignment(HorizontalAlignment.CENTER);
        dateStyle.setVerticalAlignment(VerticalAlignment.CENTER);
        return dateStyle;
    }

    /**
     * 根据给定的参数在指定的行中创建一个单元格,并设置其值和样式。
     *
     * @param row    要创建单元格的行。
     * @param column 单元格的列索引。
     * @param value  单元格的值。
     * @param style  单元格的样式。
     */
    private static void createCell(Row row, int column, Object value, CellStyle style) {
        Cell cell = row.createCell(column);
        if (value instanceof Date) {
            cell.setCellValue((Date) value);
        } else if (value instanceof String) {
            cell.setCellValue((String) value);
        }
        cell.setCellStyle(style);
    }

}

	/**
     * 获取单元格的值。
     * 此方法根据单元格的类型,返回对应类型的值。支持的类型包括字符串、数字、日期、布尔值和公式。
     * 如果单元格为null,则返回null。对于日期类型,使用了Apache POI提供的DateUtil工具类来判断。
     *
     * @param cell 单元格对象,不能为空。
     * @return 单元格的值,根据单元格类型返回对应类型的值,如果单元格为null,则返回null。
     */
    public static Object getCellValue(Cell cell) {
        if (cell == null) {
            return null;
        }
        switch (cell.getCellType()) {
            case STRING:
                return cell.getStringCellValue();
            case NUMERIC:
                return DateUtil.isCellDateFormatted(cell) ? cell.getDateCellValue() : cell.getNumericCellValue();
            case BOOLEAN:
                return cell.getBooleanCellValue();
            case FORMULA:
                return cell.getCellFormula();
            default:
                return null;
        }
    }
  • 样式处理器
import org.apache.poi.ss.usermodel.*;

public class CenterAlignCellStyleStrategyApache {

    /**
     * 获取一个中心对齐样式的单元格样式。
     * 该方法创建并配置了一个新的单元格样式,用于在Excel表格中实现文本和数字的中心对齐显示。
     * 同时,设置了单元格的边框为细边框,以提高表格的可读性。
     *
     * @param workbook 正在被操作的Excel工作簿对象,用于创建新的单元格样式。
     * @return 返回一个已配置好的,用于中心对齐显示的单元格样式。
     */
    public static CellStyle getCenterAlignStyle(Workbook workbook) {
        // 创建一个新的单元格样式对象
        CellStyle style = workbook.createCellStyle();

        // 配置单元格内容水平方向和垂直方向都居中对齐
        style.setAlignment(HorizontalAlignment.CENTER);
        style.setVerticalAlignment(VerticalAlignment.CENTER);

        // 启用文本自动换行,以适应单元格宽度
        style.setWrapText(true);

        // 设置单元格四边的边框样式为细边框
        style.setBorderLeft(BorderStyle.THIN);
        style.setBorderTop(BorderStyle.THIN);
        style.setBorderRight(BorderStyle.THIN);
        style.setBorderBottom(BorderStyle.THIN);

        // 返回配置好的单元格样式
        return style;
    }

}
  • main 测试
public static void main(String[] args) {

        List<AttendanceRecordExcelTest> processedRecords = new ArrayList<>();
        // processedRecords 定义测试数据
        processedRecords.add(new AttendanceRecordExcelTest("刘备", new Date(), new Date(), "秦始皇", "统一六国", new Date()));
        processedRecords.add(new AttendanceRecordExcelTest("刘备", new Date(), new Date(), "秦始皇", "统一文字", new Date()));
        processedRecords.add(new AttendanceRecordExcelTest("关羽", new Date(), new Date(), "曹操", "官渡之战,诛杀颜良,解白马之围,受封汉寿亭侯", new Date()));
        processedRecords.add(new AttendanceRecordExcelTest("张飞", new Date(), new Date(), "张郃", "巴西之战败张郃", new Date()));
        processedRecords.add(new AttendanceRecordExcelTest("赵云", new Date(), new Date(), "刘备", "单骑救主", new Date()));
        processedRecords.add(new AttendanceRecordExcelTest("赵云", new Date(), new Date(), "刘备", "七进七出救阿斗", new Date()));
        processedRecords.add(new AttendanceRecordExcelTest("黄忠", new Date(), new Date(), "刘备", "助刘备攻破益州刘璋", new Date()));

        String filePath = "F:\\ChromeDownloadLocation\\test1.xlsx";
        try (Workbook workbook = new XSSFWorkbook()) {
                Sheet sheet = workbook.createSheet("Sheet1");

                // 设置样式处理器
                CellStyle centeredStyle = ApacheCenterAlignCellStyleStrategy.getCenterAlignStyle(workbook);
                // 设置日期的单元格样式
                CellStyle dateStyle = ApacheMergeColumnHandler.getDateStyle(workbook, "yyyy-MM-dd HH:mm:ss");

                // 写入表头
                ApacheMergeColumnHandler.writeHeader(sheet, centeredStyle, MergeCellsExcelDto.class);

                // 写入内容
                ApacheMergeColumnHandler.writeContent(sheet, processedRecords, centeredStyle, dateStyle, MergeCellsExcelDto.class);

                // 合并所有列
//                ApacheMergeColumnHandler.mergeColumns(sheet);
                // 合并指定列
                ApacheMergeColumnHandler.mergeColumns(sheet, 0, 1);

                try (FileOutputStream out = new FileOutputStream(Paths.get(filePath).toFile())) {
                    workbook.write(out);
                }

                System.out.println("Processed data exported successfully to " + filePath);
            } catch (IOException e) {
                e.printStackTrace();
            }

    }
  • 默认效果
    在这里插入图片描述

  • 指定列合并效果
    在这里插入图片描述

  • 全部列合并效果
    在这里插入图片描述

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值