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);// 关闭ExcelWriterexcelWriter.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;}@Overridepublic void beforeCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Head head, Integer columnIndex, Integer relativeRowIndex, Boolean isHead) {}@Overridepublic void afterCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {}@Overridepublic void afterCellDataConverted(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, CellData cellData, Cell cell, Head head, Integer integer, Boolean aBoolean) {}@Overridepublic 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;}@Overridepublic void beforeCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Head head, Integer integer, Integer integer1, Boolean aBoolean) {}@Overridepublic void afterCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Cell cell, Head head, Integer integer, Boolean aBoolean) {}@Overridepublic void afterCellDataConverted(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, CellData cellData, Cell cell, Head head, Integer integer, Boolean aBoolean) {}@Overridepublic 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);// 关闭ExcelWriterexcelWriter.finish();
} catch (UnsupportedEncodingException e) {throw new RuntimeException(e);
} catch (IOException e) {throw new RuntimeException(e);
}

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


总结

阿巴阿巴…

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

asp.net校园招聘管理系统VS开发sqlserver数据库web结构c#编程Microsoft Visual Studio

一、源码特点 asp.net 校园招聘管理系统是一套完善的web设计管理系统&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为vs2010&#xff0c;数据库为sqlserver2008&#xff0c;使用c#语言开发 应用技术&#xff1a;asp.net c#s…

IP行业API助力于网络分析和数据挖掘

引言 在当今数字化时代&#xff0c;数据成为了企业、科研机构和政府决策者的重要资源&#xff0c;而IP行业API则成为了数据分析及挖掘的工具之一。IP行业API是一种能够查询IP地址所属的行业分类信息的应用程序接口&#xff0c;它能够提供在网络分析、用户行为分析及大数据挖掘…

LeetCode 面试题 16.20. T9键盘

文章目录 一、题目二、C# 题解 一、题目 在老式手机上&#xff0c;用户通过数字键盘输入&#xff0c;手机将提供与这些数字相匹配的单词列表。每个数字映射到0至4个字母。给定一个数字序列&#xff0c;实现一个算法来返回匹配单词的列表。你会得到一张含有有效单词的列表。映射…

农业大棚智能化改造升级与远程视频监管方案,助力智慧农业建设发展

一、需求分析 随着现代化技术的发展&#xff0c;农业大棚的智慧化也成为当前备受关注的智慧农业发展手段。利用先进的信息化手段来对农业大棚进行管理&#xff0c;采集和掌握作物的生长状况、作业监督、生态环境等信息数据&#xff0c;实现精准操作、精细管理&#xff0c;远程…

美格智能5G RedCap模组顺利完成中国联通5G物联网OPENLAB开放实验室认证

近日&#xff0c;美格智能5G RedCap模组SRM813Q顺利通过中国联通5G物联网OPENLAB开放实验室端到端的测试验收&#xff0c;并获得OPENLAB实验室的认证证书。这标志着该模组产品各项性能均已符合RedCap商用标准&#xff0c;为5G RedCap规模商用奠定了坚实基础。 中国联通5G物联网…

Kubernetes平台部署Grafana Loki Promtail系统

部署结构图&#xff1a; Loki 是主服务&#xff0c;负责存储日志和处理查询promtail 是代理&#xff0c;负责收集日志并将其发送给 lokiGrafana 用于 UI 展示 只要在应用程序服务器上安装promtail来收集日志然后发送给Loki存储&#xff0c;就可以在Grafana UI界面通过添加Lok…

Python基础教程:类--继承和方法的重写

嗨喽&#xff0c;大家好呀~这里是爱看美女的茜茜呐 什么是继承 继承就是让类与类之间产生父子关系&#xff0c;子类可以拥有父类的静态属性和方法 继承就是可以获取到另一个类中的静态属性和普通方法&#xff08;并非所有成员&#xff09; 在python中&#xff0c;新建的类可…

导轨式安装压力应变桥信号处理差分信号输入转换变送器0-10mV/0-20mV/0-±10mV/0-±20mV转0-5V/0-10V/4-20mA

主要特性 DIN11 IPO 压力应变桥信号处理系列隔离放大器是一种将差分输入信号隔离放大、转换成按比例输出的直流信号导轨安装变送模块。产品广泛应用在电力、远程监控、仪器仪表、医疗设备、工业自控等行业。此系列模块内部嵌入了一个高效微功率的电源&#xff0c;向输入端和输…

Visual Studio2022安装教程【图文详解】(大一小白)编译软件

工欲善其事&#xff0c;必先利其器。想要学好编程&#xff0c;首先要把手中的工具利用好&#xff0c;今天小编教一下大家如何下载安装并使用史上最强大的编译器--Visual Studio&#x1f357; 一.Visual Studio下载及安装 https://visualstudio.microsoft.com/ 打开文件 点击.ex…

