这段时间项目需要下载文件,刚开始直接用a标签的href加后端地址的方式就可以下载,这类方法可以下载zip文件等浏览器不能识别的文件,但是遇到下载txt和图片等文件就莫得办法了,因为浏览器会自动打开。
然后就去网上搜了一下,发现有很多人都说直接在a标签里加一个download属性就好了,并且这个可以设置文件名,但是很多都没有接受什么时候加这个属性会生效,所以用我亲自踩完坑的经历来解释一下,先看一个列子,比如:
<a href="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png" download="text.png" target="_blank">点击下载</a>
各位可以运行上面这段代码试一试,地址是有效的
但是,很灵性的但是哈。(各位,重点来了,一定要仔细看)
但是这种方法根本不管用!浏览器还是直接打开图片了,原因是a标签加这个属性得分情况,必须要在同源(同域名、同协议、同端口号)时,才会生效,什么概念呢?
比如说前端项目布在http://192.168.88:8888 这个服务器上面,后端项目也布在同一个服务器同一个端口号下面,这种情况才可以下载,否则的话都不行。
并且就算是以上这种情况前端在本地调试的时候也是不会下载的,原因是在本地调试的时候前端地址一般都是http://localhost:300,很明显和后端地址不是同源嘛。
虽然做了反向代理,但还是不会下载的,不过推到线上就生效了。所以各位在用a标签下载的时候需要注意是否同源(主要看发布后是否同源)
既然说了ajax的方法肯定就还没完嘛,接下来讲一下第二种
先上代码:
//ajax方法
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png', true);
xhr.responseType = 'blob';
xhr.onload = function () {
if (this.status === 200) {
const fileName = 'test.png';
const blob = new Blob([this.response]);
const blobUrl = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = blobUrl;
a.download = fileName;
a.click();
window.URL.revokeObjectURL(blobUrl);
}
};
xhr.send();
// 用fetch发送请求
fetch('https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png').then((res) => {
res.blob().then((blob) => {
const blobUrl = window.URL.createObjectURL(blob);
console.log(blob);
console.log(blobUrl);
// 这里的文件名根据实际情况从响应头或者url里获取
const filename = 'user.jpg';
const a = document.createElement('a');
a.href = blobUrl;
a.download = filename;;
a.click();
window.URL.revokeObjectURL(blobUrl);
});
});
// axios下载
import axios from "axios";
import { message } from 'antd'
export const downloadFile = (method, url, config) => {
let _data = null
let _params = null
if (method === 'GET' || method === 'get') {
_params = config.params
} else {
_data = config.data
}
axios({
url,
method,
params: _params,
data: _data,
responseType: 'blob',
headers: {
'Csrf-Token': '123',
},
}).then(res => {
console.log(res)
const str = res.headers['content-disposition']
if (!res || !str) {
message.error(res.message || '下载失败!')
return
}
if (res && res.status === 200 && res.data) {
const { data, headers, config } = res
let fileName
if (headers['content-disposition']) {
fileName = headers['content-disposition'].replace(/\w+;filename=(.*)/, '$1')
} else if (data.fileName) {
fileName = data.fileName
} else {
fileName = config.params?.fileName
}
// 此处当返回json文件时需要先对data进行JSON.stringify处理,其他类型文件不用做处理
const blob = new Blob([data], { type: headers['content-type'] })
const dom = document.createElement('a')
const downUrl = window.URL.createObjectURL(blob)
dom.href = downUrl
dom.download = decodeURIComponent(fileName)
dom.style.display = 'none'
document.body.appendChild(dom)
dom.click()
dom.parentNode.removeChild(dom)
window.URL.revokeObjectURL(url)
} else {
message.error(res.message || '下载失败!')
}
}).catch(err => {
message.error(err || '下载失败!')
})
}
上面的代码也是可以运行的,地址有效
买一送一再来了一个fetch的
这种方法就不管同源不同源都会下载了,但是这毕竟是ajax请求了嘛,所以会涉及到跨域的问题,需要解决一下跨域问题,不过现在都会配置反选代理,所以也不是上面问题了,但是会有些小细节(如果不细节效果也出不来)
下面看实际项目中的用法
正常来说在项目中请求数据时地址都会写成/aip/xxx/xxx的对吧,所以这个地方一般会写成fetch("/api/xxx/xxx.txt"),对吧,看似很合理,但这样写了点击下载没有反应,也不报错,network也请求成功,但就是不会下载,为什么呢?我也不知道,哪个大佬要是知道一定评论解释一下。
但是我有个解决方案,只要把api换成后端地址就ok了,写成这样:fetch("/http://192.168.88:8888/xxx/xxx.txt"),前面那个"/"必须留着,否则就包跨域的错了,或者也可以写成fetch("http:localhost:3000/http://192.168.88:8888/xxx/xxx.txt")也可以,但是这种前面会被写死,等上线的时候还得改成线上的地址,所以推荐第一种,第一种会自动补全前面的地址,请求完的结果是:
接下来看项目实际代码: