1. 程式人生 > 實用技巧 >前端實現檔案下載功能

前端實現檔案下載功能

1、通過window.open()開啟新頁面下載檔案

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

使用場景:下載excel檔案,後端提供介面,介面返回的是檔案流,可以直接使用window.open(),最簡單的方式。

優點:最簡潔;

弊端:當引數錯誤時,或其它原因導致介面請求失敗,這時無法監聽到介面返回的錯誤資訊,需要保證請求必須是正確的且能正確返回資料流,不然開啟頁面會直接輸出介面返回的錯誤資訊,體驗不好。

2、通過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屬性可以自定義檔名。

弊端:同window.open()方式一樣,無法監聽錯誤資訊。

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

3、通過檔案流的方式下載

為了解決.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物件,同等作用

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請求的方式下載檔案,可以解決第1、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)
  })
}

資源搜尋網站大全 https://www.renrenfan.com.cn 廣州VI設計公司https://www.houdianzi.com

4、如何實現批量下載,且打包檔案

在第3點的基礎上,如果要實現批量下載,那能做到的只是連續多次呼叫download方法,這樣無法批量集中的下載檔案。這個時候就需要能夠對已獲取到的檔案流,進行一個打包的操作,然後一次下載完畢。

這時,需要用到兩個庫jszip和file-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/

file-saver:

https://github.com/eligrey/

貼上完整程式碼

/**
 * 獲取檔案
 * @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儲存檔案
    })
  })
}

注意:

由於通過瀏覽器進行打包壓縮,如果檔案過大,或者下載的內容過多,可能導致瀏覽器崩潰。