文章目录

  • 写在前面
  • 正文
    • 1. springboot后端引入easyexcel及使用
      • 1.1 引入依赖
      • 1.2 接口serviceImpl方法
      • 1.3 提供一个对list集合去重的方法(根据相同key,去除重复,合并value值)
      • 1.4 BizMergeStrategy合并策略类
      • 1.5 自定义ExcelUtil工具类
    • 2. vue前端调用后台下载excel接口实现点击按钮完成下载
      • 2.1 上图对应vue代码
      • 2.2 export_excel() 方法
    • 3. vue多种方式实现调用后台接口下载excel (本小节借鉴他人总结)
    • 4. 总结碰到的一些问题,避免小伙伴踩坑

写在前面

仅作记录,如能帮助尚在迷途的小伙伴,不胜荣幸。
这两三天好好搞了一下这个利用easyexcel导出并下载excel表格的事情。也是感受颇多,本着前人栽树后人乘凉的人道主义精神,赶紧码下这一篇文章,顺带加深自己的记忆。

正文

1. springboot后端引入easyexcel及使用

1.1 引入依赖
<dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId>
</dependency>
1.2 接口serviceImpl方法

xxxServiceImpl.java

public void exportExcelData(String wo_id, HttpServletResponse response) throws BusinessException {// 这里注意 有同学反应使用swagger 会导致各种问题,请直接用浏览器或者用postmantry {// 模板文件//此处getResourceAsStream 用于获取服务器打包后的Excel模板文件流;//如果采用getPath方法获取文件地址本地ieda环境可以获取到,上传到服务器后会失效。采用流可以都生效,具体原因暂未仔细查看。有兴趣的童鞋可以自己去尝试!//InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream("templates/excelTemplate.xls");// 获取表头数据if (wo_id == null || wo_id.isEmpty()) {throw new BusinessException(BusinessCodeEnum.PARAMETER_ERROR, "工单id获取为空!");}// 下面2行这是我自己的业务,不用管CkdPoInfo ckdPoInfo = ckdPoInfoMapper.selectByPrimaryKey(wo_id);String work_order = ckdPoInfo.getWork_order();List<ExportExcelData> excelDataList = getData(wo_id);  // 从数据库获取excel表体数据WriteCellStyle headWriteCellStyle = new WriteCellStyle();//设置头居中headWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);//内容策略WriteCellStyle contentWriteCellStyle = new WriteCellStyle();//设置 水平居中contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.LEFT);HorizontalCellStyleStrategy horizontalCellStyleStrategy = new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle); //由于自定义了合并策略,所以此处默认合并策略并未使用response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");response.setCharacterEncoding("utf-8");// 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系String fileName = URLEncoder.encode(work_order+"报表导出测试", "UTF-8").replaceAll("\\+", "%20");
//            response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");response.addHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");response.addHeader("Access-Control-Expose-Headers", "Content-disposition");// 自定义的合并策略,重要Map<String, List<RowRangeDto>> srategyMap = ExcelUtil.addMergeStrategy(excelDataList);// 这里需要设置不关闭流;EasyExcel.write(response.getOutputStream(), ExportExcelData.class).autoCloseStream(Boolean.FALSE)// 注册合并策略.registerWriteHandler(new BizMergeStrategy(srategyMap)) // 这里调用自定义的合并策略.registerWriteHandler(ExcelUtil.CellStyleStrategy()) // 调用自定义的工具类.sheet("xxx信息表").doWrite(excelDataList); // 写入获取的list数据集合} catch (Exception e) {// 重置responseresponse.reset();response.setContentType("application/json");response.setCharacterEncoding("utf-8");
//            Map<String, String> map = MapUtils.newHashMap();
//            map.put("status", "failure");
//            map.put("message", "文件下载失败" + e.getMessage());
//            try {//                response.getWriter().println(JSON.toJSONString(map));
//            } catch (IOException ex) {//                ex.printStackTrace();
//            }e.printStackTrace();throw new BusinessException(BusinessCodeEnum.PARAMETER_ERROR, "导出excel失败!");}
}
  • 以上try_catch语句调用easyexcel写文件流我写在了serviceImpl的类方法里,也可以把这段直接写在controller里。个人所有业务层代码都放在service里,controller层只负责调用service层。
  • wo_id是我需要传的参数,这里根据个人实际情况,可传其他也可不传。
  • excelDataList就是我封装的获取我数据库数据的list集合,getData()是具体实现方法;
  • getData()不同场景实现都不同,这里就不再贴出有关它的实现
  • 自定义合并策略、自定义ExcelUtil工具类下面会放,不急。
