当前位置:   article > 正文

浏览器下载文件的方法总结_a.setattribute('target', '_blank')

a.setattribute('target', '_blank')

背景

工作中遇到了导入导出的需求,导入较简单,导出分两种,一种是由当前已经拿到的数据生成excel文件,这部分前端借助插件也比较容易完成。但另一种导出,由后台生成文件流,前端接收并下载,我们在这里耗费了较多时间。

我们的框架是vue,http请求使用的是axios,很自然地,我们按照后台提供的API进行请求,期望可以返回一个文件地址,或者开始下载文件,并且可以正常打开。然而我们拿到的返回结果很奇怪。
在这里插入图片描述

看着是一堆乱码。

后来知道这是文件转成的二进制流。但具体怎样实现下载,开始并没有思路。

a标签的download属性规定下载文件的名称。所允许的值没有限制,浏览器将自动检测正确的文件扩展名并添加到文件 (.img, .pdf, .txt, .html, 等等)。

下载zip文件流当遇到下载失败的时候,就需要将后台错误信息返回给用户看,但是responseType: "blob"格式默认转为二进制。所以当下载错误的时候需要转为json格式,拿到code将错误信息返回给用户.

AJAX无法下载文件的原因,

response 会被jQuery转为string,无法拿到blob对象,也就无法转成我们想要的文件url地址。

下载的实现方式

下载其实是浏览器的内置事件,而能够触发下载事件的浏览器的get请求(iframe、a)、post请求(form)具有以下特点:

  • response交由浏览器处理
  • response内容的格式不会被更改,可以为二进制文件、字符串等。

浏览器会根据response的格式判断应该读取内容还是下载文件等。

我们要做的,就是想办法找到触发浏览器的get或post请求的方法。

get请求

使用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)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
// 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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

缺点: 1.无法满足POST请求类型;
2.如果请求接口出现报错,无法感知;
3.无法获得请求完成的时机;
4.无法上传token
5.参数通过url传递,如果url长度超过2048会被浏览器截断。

post请求

利用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)
        }
    }
},
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

其中,iframe在这里起到的作用就是避免请求结束后页面刷新;同时可以获取错误提示。

缺点: 1.无法获得请求完成的时机;
2.无法上传token

如果不需要上传token,表单已经基本可以完成我们的需求了,但还是存在无法获得下载进度的缺陷。

除了以上两种直接触发浏览器请求并下载的方式,还可以通过提交请求,将拿到的blob文件转成url地址,赋值给动态添加的iframe/a,从而触发浏览器的下载。

思路即:

  • 发送请求
  • 获得response
  • 通过response判断返回是否为流文件
  • 如果是文件则在页面中插入frame/a标签
  • 利用frame/a标签实现浏览器的get下载

XMLHttpRequest原生请求

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);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

完美解决我们下载中遇到的很多问题,而且请求与response处理方法在一起,可以封装成一个统一的方法,比较推荐该方法

axios请求拦截

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)
})

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49

与原生相比,使用axios拦截请求并下载文件稍微麻烦一点,因为请求与response处理分开了。但优点是,封装后调用较为简单,且可以使用axios统一封装的方法,如网络情况较差时,阻止同一地址连续多次发出请求等。

相关概念

Blob

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)

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
// 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)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

Blob()构造函数允许用其它对象创建 Blob 对象。比如,用字符串构建一个 blob:

var debug = {hello: "world"};
var blob = new Blob([JSON.stringify(debug, null, 2)],
  {type : 'application/json'});
  • 1
  • 2
  • 3

使用 Blob 创建一个指向类型化数组的URL

var url = URL.createObjectURL(blob);
// 会产生一个类似blob:d3958f5c-0777-0845-9dcf-2cb28783acaf 这样的URL字符串
// 你可以像使用一个普通URL那样使用它,比如用在img.src上。
  • 1
  • 2
  • 3

主要区别:
通过FileReader.readAsDataURL(file)可以获取一段data:base64的字符串

通过URL.createObjectURL(blob)可以获取当前文件的一个内存URL

二进制流

总结

总结了拦截请求并下载文件的几种方法,由简单粗陋到较完善,推荐的几种方式:
1.form+隐藏的iframe(缺点:无法定义请求完成的处理函数);
2.XMLHttpRequest原生请求;
3.axios请求

参考文章

Axios如何下载文件

URL.createObjectURL()的使用方法

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/盐析白兔/article/detail/63564
推荐阅读
相关标签
  

闽ICP备14008679号