1. 程式人生 > >JS處理檔案流

JS處理檔案流

最近做一個專案,遇到了一個問題,就是匯出Excel功能。多普通呀,多大眾化,哪裡都有,可惜我們後臺說給我JSON資料,自己處理。我果斷拒絕了,拒絕的裡有是我菜,實現不了啊。然後後臺開發看不下去了,就是轉成檔案流給我吧。他們那裡是分散式部署,也沒有辦法持久化儲存。遂發生了一下的故事

百度

沒有怎麼做過,肯定是百度啦,然後找打了一段程式碼,程式碼內容如下

function download() {
	    var xmlResquest = new XMLHttpRequest();
	    xmlResquest.open("POST", "/eksoft/fileUpload/download"
, true); xmlResquest.setRequestHeader("Content-type", "application/json"); xmlResquest.setRequestHeader("Authorization", "Bearer 6cda86e3-ba1c-4737-972c-f815304932ee"); xmlResquest.responseType = "blob"; xmlResquest.onload = function (oEvent) { var content = xmlResquest.response;
var elink = document.createElement('a'); elink.download = "test.xlsx"; elink.style.display = 'none'; var blob = new Blob([content]); elink.href = URL.createObjectURL(blob); document.body.appendChild(elink); elink.click(); document.body.removeChild(elink); }
; xmlResquest.send(); }
  • 簡單的分析了一下,自己有用的程式碼如下:
var content = 'content';
var elink = document.createElement('a');
var blob = new Blob([content]);
	elink.download = "test.xlsx";
	elink.style.display = 'none';
	elink.href = URL.createObjectURL(blob);
	elink.click();
	
  • 看到這裡,發現有兩個不認識的api,果斷去mdn取經

createObjectURL

  • URL.createObjectURL(), 靜態方法,會建立一個DOMString,其中包含一個表示引數中給出的物件URL。這個URL的生命週期和建立他的視窗中的document繫結,這個新的URL物件表示File物件或者Blob物件。
  • 語法:objectURL = URL.createObjectURL(blob);

revokeObjectURL

  • URL.revokeObjectURL靜態方法。 來釋放之前通過呼叫createObjectURL建立的已經存在的物件。當結束使用某個URL物件時,應該通過這個方法來訪瀏覽器知道不再需要這個檔案的引用了。
  • 語法:window.URL.revokeObjectURL(objectURL); bjectURL: 是一個 DOMString,表示通過呼叫 URL.createObjectURL 方法產生的 URL 物件

DOMString

  • DOMString 是一個UTF-16字串。由於JavaScript已經使用了這樣的字串,所以DOMString 直接對映到 一個String
  • null傳遞給接受DOMString的方法或引數時通常會把其stringifies為“null”。

動手改造階段

  • 通過有了以上的條件,具備了自己動手改造的條件。
  • 我的思路如下,
    • 通過fetch請求拿到資料流
    • 將下載的程式碼封裝為一個函式
    • 將下載拿到的檔案流直接傳入該函式
    • 函式內部處理下載,然後刪除該連結
/**
 * 匯出檔案工具方法
 * 需要將返回的檔案流物件直接傳入,
 * 如果沒有資料, 返回一個物件
 */
export let exportFile = (data, name = 'name') => {
	return data.blob().then((blob) => {
		// js無法判斷檔案劉是否存在,只能通過型別
		// 檔案流沒有資料的時候轉碼是/html結尾,我這個直接返回一個物件,方便呼叫的時候處理
		if (blob.type.endsWith('/html')) {
			return {
				msg: "暫無資料"
			}
		}
		let downloadUrl = window.URL.createObjectURL(blob);
		let anchor = document.createElement("a");
		let filename = data.headers.get('Content-Disposition');
		anchor.href = downloadUrl;
		anchor.download = filename.replace('filename=', '');
		anchor.click();
		window.URL.revokeObjectURL(blob);
	})
}

// 呼叫檔案匯出方法
async function export () {
    let res = await axios.post('http://xxx.com', {})
    let err = await exportFile(res.data, '推送日誌')
	if (err) message.warning(err.msg)
}

export()
  • 改造後的方法如上,在本地實現了檔案流儲存到磁碟。
  • 但是我程式碼到生產環境的時候發現fetch的response的type變了,在本地,response.type是base, 但是在線上去成了cors,嗯麼麼,具體別的沒有感受到區別,就是報錯了
  • 到了這裡,當然是檢查自己的程式碼…ok,檢查響應頭…ok,百度… 沒有類似的情況。找後端同事商量…沒有個所以然, 然後回頭仔細看了一眼程式碼,提示filename.replace沒有這個方法,索性自己慢慢的使用console.log檢視,就是無法從data.headers中拿到檔案描述了,然後在下載賦值檔名的時候報錯了。
  • 遂自己和後臺商量了一下,採用簡單的規則,我自己本地指定檔名,不讀取相應頭了。更在如下
/**
 * 匯出檔案工具方法
 * 需要將返回的檔案流物件直接傳入,
 * 如果沒有資料, 返回一個物件
 * 檔案命名規範為手動傳入一個檔名,然後加上日期,時分秒
 */
export let exportFile = (data, name = 'name') => {
	return data.blob().then((blob) => {
		if (blob.type.endsWith('/html')) {
			return {
				msg: "暫無資料"
			}
		}
		let downloadUrl = window.URL.createObjectURL(blob);
		let anchor = document.createElement("a");
		anchor.href = downloadUrl;

		anchor.download = `${name}${Format(new Date(), 'yyyyMMddhhmmss')}.csv`;
		anchor.click();
		window.URL.revokeObjectURL(blob);
	})
	

}

總結

到了這裡,簡單的檔案處理就結束了,後臺又差了一下,說這個會有相容性問題,然後有一段處理相容的程式碼,我這裡目前沒有管ie,遂沒有驗證,但是還是把程式碼貼過來,留著總是好的

   if (window.navigator.msSaveOrOpenBlob) {
        navigator.msSaveBlob(blob, filename);
    } else {

    var a = document.createElement('a');
     blob.type = "application/excel";
     var url = createObjectURL(blob);
     a.href = url;
     a.download = filename;
     a.click();
     window.URL.revokeObjectURL(url);
    }