SpringBoot 使用EasyExcel 导出Excel报表(单元格合并)

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 前言
  • 一、导入依赖
  • 二、代码
      • 1. 导出简单的Excel
      • 2. 代码控制导出报表的格式
  • 总结


前言

SpringBoot 使用Alibaba提供的EasyExcel导出Excel报表。

本文中涉及的业务逻辑有:

  1. 前端发起请求,后端封装Excel信息,直接在浏览器完成下载业务;
  2. 在后端,通过代码,控制导出的Excel样式。(主要包括:横向、纵向合并单元格

本文中后端导出Excel的数据,只包括导出列表数据,具体指:将一个List< List< String>>类型的数据导出到Excel中。(可将数据导出到不同的sheet中)

也就是说导出的数据没有固定的对象格式。如果需要导出List< Object> 的列表,需要封装导出对象,并对属性配置@ExcelProperty等信息,此文目前将不涉及这部分的内容。

最终效果展示:
在这里插入图片描述


一、导入依赖

导入相关依赖

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

二、代码

1. 导出简单的Excel

// 导出的Excel 文件名称
String fileName = "xxx.xlsx";
try {
    response.setContentType("application/vnd.ms-excel");
    response.setHeader("Content-Type", "application/vnd.ms-excel");
    fileName = URLEncoder.encode(fileName, "UTF-8");
    response.setHeader("Access-Control-Expose-Headers","Content-Disposition");
    response.setHeader("Content-Disposition", "attachment;filename=" + fileName);
    response.addHeader("Cache-Control", "no-cache");

    // 创建ExcelWriter对象
    ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream()).build();

    // 写入第一个sheet的数据
    WriteSheet sheet1 = EasyExcel.writerSheet(0, "Sheet1")
            .registerWriteHandler(mergePrevCol1)
            .build();
    // 设置Excel表头(表头横向展示)
    sheet1.setHead(Arrays.asList(Arrays.asList(""),Arrays.asList("1#加热炉"),Arrays.asList("1"),Arrays.asList("2"),Arrays.asList("3#加热炉"),Arrays.asList("1"),Arrays.asList("2")));
    // 写入数据
    excelWriter.write(heatFurnace1And3MZLValueList, sheet1);

    // 关闭ExcelWriter
    excelWriter.finish();
} catch (UnsupportedEncodingException e) {
    throw new RuntimeException(e);
} catch (IOException e) {
    throw new RuntimeException(e);
}

以上就能完成简单的导出Excel报表的功能

请注意Controller对应接口的返回值,一般设置为void

    @PostMapping(value = "/export")
    @ApiOperation(value = "导出报表")
    public void export(@RequestBody RecordExportBO bean, HttpServletResponse response) {
    	// 这里的RecordExportBO 为封装接收前端数据的对象
        iService.export(bean,response);
}

2. 代码控制导出报表的格式

这里主要包括控制表头的格式以及合并单元格

A. 合并行的工具类

package cisdi.info.imc.device.model.common.util;

import com.alibaba.excel.metadata.CellData;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.write.handler.CellWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteTableHolder;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.util.CellRangeAddress;

import java.util.List;

/**
 * 合并Excel的行(内容相同的行才合并)
 */
public class ExcelFillCellMergeStrategyUtil implements CellWriteHandler {
    private int[] mergeColumnIndex; //数组存放这一行需要合并那几列  [0,1,2] 在这mergeRowIndex行中合并 0 、1、2列
    private int mergeRowIndex;  // 存放需要向上合并的列
    private Integer noMergeRowIndex;// 不要合并的列

    public ExcelFillCellMergeStrategyUtil() {
    }

    public ExcelFillCellMergeStrategyUtil(int mergeRowIndex, int[] mergeColumnIndex) {
        this.mergeRowIndex = mergeRowIndex;
        this.mergeColumnIndex = mergeColumnIndex;
    }

    public ExcelFillCellMergeStrategyUtil(int mergeRowIndex, int[] mergeColumnIndex, Integer noMergeRowIndex) {
        this.mergeColumnIndex = mergeColumnIndex;
        this.mergeRowIndex = mergeRowIndex;
        this.noMergeRowIndex = noMergeRowIndex;
    }

