全部导出
批量导出
报错问题分析
经过排查,原因是因为在发起 axios 请求的时候,没有指定响应的数据类型(这里需要指定响应的数据类型为 blob 二进制文件)
当响应数据回来后,会执行 axios 后置拦截器的代码,因为没有对响应头的类型进行判断,而是判断为字符串 String,将该流转为 JSON 对象而报错
导出的 思路分析:
1. 得到用户选中的 ids 数组(前端 vue)
2. 请求导出的后台接口
3. 根据 id,从数据库中查询记录(springboot)
4.拿到数据之后,使用流的方式,响应给浏览器/客户端
Vue2+Elementui
1.新增导出按钮
<el-button type="primary" @click="exportUsers">批量导出</el-button>
2.将选择的 ids 集合当作参数提交给后端 springboot
//导出
exportUsers() {//如果没有选择行数据,则全部导出或者按照检索条件导出
this.$confirm("您是否需要导出?", "提示", {
iconClass: "el-icon-question",//自定义图标样式
confirmButtonText: "确认",//确认按钮文字更换
cancelButtonText: "取消",//取消按钮文字更换
showClose: true,//是否显示右上角关闭按钮
type: "warning",//提示类型 success/info/warning/error
}).then(() => {
//确认操作
//请求批量导出的接口
this.$request.get('/user/exportUsersById', {
params: { //ids携带过去,
ids: this.ids //存的是勾选的id的数组
},
responseType: 'blob', // 设置响应类型为blob(响应的数据是二进制文件)
paramsSerializer: params => {//get方法,传的参数的是数组解决uri的路径问题 ?ids[]=225&ids[]=226
return qs.stringify(params, {indices: false})
}
}).then(response => {
// console.log("response=", response)
if (response.size > 0) {//返回的是blob,判断文件的大小
this.$message.success("导出成功");
} else {
this.$message.warning("导出失败");
}
})
}).catch(() => {
//取消操作
});
},
3.在 axios 后置拦截器中将 blob 二进制文件转为 excel
import axios from 'axios'
import router from "@/router";
import {saveAs} from 'file-saver';//导入该依赖
// response 拦截器
// 可以在接口响应后统一处理结果
request.interceptors.response.use(response => {
//简化.data操作 直接使用res.data就能得到数据
let res = response.data;
console.log("res=", res)
// 判断是否为二进制数据
if (response.config.responseType === 'blob') {
console.log("该文件是二进制文件")
// 从响应头中获取文件名
const contentDisposition = response.headers.get('Content-Disposition');
const filenameRegex = /filename=(.+)/
const fileNameMatch = contentDisposition && contentDisposition.match(filenameRegex);
const fileName = fileNameMatch && fileNameMatch[1];
// 对文件名进行解码,换原成原始文件名
const decodedFileName = decodeURIComponent(fileName);
// 对文件名进行解码,换原成原始文件名
// const decodedFileName = decodeURIComponent(fileName);
// 使用FileSaver库保存文件
const blob = new Blob([response.data], {type: response.headers['content-type']});
// saveAs(blob, 'file.xlsx');
saveAs(blob, decodedFileName || 'file.xlsx');
}
// // 兼容服务端返回的字符串数据
// if (typeof res === 'string') {
// res = res ? JSON.parse(res) : res
// }
// //返回接口的状态码401,返回登录页面
// if (res.code === '401') {
// router.push('/login')
// }
return res;
}, error => {
console.error('response error: ' + error) // for debug
return Promise.reject(error)
})
SpringBoot
导入 maven 依赖
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.1.2</version>
</dependency>
@RequestMapping("/exportUsersById")
public void exportUsersById(@RequestParam(value = "ids", required = false) List<Integer> ids, HttpServletResponse response) {
log.info("idList=" + ids);
List<User> userList;// 用户数据集合
String fileName = "";// 文件名
if (ObjectUtil.isEmpty(ids) || ids.contains(-1)) {// 全部导出
userList = UserServiceImpls.list();
fileName = "所有用户";
log.info("所有用户=" + userList);
if (ObjectUtil.isEmpty(userList)) { // 列表为空
throw new BizException("用户列表为空");
}
} else {
userList = UserServiceImpls.listByIds(ids);// 批量导出
fileName = "部分用户";
log.info("部分用户=" + userList);
}
// 使用huTools工具类-Excel导出
ExcelWriter writer = ExcelUtil.getWriter(true);
writer.write(userList, true);
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8");
try {
// 将Content-Disposition暴露,前端才能得到Content-Disposition的value值
response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "utf-8") + ".xlsx");
writer.flush(response.getOutputStream(), true);
} catch (IOException e) {
// e.printStackTrace();
} finally {
writer.close();
}
}
批量导入
<el-upload
style="display: inline-block"
action="http://localhost:9000/user/importData"
:headers="{token:loginUSer.token}"
:show-file-list="false"
:on-success="handleFileSuccess">
<el-button type="success" class="my-button">批量导入</el-button>
</el-upload>
/**
* 导入数据
*
* @param file:
* @return ResultResponse<String>
* @author "卒迹"
* @description TODO
* @date 17:32
*/
@PostMapping("/importData")
public ResultResponse<String> importData(MultipartFile file) throws IOException {
// 写入文件流
ExcelReader reader = ExcelUtil.getReader(file.getInputStream());
// 以User类的格式导入数据-返回1个集合对象,这里取决于alias注解
List<User> userList = reader.readAll(User.class);
if (ObjectUtil.isEmpty(userList)) {// 导入的数据为空
return ResultResponse.error("导入失败");
}
// 写入数据到数据库
boolean isSave = false;
try {
isSave = UserServiceImpls.saveBatch(userList);
} catch (Exception e) {
// e.printStackTrace();
return ResultResponse.error("导入出错");
}
return isSave ? ResultResponse.success("导入成功") : ResultResponse.error("导入失败");
}
//导入
handleFileSuccess(response, file, fileList) {
console.log("response=", response)
if (response.code === "2000") {
this.$message.success(response.message);
//刷新数据
this.queryUserByUsernameOrName(this.isDisplayMsg = false, 1)
}
if (response.code === "-1") {
this.$message.error(response.message);
}
},