赞
踩
工作中遇到了导入导出的需求,导入较简单,导出分两种,一种是由当前已经拿到的数据生成excel文件,这部分前端借助插件也比较容易完成。但另一种导出,由后台生成文件流,前端接收并下载,我们在这里耗费了较多时间。
我们的框架是vue,http请求使用的是axios,很自然地,我们按照后台提供的API进行请求,期望可以返回一个文件地址,或者开始下载文件,并且可以正常打开。然而我们拿到的返回结果很奇怪。
看着是一堆乱码。
后来知道这是文件转成的二进制流。但具体怎样实现下载,开始并没有思路。
a标签的download属性规定下载文件的名称。所允许的值没有限制,浏览器将自动检测正确的文件扩展名并添加到文件 (.img, .pdf, .txt, .html, 等等)。
下载zip文件流当遇到下载失败的时候,就需要将后台错误信息返回给用户看,但是responseType: "blob"格式默认转为二进制。所以当下载错误的时候需要转为json格式,拿到code将错误信息返回给用户.
response 会被jQuery转为string,无法拿到blob对象,也就无法转成我们想要的文件url地址。
下载其实是浏览器的内置事件,而能够触发下载事件的浏览器的get请求(iframe、a)、post请求(form)具有以下特点:
浏览器会根据response的格式判断应该读取内容还是下载文件等。
我们要做的,就是想办法找到触发浏览器的get或post请求的方法。
使用iframe和a标签发起get请求时,其实有两个方面的涵义,一个是直接发出http请求,另一个是传入url地址,
我们先直接使用最简单的,发出http请求。
当接口给我们的是get请求时,直接利用iframe的src属性和a标签的href属性可以发送get请求,从而实现下载。
// iframe
const url = "/lims/export/export/workTemplateExport?"+str;
let iframe = document.createElement('iframe')
iframe.style.display = 'none'
iframe.src = url
iframe.onload = function () {
document.body.removeChild(iframe)
}
document.body.appendChild(iframe)
// a标签
const url = "http://172.16.157.172:5000/lims/export/export/workTemplateExport?"+str;
let a = document.createElement('a')
a.style.display = 'none'
a.href = url
a.target = '_self'
document.body.appendChild(a)
a.click()
document.body.removeChild(a)
缺点: 1.无法满足POST请求类型;
2.如果请求接口出现报错,无法感知;
3.无法获得请求完成的时机;
4.无法上传token
5.参数通过url传递,如果url长度超过2048会被浏览器截断。
利用form提交请求
// html <iframe id="id_iframe" name="nm_iframe" style="display:none;" @load="onLoadIframe"></iframe> // js // 提交请求并下载文件 onDownloadExcel(url,params){ var form = document.createElement('form'); <!-- 避免请求结束后页面刷新 --> form.setAttribute('target', 'nm_iframe'); // iframe form.setAttribute('method', 'get'); form.setAttribute('action', '/lims'+url); document.body.appendChild(form); for (var obj in params) { if (params.hasOwnProperty(obj)) { var input = document.createElement('input'); input.tpye = 'hidden'; input.name = obj; input.value = params[obj]; form.appendChild(input); } } form.submit(); document.body.removeChild(form); }, // 捕获导出接口的错误提示 onLoadIframe(){ let iframe_obj = document.getElementById("id_iframe"); let pre = iframe_obj.contentWindow.document.getElementsByTagName("pre"); let res = {}; if(pre.length>0){ res = JSON.parse(pre[0].innerText); if(res.code!='0'){ utils.msg.error(res.message) } } },
其中,iframe在这里起到的作用就是避免请求结束后页面刷新;同时可以获取错误提示。
缺点: 1.无法获得请求完成的时机;
2.无法上传token
如果不需要上传token,表单已经基本可以完成我们的需求了,但还是存在无法获得下载进度的缺陷。
除了以上两种直接触发浏览器请求并下载的方式,还可以通过提交请求,将拿到的blob文件转成url地址,赋值给动态添加的iframe/a,从而触发浏览器的下载。
思路即:
ajax无法下载文件的原因是返回结果被ajax转为字符串,那么我们可以使用原生的XMLHttpRequest对象发送请求
var xhr = new XMLHttpRequest(); var url = "/lims/export/export/workTemplateExport?"+str; var params = str; xhr.open('GET', url, true); xhr.responseType = "blob"; // 返回类型blob // 定义请求完成的处理函数,请求前也可以增加加载框/禁用下载按钮逻辑 xhr.onload = function () { // 请求完成 if (this.status === 200) { // 返回200 var blob = this.response; var name = xhr.getResponseHeader("Content-disposition"); var filename = name.match(/filename=(.*)/)[1]; var reader = new FileReader(); reader.readAsDataURL(blob); // 转换为base64,可以直接放入a表情href reader.onload = function (e) { console.log('reader',e) // 转换完成,创建一个a标签用于下载 var a = document.createElement('a'); // decodeURI(filename) 解码文件名 a.download = decodeURI(filename); a.href = e.target.result; document.body.append(a); a.click(); document.body.removeChild(a) } } }; xhr.send(url);
完美解决我们下载中遇到的很多问题,而且请求与response处理方法在一起,可以封装成一个统一的方法,比较推荐该方法
axios也是用XMLHttpRequest封装的,而且区别于ajax,返回结果可以拿到blob对象
// http请求 onDownloadExcel(str){ return this.$axios({ url: "/export/export/workTemplateExport?"+str, method: "GET", responseType: 'blob', //返回数据的格式,可选值为arraybuffer,blob,document,json,text,stream,默认值为json timeout: 10*60*1000 }) } // 处理responses $axios.onResponse(response => { if (response.request.responseType === 'blob') { if(response.headers['content-disposition']){ // 返回待返还文件 // 提取文件名 const filename = response.headers['content-disposition'].match(/filename=(.*)/)[1] var blob = response.data; var reader = new FileReader(); reader.readAsDataURL(blob); // 读取Blob内容并将文件数据转换为base64,可以直接放入a标签href reader.onload = function (e) { // 转换完成,创建一个a标签用于下载 var a = document.createElement('a'); // decodeURI(filename) 解码文件名 a.download = decodeURI(filename); a.href = e.target.result; document.body.append(a); a.click(); document.body.removeChild(a) } }else{ // 返回错误信息 let data = response.data; var reader = new FileReader(); reader.readAsText(data, 'utf-8'); reader.onload = () =>{ if(utils.isJSON(reader.result)){ <!-- 解析错误信息 --> data = JSON.parse(reader.result); if(data.code != '0'){ utils.msg.error(data.message) } } } } } // 返回promise,调用时便于定义请求完成的处理函数,请求前也可以增加加载框/禁用下载按钮逻辑 return Promise.resolve(response) })
与原生相比,使用axios拦截请求并下载文件稍微麻烦一点,因为请求与response处理分开了。但优点是,封装后调用较为简单,且可以使用axios统一封装的方法,如网络情况较差时,阻止同一地址连续多次发出请求等。
Blob 对象表示一个不可变、原始数据的类文件对象
有两种方式可以将读取Blob对象并下载文件
// FileReader let blob = response.data; let reader = new FileReader(); reader.readAsDataURL(blob); // 转换为base64,可以直接放入a标签href reader.onload = function (e) { // 转换完成,创建一个a标签用于下载 const a = document.createElement('a'); // decodeURI(filename) 解码文件名 a.setAttribute('download', decodeURI(filename)) // 兼容:某些浏览器不支持HTML5的download属性 if (typeof a.download === 'undefined') { a.setAttribute('target', '_blank') } a.style.display = 'none' a.href = e.target.result; document.body.append(a); a.click(); document.body.removeChild(a)
// window.URL.createObjectURL 将二进制流转为blob let blob = new Blob([response.data], { type: 'application/octet-stream' }) // 创建新的内存URL并指向File对象或者Blob对象的地址 // 每次都会创建一个新的URL对象,所以使用完成后要注意及时使用URL.revokeObjectURL()来释放这个内存地址 const blobURL = window.URL.createObjectURL(blob) // 创建a标签,用于跳转至下载链接 const a = document.createElement('a') a.style.display = 'none' a.href = blobURL a.setAttribute('download', decodeURI(filename)) // 兼容:某些浏览器不支持HTML5的download属性 if (typeof a.download === 'undefined') { a.setAttribute('target', '_blank') } // 挂载a标签 document.body.appendChild(a) a.click() document.body.removeChild(a) // 释放blob URL地址 必须步骤 window.URL.revokeObjectURL(blobURL)
Blob()构造函数允许用其它对象创建 Blob 对象。比如,用字符串构建一个 blob:
var debug = {hello: "world"};
var blob = new Blob([JSON.stringify(debug, null, 2)],
{type : 'application/json'});
使用 Blob 创建一个指向类型化数组的URL
var url = URL.createObjectURL(blob);
// 会产生一个类似blob:d3958f5c-0777-0845-9dcf-2cb28783acaf 这样的URL字符串
// 你可以像使用一个普通URL那样使用它,比如用在img.src上。
主要区别:
通过FileReader.readAsDataURL(file)可以获取一段data:base64的字符串
通过URL.createObjectURL(blob)可以获取当前文件的一个内存URL
总结了拦截请求并下载文件的几种方法,由简单粗陋到较完善,推荐的几种方式:
1.form+隐藏的iframe(缺点:无法定义请求完成的处理函数);
2.XMLHttpRequest原生请求;
3.axios请求
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。