前端下载文件已经是一个非常普遍的需求了,同时也已有了非常成熟的解决方案。简单做个罗列,并把可能会遇到的问题描述出来
window.open
或 window.location.href
最简单的方式,同 a
标签访问下载
window.open(url); // 默认是 '_self' 页面会闪一下,体验不是很完美
window.location.href = url;
url
可以是文件地址,也可以是一个GET
下载请求地址浏览器可直接浏览的文件类型是不能通过这种方式下载的(当然我指的是
url
不是一个下载的get
请求时)。例如:txt/png/pdf...
同时,这种方式还有个弊端,无法获取下载进度
a
标签下载
指定下载的文件名并添加下载属性,不设置默认即为文件原名(考虑到兼容性问题,filename
后面尽量加上文件的类型后缀)。
<a href="url" download="filename">下载</a>
相比较前面通过
location.href
的方式,这种方式可以下载任意类型的文件。
export function downloadFile(url: string, filename: string) {
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a); // appendChild 是为了更好的兼容
a.click();
document.body.removeChild(a);
}
iframe
下载
相较于前两种下载方式,iframe
下载方式没有并发数限制。
export function downloadFile() {
const iframe = document.createElement('iframe');
iframe.style.display = 'none';
iframe.src = url; // url 是可下载的文件链接
document.body.appendChild(iframe);
iframe.onload = () => {
setTimeout(() => {
document.body.removeChild(iframe);
}, 0);
};
}
基于 Blob
对象下载
这种方式可以说是最通用的下载方式了,兼容性也很好。通过网络请求从后端获取,利用 Blob
对象将文件流转化为 Blob
二进制对象:
现在主流的网络请求工具,例如
axios...
是支持设置responseType
的,所以可以直接使用responseType = 'blob'
的方式下载。
export function downloadFile(url: string, filename: string) {
const res = await axios.post(url, undefined, {
responseType: 'blob',
params,
});
const resData = res.data;
const link = document.createElement('a');
link.href = URL.createObjectURL(resData);
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(link.href);
}
URL.createObjectURL
作用同fileReader.readAsDataURL
,只不过后者是转为Data(base64) URL
,前者转为Blob URL
。
通过网络请求下载,可以配置函数来自定义下载进度显示(例: axios
,则在请求体的 config.onUploadProgress
中):
const fileConfig = {};
const defaultUploadProgress: OnUploadProgress = (e) => {
const percentage = (e.loaded / e.total!) * 100;
if (percentage !== 100) {
fileConfig.percentage = Number(percentage.toFixed(2)) || 0;
}
};
通常,在下载文件时,会在服务端生成对请求文件类型的描述信息,例如 Content-Type
,Content-Disposition
等,这些信息需要与下载的文件名一起发送,例如:
res.setHeader('Content-Type', 'application/zip');
res.setHeader(
'content-disposition',
`attachment;filename=${encodeURIComponent('资源文件列表')}.zip`,
);
当服务端配置了以后,前端下载文件即可无需通过 new Blob([res], { type: 'application/octet-stream' })
进行指定。
文件预览
默认情况下,访问文件资源,部分类型的文件仅支持预览而无法下载,同理,部分能够下载的文件又不支持预览,此时,我们需要通过网络请求去获取资源,通过设置设置 content-disposition
告诉浏览器是需要预览还是下载。
例如文件资源链接 /s3/xxx/filename.pdf
,那么预览:/s3/xxx/filename.pdf?preview=true
,下载即为:/s3/xxx/filename.pdf?download=true
server
实现:
if (req.query.preview) {
res.setHeader('content-disposition', `inline;filename=filename.pdf`);
} else if (req.query.download) {
res.setHeader('content-disposition', `attachment;filename=filename.pdf`);
}
client
实现:
// preview
export function previewInNewTab(imgUrl: string) {
const img = `<iframe src="${imgUrl}&preview=true" style="width: 100vw;height: 100vh;border: 0;margin: -8px;" />`;
const popup = window.open();
popup?.document.write(img);
}
// download
export function downloadFile(fileUrl: string) {
window.location.href = `${fileUrl}&download=true`;
}