【vue3+js】文件下载方法整理

前端文件下载方式

引言

在前端开发中,文件下载是一个常见的需求。后端可能以不同的方式返回文件数据,前端需要根据不同的返回类型采用相应的处理方式。本文将总结几种常见的后端返回类型及对应的前端处理方案,主要基于Vue3和JavaScript环境。

一、后端返回文件URL

场景描述

后端直接返回文件的访问URL,通常是静态文件或已存储在服务器上的文件。

前端处理方式

// 直接使用window.open或创建a标签下载
function downloadByUrl(url, filename) {
            
  const a = document.createElement('a')
  a.href = url
  a.download = filename || url.split('/').pop(); // 设置下载文件名
  a.style.display = 'none';
  document.body.appendChild(a)
  a.click()
  document.body.removeChild(a)
  
  // 或者简单方式 在新标签页打开
  // window.open(url, '_blank')
}

Vue3中使用示例

import {
             ref } from 'vue'

const fileUrl = getApiURL()//获取接口返回的下载链接

const handleDownload = () => {
            
  downloadByUrl(fileUrl.value, 'myfile.pdf')
}

注意事项

适用于公开可访问的文件
可能需要处理跨域问题
对于大文件更高效,因为浏览器可以管理下载过程

二、后端返回二进制流(Blob)

场景描述

后端接口返回二进制流数据,通常设置responseType: 'blob'

控制台响应中返回的是file类型

前端处理方式

需要现在接口请求的地方,设置响应类型

import axios from 'axios'

async function downloadByBlob(apiUrl, filename) {
            
  try {
            
  //请求接口地址
    const response = downloadTemp(apiUrl);
    const blob = new Blob([response.data])
    const downloadUrl = window.URL.createObjectURL(blob)
    
    const a = document.createElement('a')
    a.href = downloadUrl
    a.download = filename || 'download'
    
    document.body.appendChild(a)
    a.click()
    // 清理
    window.URL.revokeObjectURL(a.href)
    document.body.removeChild(a)
  } catch (error) {
            
    console.error('下载失败:', error)
  }
}

// Vue3中使用示例
const handleBlobDownload = async () => {
            
  await downloadByBlob('/api/download', 'document.pdf')
}
请求接口的时候 也可以在请求的时候设置响应类型
const response = downloadTemp(apiUrl, {
  responseType: 'blob' // 关键设置
})
这里要看接口返回的有没有data 如果是一整个res都是file 这里直接写res
const blob = new Blob([response]);
const blob = new Blob([response.data])

注意事项

需要正确设置responseType: 'blob'
及时释放创建的URL对象,避免内存泄漏
可以处理需要认证的私有文件

三、后端返回Base64字符串

场景描述

后端返回Base64编码的文件数据,通常以字符串形式返回。

前端处理方式

function downloadByBase64(base64Data, filename, mimeType) {
            
  // 去除可能的Base64前缀
  const base64WithoutPrefix = base64Data.replace(/^data:.+;base64,/, '')
  
  // 转换为字节数组
  const byteCharacters = atob(base64WithoutPrefix)
  const byteArrays = []
  
  for (let offset = 0; offset < byteCharacters.length; offset += 512) {
            
    const slice = byteCharacters.slice(offset, offset + 512)
    const byteNumbers = new Array(slice.length)
    
    for (let i = 0; i < slice.length; i++) {
            
      byteNumbers[i] = slice.charCodeAt(i)
    }
    
    const byteArray = new Uint8Array(byteNumbers)
    byteArrays.push(byteArray)
  }
  
  const blob = new Blob(byteArrays, {
             type: mimeType || 'application/octet-stream' })
  const url = URL.createObjectURL(blob)
  
  const a = document.createElement('a')
  a.href = url
  a.download = filename || 'download'
  document.body.appendChild(a)
  a.click()
  document.body.removeChild(a)
  URL.revokeObjectURL(url)
}

// Vue3中使用示例
const base64Data = ref('data:application/pdf;base64,JVBERi0xLjQK...')

