EasyExcel导出复杂表格到邮箱

简介: 自定义单元格合并策略、调用示例、工具类Bo、Excel 模板导出工具类、Vo配置、效果图、MailUtils、模板配置

EasyExcel导出复杂表格到邮箱

📔 千寻简笔记介绍

千寻简笔记已开源,Gitee与GitHub搜索chihiro-notes,包含笔记源文件.md,以及PDF版本方便阅读,且是用了精美主题,阅读体验更佳,如果文章对你有帮助请帮我点一个Star

更新:支持在线阅读文章,根据发布日期分类。

@[toc]

简介

在企业开发中,我们经常需要面对复杂的Excel导出,本文基于EasyExcel,自定义单元格合并策略,达到我们需要导出的效果。

邮箱配置可以参考主页的ruoyi-vue-plus 配置邮箱

效果图

image-20230828185404792

版本

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

本文关键词

自定义单元格合并策略调用示例工具类BoExcel 模板导出工具类Vo配置效果图MailUtils模板配置

实现步骤

导出到邮箱

1 调用示例

  • 模板存放位置src/main/resources/templates 目录下。
  • templateName只传文件名:WorkSafetyReportTemplate.xlsx
  • setMergeRowIndex:从哪一行开始合并0开始。
  • setUniqueColumnIndex:唯一标识列。
  • setMergeColumnIndex:需要合并的列。
  • setList:导出的数据。
  • MailUtils.sendText:使用ruoyi提供的MailUtils工具发送邮件。
    /***
     * 1 根据模板的生成对应excel文件
     * 2.使用ruoyi提供的MailUtils工具发送邮件,附件为生成的excel文件
     * **/
    public Boolean exportExcelToEmail(XXXX bo) {
   
   
        // 省略业务查询
        List<Xxxxxx> dataList = iRiskXxxxxxService.queryList();

        EasyExcelExportBo exportBo = new EasyExcelExportBo();
        exportBo.setTemplateName("WorkXxxxxTemplate.xlsx");
        exportBo.setMergeRowIndex(1);
        exportBo.setUniqueColumnIndex(1);
        exportBo.setMergeColumnIndex(Sets.newHashSet(1,2,3,11));
        exportBo.setList(dataList);
        // 返回文件路径
        String filePath = new EasyExcelExportUtil().export(exportBo);
        File file = new File(filePath);
        MailUtils.sendText(bo.getEmail(), "导出附件", "导出测试清单", file);
        return true;
    }

2 工具类Bo

  • 用于传输导出必要参数。

EasyExcelExportBo.java

import lombok.Data;
import java.util.List;
import java.util.Set;

/**
 * 导出必要参数
 * @author Star
 */
@Data
public class EasyExcelExportBo {
   
   
    /**
     * 唯一标识列(该列cell内容一定是全局唯一的,当cell的值相等时才能进行下一列合并)
     */
    private Integer uniqueColumnIndex;

    /**
     * 从哪一行开始合并,从0开始
     */
    private Integer mergeRowIndex;

    /**
     * 合并列编号,从0开始:需要合并的列
     */
    private Set<Integer> mergeColumnIndex;

    /**
     * 模板文件名
     *
     * 默认存放在src/main/resources/templates 目录下
     * templateName只传文件名:WorkSafetyReportTemplate.xlsx
     */
    private String templateName;

    /**
     * 导出数据Vo
     */
    private List<?> list;
}

3 Excel 模板导出工具类

  • 封装的Excel 模板导出工具类。

EasyExcelExportUtil.java

import cn.hutool.core.io.resource.ResourceUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.google.common.collect.Sets;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.papp.model.EasyExcelExportBo;
import com.ruoyi.project.domain.vo.RiskAssessmentVo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;

/**
 * Excel 模板导出工具类
 * @author Star
 */
public class EasyExcelExportUtil {
   
   
    private static Logger logger = LoggerFactory.getLogger(EasyExcelExportUtil.class);

    /**
     * 获取resources路径
     * @return
     */
    public static String getPath() {
   
   
        return EasyExcelExportUtil.class.getResource("/").getPath();
    }