    @Override
    public void beforeCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Head head, Integer columnIndex, Integer relativeRowIndex, Boolean isHead) {

    }

    @Override
    public void afterCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {

    }

    @Override
    public void afterCellDataConverted(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, CellData cellData, Cell cell, Head head, Integer integer, Boolean aBoolean) {

    }

    @Override
    public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List<CellData> list, Cell cell, Head head, Integer integer, Boolean aBoolean) {
        int curRowIndex = cell.getRowIndex();
        int curColIndex = cell.getColumnIndex();
        if (curRowIndex > mergeRowIndex) {
            for (int i = 0; i < mergeColumnIndex.length; i++) {
                if (curColIndex == mergeColumnIndex[i]) {
                    mergeWithPrevRow(writeSheetHolder, cell, curRowIndex, curColIndex);
                    break;
                }
            }
        }
    }

    /**
     * 当前单元格向上合并
     * .
     * @param writeSheetHolder writeSheetHolder
     * @param cell             当前单元格
     * @param curRowIndex      当前行
     * @param curColIndex      当前列
     */
    private void mergeWithPrevRow(WriteSheetHolder writeSheetHolder, Cell cell, int curRowIndex, int curColIndex) {
        Object curData = cell.getCellTypeEnum() == CellType.STRING ? cell.getStringCellValue() : cell.getNumericCellValue();
        Row  preRow = cell.getSheet().getRow(curRowIndex - 1);
        if (preRow == null) {
            // 当获取不到上一行数据时,使用缓存sheet中数据
            preRow = writeSheetHolder.getCachedSheet().getRow(curRowIndex - 1);
        }
        Cell preCell = preRow.getCell(curColIndex);
        Object preData = preCell.getCellTypeEnum() == CellType.STRING ? preCell.getStringCellValue() : preCell.getNumericCellValue();
        //不需要合并的列直接跳出
        if ( (noMergeRowIndex != null) &&   noMergeRowIndex == (curRowIndex - 1)  ){
            return;
        }
        // 将当前单元格数据与上一个单元格数据比较
        Boolean dataBool = preData.equals(curData);

        //此处需要注意:所以获取每一行第一列数据和上一行第一列数据进行比较,如果相等合并
        boolean equals = cell.getRow().getCell(0).getStringCellValue().equals(cell.getSheet().getRow(curRowIndex - 1).getCell(0).getStringCellValue());
        if (dataBool && equals) {
            Sheet sheet = writeSheetHolder.getSheet();
            List<CellRangeAddress> mergeRegions = sheet.getMergedRegions();
            boolean isMerged = false;
            for (int i = 0; i < mergeRegions.size() && !isMerged; i++) {
                CellRangeAddress cellRangeAddr = mergeRegions.get(i);
                // 若上一个单元格已经被合并,则先移出原有的合并单元,再重新添加合并单元
                if (cellRangeAddr.isInRange(curRowIndex - 1, curColIndex)) {
                    sheet.removeMergedRegion(i);
                    cellRangeAddr.setLastRow(curRowIndex);
                    sheet.addMergedRegion(cellRangeAddr);
                    isMerged = true;
                }
            }
            // 若上一个单元格未被合并,则新增合并单元
            if (!isMerged) {
                CellRangeAddress cellRangeAddress = new CellRangeAddress(curRowIndex - 1, curRowIndex, curColIndex, curColIndex);
                sheet.addMergedRegion(cellRangeAddress);
            }
        }
    }

}

B. 合并列的工具类

package cisdi.info.imc.device.model.common.util;

import com.alibaba.excel.metadata.CellData;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.write.handler.CellWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteTableHolder;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.util.CellRangeAddress;

import java.util.HashMap;
import java.util.Map;
import java.util.List;

/**
 * 合并Excel的列
 */
public class ExcelFillCellMergePrevColUtil implements CellWriteHandler {
    private static final String KEY ="%s-%s";
    //所有的合并信息都存在了这个map里面
    Map<String, Integer> mergeInfo = new HashMap<>();

    public ExcelFillCellMergePrevColUtil() {
    }
    public ExcelFillCellMergePrevColUtil(Map<String, Integer> mergeInfo) {
        this.mergeInfo = mergeInfo;
    }

