前言
之前做js下载文件时,习惯性使用ajax来请求后台,但是返回的数据流一直不能正确下载下来。因为时间紧迫所以换成了a标签直接访问后台页面的方法,决定后面再看看能不能找到异步下载文件的方法。
最近时间充裕,于是查阅各种资料,算是理出来了一种解决方案。在此记录下。
解决思路
后端输出文件流,前端js设置响应类型为blob,此时接收到的数据流就是Blob类型的了。然后使用FileReader的readAsDataURL把数据转换成base64,最后放入a标签的href点击下载即可。
前端代码
纯js版
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<button>下载</button>
<script>
const btn = document.querySelector('button');
const xhr = new XMLHttpRequest();
// 设置响应类型为blob
xhr.responseType = "blob";
xhr.onload = function () {
// 请求完成
if (this.status === 200) {
var blob = this.response; // 响应的直接是个blob对象
var reader = new FileReader();
reader.readAsDataURL(blob); // 转换为base64,可以直接放入a标签的href
reader.onload = function (e) {
// 转换完成,创建一个a标签用于下载
var a = document.createElement('a');
a.download = 'test.xlsx';
a.href = e.target.result;
// 在按钮后面插入a元素
btn.insertAdjacentElement('afterend', a);
a.click();
a.remove();
}
}
};
btn.onclick = function() {
xhr.open('POST', 'index.php');
xhr.send();
};
</script>
</body>
</html>
jq版
jq异步下载有两个坑要注意下:
- 后端响应的数据一定要设置响应头Content-type,不然返回的不是个正确的Blob对象,FileReader的readAsDataURL也读取不到里面的数据。
- 默认 jq 的 ajax 对象中的 dataType 无法设置返回资源为 blob 那么就需要手动设置,使其能够最终请求一个 blob 对象。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="jquery.min.js"></script>
</head>
<body>
<button>下载</button>
<script>
$('button').click(e => {
$.ajax({
url: 'index.php',
method: 'post',
xhrFields: { // 在这设置下响应类型是blob
responseType: 'blob'
},
}).done(function(data) {
const r = new FileReader();
r.readAsDataURL(data);
r.onload = function (e) {
// 转换完成,创建一个a标签用于下载
const a = document.createElement('a');
a.download = 'test.xlsx';
a.href = e.target.result;
$("body").append(a); // 修复firefox中无法触发click
a.click();
$(a).remove();
}
}).fail(function(jqXHR, textStatus) {
console.log('error');
console.log(jqXHR);
console.log(textStatus);
});
});
</script>
</body>
</html>
后端代码
<?php
// 对于js请求的可以不用设置这个响应头设置,但jq请求的必须设置
// 响应头表示输出类型是某种二进制数据
header('Content-type: application/octet-stream');
// 这个响应头可以不用写,但写了显得正规
header('Content-Disposition: attachment; filename="test.xlsx"');
// 读取文件并写入到输出缓冲
readfile(__DIR__ . '/test.xlsx');
补充
加与不加Content-type响应头的区别
不加Content-type,应该是把内容当成个字符串输出。
加Content-type,应该是把内容把二进制输出。