easyExcel 模版导出 中间数据纵向延伸,并且对指定列进行合并

想要达到的效果

引入maven引用

java 复制代码
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>easyexcel</artifactId>
            <version>3.2.1</version>
        </dependency>

按照要求创建模版

备注 :

模板注意 用{} 来表示你要用的变量 如果本来就有"{","}" 特殊字符 用"\{","\}"代替
// {} 代表普通变量 {.} 代表是list的变量 {前缀.} 前缀可以区分不同的list

代码部分 :

合并策略代码 :

java 复制代码
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.write.merge.AbstractMergeStrategy;
import io.jsonwebtoken.lang.Collections;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.util.CellRangeAddress;

import java.util.ArrayList;
import java.util.List;

// 自定义合并策略 该类继承了AbstractMergeStrategy抽象合并策略,需要重写merge()方法
  public class CustomMergeStrategy extends AbstractMergeStrategy {

    /**
     * 分组,每几行合并一次
     */
    private List<Integer> exportFieldGroupCountList;

    /**
     * 目标合并列index
     */
    private Integer targetColumnIndex;

    // 需要开始合并单元格的首行index
    private Integer rowIndex;

    // exportDataList为待合并目标列的值 
    public CustomMergeStrategy(List<String> exportDataList, Integer targetColumnIndex) {
      this.exportFieldGroupCountList = getGroupCountList(exportDataList);
      this.targetColumnIndex = targetColumnIndex;
    }


    @Override
    protected void merge(Sheet sheet, Cell cell, Head head, Integer relativeRowIndex) {

      if (null == rowIndex) {
        rowIndex = cell.getRowIndex();
      }
      // 仅从首行以及目标列的单元格开始合并,忽略其他
      if (cell.getRowIndex() == rowIndex && cell.getColumnIndex() == targetColumnIndex) {
        mergeGroupColumn(sheet);
      }
    }

    private void mergeGroupColumn(Sheet sheet) {
      int rowCount = rowIndex;
      for (Integer count : exportFieldGroupCountList) {
        if(count == 1) {
          rowCount += count;
          continue ;
        }
        // 合并单元格
        CellRangeAddress cellRangeAddress = new CellRangeAddress(rowCount, rowCount + count - 1, targetColumnIndex, targetColumnIndex);
        sheet.addMergedRegionUnsafe(cellRangeAddress);
        rowCount += count;
      }
    }

    // 该方法将目标列根据值是否相同连续可合并,存储可合并的行数 
    private List<Integer> getGroupCountList(List<String> exportDataList){
      if (Collections.isEmpty(exportDataList)) {
        return new ArrayList<>();
      }

      List<Integer> groupCountList = new ArrayList<>();
      int count = 1;

      for (int i = 1; i < exportDataList.size(); i++) {
        if (exportDataList.get(i).equals(exportDataList.get(i - 1))) {
          count++;
        } else {
          groupCountList.add(count);
          count = 1;
        }
      }
      // 处理完最后一条后
      groupCountList.add(count);
      return groupCountList;
    }
  }

导出部分 :

java 复制代码
@Test
    public void complexFillTest() throws IOException {
        //正式代码时 改成 input或者 file 传入
        String templateFileName = "D:/模版.xls";
        //改成导出的output或者其他方式写出 也可
        String fileName = "D:/下载的文件.xls";

        // 方案1
        try (ExcelWriter excelWriter = EasyExcel.write(fileName).withTemplate(templateFileName).excelType(ExcelTypeEnum.XLS).build()) {

            //数据准备 改成自己的即可
            InvoiceInfo invoiceInfo = invoiceInfoService.lambdaQuery()
                    .eq(InvoiceInfo::getId, 21)
                    .one();

            List<FreightOrderResponse> freightOrderResponseList = new ArrayList<>();

            ArrayList<Map<String, String>> dataList = new ArrayList<>();
            for (int i = 0; i < freightOrderResponseList.size(); i++) {
                FreightOrderResponse freightOrderResponse = freightOrderResponseList.get(0);
                Map<String, String> map = new HashMap<>();
                map.put("companyName",freightOrderResponse.getComName());
                if (freightOrderResponseList.size() / 2 < i){
                    map.put("companyName",freightOrderResponse.getComName() + "1");
                }
                map.put("sendAddr",freightOrderResponse.getSendProvince());
                map.put("arriveAddr",freightOrderResponse.getReceiveProvince());
                map.put("goodsName",freightOrderResponse.getGoodsName());
                map.put("practicalFee",freightOrderResponse.getOrderPrice().toString());
                dataList.add(map);
            }
            WriteSheet writeSheet = EasyExcel.writerSheet()
                    // 合并的单元格 并且将需要合并的那一列数据传入
                    .registerWriteHandler(new CustomMergeStrategy(dataList.stream().map(map -> map.get("companyName")).collect(Collectors.toList()), 0))
                    .build();
            // 这里注意 入参用了forceNewRow 代表在写入list的时候不管list下面有没有空行 都会创建一行,然后下面的数据往后移动。默认 是false,会直接使用下一行,如果没有则创建。
            // forceNewRow 如果设置了true,有个缺点 就是他会把所有的数据都放到内存了,所以慎用
            // 简单的说 如果你的模板有list,且list不是最后一行,下面还有数据需要填充 就必须设置 forceNewRow=true 但是这个就会把所有数据放到内存 会很耗内存
            // 如果数据量大 list不是最后一行 参照下一个

            FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
            excelWriter.fill(dataList, fillConfig, writeSheet);
            Map<String, Object> map = MapUtils.newHashMap();
            BigDecimal reduce = freightOrderResponseList.stream().map(obj -> obj.getOrderPrice() != null ? obj.getOrderPrice() : BigDecimal.ZERO).reduce(BigDecimal.ZERO, BigDecimal::add);
            map.put("practicalFee", reduce);
            map.put("customName", invoiceInfo.getCustomName());
            map.put("sellerComName", invoiceInfo.getSellerComName());
            excelWriter.fill(map, writeSheet);
            excelWriter.finish();
        }
    }

最终效果 :

参考资料 :

官方文档 :

填充Excel | Easy Excel

合并代码参考 :

https://www.cnblogs.com/monianxd/p/16359369.html

相关推荐
dadaobusi22 分钟前
学习:RV lkvm SBI
java·学习·spring
HEJOO93 小时前
深入理解 Java volatile 关键字:原理、用法与常见误区
java·开发语言·spring
曹牧3 小时前
Java:no content to map due to end of input
java·开发语言
子非鱼a3 小时前
【WEB】multiSQL
前端
阿维的博客日记3 小时前
Example 类全场景实战指南
java·mybatis
两点王爷3 小时前
SpringBoot 项目集成 GeoServer 源码,实现地图服务发布
java·spring boot·后端
Javatutouhouduan3 小时前
Java如何速通性能优化难题?
java·java面试·jvm调优·后端开发·java程序员·java八股文·java性能优化
lemon_sjdk3 小时前
DOM 节点
java·前端·javascript
nvd113 小时前
深入现代 Web 鉴权架构:网关统一代理 (Forward-Auth) vs 前端持有 JWT 的终极选型与边缘同域实践
前端·架构·状态模式
leoZ2314 小时前
2026-09-09-springboot-cloud-deploy-pitfalls
java·前端·javascript·vue.js·人工智能·spring boot·后端