    @Override
    public void beforeCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Head head, Integer integer, Integer integer1, Boolean aBoolean) {

    }

    @Override
    public void afterCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Cell cell, Head head, Integer integer, Boolean aBoolean) {

    }

    @Override
    public void afterCellDataConverted(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, CellData cellData, Cell cell, Head head, Integer integer, Boolean aBoolean) {

    }

    @Override
    public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List<CellData> list, Cell cell, Head head, Integer integer, Boolean aBoolean) {
        //当前行
        int curRowIndex = cell.getRowIndex();
        //当前列
        int curColIndex = cell.getColumnIndex();

        Integer num = mergeInfo.get(String.format(KEY, curRowIndex, curColIndex));
        if(null != num){
            // 合并最后一行 ,列
            mergeWithPrevCol(writeSheetHolder, cell, curRowIndex, curColIndex,num);
        }
    }

    public void mergeWithPrevCol(WriteSheetHolder writeSheetHolder, Cell cell, int curRowIndex, int curColIndex, int num) {
        Sheet sheet = writeSheetHolder.getSheet();
        CellRangeAddress cellRangeAddress = new CellRangeAddress(curRowIndex, curRowIndex, curColIndex, curColIndex + num);
        sheet.addMergedRegion(cellRangeAddress);
    }

    //num从第几列开始增加多少列
    // curRowIndex 在第几行进行行合并
    // curColIndex 在第几列进行合并
    // num 合并多少格
    // 比如我上图中中心需要在第三行 从0列开始合并三列 所以我可以传入 (3,0,2)
    public void add (int curRowIndex,  int curColIndex , int num){
        mergeInfo.put(String.format(KEY, curRowIndex, curColIndex),num);
    }

}

C. 控制表头格式

/**
* 设置导出报表的表格格式
* @return
*/
public CellStyleStrategyUtil horizontalCellStyleStrategyBuilder() {
   WriteCellStyle headWriteCellStyle = new WriteCellStyle();
   //设置头字体
   WriteFont headWriteFont = new WriteFont();
   headWriteFont.setFontHeightInPoints((short) 15);
   headWriteFont.setBold(true);
   headWriteCellStyle.setWriteFont(headWriteFont);
   //设置头居中
   headWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
   //内容策略
   WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
   //设置 水平居中
   contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
   //垂直居中
   contentWriteCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);

   return new CellStyleStrategyUtil(headWriteCellStyle, contentWriteCellStyle,Boolean.TRUE);
    }

D. 具体用法

直接结合具体例子,通过例子中关键代码的注解进行介绍:

// 导出的Excel 文件名称
String fileName = "xxx.xlsx";
try {
    response.setContentType("application/vnd.ms-excel");
    response.setHeader("Content-Type", "application/vnd.ms-excel");
    fileName = URLEncoder.encode(fileName, "UTF-8");
    response.setHeader("Access-Control-Expose-Headers","Content-Disposition");
    response.setHeader("Content-Disposition", "attachment;filename=" + fileName);
    response.addHeader("Cache-Control", "no-cache");

    // 创建ExcelWriter对象
    ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream()).build();

    // 创建第一个sheet的列合并工具类对象
    ExcelFillCellMergePrevColUtil mergePrevCol1 = new ExcelFillCellMergePrevColUtil();
    // 第一个参数:合并哪一行;第二个参数:合并的起始列;第三个参数:往右合并的列数;
    // 指一条代表合并第0行的第1,2,3列。
    mergePrevCol1.add(0,1,2);
    // 这一行代表合并第0行的4,5,6列
    mergePrevCol1.add(0,4,2);

    // 写入第一个sheet的数据
    WriteSheet sheet1 = EasyExcel.writerSheet(0, "Sheet1")
            // 字体表格样式工具类,下方展示
            .registerWriteHandler(horizontalCellStyleStrategyBuilder())
            // 合并行。第一个参数:从第几行开始合并、第二个参数:合并哪几列、第三个参数:到哪一行为止。
            // 这一行的意思是:从第0行开始合并第0列,到第2行为止。也就是合并了第0行和第1行的第0列。(注意这里必须合并列的内容相同才能够合并)
            .registerWriteHandler(new ExcelFillCellMergeStrategyUtil(0,new int[]{0}, 2))
            // 合并列。
            .registerWriteHandler(mergePrevCol1)
            .build();
    // 设置Excel表头
    // Collections.singletonList("") 这样设置表头的第0列才能与第1行的第0列合并。使用Arrays.asList("")是不行的。
    sheet1.setHead(Arrays.asList(Collections.singletonList(""),Arrays.asList("1#加热炉"),Arrays.asList("1"),Arrays.asList("2"),Arrays.asList("3#加热炉"),Arrays.asList("1"),Arrays.asList("2")));
    // 写入数据
    // list1的类型为List<List<String>>
    excelWriter.write(list1, sheet1);

    // 创建第二个sheet的列合并工具类对象
    ExcelFillCellMergePrevColUtil mergePrevCol2 = new ExcelFillCellMergePrevColUtil();
    mergePrevCol2.add(0,1,3);
    mergePrevCol2.add(0,5,3);

    // 写入第二个sheet的数据
    WriteSheet sheet2 = EasyExcel.writerSheet(1, "Sheet2")
            .registerWriteHandler(horizontalCellStyleStrategyBuilder()) // 字体表格样式工具类,下方展示
            .registerWriteHandler(new ExcelFillCellMergeStrategyUtil(0,new int[]{0}, 2))
            .registerWriteHandler(mergePrevCol2)
            .build();
    sheet2.setHead(Arrays.asList(Collections.singletonList(""),Arrays.asList("2#加热炉"),Arrays.asList("1"),Arrays.asList("2"),Arrays.asList("3"),Arrays.asList("4#加热炉"),Arrays.asList("1"),Arrays.asList("2"),Arrays.asList("3")));
     // list2的类型为List<List<String>>
    excelWriter.write(list2, sheet2);

    // 关闭ExcelWriter
    excelWriter.finish();
} catch (UnsupportedEncodingException e) {
    throw new RuntimeException(e);
} catch (IOException e) {
    throw new RuntimeException(e);
}

