一:直接给文件地址,无需调用接口即可下载文件
1. window.open(filePath):打开新窗口下载文件;
2. 利用a链接:
const filePath = /isoc/api/v1/abnormal/down_file?file_path=${this.fileName};
const link = document.createElement("a");
link.style.display = "none";
link.href = filePath;
link.setAttribute("download",this.fileName);
document.body.appendChild(link);
link.click();
a标签直接指向一个文件地址链接时,浏览器会自动下载该文件,对于单文件下载没有什么问题,但是如果需要下载多个文件的话,点击过快就会重置掉前面的请求;
3. window.location.href:同a链接,如果直接指向一个文件地址链接的话,浏览器就会直接下载该文件;href链接静态资源并且适合用于浏览器无法识别文件,如果是html、jpg、pdf等会直接解析展示,而不会下载
二:通过创建form表单方式
利用form表单提交也能够发起浏览器请求,并且也可以作为多文件下载来使用:
let params = {
id: "xx",
name: "xx",
};
let form = document.createElement("form");
form.id = "form";
form.name = "form";
document.body.appendChild(form);
for (let obj in params) {
if (params.hasOwnProperty(obj)) {
let input = document.createElement("input");
input.type = "hidden";
input, (name = obj);
input.value = params[obj];
form.appendChild(input);
}
}
form.method = "GET";
form.action = runEnv.api_url + "请求地址";
form.submit();
document.body.removeChild(form);
三:会对后端发post请求,使用blob格式,后端返回文件二进制流
一般是调用导出也即文件下载接口,后端直接返回一个二进制的文件流,然后前端拿到res返回值就可直接下载文件并保存,可以基于blob格式(调用接口拿到接口返回值之后的操作就是第一种文件下载,直接利用a链接href属性到对应文件地址下载文件即可)
Blob对象:不可变、原始数据的类文件对象,Blob表示的不一定是JavaScript原生格式的数据,axios的responseType设置为arraybuffer,用于接受二进制流;Angular的httpClient就需要设置responseType为blob,具体实现如下:
const params = {
ip: ips as string[],
};
// Angular的httpClient调用接口传递的参数
this.httpClient
.get("接口url", {
responseType: "blob",
params: params,
})
.subscribe((res) => {
console.log(res); // 此处接口返回值res就是二进制文件流
const blob = new Blob([res]);
let link = document.createElement("a");
link.href = window.URL. createObjectURL(blob);
link.style.display = ‘none’;
link.download = "download.zip"; // 设置文件下载名称
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(link.href);
});
// 基于axios调接口
axios({
method: 'post',
url: '/export',
responseType: 'arraybuffer',})
.then(res => {
// 假设 data 是返回来的二进制数据
const data = res.data
const url = window.URL.createObjectURL(new Blob([data], {type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"}))
const link = document.createElement('a')
link.style.display = 'none'
link.href = url
const fileNames = res.headers['content-disposition'] // 获取到Content-Disposition;filename
const regFileNames = decodeURI(fileNames.match(/=(.*)$/)[1])
link.setAttribute('download', regFileNames) // 设置下载的文件名
document.body.appendChild(link)
link.click() // 自动点击a链接,即会跳转至href指向的地址下载文件
document.body.removeChild(link)})
四:获取文件下载的实时进度,然后基于progress进度条展示
文件下载功能不友好,特别是文件较大时,下载消耗时间很长,需要提高用户体验感,此时就需要添加一个文件下载进度条展示;后端需要返回计算好的文件大小,否则前端计算进度时取的total永远都是0(坑),也即请求返回头里需要添加content-length
上传文件显示进度示例:
postRecoveryFile() {
if (!this.uploadFileName) return this.$message.error("请上传配置文件");
this.disabledpostRecoveryFile = true;
this.progressStatus = undefined; // 进度条状态
this.uploadProgress = 0; // 进度条百分比值
const formData = new FormData();
formData.append("fileType", this.fileType);
formData.append("uploadFile", this.uploadFile);
let config = {
headers: {
"Content-Type": "multipart/form-data", // 上传文件为formData类型
},
// axios调用接口直接再config中设置进度值
onUploadProgress: (progressEvent) => {
let persent =
((progressEvent.loaded / progressEvent.total) * 100) | 0; //上传进度百分比
this.uploadProgress = persent;
},
};
this.progressShow = true;
postRecoveryFile(formData, config).then(
(res) => {
let { code, data, msg } = res.data;
if (code === 200) {
this.disabledpostRecoveryFile = false;
this.$message.success("操作成功");
} else {
this.disabledpostRecoveryFile = false;
this.progressStatus = "exception"; // 上传失败则修改进度条状态为exception
this.$message.error(msg);
}
this.uploadFile = null;
this.uploadFileName = null;
},
(error) => {
this.disabledpostRecoveryFile = false;
this.progressStatus = "exception";
}
);
},
下载文件显示进度示例:
export const dowloadFile = (data, callback) => {
return request({
url: 'file/downloadFile',
method: 'post',
responseType: 'blob', // 基于Blob下载文件,设置返回值类型为blob
data: data,
onDownloadProgress (progress) {
// 实时获得上传进度百分比
callback(progress)
}
})}
async batchDownloadFile (id) {
this.percentage = 0
this.$refs.FileProgress.progressShow = true
const res = await batchDownloadFile(id, this.processCallback)
if (res.status !== 200) {
this.$message.warning('操作失败')
this.$refs.FileProgress.progressShow = false // 关闭进度条显示层
return false
}
const fileNames = res.headers['content-disposition'] // 获取到Content-Disposition;filename
const regFileNames = decodeURI(fileNames.match(/=(.*)$/)[1])
const url = window.URL.createObjectURL(new Blob([res.data]))
const link = document.createElement('a')
link.style.display = 'none'
link.href = url
link.setAttribute('download', regFileNames)
document.body.appendChild(link)
link.click()
this.$refs.FileProgress.progressShow = false // 关闭进度条显示层
},
processCallback (progressEvent) {
const progressBar = Math.round(progressEvent.loaded / progressEvent.total * 100)
this.percentage = progressBar
},
五:利用封装的download.js的形式下载文件,会兼容各种文件上传下载形式
(function (root, factory) {
if (typeof define === "function" && define.amd) {
// 针对amd规范,注册一个匿名模块
define([], factory);
} else if (typeof exports === "object") {
// 针对node环境,不支持严格模式
module.exports = factory();
} else {
// 针对浏览器全局变量支持
root.download = factory();
}
})(this, function () {
// 第一个参数:数据,第二个参数文件名,第三个参数mime类型
// 下载服务器上的文件直接第一个参数传入url即可,后面两个参数不用传入
return function download(data, strFileName, strMimeType) {
// 此处脚本仅支撑客户端
let self = window,
// 默认mime类型,也即后端接口返回值类型
// 可直接是二进制流类型,然后基于blob直接下载即可
defaultMime = "application/octet-stream",
mimeType = strMimeType || defaultMime,
payload = data,
// 如果仅传入第一个参数,就将其解析为下载url
url = !strFileName && !strMimeType && payload,
// 创建a标签,直接利用a标签的href属性和点击事件下载文件
anchor = document.createElement("a"),
toString = function (a) {
return String(a);
},
// 根据浏览器兼容性,设置blob即文件下载形式
myBlob = self.Blob || self.MozBlob || self.WebKitBlob || toString,
fileName = strFileName || "download",
blob,
reader;
myBlob = myBlob.call ? myBlob.bind(self) : Blob;
// 调换参数的顺序,允许download.bind(true, 'text/xml','export.xml')这种写法
if (String(this) === "true") {
payload = [payload, mimeType];
mimeType = payload[0];
payload = payload[1];
}
// 根据传入的url参数下载文件(必须同源,因为根据XMLHttpRequest)
if (url && url.length < 2048) {
// 如果没有文件名或者返回文件类型,就假定url是唯一的参数,直接从url中获取文件名
fileName = url.split("/").pop().split("?")[0];
// 设置a标签的href,也即文件下载链接地址
anchor.href = url;
// 避免文件下载链接不可用
if (anchor.href.indexOf(url) !== -1) {
// 构造XMLHtteRequest请求
let ajax = new XMLHttpRequest();
// get方法
ajax.open("GET", url, true);
// 设置相应类型,也即responseType为blob,避免浏览器直接解析出来展示
// 即为了解决图片自动打开的问题,将响应类型改为blob,让浏览器无法识别从而避免自动打开
ajax.responseType = "blob";
// 调用下载接口的回调函数
ajax.onload = function (e) {
// 再次调用自身,相当于递归,把xhr返回的blob数据生成对应的文件
// 也即利用接口返回的文件二进制流数据生成对应的blob文件并下载
download(e.target.response, fileName, defaultMime);
};
// 发送请求
setTimeout(() => {
ajax.send();
}, 0);
return ajax;
}
}
// 如果是dataUrl,则直接生成文件
// 兼容低版本的ie浏览器
if (/^data:[\w+-]+/[\w+-]+[,;]/.test(payload)) {
// 如果满足条件(大于2M,并且myBlob!==toString,直接通过dataUrlBlob生成文件)
if (payload.length > 1024 * 1024 * 1.999 && myBlob !== toString) {
payload = dataUrlToBlob(payload);
mimeType = payload.type || defaultMime;
} else {
// 如果是ie,走navigator.msSaveBlob
return navigator.msSaveBlob // IE10 can't do a[download], only Blobs:
? navigator.msSaveBlob(dataUrlToBlob(payload), fileName)
: // 否则走saver方法
saver(payload); // everyone else can save dataURLs un-processed
}
}
blob =
payload instanceof myBlob
? payload
: new myBlob([payload], { type: mimeType });
// 根据传入的dataurl,通过myBlob生成文件
function dataUrlToBlob(strUrl) {
var parts= strUrl.split(/[:;,]/),
type= parts[1],
decoder= parts[2] == "base64" ? atob : decodeURIComponent,
binData= decoder( parts.pop() ),
mx= binData.length,
i= 0,
uiArr= new Uint8Array(mx);;
for(i;i<mx;++i) uiArr[i]= binData.charCodeAt(i);
return new myBlob([uiArr], {type: type});
}
// winMode 是否是在window上调用
function saver(url, winMode){
// 如果支持download标签,通过a标签的download来下载
if ('download' in anchor) { //html5 A[download]
anchor.href = url;
anchor.setAttribute("download", fileName);
anchor.className = "download-js-link";
anchor.innerHTML = "downloading...";
anchor.style.display = "none";
document.body.appendChild(anchor);
setTimeout(function() {
// 模拟点击下载
anchor.click();
document.body.removeChild(anchor);
// 如果在window下,还需要解除url跟文件的链接
if(winMode===true){setTimeout(function(){ self.URL.revokeObjectURL(anchor.href);}, 250 );}
}, 66);
return true;
}
// handle non-a[download] safari as best we can:
// 针对不支持download的safari浏览器,走window.open的降级操作,优化体验
if(/(Version)/(\d+).(\d+)(?:.(\d+))?.*Safari//.test(navigator.userAgent)) {
url=url.replace(/^data:([\w/-+]+)/, defaultMime);
if(!window.open(url)){ // popup blocked, offer direct download:
if(confirm("Displaying New Document\n\nUse Save As... to download, then click back to return to this page.")){ location.href=url; }
}
return true;
}
//do iframe dataURL download (old ch+FF):
// 针对老的chrome或者firefox浏览器,创建iframe,通过设置iframe的url来达成下载的目的
var f = document.createElement("iframe");
document.body.appendChild(f);
if(!winMode){ // force a mime that will download:
url="data:"+url.replace(/^data:([\w/-+]+)/, defaultMime);
}
f.src=url;
// 移除工具节点
setTimeout(function(){ document.body.removeChild(f); }, 333);
};}
// 针对ie10+ 走浏览器自带的msSaveBlob
if (navigator.msSaveBlob) { // IE10+ : (has Blob, but not a[download] or URL)
return navigator.msSaveBlob(blob, fileName);
}
// 如果全局对象下支持URL方法
if(self.URL){ // simple fast and modern way using Blob and URL:
// 根据blob创建指向文件的ObjectURL
saver(self.URL.createObjectURL(blob), true);
}else{
// handle non-Blob()+non-URL browsers:
// 针对不支持Blob和URL的浏览器,通过给saver传入dataUrl来保存文件
if(typeof blob === "string" || blob.constructor===toString ){
try{
return saver( "data:" + mimeType + ";base64," + self.btoa(blob) );
}catch(y){
return saver( "data:" + mimeType + "," + encodeURIComponent(blob) );
}
}
// Blob but not URL support:
// 支持Blob但是不支持URL方法的浏览器,通过构造文件阅读器来保存文件
reader=new FileReader();
reader.onload=function(e){
saver(this.result);
};
reader.readAsDataURL(blob);
}
return true;
})();
/*
a标签下载体验效果最佳,针对图片资源等文件,同源时,使用download可获得较好的效果,window.open会打开新的tab页,页面存在跳转
使用构造表单提交,存在浏览器会直接解析文件的问题
*/
总结:
如果给定地址链接过去可直接下载,不需要传递任何参数即可;
若需要调接口传递参数,利用接口返回值下载文件时,前端传递的参数一般都是formData类型,并且请求头参数(主要是reponseType)需要根据后端返回参数类型进行设置,