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

相关推荐
腾讯TNTWeb前端团队2 小时前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
mghio4 小时前
Dubbo 中的集群容错
java·微服务·dubbo
范文杰5 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪6 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪6 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy6 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom7 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom7 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom7 小时前
React与Next.js:基础知识及应用场景
前端·面试·github