1.3 提供一个对list集合去重的方法(根据相同key,去除重复,合并value值)
public List<CkdMaterialPackage> getNewList(List<CkdMaterialPackage> oldList) {List<CkdMaterialPackage> newList = new ArrayList<>();HashMap<NewCkdMtrPackage, CkdMaterialPackage> tempMap = new HashMap<NewCkdMtrPackage, CkdMaterialPackage>();// 去掉重复keyfor (CkdMaterialPackage ckdMaterialPackage : oldList) {String odm_pn = ckdMaterialPackage.getOdm_pn();  // 料号充当键值名String exporter_pn = ckdMaterialPackage.getExporter_pn();String importer_pn = ckdMaterialPackage.getImporter_pn();NewCkdMtrPackage newCkdMtrPackage = new NewCkdMtrPackage();  // 作为map的key值// 给属性赋值,以下3个整体构成的类对象作为keynewCkdMtrPackage.setOdm_pn(odm_pn);newCkdMtrPackage.setExporter_pn(exporter_pn);newCkdMtrPackage.setImporter_pn(importer_pn);// if (tempMap.containsKey(newCkdMtrPackage)) {// 合并相同料号的valueckdMaterialPackage.setQuantity(tempMap.get(newCkdMtrPackage).getQuantity() + ckdMaterialPackage.getQuantity());// hashmap不允许key重复,当有key重复时,前面key对应的value值会被覆盖tempMap.put(newCkdMtrPackage, ckdMaterialPackage);} else {tempMap.put(newCkdMtrPackage, ckdMaterialPackage);}}for (Map.Entry<NewCkdMtrPackage, CkdMaterialPackage> entry : tempMap.entrySet()) {newList.add(entry.getValue());}return newList;
}
  • 该方法就是传入一个原始的数据list,然后返回一个去重合并后的list
  • List<CkdMaterialPackage> oldList中 CkdMaterialPackage是我自己的数据库表实体类,换成你自己的
  • NewCkdMtrPackage这个是专门定义的实体类,可以理解为里面的属性整体充当key;举个例子,一个list集合里包含 name,sex,height,score几个元素,我想要合并重复的name,sex,height,且将score求和。就是说我合并的列不止一列。那么把这些重复的封装成一个类,整体作为key来执行。
