问题
想要实现一个点击下载文件的功能,但当文件是txt,png之类的能被浏览器直接打开的文件,浏览器就会自动打开而不会执行下载
原因
首先要知道的是 媒体类型(通常称为 Multipurpose Internet Mail Extensions 或 MIME 类型 )的概念,它表示传递的数据类型(图像/文本/音频等等)
浏览器通过请求头Content-Type中的MIME类型(如 image/jpeg application/pdf)识别数据类型,对相应的数据做出相应处理,而对于图像文本等浏览器可以直接打开的文件,默认处理方式就是打开
两种解决办法
方法一 后台设置响应头
Content-Disposition: attachment; filename="filename.jpg"
Content-Disposition 该响应头表示请求返回的内容该以何种形式展现
第一个参数默认值为 inline 表示回复中的消息体会以页面的一部分或者整个页面的形式展示;attachment 意味着消息体应该被下载到本地;大多数浏览器会呈现一个“保存为”的对话框
第二个参数filename 为可选参数,为保存框中预填的文件名
方法二 通过字节流的形式在前端生成文件
// 这里写成一个点击事件的处理函数
const handleDownload = (e, url) => {
e.preventDefault();
e.stopPropagation();
fetch(url).then((res) => {
res.blob().then((blob) => {
const blobUrl = window.URL.createObjectURL(blob);
// 这里的文件名根据实际情况从响应头或者url里获取
const index = url.lastIndexOf('/');
const filename = url.slice(index + 1);
const a = document.createElement('a');
a.style.display = 'none';
a.href = blobUrl;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(blobUrl);
});
}).catch((error) => {
console.log('文件下载失败', error);
});
};
上面通过原生fetch请求,动态生成一个a标签实现文件下载
res.blob() 该方法将后端返回的字节流转换为返回blob的Promise;blob(Binary Large Object)是一个二进制类型的对象,记录了原始数据信息
URL.createObjectURL(blob) 该方法的返回值可以理解为一个指向 传入参数对象的url 可以通过该url访问 参数传入的对象
该方法需要注意的是,即便传入同一个对象作为参数,每次返回的url对象都是不同的
该url对象保存在内存中,只有在当前文档(document)被卸载时才会被清除,因此为了更好的性能,需要通过URL.revokeObjectURL(blobUrl) 主动释放
在实际项目中还要注意的是请求时的 跨域 和 鉴权 问题
踩坑记录
给a标签添加download属性,此属性指示浏览器下载URL而不是导航到它,download的值会作为预填充的文件名,问题是 此属性仅适用于同源 URL ,跨域无效
window.open()
Window 接口的 open() 方法,是用指定的名称将指定的资源加载到浏览器上下文(窗口 window ,内嵌框架 iframe 或者标签 tab )。如果没有指定名称,则一个新的窗口会被打开并且指定的资源会被加载进这个窗口的浏览器上下文中
说白了就是新开个窗口打开资源链接,浏览器能打开的还是会自动打开不会下载
以上哪里不对望大佬们指正