平時在前端下載文件有兩種方式,一種是后台提供一個 URL,然后用 window.open(URL)
下載,另一種就是后台直接返回文件的二進制內容,然后前端轉化一下再下載。
由於第一種方式比較簡單,在此不做探討。本文主要講解一下第二種方式怎么實現。
Blob、ajax(axios)
mdn 上是這樣介紹 Blob
的:
Blob 對象表示一個不可變、原始數據的類文件對象。Blob 表示的不一定是JavaScript原生格式的數據
具體使用方法
axios({
method: 'post',
url: '/export',
})
.then(res => {
// 假設 data 是返回來的二進制數據
const data = res.data
const url = window.URL.createObjectURL(new Blob([data], {type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"}))
const link = document.createElement('a')
link.style.display = 'none'
link.href = url
link.setAttribute('download', 'excel.xlsx')
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
})
打開下載的文件,看看結果是否正確。
一堆亂碼...
一定有哪里不對。
最后發現是參數 responseType
的問題,responseType
它表示服務器響應的數據類型,由於后台返回來的是二進制數據,所以我們要把它設為 arraybuffer
,
接下來再看看結果是否正確。
axios({
method: 'post',
url: '/export',
responseType: 'arraybuffer',
})
.then(res => {
// 假設 data 是返回來的二進制數據
const data = res.data
const url = window.URL.createObjectURL(new Blob([data], {type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"}))
const link = document.createElement('a')
link.style.display = 'none'
link.href = url
link.setAttribute('download', 'excel.xlsx')
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
})
這次沒有問題,文件能正常打開,內容也是正常的,不再是亂碼。
根據后台接口內容決定是否下載文件
作者的項目有大量的頁面都有下載文件的需求,而且這個需求還有點變態。
具體需求如下
- 如果下載文件的數據量條數符合要求,正常下載(每個頁面限制下載數據量是不一樣的,所以不能在前端寫死)。
- 如果文件過大,后台返回
{ code: 199999, msg: '文件過大,請重新設置查詢項', data: null }
,然后前端再進行報錯提示。
先來分析一下,首先根據上文,我們都知道下載文件的接口響應數據類型為 arraybuffer
。返回的數據無論是二進制文件,還是 JSON 字符串,前端接收到的其實都是 arraybuffer
。所以我們要對 arraybuffer
的內容作個判斷,在接收到數據時將它轉換為字符串,判斷是否有 code: 199999
。如果有,則報錯提示,如果沒有,則是正常文件,下載即可。具體實現如下:
axios.interceptors.response.use(response => {
const res = response.data
// 判斷響應數據類型是否 ArrayBuffer,true 則是下載文件接口,false 則是正常接口
if (res instanceof ArrayBuffer) {
const utf8decoder = new TextDecoder()
const u8arr = new Uint8Array(res)
// 將二進制數據轉為字符串
const temp = utf8decoder.decode(u8arr)
if (temp.includes('{code:199999')) {
Message({
// 字符串轉為 JSON 對象
message: JSON.parse(temp).msg,
type: 'error',
duration: 5000,
})
return Promise.reject()
}
}
// 正常類型接口,省略代碼...
return res
}, (error) => {
// 省略代碼...
return Promise.reject(error)
})