    /**
     * 导出方法
     * @param paramet
     * @return 文件路径
     */
    public String export(EasyExcelExportBo paramet){
   
   
        if (paramet.getTemplateName() == null) {
   
   
            throw new ServiceException("请填写模板文件名");
        }
        if (paramet.getMergeRowIndex() == null) {
   
   
            throw new ServiceException("请填写合并行");
        }
        if (paramet.getUniqueColumnIndex() == null) {
   
   
            throw new ServiceException("请填写唯一标识列");
        }
        if (paramet.getList() == null || paramet.getList().size() == 0) {
   
   
            throw new ServiceException("请填充导出的数据");
        }
        if (paramet.getMergeColumnIndex() == null || paramet.getMergeColumnIndex().size() == 0) {
   
   
            throw new ServiceException("请输入需要合并的列");
        }

        //处理文件导出
        InputStream template = null;
        try {
   
   
            // 模板路径
            template = ResourceUtil.getStream("classpath:templates/"+paramet.getTemplateName());
            // 导出文件名称
            String fileName = getPath() + "templateWrite" + System.currentTimeMillis() + ".xlsx";

            //读取Excel
            ExcelWriter excelWriter = EasyExcel.write(fileName)
                .withTemplate(template)
                .excelType(ExcelTypeEnum.XLSX)
                .registerWriteHandler(new CellMergeStrategy(paramet.getUniqueColumnIndex(), paramet.getMergeRowIndex(), paramet.getMergeColumnIndex()))
                .build();
            WriteSheet writeSheet = EasyExcel.writerSheet().build();

            //list map 是查询并需导出的数据,并且里面的字段和excel需要导出的字段对应
            // 直接写入Excel数据
            excelWriter.fill(paramet.getList(), writeSheet);
            excelWriter.finish();
            logger.info("文件导出完毕,路径:"+fileName);
            return fileName;
        } catch (Exception e) {
   
   
            System.out.println(e.getMessage());
            throw new RuntimeException("导出文件错误");
        } finally {
   
   
            if (template != null) {
   
   
                try {
   
   
                    template.close();
                } catch (IOException e) {
   
   
                    e.printStackTrace();
                }
            }
        }
    }

}

4 自定义单元格合并策略(重点)

  • 自定义单元格合并策略,可以指定某一个唯一列uniqueColumnIndex做为合并的条件。

CellMergeStrategy.java

import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.write.merge.AbstractMergeStrategy;
import com.google.common.collect.Lists;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.util.CellRangeAddress;

import java.util.List;
import java.util.Set;


/**
 * 自定义单元格合并策略:推荐使用
 * @author Star
 */
public class CellMergeStrategy extends AbstractMergeStrategy {
   
   

    /** 唯一标识列(该列cell内容一定是全局唯一的,当cell的值相等时才能进行下一列合并) */
    private Integer uniqueColumnIndex;
    /** 从哪一行开始合并 */
    private Integer mergeRowIndex = 0;
    /** 合并列编号,从0开始 */
    private List<Integer> mergeColumnIndex = Lists.newArrayList();

    private CellMergeStrategy() {
   
   
    }

    /**
     * @param uniqueColumnIndex 唯一标识列(该列cell内容一定是全局唯一的,当cell的值相等时才能进行下一列合并)
     * @param mergeRowIndex 从哪一行开始合并
     * @param mergeColumnIndex 合并列编号,从0开始:需要合并的列
     */
    public CellMergeStrategy(Integer uniqueColumnIndex, Integer mergeRowIndex, Set<Integer> mergeColumnIndex) {
   
   
        mergeColumnIndex.stream().forEach(item -> {
   
   
            this.mergeColumnIndex.add(item);
        });
        this.mergeColumnIndex.stream().sorted();

        if (null == uniqueColumnIndex) {
   
   
            this.uniqueColumnIndex = this.mergeColumnIndex.get(0);
        } else {
   
   
            this.uniqueColumnIndex = uniqueColumnIndex;
        }
        this.mergeRowIndex = mergeRowIndex;
    }

    @Override
    protected void merge(Sheet sheet, Cell cell, Head head, Integer relativeRowIndex) {
   
   
        int curColIndex = cell.getColumnIndex();
        int curRowIndex = cell.getRowIndex();

        // 判断该列是否需要合并
        if (!mergeColumnIndex.contains(curColIndex)) {
   
   
            return;
        }

        if (curRowIndex > mergeRowIndex) {
   
   
            for (int i = 0; i < mergeColumnIndex.size(); i++) {
   
   
                if (curColIndex == mergeColumnIndex.get(i)) {
   
   
                    this.mergeRow(sheet, cell, curRowIndex, curColIndex, uniqueColumnIndex);
                    break;
                }
            }
        }
    }

