以上是症状,实际的关键做法是在发送请求的时候,加上一个参数:responseType:"blob"
网上的做法或者声称的解决方案,好多都没用,所以我整理了一个保姆教程。
因为我是基于vue2+ElementUi做的前端 Maven+springboot做的后台,所以技术栈不契合的,请自行补课。
前端代码:
vue页面:
<!--elementUi中的按钮组件-->
<el-button type="primary" @click="exportData">导出今日数据</el-button>
vue生命周期中的methods内,完成导出函数
//导入请求api,这里解构的方法是下方调用的方法
import { exportData } from '@/api/orders'
//这个是自定义的文件下载js,下面会贴出完整代码
import { download } from '@/utils/downloadfile'
methods: {
exportData(){
let _this=this;
//api中声明请求后台的方法
exportData().then(response => {
download(response,"data.xlsx"); //启动下载方法,这里的文件名可自定义,但是后缀一定得是“.xlsx”
})
}
}
@/api/orders.js中的请求方法
//网络请求及鉴权组件(elementUi自带)
import request from '@/utils/request'
import { getToken,setToken } from '@/utils/auth'
const state = {
token: getToken()
}
//api请求
export function exportData(){
return request({
url: '/orders/exportData',//因为要用后台的response返回文件流,所以这个跳转的后台方法返回值要设置为void
method: 'get',
responseType:"blob",//我在这里卡了2个多小时,几乎95%以上的教程都没提到需要设置这个参数
params:{
token:getToken() //也可在封装的request.js中挂载token,看个人爱好
}
})
}
上面还会有个问题,因为ElementUi集成的网络请求,要求服务端返回响应状态码。但是后端方法设置返回值为void,导致框架无法正常获取响应状态码,所以在处理响应的时候,要考虑一下这里的特殊情况!!!
@/unit/request.js
// response interceptor
service.interceptors.response.use(
/**
* If you want to get http information such as headers or status
* Please return response => response
*/
/**
* Determine the request status by custom code
* Here is just an example
* You can also judge the status by HTTP Status Code
*/
response => {
const res = response.data;
//如果没有返回code则直接向调用端返回response.data
//当然我这里只是做个提醒,相关返回操作您可以根据自己的实际需要,做的更优雅一些
if (res.code==undefined){
return res;
}
// if the custom code is not 200, it is judged as an error.
if (res.code !== 200) {
Message({
message: res.message || 'Error',
type: 'error',
duration: 5 * 1000
})
return Promise.reject(new Error(res.message || 'Error'))
} else {
return res
}
},
error => {
console.log('err' + error) // for debug
Message({
message: error.message,
type: 'error',
duration: 5 * 1000
})
return Promise.reject(error)
}
)
@/utils/downloadfile.js 下载工具
//主要作用是将返回的文件流以blob的方式,模拟生成下载链接并模拟点击,完成下载
export function download(data, filename) {
let blob = new Blob([data], { type: 'application/x-www-form-urlencoded' });
let blobUrl = window.URL.createObjectURL(blob);
const aElement = document.createElement('a');
document.body.appendChild(aElement);
aElement.style.display = 'none';
aElement.href = blobUrl;
aElement.download = filename;
aElement.click();
document.body.removeChild(aElement);
}
后台代码:
pom.xml中引入Excel相关依赖
<dependencies>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>4.1.2</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.1.2</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml-schemas</artifactId>
<version>4.1.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.2.6</version>
</dependency>
</dependencies>
设置导出实体类,其中继承了Excel设置
注意:该类可以是项目原来的对象类加Excel注解,如果有需要的话,也可以自己写一个导出类,方便导出数据格式化。
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import java.io.Serializable;
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class exportData implements Serializable {
@ColumnWidth(10)
@ExcelProperty(value = "序号", index = 0)
private Integer id;
@ColumnWidth(15)
@ExcelProperty(value = "用户姓名", index = 1)
private String cusname;
@ColumnWidth(25)
@ExcelProperty(value = "用户电话", index = 2)
private String cusphoneNo;
}
这里还有个坑:如果导出的数据中有null值,也会导致Excel导出异常,所以sql语句配置要处理一下null值。
下方为mybatis/Mapper文件中的查询设置:
<!--(@i:=@i+1) AS 'id'/(SELECT @i:=0) AS itable 这部分是生成行号的操作,因为数据id不一定是连续的,导出的文件中不加行号比较不易阅读-->
<!--IFNULL(cusphoneNo,'') AS 'cusphoneNo' 将空值转为空字符串,用来填充文件-->
<select id="toexport" resultType="com.hz.entity.exportData">
SELECT (@i:=@i+1) AS 'id',cusname AS 'cusname',IFNULL(cusphoneNo,'') AS 'cusphoneNo' from orders,(SELECT @i:=0) AS itable;
</select>
后台导出方法
@GetMapping("/exportData")
@ApiOperation(value = "导出数据")
@ResponseBody //实现数据接口
public void exportData(HttpServletResponse response) {
try{
response.setContentType("application/vnd.ms-excel");
response.setCharacterEncoding("utf-8");
// URLEncoder.encode防止中文乱码
String filName = URLEncoder.encode("导出的数据表", "utf-8");
response.setHeader("Content-disposition", "attachment;filename=" + filName +".xlsx");
//用model层处理数据,mybatis做相关数据操作,这里就不细说了,别说你连不上数据库
List<exportData> etlst = ordersService.toexport();
EasyExcel.write(response.getOutputStream(),exportData.class).excelType(ExcelTypeEnum.XLSX).sheet("sheet1").doWrite(etlst);
}catch (Exception e){
System.out.println(e.getMessage());
}
}
注意的坑:
1、前端请求时一定要带要求返回的类型,即responseType:"blob"
2、因为要用后台的response返回文件流,所以后台导出方法返回值要设置为void,让EasyExcel的write和response输出流进行主动返回。
3、mybatis中的查询及返回给导出类的映射数据不能为空,所以如果可能出现空值的字段,要提前做好处理。
总之,导出的Excel文件无法打开,不是数据格式有问题,就是文件格式有问题,但是照我写的来,基本都能成功。