基于ubuntu 22, jdk 8x64搭建图数据库环境 hugegraph--google镜像chatgpt

基于ubuntu 22, jdk 8x64搭建图数据库环境 hugegraph download 环境 uname -a #Linux whiltez 5.15.0-46-generic #49-Ubuntu SMP Thu Aug 4 18:03:25 UTC 2022 x86_64 x86_64 x86_64 GNU/Linuxwhich javac #/adoptopen-jdk8u332-b09/bin/javac which java #/adoptopen-jdk8u33…

对于线程的收尾

一)对于synchronized的锁策略: synchronzed是一个自适应的锁&#xff0c;应该根据具体情况来决定选取那种锁策略&#xff1b; 1)synchronized既是一个乐观锁又是一个悲观锁&#xff0c;一开始是一个乐观锁&#xff0c;但是如果发现锁冲突的概率比较高&#xff0c;就会自动转化成…

主题模型LDA教程:一致性得分coherence score方法对比(umass、c_v、uci)

文章目录 主题建模潜在迪利克雷分配&#xff08;LDA&#xff09;一致性得分 coherence score1. CV 一致性得分2. UMass 一致性得分3. UCI 一致性得分4. Word2vec 一致性得分5. 选择最佳一致性得分 主题建模 主题建模是一种机器学习和自然语言处理技术&#xff0c;用于确定文档…

Linux驱动开发——USB设备驱动

目录 一、 USB 协议简介 二、 Linux USB 驱动 三、 USB 设备驱动实例 一、 USB 协议简介 USB(Universal Serial Bus&#xff0c;通用串行总线)正如它的名字一样&#xff0c;是用来连接PC外设的一种通用串行总线&#xff0c;即插即用和易扩展是它最大的特点。所谓即插即用&am…

Edge浏览器新建标签页如何更改为指定网址

Edge浏览器新建标签页如何更改为指定网址&#xff1f; 启动时新建标签页 不是说启动时&#xff0c;而是加号新建标签页时候 启动时 新建标签页 New Tab Changer 可以了 如果没有需要应用商店下载 参考文章

Clickhouse 学习笔记(6)—— ClickHouse 分片集群

前置知识&#xff1a; Clickhouse学习笔记&#xff08;5&#xff09;—— ClickHouse 副本-CSDN博客 与副本对比&#xff1a; 副本虽然能够提高数据的可用性&#xff0c;降低丢失风险&#xff0c;但是每台服务器实际上必须容纳全量数据&#xff0c;对数据的横向扩容没有解决 …

工厂设备报修的流程是怎样的?维修流程要如何优化?

在当今高度自动化的生产环境中&#xff0c;工厂设备的正常运行无疑对于企业的生产效率和经济效益具有至关重要的影响。然而&#xff0c;设备故障是生产过程中不可避免的现象。当设备发生故障时&#xff0c;如何快速、有效地进行报修、维修&#xff0c;以恢复设备的正常运转&…

企业年会/年终活动如何邀请媒体记者报道?

​媒体邀约是企业或组织进行宣传的重要手段之一。通过邀请媒体参加活动&#xff0c;可以增加活动的曝光度和知名度&#xff0c;吸引更多的关注和参与。同时&#xff0c;媒体报道还可以提高企业或组织的权威性和可信度&#xff0c;从而让公众更容易接受其传达的信息。 企业年会或…

【 云原生 | K8S 】kubectl 详解

目录 1 kubectl 2 基本信息查看 2.1 查看 master 节点状态 2.2 查看命名空间 2.3 查看default命名空间的所有资源 2.4 创建命名空间app 2.5 删除命名空间app 2.6 在命名空间kube-public 创建副本控制器&#xff08;deployment&#xff09;来启动Pod&#xff08;nginx-wl…

双十一“静悄悄”?VR购物拉满沉浸式购物体验

以往每年的双十一&#xff0c;都会因为电商购物狂欢而变得热闹非凡&#xff0c;而各大电商平台也会在这天推出各种促销活动。但是&#xff0c;近几年来&#xff0c;双十一正在变得“静悄悄”。一个原因是消费群体越发理性消费&#xff0c;更加重视商品本身的质量和体验&#xf…

LabVIEW在OPC中使用基金会现场总线

LabVIEW在OPC中使用基金会现场总线 本文讨论了如何使用开放的OPC&#xff08;用于过程控制的OLE&#xff09;接口访问基金会现场总线网络和设备。 NI-FBUS通信管理器随附了一个OPC数据访问服务器。 &#xff08;NI-FBUS Configurator自动包含NI-FBUS通信管理器。&#xff09…