JAVA导入数据到Excel自定义单元格合并

最终的目标(不包含颜色)

很显然,行也在合并,列也在合并,没关系,我们首先制作一个模板,然后使用EasyExcel增加自定义的处理器,就可以实现。

制作Excel模板

准备数据

​​​​​​

定义两个实体,一个对应表头的数据,这里只有一个time。一个对应列表中的数据,通常这些都是我们的业务数据,通过数据库统计而来,然后经过我们处理。

import lombok.Data;

@Data
public class FormData {
    private String time;
}
import lombok.Data;

@Data
public class UserSum {
    private String province;
    private String city;
    private String gender;
    private Integer studentNum;
}

引入依赖

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>3.1.1</version>
</dependency>

合并处理器

合并的核心代码如下,向sheet中增加CellRangeAddress对象

sheet.addMergedRegion(new CellRangeAddress(startRow, endRow, column, column));

需要注意以下几点:

A、CellRangeAddress的四个参数分别是:起始行、结束行、起始列、结束列

B、一个sheet页,我们添加的CellRangeAddress是不能有行列交叉的,或者说任何两个合并单元格不能包含相同的原始单元格,比如合并单元格( 3、4、1、1 )与合并单元格(3、6、1、1)包含相同的单元格(第3行第1列、第4行第1列)

C、最终合并单元格的数据,是合并前从左往右、从上往下第一个单元格的数据,所以我们在组装数据的时候一定要给这个单元格赋值,其他单元格有没有值不影响,最后合并单元格才会有值,比如合并单元格(2、3、4、5),最后展现的就是合并前(第2行第4列)的数据。

import com.alibaba.excel.write.handler.RowWriteHandler;
import com.alibaba.excel.write.handler.context.RowWriteHandlerContext;
import com.alibaba.fastjson2.JSONObject;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.DateUtil;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.util.CellRangeAddress;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

public class MyExcelHandler implements RowWriteHandler {

    private static final Logger logger = LoggerFactory.getLogger(MyExcelHandler.class);

    private final Set<Integer> columnsToMerge;//需要进行合并的列
    private final boolean mergeAllColumns;//是否所有列都需要合并
    private final Map<Integer, Object> previousValues;//记录上一行的值
    private final Map<Integer, Integer> previousRowIndices;//记录合并的起始位置

    public MyExcelHandler() {
        this.columnsToMerge = new HashSet<>();
        this.mergeAllColumns = true;
        this.previousValues = new HashMap<>();
        this.previousRowIndices = new HashMap<>();
    }

    public MyExcelHandler(Set<Integer> columnsToMerge) {
        this.columnsToMerge = columnsToMerge;
        this.mergeAllColumns = false;
        this.previousValues = new HashMap<>();
        this.previousRowIndices = new HashMap<>();
    }

    @Override
    public void afterRowDispose(RowWriteHandlerContext context) {
        // 获取当前操作的Sheet对象
        Sheet sheet = context.getWriteSheetHolder().getSheet();
        // 获取当前行索引
        int currentRowIndex = context.getRowIndex();
        if (currentRowIndex < 3) {
            return;
        }
        logger.info("当前行号:{}", currentRowIndex);
        // 获取当前行对象
        Row currentRow = context.getRow();
        // 遍历当前行的所有单元格
        for (Cell cell : currentRow) {
            // 获取当前单元格的列索引
            int columnIndex = cell.getColumnIndex();
            logger.info("当前单元格序号:{}", columnIndex);
            // 如果不合并所有列且当前列不在合并列列表中,则跳过当前列
            if (!mergeAllColumns && !columnsToMerge.contains(columnIndex)) {
                continue;
            }
            // 获取当前单元格的值
            Object currentValue = getCellValue(cell);
            if (currentValue != null && currentValue.equals("总人数")) {
                sheet.addMergedRegion(new CellRangeAddress(currentRowIndex, currentRowIndex, columnIndex, columnIndex+1));
            }
            logger.info("当前单元格的值:{}", currentValue);
            logger.info("上一行的值:{}", JSONObject.toJSONString(previousValues));
            // 如果当前值不为空
            if (currentValue != null) {
                // 如果当前值与前一值相同,则合并单元格
                if (currentValue.equals(previousValues.get(columnIndex))) {
                    logger.info("当前单元格的值,与上一行序号相同单元格值一样,开始合并");
                    logger.info("........................................");
                    // 合并单元格
                    mergeColumns(sheet, previousRowIndices.get(columnIndex), currentRowIndex, columnIndex);
                } else {
                    logger.info("当前单元格的值,与上一行序号相同单元格值不一样,替换上一行的值");
                    // 否则,更新前一值和前一行索引为当前值和当前行索引
                    previousValues.put(columnIndex, currentValue);
                    previousRowIndices.put(columnIndex, currentRowIndex);
                    logger.info("替换之后");
                    logger.info("{}", JSONObject.toJSONString(previousValues));
                    logger.info("{}", JSONObject.toJSONString(previousRowIndices));
                }
            }
        }
    }