const handleBase64Download = () => {
            
  downloadByBase64(base64Data.value, 'file.pdf', 'application/pdf')
}

注意事项

需要正确处理Base64前缀(如data:image/png;base64,
大文件可能会导致性能问题
需要知道文件的MIME类型

四、后端返回文件ID或令牌

场景描述

后端返回文件ID或临时访问令牌,前端需要再次请求才能获取文件。

前端处理方式

async function downloadByFileId(fileId, filename) {
            
  try {
            
    // 第一步:获取文件访问令牌或URL
    const {
             data } = await axios.get(`/api/files/${
              fileId}/token`)
    
    // 第二步:使用令牌下载文件
    const response = await axios.get(`/api/files/${
              fileId}/download`, {
            
      responseType: 'blob',
      headers: {
            
        'Authorization': `Bearer ${
              data.token}`
      }
    })
    
    // 处理Blob下载
    const blob = new Blob([response.data])
    const url = window.URL.createObjectURL(blob)
    const a = document.createElement('a')
    a.href = url
    a.download = filename || data.filename || 'download'
    document.body.appendChild(a)
    a.click()
    window.URL.revokeObjectURL(url)
    document.body.removeChild(a)
  } catch (error) {
            
    console.error('下载失败:', error)
  }
}

// Vue3中使用示例
const handleTokenDownload = async () => {
            
  await downloadByFileId('12345', 'secured_file.pdf')
}

注意事项

需要两次API调用
适用于需要严格权限控制的文件
令牌通常有有效期限制

五、后端返回JSON包含文件信息

场景描述

后端返回JSON数据,其中包含文件内容或文件访问信息。

前端处理方式

async function downloadByJsonResponse(apiUrl, filename) {
            
  try {
            
    const {
             data } = await axios.get(apiUrl)
    
    if (data.fileContent) {
            
      // 如果JSON中包含Base64文件内容
      downloadByBase64(data.fileContent, filename || data.fileName, data.mimeType)
    } else if (data.fileUrl) {
            
      // 如果JSON中包含文件URL
      downloadByUrl(data.fileUrl, filename || data.fileName)
    } else if (data.downloadUrl) {
            
      // 可能需要带token访问
      const response = await axios.get(data.downloadUrl, {
            
        responseType: 'blob',
        headers: {
            
          'Authorization': `Bearer ${
              data.token}`
        }
      })
      
      const blob = new Blob([response.data])
      const url = window.URL.createObjectURL(blob)
      const a = document.createElement('a')
      a.href = url
      a.download = filename || data.fileName || 'download'
      document.body.appendChild(a)
      a.click()
      window.URL.revokeObjectURL(url)
      document.body.removeChild(a)
    }
  } catch (error) {
            
    console.error('下载失败:', error)
  }
}

注意事项

需要根据后端返回的数据结构灵活处理
可能需要组合多种下载方式
适用于复杂的文件下载场景

六、特殊处理:大文件分片下载

场景描述

对于大文件,前端可以实现分片下载以提高可靠性和用户体验。

前端处理方式

async function downloadLargeFile(url, filename, chunkSize = 1024 * 1024) {
            
  try {
            
    // 1. 获取文件大小
    const headResponse = await axios.head(url)
    const fileSize = parseInt(headResponse.headers['content-length'], 10)
    
    // 2. 分片下载
    const chunks = Math.ceil(fileSize / chunkSize)
    const blobParts = []
    
    for (let i = 0; i < chunks; i++) {
            
      const start = i * chunkSize
      const end = Math.min(start + chunkSize, fileSize) - 1
      
      const chunkResponse = await axios.get(url, {
            
        responseType: 'blob',
        headers: {
            
          'Range': `bytes=${
              start}-${
              end}`
        }
      })
      
      blobParts.push(chunkResponse.data)
      
      // 更新进度
      const progress = Math.round(((i + 1) / chunks * 100)
      console.log(`下载进度: ${
              progress}%`)
    }
    
    // 3. 合并分片并下载
    const fullBlob = new Blob(blobParts)
    const downloadUrl = window.URL.createObjectURL(fullBlob)
    const a = document.createElement('a')
    a.href = downloadUrl
    a.download = filename || 'download'
    document.body.appendChild(a)
    a.click()
    window.URL.revokeObjectURL(downloadUrl)
    document.body.removeChild(a)
  } catch (error) {
            
    console.error('大文件下载失败:', error)
  }
}

注意事项

需要服务器支持Range请求
可以实现断点续传和进度显示
适用于非常大的文件下载

七、通用下载工具函数

下面是一个整合了多种下载方式的通用工具函数:

// download-utils.js
import axios from 'axios'

export const downloadFile = {
            
  /**
   * 通过URL下载文件
   * @param {string} url 文件URL
   * @param {string} filename 下载文件名
   */
  byUrl(url, filename) {
            
    const a = document.createElement('a')
    a.href = url
    a.download = filename || 'download'
    document.body.appendChild(a)
    a.click()
    document.body.removeChild(a)
  },
  
  /**
   * 通过Blob下载文件
   * @param {Blob|Response} blob Blob对象或Response
   * @param {string} filename 下载文件名
   */
  byBlob(blob, filename) {
            
    const blobUrl = URL.createObjectURL(blob instanceof Response ? blob.blob() : blob)
    this.byUrl(blobUrl, filename)
    setTimeout(() => URL.revokeObjectURL(blobUrl), 100)
  },
  
  /**
   * 通过Base64下载文件
   * @param {string} base64Data Base64字符串
   * @param {string} filename 下载文件名
   * @param {string} mimeType 文件类型
   */
  byBase64(base64Data, filename, mimeType = 'application/octet-stream') {
            
    const byteCharacters = atob(base64Data.replace(/^data:.+;base64,/, ''))
    const byteArrays = []
    
    for (let offset = 0; offset < byteCharacters.length; offset += 512) {
            
      const slice = byteCharacters.slice(offset, offset + 512)
      const byteNumbers = new Array(slice.length).fill(0).map((_, i) => slice.charCodeAt(i))
      byteArrays.push(new Uint8Array(byteNumbers))
    }
    
    this.byBlob(new Blob(byteArrays, {
             type: mimeType }), filename)
  },
  
  /**
   * 通过API请求下载文件
   * @param {string} url API地址
   * @param {string} filename 下载文件名
   * @param {Object} options 请求选项
   */
  async byApi(url, filename, options = {
             }) {
            
    const response = await axios({
            
      url,
      method: options.method || 'GET',
      responseType: 'blob',
      ...options
    })
    
    // 尝试从Content-Disposition获取文件名
    const contentDisposition = response.headers['content-disposition']
    const finalFilename = filename || 
      (contentDisposition && contentDisposition.split('filename=')[1]) || 
      'download'
    
    this.byBlob(response.data, finalFilename)
  }
}

// Vue3中使用示例
import {
             downloadFile } from '@/utils/download-utils'

// 使用URL下载
downloadFile.byUrl('http://example.com/file.pdf', 'document.pdf')

// 使用API下载
downloadFile.byApi('/api/download', 'report.pdf', {
            
  headers: {
             'Authorization': 'Bearer token' }
})

总结

后端返回类型 前端处理方式 适用场景 优点 缺点
文件URL 直接创建a标签或window.open 公开静态文件 简单高效,浏览器管理下载 需要文件公开可访问
二进制流(Blob) 设置responseType: ‘blob’,转为对象URL 需要认证的私有文件 安全,可处理权限控制 需要内存管理
Base64字符串 解码Base64,转为Blob 小文件或图片 可直接嵌入数据 大文件性能差
文件ID/令牌 二次请求获取实际文件 需要严格权限控制的文件 安全性高 需要多次请求
JSON包含文件信息 根据数据结构选择上述方法 复杂业务场景 灵活 需要额外解析
大文件分片 Range请求分片下载合并 超大文件 可靠,可显示进度 实现复杂,需服务器支持

在实际项目中,应根据后端接口的具体返回格式和业务需求选择合适的下载方式。同时要注意错误处理、内存管理和用户体验等问题,确保文件下载功能稳定可靠。

© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容