文件导出

在后台管理系统中,咱们常常会遇到文件导出这个需求,下面,我将几种常见的导出方式作一个简单的介绍,让你们在之后遇到此类需求时,可以切合实际状况,采起相对合理的方式。html

导出目标

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

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

导出方式

image.png

a.download

html5新增的属性git

文件名由前端指定,前台下达保存指令github

缺点:ie不支持,而且,在跨域时,即便后台设置了容许跨域的响应头,也没法下载,也就是说,必须与当前域一致ajax

<a href="http://192.168.1.103:3000/imgs/xx.jpg" download />
<a href="http://192.168.1.103:3000/imgs/xx.jpg" download="xx.jpg" />

ajax + a.download

文件名由前台指定,前台下达保存指令chrome

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

缺点:ie不支持,blob有内存限制
image.png跨域

可是避免了单独使用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专有api

缺点: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

文件名由后台指定,此种方式指定的文件名优先级高于a.download

前端发送请求便可,须要注意的一点是,不能与ajax组合(使用ajax后,会变成二进制流,须要前端对流处理)

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

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

function useLocationDownload(url) {
  window.location.href = url;
}
useLocationDownload(url)

window.open

function useWindowOpenDownload(url) {
  window.open(url);
}
useWindowOpenDownload(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)
  • 一些问题

    1. 跨域

      a.download无效,即便后台设置了Access-Control-Allow-Origin也无效,download的值须要与当前域名一致,让ngnix进行转发能够解决。

      https://html.spec.whatwg.org/dev/links.html#downloading-resources

    2. 大文件

      ajax的方式须要用到blob,而blob是有限制的,例如chrome的上限是2GB,因此ajax的方式须要被排除,可选的方式是a.download和Content-Disposition,这两种方式没有用到blob,因此也没有具体的限制。

    3. 在使用a标签或者form时,为了不在导出时发生页面闪烁现象,咱们可使用target非_self的值,来避免,可是当target为_self时,若是导出请求失败了,页面会被覆盖掉,用哪一种方式能够避免这个问题?

      可选的方式为ajax和iframe,这二者均可以免失败时覆盖掉当前页面,而且ajax还能够告诉用户失败的缘由

  • 总结

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

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

资料

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