文件導出

在後臺管理系統中,咱們常常會遇到文件導出這個需求,下面,我將幾種常見的導出方式作一個簡單的介紹,讓你們在之後遇到此類需求時,可以切合實際狀況,採起相對合理的方式。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