在前端開發中,有時需要從後端提供數據並生成文件提供用戶下載。以下將以下載 Excel 為例,介绍如何使用 axios 從後端取得文件,並在前端處理文件的下載過程。我们還會顯示如何從 HTTP header 中提取文件名,以 f 確保下載的文件命名正確。
Api 設定
要記得設置告诉 axios 期望接收的類型為 blob,若是不設置,則會預設為 json,收到的資料會是亂碼。
1 2 3 4 5 6 7 8
   |  exportFile(searchData) {   return axios.get("/data/export", {     params: searchData,     responseType: "blob"   }); }
 
 
  | 
 
前端處理文件下載
獲取到後端返回的文件數據後,我們需要在前端將其轉換為可下載的文件格式。以下是一個完整的示例,展示瞭如何將 blob 數據生成 Excel 文件並觸發下載。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
   | const handleExportFile = async () => {   try {     const res = await exportFile(data);
      const blob = new Blob([res.data], {       type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",     });
      const downloadUrl = window.URL.createObjectURL(blob);     const link = document.createElement("a");     link.href = downloadUrl;
      const fileName = "export.xlsx";     link.setAttribute("download", fileName);     document.body.appendChild(link);     link.click();     link.remove();
      window.URL.revokeObjectURL(downloadUrl);   } catch (error) {     console.error(error);   } };
  | 
 
new Blob([res.data], { type: … }) 做了什麼?
這里,用 new Blob() 創建了一個 Blob 對象。
res.data 是從後端 API 接收到的文件數據。
我們將這個數據放入 Blob 中,同時指定了文件的類型,即 application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,這是 Excel 文件的 MIME 類型。這樣瀏覽器就能識別出這是一個 Excel 文件。
動態創建一個 <a> 標簽,並通過模擬點擊觸發文件下載。
使用 window.URL.createObjectURL(blob) 創建了一個特殊的 URL,它指向我們剛才創建的 Blob 對象。
可以理解為它生成了一個臨時的下載地址,使用者可以透過這個地址下載文件。
接著 document.createElement(“a”) 創建 <a> 標籤,接下來要利用這個標籤來實現文件下載。
也就是 link.href = downloadUrl; 給 <a> 標籤賦值,將剛才生成的 downloadUrl 設定為這個 <a> 標籤的 link 地址 (href),也就是說,點擊這個 link 會指向我們的 Blob 物件(即文件資料)。
設定文件名並觸發下載
使用 setAttribute 方法給<a> 標籤添加一個 download 屬性,並設定文件名為 “export.xlsx”。這樣,當用戶點擊鏈接時,瀏覽器會提示下載文件,並自動將文件保存為 export.xlsx。
document.body.appendChild 將 <a> 標籤臨時加到 document 的 body 中。雖然使用者不會看到這個 link,但它在頁面上是存在的。
然後模擬一次使用者點擊這個連結 (link.click()),觸發文件的下載過程。
link.remove();下載操作完成後,我們把這個臨時創建的 <a> 標籤從頁面中移除。
有時,後端會通過 Content-Disposition 頭信息傳遞文件名。為了確保下載時使用正確的文件名,我們可以從響應頭中解析文件名。
這時候要注意前端, axios 是如何處理 response (你能拿到的回傳內容是否有無 header)
需要從回傳的 response headers 取得檔名,這樣才能下載時有正確的檔名。
1 2 3 4 5 6 7 8
   | headers:{     "access-control-allow-origin": "http://localhost:5143",     "access-control-expose-headers": "*",     "content-disposition": "attachment; filename=123123_202408.xlsx; filename*=UTF-8''123123_202408.xlsx",     "content-length": "12111",     "content-type": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",      ... }
  | 
 
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
   | const handleExportFile = async () => {   try {     $q.loading.show();     const data = {       sourceId: Number(route.params.id),       month: followQuery.month,     };     const res = await getFollowMonthlySummaryDownload(data);     $q.loading.hide();
      const blob = new Blob([res.data], {       type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",     });
      const downloadUrl = window.URL.createObjectURL(blob);     const link = document.createElement("a");     link.href = downloadUrl;
           const contentDisposition = res.headers["content-disposition"];     let fileName = "default-filename.xlsx";
      if (contentDisposition) {       const fileNameMatch = contentDisposition.match(         /filename\*=UTF-8''([^;]+)/       );       if (fileNameMatch) {                  fileName = decodeURIComponent(fileNameMatch[1]);       } else {                  const fileNameSimpleMatch =           contentDisposition.match(/filename=([^;]+)/);         if (fileNameSimpleMatch) {           fileName = fileNameSimpleMatch[1].trim();         }       }     }     link.setAttribute("download", fileName);     document.body.appendChild(link);     link.click();     link.remove();
      window.URL.revokeObjectURL(downloadUrl);   } catch (error) {     console.error(error);   } };
  |