1、前言
导出功能是项目开发中经常会遇到的需求,在此就前端方面需要做的部分做一个总结。
2、实现方式
技术实现方面可以从两个维度分析:一是文件大小,二是从数据方面(后端是否维护数据,前端生成下载链接还是后端生成下载链接)
2.1、小文件
点击“导出”,调用接口,后端直接文件流的形式返回给前端,前端接收文件流并下载就可,前端生成url链接,不会占用服务端资源。
2.2、大文件
数据量大,后端收集数据需要花费一定的时间,这样当用户点击直接进行下载需要等待的时间就比较长,体验很糟糕,这种情况的一般做法就是做成任务的形式,当点击“导出”的时候,新建任务传给后台,后台收据收集完之后,通过webSocket的形式服务端主动通知客户端,客户端接收到消息之后在执行真正的导出功能。
功能优化,需要做一个任务列表,方便查看任务状态
2.3、接口返回数据格式
-
后端生成下载链接,前端直接 download 即可。(需要有文件服务器,文件服务器优化-限制带宽)
a. 针对后端的get请求,直接用个a标签来接受后端的文件流
-
后端不生成下载链接,搂完数据直接返回给前端(通常是blob格式),前端拿到数据后,由前端来生成下载链接进行下载(不会占用服务器资源去生成链接)。需要注意的是,前端接收数据后的处理方式
a. 针对后端的post请求,利用原生的XMLHttpRequest方法实现
function request () {
const req = new XMLHttpRequest();
req.open('POST', '<接口地址>', true);
req.responseType = 'blob'; // 包装返回数据格式, 打印出来是 Blob 格式的数据,不是乱码的文本
req.setRequestHeader('Content-Type', 'application/json');
req.onload = function() {
const data = req.response;
const a = document.createElement('a');
const blob = new Blob([data]);
const blobUrl = window.URL.createObjectURL(blob);
download(blobUrl) ;
};
req.send('<请求参数:json字符串>');
};
function download(blobUrl) {
const a = document.createElement('a');
a.style.display = 'none';
a.download = '<文件名>';
a.href = blobUrl;
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(blobUrl);
}
request();
b. 针对后端的post请求,利用原生的fetch方法实现
function request() {
fetch('<接口地址>', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: '<请求参数:json字符串>',
})
.then(res => res.blob())
.then(data => {
let blobUrl = window.URL.createObjectURL(data);
download(blobUrl);
});
}
function download(blobUrl) {
const a = document.createElement('a');
a.style.display = 'none';
a.download = '<文件名>';
a.href = blobUrl;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(blobUrl);
}
request();
c. 引用了第三方请求包来发送请求,比如axios
// `responseType` 表示浏览器将要响应的数据类型
// 选项包括: 'arraybuffer', 'document', 'json', 'text', 'stream'
// 浏览器专属:'blob'
responseType: 'json', // 默认值
responseType:'blob' // 包装返回数据格式, 打印返回的数据是 Blob 格式的数据,不是乱码的文本
this.$axios.post("/api/xxx/xxx/xxx",this.$qs.stringify({range:0,}),{responseType:'blob'}).then(msg=>{
console.log(msg.data) //打印出来是blob对象,已经不是乱码了
let url = window.URL.createObjectURL(msg.data); //表示一个指定的file对象或Blob对象
let a = document.createElement("a");
document.body.appendChild(a);
let fileName=msg.headers["content-disposition"].split(";")[1].split("=")[1]; //filename名称截取
a.href = url;
a.download = fileName; //命名下载名称
a.click(); //点击触发下载
window.URL.revokeObjectURL(url); //下载完成进行释放
})
window.URL.createObjectURL()可以直接生成blob:开头的链接,该链接产生于浏览器端,不会占用服务器资源。
2.4、window.URL.createObjectURL()浏览器兼容性:
window.URL.createObjectURL()可以直接生成blob:开头的链接,该链接产生于浏览器端,不会占用服务器资源。
在IE10, IE11以及Microsoft Edge中生成的blob:链接,你不能把它加到一个a节点上,也不能直接在浏览器地址栏打开访问,并且得到“Error: 拒绝访问。”错误
原因在于不同的浏览器得到的blob:链接形式不一样
-
第一种为chrome和firefox生成的带有当前域名的标准blob链接形式
-
第二种为Microsoft IE和Microsoft Edge生成的不带域名的blob链接。
问题解决
可以通过
// blob:http://localhost:8080/9f3a8b63-02a7-43e5-865b-9a54051040a7 // Chrome, Firefox
// blob:00529B67-4C68-4C30-8D1E-295A05C52EDD // IE
window.URL.createObjectURL(new Blob()).indexOf(location.host) != -1
来检测是否是IE或早期生成Object URL不带域名的Edge。如果表达式返回true则时IE或Edge旧版本。
存在window.navigator.msSaveOrOpenBlob方法,使用该方法创建Blob链接可以下载
3、完美的文件数据处理方式
window.downFile = function (resBlob, fileName, fileType = '.xls', target = '_self') {
const blob = new Blob([resBlob], {
type: 'application/vnd.ms-excel;charset=utf-8'
})
if (window.navigator && window.navigator.msSaveOrOpenBlob) {
// 兼容IE/Edge
window.navigator.msSaveOrOpenBlob(blob, fileName + fileType)
} else {
const url = window.URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.target = target
a.style.display = 'none'
a.setAttribute('download', fileName + fileType)
document.body.appendChild(a)
a.click()
document.body.removeChild(a)
window.URL.revokeObjectURL(url) // 释放资源
}
}
// 在使用axios请求的时候,加上responseType: 'blob'入参
const res = await this.$http({ data: {}, responseType: 'blob'})
window.downFile(res.blob, '文件下载')
<a target="_blank|_self|_parent|_top|framename">
Content-Length: <blob.size>
Content-Type: <blob.type>
Content-Disposition: attachment;filename=<defaultName>
URL.revokeObjectURL() 静态方法用来释放一个之前已经存在的、通过调用 URL.createObjectURL() 创建的 URL 对象。当你结束使用某个 URL 对象之后,应该通过调用这个方法来让浏览器知道不用在内存中继续保留对这个文件的引用了。