1.4 BizMergeStrategy合并策略类
public class BizMergeStrategy extends AbstractMergeStrategy {private Map<String, List<RowRangeDto>> strategyMap; // RowRangeDto 行起始结束范围类private Sheet sheet;public BizMergeStrategy(Map<String, List<RowRangeDto>> strategyMap) {this.strategyMap = strategyMap;}@Overrideprotected void merge(org.apache.poi.ss.usermodel.Sheet sheet, Cell cell, Head head, Integer integer) {this.sheet = sheet;if (cell.getRowIndex() == 1 && cell.getColumnIndex() == 0) {/*** 保证每个cell被合并一次,如果不加上面的判断,因为是一个cell一个cell操作的,* 例如合并A2:A3,当cell为A2时,合并A2,A3,但是当cell为A3时,又是合并A2,A3,* 但此时A2,A3已经是合并的单元格了*/for (Map.Entry<String, List<RowRangeDto>> entry : strategyMap.entrySet()) {Integer columnIndex = Integer.valueOf(entry.getKey());entry.getValue().forEach(rowRange -> {//添加一个合并请求sheet.addMergedRegionUnsafe(new CellRangeAddress(rowRange.getStart(),rowRange.getEnd(), columnIndex, columnIndex));});}}}}
  • RowRangeDto
/*** 行分段的起始位置和结束位置dto类*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class RowRangeDto {private int start;private int end;
}
1.5 自定义ExcelUtil工具类
public class ExcelUtil{public static Map<String, List<RowRangeDto>> addMergeStrategy(List<ExportExcelData> excelDataList) {Map<String, List<RowRangeDto>> strategyMap = new HashMap<>();ExportExcelData preExcelData = null;for (int i = 0; i < excelDataList.size(); i++) {ExportExcelData currExcelData = excelDataList.get(i);if (preExcelData != null) {//从第二行开始判断是否需要合并if (currExcelData.getPallet_number().equals(preExcelData.getPallet_number())) {//如果栈板号一样,则可合并栈板号、栈板毛重、栈板尺寸 3列fillStrategyMap(strategyMap, "0", i);fillStrategyMap(strategyMap, "1", i);fillStrategyMap(strategyMap, "2", i);//如果栈板号一样,并且卡通箱号一样,则可合并卡通箱号、单箱毛重、单箱尺寸 3列if (currExcelData.getCarton_number().equals(preExcelData.getCarton_number())) {fillStrategyMap(strategyMap, "3", i);fillStrategyMap(strategyMap, "4", i);fillStrategyMap(strategyMap, "5", i);
//                        //如果栈板号、卡通箱号一样,并且物料料号也一样,则可合并物料料号一列  物料信息查询时已合并
//                        if (currExcelData.getCoopOrg().equals(preExcelDto.getCoopOrg())) {//                            fillStrategyMap(strategyMap, "2", i);
//                        }}}}preExcelData = currExcelData;}return strategyMap;}private static void fillStrategyMap(Map<String, List<RowRangeDto>> strategyMap, String key, int index) {List<RowRangeDto> rowRangeDtoList = strategyMap.get(key) == null ? new ArrayList<>() : strategyMap.get(key);boolean flag = false;for (RowRangeDto dto : rowRangeDtoList) {//分段list中是否有end索引是上一行索引的,如果有,则索引+1if (dto.getEnd() == index) {dto.setEnd(index + 1);flag = true;}}//如果没有,则新增分段if (!flag) {rowRangeDtoList.add(new RowRangeDto(index, index + 1));}strategyMap.put(key, rowRangeDtoList);}public static HorizontalCellStyleStrategy CellStyleStrategy(){WriteCellStyle headWriteCellStyle = new WriteCellStyle();//设置背景颜色headWriteCellStyle.setFillForegroundColor(IndexedColors.WHITE.getIndex());//设置头字体WriteFont headWriteFont = new WriteFont();headWriteFont.setFontHeightInPoints((short)13);headWriteFont.setBold(true);headWriteCellStyle.setWriteFont(headWriteFont);//设置头居中headWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);//内容策略WriteCellStyle contentWriteCellStyle = new WriteCellStyle();//设置 水平居中contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);contentWriteCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);HorizontalCellStyleStrategy horizontalCellStyleStrategy = new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle);return horizontalCellStyleStrategy;}
}

Controller方法:

/*** 导出并下载excel报表* @param wo_id* @param response* @throws BusinessException*/@GetMapping("/downloadExcel")public void exportExcelData(@RequestParam(value = "wo_id") String wo_id,HttpServletResponse response) throws BusinessException {// 获取报表数据ckdPoInfoService.exportExcelData(wo_id, response);}

以上是全部的后端接口及方法。

2. vue前端调用后台下载excel接口实现点击按钮完成下载

2.1 上图对应vue代码
<span v-if="(scope.row.status&63)===60"><el-popconfirm   // 气泡框style="margin-left:20px;"@confirm="export_excel(scope.row.wo_id)"title="导出报关单?"><el-button type="text" slot="reference" size="small" icon="el-icon-document-copy">导出报关单</el-button></el-popconfirm>
</span>
2.2 export_excel() 方法
export_excel(wo_id){//scope.row.wo_idconsole.log("wo_id = "+wo_id);const url = this.BASE_API_URL + 'springbootApi/downloadExcel?wo_id='+wo_id;axios.get(url, {responseType: 'blob'        <!--响应类型必须设为二进制文件流-->}).then((res) => {if (!res) returnconsole.log("res data = "+res.data);let blob = new Blob([res.data], {type: 'application/vnd.ms-excel;charset=utf-8'}) // 文件类型console.log(res.headers['content-disposition']); // 从response的headers中获取filename, 后端response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx") 设置的文件名;//以=分割取数组[1]元素为文件名let filename = window.decodeURI(res.headers['content-disposition'].split('=')[1])let url = window.URL.createObjectURL(blob);  // 创建下载链接let aLink = document.createElement("a");    // 赋值给a标签的href属性aLink.style.display = "none";aLink.href = url;aLink.setAttribute("download", filename);document.body.appendChild(aLink);   // 将a标签挂载上去aLink.click();          // a标签click事件document.body.removeChild(aLink);  // 移除a标签window.URL.revokeObjectURL(url);   // 销毁下载链接return this.$message.success("导出报关单成功");}).catch(function (error) {console.log(error);})
}

3. vue多种方式实现调用后台接口下载excel (本小节借鉴他人总结)

方式一:直接通过a标签

<a href="/images/logo.jpg" download="logo" />

优点:简单方便。
缺点:这种下载方式只支持Firefox和Chrome不支持IE和Safari,兼容性不够好。

方式二: 通过window.location

window.location = 'http://127.0.0.1:8080/api/download?name=xxx&type=xxx'

其实就是类似我直接在浏览器url地址栏输入接口地址,回车下载。
优点:简单方便。
缺点:只能进行get请求,当有token校验的时候不方便。

方式三:axios请求后台接口 (目录2就是基于此)

4. 总结碰到的一些问题,避免小伙伴踩坑

① 首先后端接口写好后,我直接浏览器输入api接口去调用测试:

http://localhost:8989/xxx/downloadExcel?wo_id=33b948460598420eb533d62930c9

结果弹出下载好的文件并保存框,证明后端接口可用;测试时可以前后端分开编写和测试。

② 我定义下载逻辑用的是easyexcel,但是上传文件逻辑前人用的是 excelkit,然后poi版本是3.1,但是我easyexcel内包含4.0版本的poi,所以导致上传文件逻辑一些方法报错。
解决办法:pom.xml依赖里手动添加发生冲突的jar包更高版本即可。
③ 后端设置好header里定义好了下载的excel表格名称,但是vue前端并没有获取到:

// 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系String fileName = URLEncoder.encode(work_order+"报表导出测试", "UTF-8").replaceAll("\\+", "%20");
//            response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");response.addHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");//response.addHeader("Access-Control-Expose-Headers", "Content-disposition"); 此时还没有哦
console.log(res.headers['content-disposition']);  // 这里控制台输出是undefined

查资料得知必须后台在设置请求头时将 content-disposition 加入到 Access-Control-Expose-Headers里,前端才能获取到。
即后台加一段代码:

response.addHeader("Access-Control-Expose-Headers", "Content-disposition");

然后vue就能获取到文件名了。


参考文章:
1. Vue项目利用axios请求接口下载excel(附前后端代码)
2. vue中axios实现二进制流文件下载
3. 老哥写的很棒
4. 这位大佬设置模板填充的,可以看看,还有打压缩包下载,留个传送门
5. 这个帮我最大的忙
6. github源码easyexcel地址
7. 使用easy excel导出复杂表头的excel
8. 感觉有用的就放在这里了

分割线====

  1. 这里乱入一个easypoi的官方文档
  2. 使用EasyPoi完成复杂一对多excel表格导出功能

利用EasyExcel完整的springboot +vue前后端导出并下载excel表格相关推荐

  1. SpringBoot+Vue前后端分离,使用SpringSecurity完美处理权限问题(二)

    关注公众号[江南一点雨],专注于 Spring Boot+微服务以及前后端分离等全栈技术,定期视频教程分享,关注后回复 Java ,领取松哥为你精心准备的 Java 干货! 当前后端分离时,权限问题的 ...

  2. Springboot Vue前后端分离实现基础的后台管理系统

    最近在利用空闲的时间学习springboot+Vue前后端分离相关知识,然后动手写了个后台管理系统,实现登录验证.学生模块.英雄联盟模块.数据可视化(暂未开发,准备使用echarts进行):这边先以英 ...

  3. 基于SpringBoot+Vue前后端分离的在线教育平台项目

    基于SpringBoot+Vue前后端分离的在线教育平台项目 赠给有缘人,希望能帮助到你!也请不要吝惜你的大拇指,你的Star.点赞将是对我最大的鼓励与支持! 开源传送门: 后台:Gitee | Gi ...

  4. 网上书城 springboot vue前后端分离

    网上书城 springboot vue前后端分离 文章目录 网上书城 springboot vue前后端分离 前言 一.运行截图 二.pom文件 1.引入库 总结 前言 基于springboot vu ...

  5. SpringBoot + Vue前后端分离开发:全局异常处理及统一结果封装

    SpringBoot + Vue前后端分离开发:全局异常处理及统一结果封装 文章目录 SpringBoot + Vue前后端分离开发:全局异常处理及统一结果封装 前后端分离开发中的异常处理 统一结果封 ...

  6. SpringBoot+Vue前后端分离,使用SpringSecurity完美处理权限问题(六)

    当前后端分离时,权限问题的处理也和我们传统的处理方式有一点差异.笔者前几天刚好在负责一个项目的权限管理模块,现在权限管理模块已经做完了,我想通过5-6篇文章,来介绍一下项目中遇到的问题以及我的解决方案 ...

  7. SpringBoot + Vue 前后端分离(用户信息更新头像上传Markdown图片上传)

    文章目录 前言 用户信息更新 前端发送 后端接口 修改用户头像 前端 前端图片显示 图片上传 完整 代码 后端代码 图片存储 图片上传工具类 图片工具类的配置 工具类实现 效果 Markdown 图片 ...

  8. 从0搭建一个Springboot+vue前后端分离项目(一)安装工具,创建项目

    从0搭建一个Springboot+vue前后端分离项目(二)使用idea进行页面搭建+页面搭建 参考学习vue官网文档 https://v3.cn.vuejs.org/guide/installati ...

  9. 大二期末作孽(SpringBoot+Vue前后端分离博客社区(重构White Hole))

    文章目录 前言 目录 效果演示 前言 由于时间关系,完成度确实不高,而且不签只是完成了客户端,当然目前这样也是已经可以正常使用了,当然有点勉强.不过后续还是会不断的去更新维护的,不过大体的架构是这样的 ...

最新文章

  1. 悬浮球 / 悬浮按钮 / 辅助按钮
  2. php 常量 循环 1,php循环控制break、continue语句、goto语句和php常量
  3. 手写一个合格的前端脚手架
  4. Window10:不能建立到远程计算机的连接。你可能需要更改此连接的网络设置。
  5. dj电商-电子商务常见的商业模式
  6. zookeeper 单机配置
  7. Nginx学习总结(14)——Nginx配置参数详细说明与整理
  8. Java-占位符的使用
  9. 送你一份计算机视觉精品学习资料,学完拿高薪offer!
  10. matlab建立二阶开环系统仿真图,实验二 Simulink仿真实验
  11. [转载] Python函数中把列表(list)当参数
  12. QTableView修改数据后弹出是否保存的提示框。
  13. Gantt - attachEvent事件监听 - (必须)拥有返回值事件
  14. SGVision正反检测
  15. html下拉菜单hover,css用hover制作下拉菜单
  16. 迅雷总提示版权什么的。。
  17. 正则表达式匹配身份证号
  18. ios : Provision Profile 添加设备 device的 udid
  19. F-Pairwise Modulo
  20. 无法启动此程序因为计算机丢失msvcr110.dll,计算机中丢失msvcr110.dll怎么办

热门文章

  1. iOS上架app store详细教材
  2. 将Abp移植进.NET MAUI项目
  3. springboot增加tp90监控
  4. Introspect MIPI D-PHY/C-PHY DSI/CSI总线协议分析仪(Analyzer)
  5. Flex Cairngorm简介
  6. 日本語トレーニング(一)
  7. 购买佳能A710全过程
  8. QQ电脑管家中的远程地址
  9. 企业舆情监控排查什么,TOOM讲解企业舆情监控工作方案?
  10. 小学5年级计算机考试作文,小学五年级期中考试作文