關於前端實現文件下載功能

一、經過window.open()打開新頁面下載文件

window.open(`url`, '_self')

使用場景:下載excel文件,後端提供接口,接口返回的是文件流,能夠直接使用window.open(),最簡單的方式。html

優勢:最簡潔;ios

弊端:當參數錯誤時,或其它緣由致使接口請求失敗,這時沒法監聽到接口返回的錯誤信息,須要保證請求必須是正確的且能正確返回數據流,否則打開頁面會直接輸出接口返回的錯誤信息,體驗很差。git

二、經過a標籤打開新頁面下載文件

export const exportFile = (url, fileName) => {
  const link = document.createElement('a')
  const body = document.querySelector('body')

  link.href = url
  link.download = fileName

  // fix Firefox
  link.style.display = 'none'
  body.appendChild(link)

  link.click()
  body.removeChild(link)
}

經過a標籤下載的方式,同window.open()是同樣的,惟一的優勢是能夠自定義下載後的文件名,a標籤裏有download屬性能夠自定義文件名。github

弊端:同window.open()方式同樣,沒法監聽錯誤信息。ajax

問題:以上兩種方式,當在下載.mp3格式,或者視頻文件時,瀏覽器會直接播放該文件,而達不到直接下載的功能,此時,當下載音視頻文件時沒法使用以上兩種方式。axios

三、經過文件流的方式下載

爲了解決.mp3文件下載所帶來的問題,經過ajax請求返回Blob對象,或者ArrayBuffer對象。後端

(1)、獲取文件

以下:經過原生ajax請求返回Blob對象數組

const getBlob = (url) => {
  return new Promise((resolve) => {
    const xhr = new XMLHttpRequest()

    xhr.open('GET', url, true)
    xhr.responseType = 'blob'
    xhr.onload = () => {
      if (xhr.status === 200) {
        resolve(xhr.response)
      }
    }

    xhr.send()
  })
}

一樣,也能夠經過axios返回ArrayBuffer對象,同等做用promise

import axios from 'axios'
const getFile = url => {
    return new Promise((resolve, reject) => {
        axios({
            method:'get',
            url,
            responseType: 'arraybuffer'
        }).then(data => {
            resolve(data.data)
        }).catch(error => {
            reject(error.toString())
        })
    })
}

ArrayBuffer(又稱類型化數組)瀏覽器

ArrayBuffer對象用來表示通用的、固定長度的原始二進制數據緩衝區。ArrayBuffer 不能直接操做,而是要經過類型數組對象或 DataView 對象來操做,它們會將緩衝區中的數據表示爲特定的格式,並經過這些格式來讀寫緩衝區的內容。

Blob(Binary Large Object): 二進制大數據對象

Blob 對象表示一個不可變、原始數據的類文件對象。Blob 表示的不必定是JavaScript原生格式的數據。File 接口基於Blob,繼承了 blob 的功能並將其擴展使其支持用戶系統上的文件。

注意:

若是下載文件是文本類型的(如: .txt, .js之類的), 那麼用responseType: 'text'也能夠, 可是若是下載的文件是圖片, 視頻之類的, 就得用arraybuffer或blob,更多詳情請 查看MDN

經過ajax請求的方式下載文件,能夠解決第一、2中存在的弊端,當請求錯誤時或捕獲到錯誤信息

(2)、保存文件

當獲取到文件後,這時須要保存文件

const saveAs = (blob, filename) => {
  if (window.navigator.msSaveOrOpenBlob) {
    navigator.msSaveBlob(blob, filename)
  } else {
    const link = document.createElement('a')
    const body = document.querySelector('body')

    link.href = window.URL.createObjectURL(blob) // 建立對象url
    link.download = filename

    // fix Firefox
    link.style.display = 'none'
    body.appendChild(link)

    link.click()
    body.removeChild(link)

    window.URL.revokeObjectURL(link.href) // 經過調用 URL.createObjectURL() 建立的 URL 對象
  }
}
爲了解決IE(ie10 - 11)和Edge沒法打開Blob URL連接的方法,微軟本身有一套方法 window.navigator.msSaveOrOpenBlob(blob, filename),打開並保存文件,以上代碼作了簡單的兼容, navigator.msSaveBlob(blob, filename)是直接保存。注意,此爲非標準功能,詳情請查看 相關文檔