    private void mergeColumns(Sheet sheet, int startRow, int endRow, int column) {
        logger.info("开始行:{},结束行:{},第{}列", startRow, endRow, column);
        // 遍历工作表中所有的合并区域
        for (int i = 0; i < sheet.getNumMergedRegions(); i++) {
            CellRangeAddress region = sheet.getMergedRegion(i);
            // 检查当前合并区域是否与指定的起始行和列匹配
            if (region.getFirstRow() == startRow && region.getFirstColumn() == column) {
                // 如果匹配,移除当前合并区域,以便可以重新设置它的范围
                sheet.removeMergedRegion(i);
                // 调整合并区域的结束行为指定的结束行
                region.setLastRow(endRow);
                // 重新添加调整后的合并区域
                sheet.addMergedRegion(region);
                // 返回,完成合并
                return;
            }
        }
        // 如果没有找到匹配的合并区域,则创建一个新的合并区域并添加到工作表中
        logger.info("创建全新单元格合并:{},{},{},{}", startRow, endRow, column, column);
        sheet.addMergedRegion(new CellRangeAddress(startRow, endRow, column, column));
    }

    private 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;
        }
    }

}

上面代码简单解释一下:

afterRowDispose方法,每次导入一行数据后调用

1、因为两个合并单元格不能包含同一个原始单元格,所以在合并时候,会不断的做判断、删除、重新创建合并单元格,这个代码体现在mergeColumns方法中。

2、代码中下面一段

if (currentValue != null && currentValue.equals("总人数")) {
                sheet.addMergedRegion(new CellRangeAddress(currentRowIndex, currentRowIndex, columnIndex, columnIndex+1));
            }

是针对总人数单独处理,发现总人数,就合并连续两列。

开始使用处理器导入数据

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.fill.FillConfig;
import com.alibaba.fastjson2.JSONArray;
import org.apache.commons.lang3.exception.ExceptionUtils;

import java.io.*;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class MainTest {

    private static FormData formData;
    private static List<UserSum> userSumList;

    public static void main(String[] args) throws Exception {
        initData();
        File fileTemplate = new File("E:\\技术沉淀\\Template20240711.xlsx");
        InputStream excelTemplateStream = new FileInputStream(fileTemplate);//模板
        File fileOutPut = new File("E:\\技术沉淀\\OutPut20240711.xlsx");
        OutputStream outputStream = new FileOutputStream(fileOutPut);
        Set<Integer> needMergeCols = new HashSet<>();
        needMergeCols.add(0);//第一列需要合并
        needMergeCols.add(1);//第二列需要合并
        needMergeCols.add(2);//第二列需要合并
        //工作簿对象
        ExcelWriter excelWriter = EasyExcel.write(outputStream).withTemplate(excelTemplateStream)
                .registerWriteHandler(new MyExcelHandler(needMergeCols)).build();
        //工作表对象
        WriteSheet writeSheet = EasyExcel.writerSheet().build();
        //组合填充换行
        excelWriter.fill(formData, writeSheet);
        FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
        excelWriter.fill(userSumList, fillConfig, writeSheet);
        try {
            excelWriter.finish();
        } catch (Exception e) {
            System.out.println(ExceptionUtils.getStackTrace(e));
        }
    }

    /*******************************************
     * @Description: 初始化数据
     ******************************************/
    private static void initData() {
        formData = new FormData();
        formData.setTime("2024-09-01");
        userSumList = new ArrayList<>();
        String jsonStr = "[{\"city\":\"达州市\",\"province\":\"四川省\",\"gender\":\"男\",\"studentNum\":12},{\"city\":\"达州市\",\"province\":\"四川省\",\"gender\":\"女\",\"studentNum\":2},{\"city\":\"达州市\",\"province\":\"四川省\",\"gender\":\"合计\",\"studentNum\":14},{\"city\":\"成都市\",\"province\":\"四川省\",\"gender\":\"男\",\"studentNum\":4},{\"city\":\"成都市\",\"province\":\"四川省\",\"gender\":\"女\",\"studentNum\":13},{\"city\":\"成都市\",\"province\":\"四川省\",\"gender\":\"合计\",\"studentNum\":17},{\"city\":\"总人数\",\"province\":\"四川省\",\"gender\":\"\",\"studentNum\":31},{\"city\":\"武汉市\",\"province\":\"湖北省\",\"gender\":\"男\",\"studentNum\":2},{\"city\":\"武汉市\",\"province\":\"湖北省\",\"gender\":\"合计\",\"studentNum\":2},{\"province\":\"湖北省\",\"city\":\"总人数\",\"gender\":\"\",\"studentNum\":2}]";
        userSumList = JSONArray.parseArray(jsonStr, UserSum.class);
    }

}

引用模板、使用自定义合并处理器:

ExcelWriter excelWriter = EasyExcel.write(outputStream).withTemplate(excelTemplateStream)
        .registerWriteHandler(new MyExcelHandler(needMergeCols)).build();

填充表单数据:

excelWriter.fill(formData, writeSheet);

填充数据列表:

excelWriter.fill(userSumList, fillConfig, writeSheet);

最终生成文件

如下,和期待的有点差异,设计模板的时候增加一下居中,就可以了。

希望对大家有帮助。

  • 7
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值