js下載檔案的實現方式

__青春的Smile 發表於 2021-10-14
供參考,平時遇到的問題記錄

一、普通下載

當後端返回的資源是連結時,此時可以使用 a 標籤或者 window.location.href直接開啟。

1、a 標籤形式

在H5中,a標籤新增了download 屬性,包含該屬性的連結被點選時,瀏覽器會以下載檔案方式下載 href 屬性上的連結。

 <a href="https://example.com" download="example.html">下載</a>

// 或者封裝
function download(href, title) {
    const a = document.createElement('a');
    a.setAttribute('href', href);
    a.setAttribute('download', title);
    a.click();
}
download('https://example.com', 'test')

2、window.location.href 直接開啟

window.location.href === 'https://example.com'

二、流檔案下載

當後端返回的檔案是流檔案時,以 umi-request 請求方法為例

首先應在請求中設定返回型別:responseType: "blob"

import request from "umi-request";

export const downLoad = (url, fileName) =>
  request(url, {
    method: "POST",
    data: data,
    responseType: "blob", // 代表記憶體之中的一段為二進位制
  }).then(data) => {
    const blob = data;
    let link = document.createElement("a");
    link.href = URL.createObjectURL( new Blob([blob], { type: "application/vnd.ms-excel" }) );
    link.download = fileName;
    document.body.appendChild(link);
    link.click();
    URL.revokeObjectURL(link.href);
};

1、自定義檔名

此時可以發現後端返回的是流檔案,前端接到的是亂碼。當前端自定義檔名時,可以直接下載

downLoad('https://example.com/api/download', 'test.xlsx')

2、採用後端定義的檔名

當採用後端的fileName時,此時要獲取後端在 content-disposition 中定義的檔名。

首先呼叫介面,發現檔名是在請求頭content-disposition 中,需要注意的是:雖然我們能看到,但是卻拿不到請求頭。

如果想讓瀏覽器能訪問到其他響應頭的話,需要後端在伺服器上設定 Access-Control-Expose-Headers

// 後端面大致寫法
headers.add("Access-Control-Expose-Headers", "Content-Disposition"); 

此時可以拿到相關資訊,發現是編碼的,需要decodeURI 進行解碼

const disposition = response.headers.get('Content-Disposition');

const fileName = decodeURI(disposition.split(";")[1].split("filename=")[1])
注:直接在請求方法裡是不能獲取到請求頭資訊的,需要對請求進行攔截
request.interceptors.response.use(async (response) => {
  const disposition = response.headers.get("Content-Disposition"); // 獲取Content-Disposition
  return disposition // 當Content-Disposition中有值的時候進行處理,其他請求的響應則放過
    ? {
        blob: await response.blob(), // 將二進位制的資料轉為blob物件,這一步是非同步的因此使用async/await
        fileName: decodeURI(disposition.split(";")[1].split("filename=")[1]), // 處理Content-Disposition,獲取header中的檔名
      }
    : response;
});

完整的程式碼如下:

request 檔案

import request from "umi-request";

// 響應攔截
request.interceptors.response.use(async (response) => {
  const disposition = response.headers.get("Content-Disposition"); // 獲取Content-Disposition
  return disposition // 當Content-Disposition中有值的時候進行處理,其他請求的響應則放過
    ? {
        blob: await response.blob(), // 將二進位制的資料轉為blob物件,這一步是非同步的因此使用async/await
        fileName: decodeURI(disposition.split(";")[1].split("filename=")[1]), // 處理Content-Disposition,獲取header中的檔名
      }
    : response;
});

export const downLoadExcel = (url) =>
  request(url, {
    method: "POST",
    data: data,
    // responseType: "blob", // 註釋掉這一段
  }).then(data => {
    const { blob, fileName } = response;
    let link = document.createElement("a");
    link.href = URL.createObjectURL( new Blob([blob], { type: "application/vnd.ms-excel" }) );
    link.download = fileName;
    document.body.appendChild(link);
    link.click();
    URL.revokeObjectURL(link.href);
    document.body.removeChild(link);
});

react 檔案

<Buttton onClick={download}> 下載 </Button>

js 檔案

async download() {
   await downLoadExcel('http://example.com/api/download');
},