如下爲完整代碼

const getBlob = (url) => {
  return new Promise((resolve) => {
    const xhr = new XMLHttpRequest()

    xhr.open('GET', url, true)
    xhr.responseType = 'blob'
    xhr.onload = () => {
      if (xhr.status === 200) {
        resolve(xhr.response)
      }
    }

    xhr.send()
  })
}

const saveAs = (blob, filename) => {
  if (window.navigator.msSaveOrOpenBlob) {
    navigator.msSaveBlob(blob, filename)
  } else {
    const link = document.createElement('a')
    const body = document.querySelector('body')

    link.href = window.URL.createObjectURL(blob) // 建立對象url
    link.download = filename

    // fix Firefox
    link.style.display = 'none'
    body.appendChild(link)

    link.click()
    body.removeChild(link)

    window.URL.revokeObjectURL(link.href) // 經過調用 URL.createObjectURL() 建立的 URL 對象
  }
}

export const download = (url, filename = '') => {
  getBlob(url).then((blob) => {
    saveAs(blob, filename)
  })
}

四、如何實現批量下載,且打包文件

在第3點的基礎上,若是要實現批量下載,那能作到的只是連續屢次調用download方法,這樣沒法批量集中的下載文件。這個時候就須要可以對已獲取到的文件流,進行一個打包的操做,而後一次下載完畢。

這時,須要用到兩個庫jszipfile-saver

完整的思路,經過ajax獲取文件,而後用 jszip 壓縮文件, 再用 file-saver 生成文件

(1)、獲取文件

第3點中的第(1)點

(2)、打包文件

export const download = () => {
  const urls = ['url', 'url']   //須要下載的路徑
  const zip = new JSZip()
  const cache = {}
  const promises = []
  urls.forEach((item) => {
    const promise = getBlob(item).then((data) => { // 下載文件, 並存成ArrayBuffer對象
      zip.file('下載文件名', data, { binary: true }) // 逐個添加文件
      cache[item.fileName] = data
    })
    promises.push(promise)
  })

  Promise.all(promises).then(() => {
    zip.generateAsync({ type: 'blob' }).then((content) => { // 生成二進制流
      FileSaver.saveAs(content, `打包下載.zip`) // 利用file-saver保存文件
    })
  })
}

相關jszip/file-saver更多詳情

jszip:

https://github.com/Stuk/jszip

http://stuk.github.io/jszip/d...

file-saver:

https://github.com/eligrey/Fi...

貼上完整代碼

/**
 * 獲取文件
 * @param url
 * @returns {Promise<any>}
 */
const getBlob = (url) => {
  return new Promise((resolve) => {
    const xhr = new XMLHttpRequest()

    xhr.open('GET', url, true)
    xhr.responseType = 'blob'
    xhr.onload = () => {
      if (xhr.status === 200) {
        resolve(xhr.response)
      }
    }

    xhr.send()
  })
}

/**
 * 批量打包zip包下載
 * @param urlArr Array [{url: 下載文件的路徑, fileName: 下載文件名稱}]
 * @param filename zip文件名
 */
export const download = (urlArr, filename = '打包下載') => {
  if (!urlArr.length > 0) return
  const zip = new JSZip()
  const cache = {}
  const promises = []
  urlArr.forEach((item) => {
    const promise = getBlob(item.url).then((data) => { // 下載文件, 並存成ArrayBuffer對象
      zip.file(item.fileName, data, { binary: true }) // 逐個添加文件
      cache[item.fileName] = data
    })
    promises.push(promise)
  })

  Promise.all(promises).then(() => {
    zip.generateAsync({ type: 'blob' }).then((content) => { // 生成二進制流
      FileSaver.saveAs(content, `${filename}.zip`) // 利用file-saver保存文件
    })
  })
}

注意:

因爲經過瀏覽器進行打包壓縮,若是文件過大,或者下載的內容過多,可能致使瀏覽器崩潰。
相關文章
相關標籤/搜索