    /**
     * 向上合并单元格
     * @param cell              当前单元格
     * @param rowIndex          当前行
     * @param colIndex          当前列
     * @param uniqueColIndex    唯一标识列(该列cell内容一定是全局唯一的,当cell的值相等时才能进行下一列合并)
     */
    private void mergeRow(Sheet sheet, Cell cell, int rowIndex, int colIndex, int uniqueColIndex) {
   
   
        Object curCellValue = getCellValue(cell);
        Object preCellValue = getCellValue(cell.getSheet().getRow(rowIndex - 1).getCell(colIndex));
        boolean cellEqual = preCellValue.equals(curCellValue);

        boolean baseCellEqual = true;
        if (colIndex >= uniqueColIndex) {
   
   
            Object baseCellValue = getCellValue(cell.getRow().getCell(uniqueColIndex));
            Object preBaseCellValue = getCellValue(cell.getSheet().getRow(rowIndex - 1).getCell(uniqueColIndex));
            baseCellEqual = baseCellValue.equals(preBaseCellValue);
        }

        /**
         * 合并条件
         * 1. 将当前单元格数据与上一个单元格数据比较,相同则执行合并逻辑
         * 2. 唯一标识列内容相同,才能进行下一列合并
         */
        if (!(cellEqual && baseCellEqual)) {
   
   
            return;
        }

        List<CellRangeAddress> mergeRegionList = sheet.getMergedRegions();
        boolean isMerged = false;
        for (int i = 0; i < mergeRegionList.size() && !isMerged; i++) {
   
   
            CellRangeAddress cellRange = mergeRegionList.get(i);
            // 若上一个单元格已经被合并,则先移出原有的合并单元,再重新添加合并单元
            if (cellRange.isInRange(rowIndex - 1, colIndex)) {
   
   
                sheet.removeMergedRegion(i);
                cellRange.setLastRow(rowIndex);
                sheet.addMergedRegion(cellRange);
                isMerged = true;
            }
        }
        // 若上一个单元格未被合并,则新增合并单元
        if (!isMerged) {
   
   
            CellRangeAddress cellRange = new CellRangeAddress(rowIndex - 1, rowIndex, colIndex, colIndex);
            sheet.addMergedRegion(cellRange);
        }
    }

    private Object getCellValue(Cell baseCell) {
   
   
        return CellType.STRING.equals(baseCell.getCellType()) ? baseCell.getStringCellValue() : baseCell.getNumericCellValue();
    }

}

5 Vo配置

  • 业务的数据

  • @ColumnWidth(100 / 8):列宽

  • @HeadStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER):表头样式 居中
  • @ContentStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER,verticalAlignment = VerticalAlignmentEnum.CENTER):主题样式:水平垂直居中
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.alibaba.excel.annotation.write.style.ContentStyle;
import com.alibaba.excel.annotation.write.style.HeadStyle;
import com.alibaba.excel.enums.poi.HorizontalAlignmentEnum;
import com.alibaba.excel.enums.poi.VerticalAlignmentEnum;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;

/**
 * 测试使用
 */
@Data
@ColumnWidth(100 / 8)
@HeadStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER)//表头样式
@ContentStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER,verticalAlignment = VerticalAlignmentEnum.CENTER)
public class WuYeDemo {
   
   
    @ExcelProperty(value = "属地")
    private String possession;

    @ExcelProperty(value = "风险点")
    private String riskPoint;

    @ExcelProperty(value = "风险点分类")
    private String riskPointClassification;

    @ExcelProperty(value = "风险场景")
    private String riskScenario;

    @ExcelProperty(value = "风险等级\n" +
        "(风险点)")
    private String riskGrade;
}

6 模板配置

  • {.字段名}:固定导出数据的位置

image-20230828190337018

7 效果图

文件导出路径:target/classes/templateWrite1693195524899.xlsx

image-20230828185404792

目录
相关文章
|
9月前
fastadmin实现导出Excel和导入Excel数据
fastadmin实现导出Excel和导入Excel数据
224 0
|
5月前
|
存储 分布式计算 DataWorks
如何使用MaxComputer将csv表格文件上传到pycharm?
如何使用MaxComputer将csv表格文件上传到pycharm?
56 1
|
6月前
|
JSON 前端开发 JavaScript
实用!Excel在线网页版表格Luckysheet源码
实用!Excel在线网页版表格Luckysheet源码
212 0
|
12月前
|
网络可视化 Windows
EndNote 中批量导出 PDF
EndNote 中批量导出 PDF
135 0
做报表pdf导出功能实现
做报表pdf导出功能实现
|
存储 JSON easyexcel
使用EasyExcel导入导出Excel报表-JAVA解析Excel工具
使用EasyExcel导入导出Excel报表-JAVA解析Excel工具
309 1
使用EasyExcel导入导出Excel报表-JAVA解析Excel工具
|
easyexcel Java
超方便.使用EasyExcel导入excel
超方便.使用EasyExcel导入excel
|
easyexcel
使用easyExcel导出数据到excel表中
使用easyExcel导出数据到excel表中
700 1
使用easyExcel导出数据到excel表中
|
关系型数据库 数据库 Oracle
如何从Excel表格导入数据批量生成二维码
目前二维码应用渐趋广泛,二维码具有储存量大、保密性高、追踪性高、抗损性强、备援性大、成本便宜等特性,这些特性特别适用于表单、安全保密、追踪、证照、存货盘点、资料备援等方面。那么我们怎么用条码打印软件从现有Excel表格中导入数据批量生成我们所需要的二维码呢? 首先我们打开条码打印软件,点击“新建”...
2616 0
|
JavaScript 前端开发
pdf.js插件使用记录,在线打开pdf
原文:pdf.js插件使用记录,在线打开pdf 天记录一个js库:pdf.js。主要是实现在线打开pdf功能。因为项目需求需要能在线查看pdf文档,所以就研究了一下这个控件。 有些人很好奇,在线打开pdf文档浏览器不是支持吗。
2305 0