前端文件下载方法总结
1、open或location.href
最简单最直接的方式,跟a标签访问下载链接一样
前提:下载地址不需要鉴权 以及 不存在跨域问题
window.location.href = url // url为下载地址
window.open('downloadFile.zip');
优点:简单方便
缺点:
- 直接访问可能会覆盖当前页面地址
- 不能添加header,也就不能进行鉴权
- 会出现URL长度限制问题
- 需要注意url编码问题
- 浏览器可直接浏览的文件类型是不提供下载的,如txt、png、jpg、gif、pdf等
- 无法知道下载的进度
2、用a标签下载
a标签可以访问下载文件的地址。如果 是对于浏览器可以直接浏览的文件类型:jpg\png\gif\txt等,a标签不能直接下载。
所以,在html5中,a标签提供了 download
属性。
简单用法:
<a href="example.jpg" download>点击下载</a>
//如果带属性值 指定下载的文件名,即重命名下载文件。不设置的话默认是文件原本名。
<a href="example.jpg" download="test">点击下载</a>
动态a标签:
下面的url
即文件或接口的地址
如需要额外参数,通过url
后问号拼接参数,后端get
请求方式接收
服务端需要配置
url
资源的响应头Content-disposition
值为attachment
,浏览器识别会调用下载弹窗,进一步配置filename
值即下载的默认文件名。
// 封装
function downloadFile (url, name) {
url = url || ''
name = name || ''
let ele = document.createElement('a')
ele.target = '_blank'
ele.href = url
ele.download = name
ele.click()
ele = null
}
// 调用
downloadFile(url)
判断浏览器是否支持download
属性:
const isSupport = 'download' in document.createElement('a');
需要注意一些信息:
- Edge 13在尝试下载data url链接时会崩溃。
- Chrome 65及以上版本只支持
同源
下载链接。 - Firefox只支持
同源
下载链接。
基于上面描述,如果你尝试下载跨域链接,那么其实download的效果就会没了,跟不设置download表现一致。即浏览器能预览的还是会预览,而不是下载。
对于在跨域下不能下载可浏览的文件,得跟后端协商,在后端层做多一层转发,最终返回给前端的文件链接跟下载页同域。
优点:download
属性能解决不能直接下载浏览器可浏览的文件
缺点:
- 不能下载跨域下的浏览器可浏览的文件
- 不能进行鉴权
- url资源或接口响应头配置了filename时,前端无法自定义下载文件名。
- 有兼容性问题,特别是IE
- 前端需要自定义展示下载进度条需求时无法支持。
3、利用Blob下载,发送ajax请求api获取文件流进行下载
这种方法除了能利用已知文件地址路径进行下载外,还适用于需要调取API获取文件流下载的形式。
有些时候后端不会直接提供一个下载地址给你直接访问,而是要调api。
方法:
前端请求接口设置responseType: 'blob'
, 统一 转为Blob 数据,然后利用URL.createObjectUrl生成url地址,赋值在a标签的href属性上,结合download进行下载.
blob 是 js 里表示二进制文件的对象。
target.response就是一个Blob对象,打印出来会看到两个属性size和type。
2种方法代码参考
文件下载方法参考,第二种可设置下载进度。
参考:https://blog.csdn.***/u010059669/article/details/122623034
/**
* 第一种方法:
* @param {String} path - 下载地址/下载请求地址。
* @param {String} name - 下载文件的名字/重命名(考虑到兼容性问题,最好加上后缀名)
*/
downloadFile (path, name) {
const xhr = new XMLHttpRequest();
xhr.open('get', path);
xhr.responseType = 'blob';
xhr.send();
xhr.onload = function () {
if (this.status === 200 || this.status === 304) {
// 如果是IE10及以上,不支持download属性,采用msSaveOrOpenBlob方法,但是IE10以下也不支持msSaveOrOpenBlob
if ('msSaveOrOpenBlob' in navigator) {
navigator.msSaveOrOpenBlob(this.response, name);
return;
}
// const blob = new Blob([this.response], { type: xhr.getResponseHeader('Content-Type') });
// const url = URL.createObjectURL(blob);
const url = URL.createObjectURL(this.response);
const a = document.createElement('a');
a.style.display = 'none';
a.href = url;
a.download = name;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
};
}
/**
* 第二种方法:
* @description: ajax下载/导出文件
* @param {string} url 资源或接口地址,可携带参数
* @param {function} progressCallback 选填,前端自定义的下载进度回调
*/
function downloadFromApi (url, progressCallback) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
xhr.open('get', url)
xhr.responseType = 'blob'
xhr.onload = e => {
if (xhr.status === 200) {
const response = xhr.response
resolve(response)
const dispoition = xhr.getResponseHeader('Content-Disposition') || ''
const nameStr = dispoition.split(';')[1] || ''
const fileName = decodeURI***ponent(nameStr.split('=')[1] || '')
if (window.navigator.msSaveOrOpenBlob) {
// 兼容处理,ie下的blob下载
window.navigator.msSaveOrOpenBlob(response, fileName)
} else {
const href = URL.createObjectURL(response)
let ele = document.createElement('a')
ele.target = '_blank'
ele.href = href
ele.download = fileName
ele.click()
ele = null
URL.revokeObjectURL(href)
}
} else {
reject(new Error(`${xhr.status}:请求失败`))
}
}
xhr.error = err => {
reject(err)
}
xhr.onprogress = e => {
if (e.length***putable) {
const percent***plete = e.loaded / e.total
progressCallback && progressCallback(percent***plete)
}
}
xhr.send(null)
})
}
下载进度设置的原理
服务端需要配置响应头的Content-Length,值为文件大小(未配置的话会在文件传输完成后才弹窗提示下载,响应感知太慢)。
前端监听xhr对象的progress事件:
xhr.onprogress = e => {
if (e.length***putable) {
const percent***plete = e.loaded / e.total
// 打印当前已完成的进度比例
console.log(percent***plete)
}
}
另一种封装方式(axios 请求)
如果是封装了axios请求,可以在调请求后,使用下面的封装的downLoadFile 方法,使用示例:
/*
httpRequest: 封装的下载API请求,记得设置responseType = 'blob'
url: 接口
*/
httpRequest(url).then(async (res) => {
console.log('下载请求返回:', res);
const { data, response } = res;
if (data && response.status === 200) {
// 调用封装好的下载文件的方法
downLoadFile(data, response);
}
}).catch(async (err) => {
console.log(err);
});
/**
* 下载文件
* @param {any} data - 下载请求返回blob对象的data, 打印出来会有一个size和type 属性。
* @param {Response} response- 请求的response
*/
const downLoadFile = (data: any, response: Response) => {
let contentType = response.headers.get('content-type') || undefined;
let contentDisposition = response.headers.get('content-disposition') || undefined;
contentType = isExistValue(contentType) ? contentType : undefined;
contentDisposition = isExistValue(contentDisposition) ? contentDisposition : undefined;
// 文件名提取
let filename = 'xxx';
if (typeof contentDisposition === 'string') {
contentDisposition = valueToLowerCase(contentDisposition);
try {
// 文件名提取
filename = contentDisposition.match(/filename=(.*)/)[1];
} catch (err) {
console.log(err);
// @ts-ignore
filename = contentDisposition;
}
}
// 创建一个 Blob 对象
const blob = new Blob([data], { type: contentType });
// @ts-ignore
if (typeof window.navigator.msSaveBlob !== 'undefined') {
// 兼容IE,window.navigator.msSaveBlob:以本地方式保存文件
window.navigator.msSaveBlob(blob, decodeURI(filename));
} else {
const href = window.URL.createObjectURL(blob);
const eLink = document.createElement('a'); // 创建一个<a>标签
eLink.style.display = 'none'; // 隐藏标签
eLink.href = href; // 配置href,指向本地文件的内存地址
eLink.download = decodeURI(filename);
document.body.appendChild(eLink);
eLink.click();
// 释放URL 对象
document.body.removeChild(eLink);
// 释放掉blob对象
URL.revokeObjectURL(href);
}
};
图片预览
如果是图片预览,也是后端返回的地址,需要带上token去请求。需要用到base64。伪代码参考思路:
const [imgSrc, setImgSrc] = useState('');// 图片预览的src
useEffect(() => {
const url = 'xxx.xx.xx/img/wx.png';
// httpRequest自己封装的axios请求 , responseType 为 'blob'
httpRequest(url).then(async (res) => {
console.log('下载返回:', res);
const { data, response } = res;
if (data && response.status === 200) {
// 生成图片预览的src
const src = await blobToBase64(data);
setImgSrc(src);
}
});
}, []);
/**
* Blob 图像对象预览
* */
const blobToBase64 = (file: Blob) => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => resolve(reader.result);
reader.onerror = (error) => reject(error);
});
};
// 在Image标签中使用:
<Image width="80%" alt={name} src={imgSrc} />
优点:
- 能解决不能直接下载浏览器可浏览的文件
- 可添加鉴权信息
缺点: 兼容性问题,IE10以下不可用