效果图
在这里插入图片描述
在这里插入图片描述


总结

阿巴阿巴…

文章内容就到这里结尾了,整个过程鄙人亲力亲为。如果你的需求相同,又还是无法达到想要的效果,抑或是出现了什么问题,欢迎找笔者沟通!!!

此外,本篇文章中单元格合并的工具类等代码,并非我自己亲自完成,如有侵权,请及时联系笔者。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/128400.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SpringDataJpa(一)

一、JPA概述 1.1 ORM概述 ORM&#xff08;Object-Relational Mapping&#xff09; 表示对象关系映射。在面向对象的软件开发中&#xff0c;通过ORM&#xff0c;就可以把对象映射到关系型数据库中。只要有一套程序能够做到建立对象与数据库的关联&#xff0c;操作对象就可以直…

K9203 996920302 面向DNP3的网络安全解决方案

K9203 996920302 面向DNP3的网络安全解决方案 2014年ISA卓越技术创新奖获得者&#xff0c;超电子&#xff0c;3eTI的CyberFence工业防火墙解决方案提供强大加密和应用程序级深度数据包检测(DPI)功能。最近&#xff0c;3eTI为其CyberFence产品线增加了DNP3(分布式网络协议)支持…

快速开发一个简单实用的MES系统?

题主在一个光伏组件工厂做生产管理&#xff0c;但工厂竟然没有MES系统&#xff0c;于是想自己开发一个简单的MES系统。那么我们来看看题主对于开发MES系统的要求—— 对系统&#xff1a;每一个产品都有一个条形码&#xff0c;希望系统可以追踪生产计划下的产品的生产状态&…

java项目之戒烟网站(ssm+vue)

项目简介 戒烟网站实现了以下功能&#xff1a; 用户可以对首页&#xff0c;用户分享&#xff0c;论坛交流&#xff0c;公告文章&#xff0c;个人中心&#xff0c;后台管理等功能进行操作。 管理员可以对网站所有功能进行管理&#xff0c;包括管理用户的基本信息。 &#x1f4…

2011年09月06日 Go生态洞察:Go语言的反射法则

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

chatglm3-6b部署及微调

chatglm3-6b部署及微调 modelscope: https://modelscope.cn/models/ZhipuAI/chatglm3-6b/filesgithub: https://github.com/THUDM/ChatGLM3镜像: ubuntu20.04-cuda11.7.1-py38-torch2.0.1-tf1.15.5-1.8.1v100 16G现存 单卡 安装 软件依赖 # 非必要无需执行 # pip install -…

OC-编译错误

明明包含了头文件&#xff0c;但是还是显示未知的类型 可能这个头文件被某个宏包住了 #if defined(__cplusplus) 在 C 代码中包含了一个 C 的头文件会显示这个错误“the util lib only be used in c”&#xff0c;此时用 #if defined(__cplusplus) #endif 包一下就行了&…

NSS [HUBUCTF 2022 新生赛]checkin

NSS [HUBUCTF 2022 新生赛]checkin 判断条件是if ($data_unserialize[username]$username&&$data_unserialize[password]$password)&#xff0c;满足则给我们flag。正常思路来说&#xff0c;我们要使序列化传入的username和password等于代码中的两个同名变量&#xff0…

数字滤波器分析---频率响应

数字滤波器分析---频率响应 幅值、相位、冲激和阶跃响应、相位和群延迟、零极点分析。 分析滤波器的频域和时域响应。可视化复平面中的滤波器极点和零点。 频率响应 数字域 freqz 使用基于 FFT 的算法来计算数字滤波器的 Z 变换频率响应。具体来说&#xff0c;语句 [h,w]…

多组学整合,快速定位关键代谢通路,解析分子机制

生物学是一种复杂的学科&#xff0c;往往单一组学无法探究想要了解的生物学问题&#xff0c;这时就要运用到多组学联合分析。近年来&#xff0c;多组学研究的不断发展和持续火热&#xff0c;越来越多的研究者开始将微生物组学和代谢组学联合起来。16s全长扩增子测序可提供细菌构…

【微信公众号开发】1.1 微信公众号开发课程内容介绍

一、微信公众号介绍 1、公众号类型及基本介绍 服务号、订阅号、小程序之间的关联及区别 2、编辑模式的使用 非开发者使用微信公众号的方式&#xff0c;通过微信公众号提供的平台来编辑 3、开发模式及预备知识介绍 如果我们不想使用默认的编辑模式&#xff0c;可以在具备一…

【算法练习Day44】最长递增子序列最长连续递增序列最长重复子数组

​&#x1f4dd;个人主页&#xff1a;Sherry的成长之路 &#x1f3e0;学习社区&#xff1a;Sherry的成长之路&#xff08;个人社区&#xff09; &#x1f4d6;专栏链接&#xff1a;练题 &#x1f3af;长路漫漫浩浩&#xff0c;万事皆有期待 文章目录 最长递增子序列最长连续递增…

利用MSF设置代理

1、介绍&#xff1a; 通过MSF拿到一个机器的权限后&#xff0c;通过MSF搭建socks代理&#xff0c;然后通内网。 拿到目标权限&#xff0c;有很多方法&#xff0c;比如&#xff1a;①ms17-010 ②补丁漏洞 ③MSF生成后门 在此直接使用MSF生成后门 MSF中有三个代理模块&#x…

k8s ingress基础

一、ingress 简介 在k8s集群中&#xff0c;service和pod的ip为内网ip&#xff0c;仅集群内部才可以访问。如果外部应用想要直接访问集群内的服务&#xff0c;就需要把外部请求通过负载均衡转发到service上&#xff0c;然后再由kube-proxy组件将其转发给后端pod。一般service可…

Centos8安装出错问题

科普介绍&#xff1a; CentOS 8 是一个基于 Linux 的操作系统&#xff0c;是 Red Hat Enterprise Linux &#xff08;RHEL&#xff09;的免费和开源版本。它提供了稳定、安全和可靠的基础设施&#xff0c;适用于服务器和桌面环境。CentOS 8 是 CentOS 系列中最新的版本&#x…

亚数受邀参加“长三角G60科创走廊量子密码应用创新联盟(中心)”启动仪式

11月8日&#xff0c;在第六届中国国际进口博览会2023长三角G60科创走廊高质量发展要素对接大会上&#xff0c;亚数信息科技&#xff08;上海&#xff09;有限公司CEO翟新元作为密码企业代表之一受邀参加“长三角G60科创走廊量子密码应用创新联盟&#xff08;中心&#xff09;”…

ios 对话框 弹框,输入对话框 普通对话框

1 普通对话框 UIAlertController* alert [UIAlertController alertControllerWithTitle:"a" message:"alert12222fdsfs" pr…

人工智能数学基础3:用Python 编程求极限

求极限&#xff0c;并用Python 编程求极限 使用洛必达法则来求解 利用泰勒展开的方法来求解这个极限 import sympyx sympy.Symbol(x) f (sympy.sin(x) - x * sympy.cos(x)) / (sympy.sin(x) ** 3)limit_value sympy.limit(f, x, 0) print(limit_value)

OAuth2.0和1.0的区别

OAuth2.0的最大改变就是不需要临时token了&#xff0c;直接authorize生成授权code&#xff0c;用code就可以换取accesstoken了&#xff0c;同时accesstoken加入过期&#xff0c;刷新机制&#xff0c;为了安全&#xff0c;要求第三方的授权接口必须是https的。OAuth2.0不能向下兼…

FL Studio21.2宿主软件中文免费版下载

纵观当下宿主软件市场&#xff0c;正值百家争鸣、百花齐放之际像Mac系统的Logic Pro X、传统宿主软件代表Cubase、录音师必备Pro Tools、后起之秀Studio One等&#xff0c;都在各自的领域具有极高的好评度。而在众多宿主软件中&#xff0c;有这么一款历久弥新且长盛不衰的独特宿…