前言
在專案中經常會遇到下載或匯出服務端資源的需求,一般分為2種做法
- 獲取檔案流,編碼後下載
- 獲取檔案的url,直接下載
本文主要探討第二種方法,在最後會提及檔案流的方法。
瀏覽器的安全策略
在介紹方法之前,我們需要知道瀏覽器的一些安全機制,防止惡意程式碼對使用者的破壞。
現代瀏覽器(ie8除外)檢測到非使用者直接操作產生的新視窗,一般會阻止,比如在ajax的回撥中開啟新的視窗,因為這些操作並不是在使用者點選的執行緒中,所以會攔截。
預開新標籤頁
做法
- 在非同步操作之前,先開啟一個新標籤頁
- 請求後端資源的地址
- 獲取url後去修改空白頁的url
const downloadTab = window.open('about:blank');
ajax.get('xxx').then(url => {
// 使用後端資源的url進行下載
downloadTab.location.href = href;
}).catch(err => {
// 處理異常
downloadTab.close();
})
複製程式碼
缺點
- 不管請求成功還是失敗都會有新頁面的閃爍出現
- 開啟的新頁面在什麼時候關閉是個問題,因為如果請求時間過長,使用者可能自己關閉新頁面,更不好處理的情況是頁面什麼時候觸發了下載,因為如果只是更改url就關閉視窗可能還沒有開始下載的操作。
生成iframe
做法
為了避免頁面閃爍與關閉時機的問題,可以在當前頁面使用動態建立iframe的方式,直接下載
ajax.get('xxx').then(href => {
if (!href) {
return;
}
if (!this.downIframe) {
this.downIframe = document.createElement('iframe'); // 動態建立iframe
this.downIframe.src = href; // iframe 中載入的頁面
this.downIframe.id = 'downloadIframe'; // iframe 中載入的頁面
this.downIframe.style.width = '1px';
this.downIframe.style.height = '1px';
this.downIframe.style.position = 'absolute';
this.downIframe.style.left = '-100px';
document.body.appendChild(this.downIframe); // 新增到當前窗體
} else {
this.downIframe.src = href; // iframe 中載入的頁面
}
}).catch(err => {
// 處理異常
})
複製程式碼
缺點
雖然可以優雅的下載檔案了,可是如果需要定製下載檔案的名稱就會令人頭疼了,我們需要時機去改變下載檔案的名稱,否則就會使服務端的檔名稱。
生成標籤
利用download屬性設定檔名,
ajax.get('xxx').then(href => {
if (!href) {
return;
}
var a = document.createElement('a');
var url = href;
var filename = 'test.zip';
a.href = url;
a.download = filename; // 在沒有download屬性的情況,target="_blank",仍會阻止開啟
a.click();
}).catch(err => {
// 處理異常
})
複製程式碼
注意:
有些瀏覽器需要將a標籤嵌入頁面才可執行。
處理檔案流
最後大概講一下如果後端返回流怎麼處理。
function funDownload(content, filename) {
// 建立隱藏的可下載連結
var link = document.createElement('a');
link.download = filename;
link.style.display = 'none';
// 字元內容轉變成blob地址
var blob = new Blob([content]);
//如果是excel格式
//var blob = new Blob([content], {type: 'application/vnd.ms-excel'}),
link.href = URL.createObjectURL(blob);
// 觸發點選
document.body.appendChild(link);
link.click();
// 然後移除
document.body.removeChild(link);
};
複製程式碼
URL.createObjectURL() 靜態方法會建立一個 DOMString,其中包含一個表示引數中給出的物件的URL。這個 URL 的生命週期和建立它的視窗中的 document 繫結。這個新的URL 物件表示指定的 File 物件或 Blob 物件。
注意:
在每次呼叫
createObjectURL()
方法時,都會建立一個新的 URL 物件,即使你已經用相同的物件作為引數建立過。當不再需要這些 URL 物件時,每個物件必須通過呼叫 URL.revokeObjectURL() 方法來釋放。瀏覽器會在文件退出的時候自動釋放它們,但是為了獲得最佳效能和記憶體使用狀況,你應該在安全的時機主動釋放掉它們。