9

在后台管理系统中,我们经常会遇到文件导出这个需求,下面,我将几种常见的导出方式做一个简单的介绍,让大家在以后遇到此类需求时,能够切合实际情况,采取相对合理的方式。

导出目标

文件地址
已经存在服务器上的静态文件,比如用户上传的图片、材料等等。
http://192.168.1.103:3000/imgs/bg.jpg

导出接口
根据用户需求,由后端动态生成的文件,常见的比如导出业务流水表格,数据汇总表格等等。
http://192.168.1.103:3000/api/export

导出方式

image.png

a.download

html5新增的属性

优点:简单。
缺点:ie不支持,并且在跨域时,即使后台设置了允许跨域的响应头,也无法下载,也就是说必须与当前域一致。

// 这里的http://192.168.1.103:3000必须与当前地址栏一致才行
<a href="http://192.168.1.103:3000/imgs/xx.jpg" download />

download属性可以指定文件名(如果content-disposition指定了文件名,以content-disposition为准),也可以为空值,两种情况具体如下:
image.png

ajax + a.download

缺点:ie不支持,blob有内存限制。
优点:避免了单独使用a.download必须与域名一致的问题。

将后台返回的二进制数据,转换成blob,然后利用URL.createObjectURL,创建一个指向内存中blobURL,再使用a标签的download属性进行导出

function useLinkDownload(url, fileName) {
    const link = document.createElement("a");

    link.style.display = "none";
    link.href = url;
    link.download = fileName;

    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
}

$.get('http://192.168.1.103:3000/api/export', { responseType: 'blob' })
    .then(function(res) {
        // 创建一个指向内存中blob的URL
        const objectURL = URL.createObjectURL(res.data);
        useLinkDownload(objectURL, 'xx.xlsx')
        URL.revokeObjectURL(objectURL)
    })

ajax + msSaveBlob

优点:支持ie,这算优点?
缺点:chrome、firefox不支持,blob有内存限制

$.get('http://192.168.1.103:3000/api/export', { responseType: 'blob' })
    .then(function(res) {
        navigator.msSaveBlob(res.data, "xx.xlsx");
    })

content-disposition

优点:兼容性极好。
缺点:需要后台支持。

前端发送请求即可(其他的交给后台),后台响应数据时,按照规范设置content-disposition,需要注意的一点是,不能与ajax组合(使用ajax后,会变成二进制数据流,流的处理被ajax接管)
(此种方式指定的文件名优先级高于a.download

var url = 'http://192.168.1.103:3000/api/export'

content-disposition可以结合以下任一一种方式进行导出:

// a标签

function useLink(url) {
  const link = document.createElement("a");

  link.style.display = "none";
  link.href = url;

  document.body.appendChild(link);
  link.click();
  document.body.removeChild(link);
}

useLink(url)
// location.href

window.location.href = url;
// window.open

window.open(url);
// form

function useFormDownload(url) {
  const form = document.createElement("form");

  form.action = url;
  form.method = "get";
  form.style.display = "none";

  document.body.appendChild(form);

  form.submit();
  form.remove();
}

useFormDownload(url)
// iframe
function useIframeDownload(url) {
  const iframe = document.createElement("iframe");

  iframe.src = url;
  iframe.style.display = "none";

  document.body.appendChild(iframe);
  document.body.removeChild(iframe);
}
useIframeDownload(url)

问题

跨域
a.download无效,即使后台设置了Access-Control-Allow-Origin也无效,download的值需要与当前域名一致,ngnix进行转发可以解决。
https://html.spec.whatwg.org/dev/links.html#downloading-resources

大文件
ajax的方式需要用到blob,而blob是有限制的,例如chrome的上限是2GB,所以ajax的方式需要被排除,可选的方式是a.downloadContent-Disposition,这两种方式没有用到blob,所以也没有明确的限制。

iframe存在的问题
在使用a标签或者form时,为了避免在导出时发生页面闪烁现象,我们可以使用targe_self的值来避免,但是当target_self时,如果导出请求失败了,页面会被覆盖掉,用哪种方式可以避免这个问题?
可选的方式为ajaxiframe,这两者都可以避免失败时覆盖掉当前页面,并且ajax还可以告诉用户失败的原因

总结

默认情况下,浏览器面对自身无法打开的文件,都会采取将其保存到本地方式,但是图片、文本文件以及pdf,浏览器会首先尝试将其打开,这在一般情况下是合理的,但当我们的目的是保存而不是打开时,这就会变成一个问题。

上文罗列了几种导出方式以及各自的局限性,综合来看Content-Disposition应该是比较通用的方式,即兼顾了兼容性,也避免了浏览器内存限制。

资料

https://github.com/eligrey/FileSaver.js/wiki/Saving-a-remote-file#using-http-header


热饭班长
3.7k 声望434 粉丝

先去做,做出一坨狗屎,再改进。