MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Vue网络请求 文件上传与下载的最佳实践

2023-12-275.5k 阅读

Vue 网络请求基础

在 Vue 项目中,网络请求是与后端服务器进行数据交互的重要手段。目前,Axios 是 Vue 生态中广泛使用的网络请求库,它具有简洁易用、支持 Promise API、能在浏览器和 Node.js 中使用等优点。

安装 Axios

在 Vue 项目的根目录下,通过 npm 或 yarn 安装 Axios:

npm install axios --save
# 或者
yarn add axios

在 Vue 中使用 Axios

在 main.js 文件中,通常会对 Axios 进行全局配置和挂载:

import Vue from 'vue'
import axios from 'axios'

// 创建一个 Axios 实例
const service = axios.create({
  baseURL: process.env.VUE_APP_BASE_API, // 基础 URL
  timeout: 5000 // 请求超时时间
})

// 请求拦截器
service.interceptors.request.use(
  config => {
    // 在发送请求之前做些什么,比如添加 token
    const token = localStorage.getItem('token')
    if (token) {
      config.headers['Authorization'] = `Bearer ${token}`
    }
    return config
  },
  error => {
    // 对请求错误做些什么
    return Promise.reject(error)
  }
)

// 响应拦截器
service.interceptors.response.use(
  response => {
    // 对响应数据做些什么
    return response.data
  },
  error => {
    // 对响应错误做些什么
    if (error.response.status === 401) {
      // 处理未授权情况,例如跳转到登录页面
      router.push('/login')
    }
    return Promise.reject(error)
  }
)

Vue.prototype.$http = service

这样在 Vue 组件中就可以通过 this.$http 来发起网络请求。例如:

<template>
  <div>
    <button @click="fetchData">获取数据</button>
    <ul>
      <li v-for="item in dataList" :key="item.id">{{ item.name }}</li>
    </ul>
  </div>
</template>

<script>
export default {
  data() {
    return {
      dataList: []
    }
  },
  methods: {
    async fetchData() {
      try {
        const response = await this.$http.get('/api/data')
        this.dataList = response
      } catch (error) {
        console.error('请求失败', error)
      }
    }
  }
}
</script>

文件上传的最佳实践

文件上传是前端开发中常见的需求,在 Vue 中结合 Axios 可以优雅地实现文件上传功能。

使用 FormData 对象

FormData 是 HTML5 新增的一个对象,用于创建表单数据。它可以轻松地将文件附加到请求中,并且支持异步上传。

<template>
  <div>
    <input type="file" @change="handleFileChange" />
    <button @click="uploadFile">上传文件</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      file: null
    }
  },
  methods: {
    handleFileChange(event) {
      this.file = event.target.files[0]
    },
    async uploadFile() {
      if (!this.file) {
        return
      }
      const formData = new FormData()
      formData.append('file', this.file)
      try {
        const response = await this.$http.post('/api/upload', formData, {
          headers: {
            'Content-Type':'multipart/form-data'
          }
        })
        console.log('文件上传成功', response)
      } catch (error) {
        console.error('文件上传失败', error)
      }
    }
  }
}
</script>

在上述代码中,通过 input 元素的 change 事件获取选中的文件,然后将文件添加到 FormData 对象中。在 Axios 的 post 请求中,设置 Content-Typemultipart/form-data 来告知服务器这是一个包含文件的表单数据请求。

显示上传进度

对于较大文件的上传,显示上传进度可以提供更好的用户体验。Axios 支持在请求配置中通过 onUploadProgress 回调函数来获取上传进度。

<template>
  <div>
    <input type="file" @change="handleFileChange" />
    <button @click="uploadFile">上传文件</button>
    <div v-if="uploadProgress">
      上传进度: {{ uploadProgress }}%
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      file: null,
      uploadProgress: null
    }
  },
  methods: {
    handleFileChange(event) {
      this.file = event.target.files[0]
    },
    async uploadFile() {
      if (!this.file) {
        return
      }
      const formData = new FormData()
      formData.append('file', this.file)
      try {
        const response = await this.$http.post('/api/upload', formData, {
          headers: {
            'Content-Type':'multipart/form-data'
          },
          onUploadProgress: progressEvent => {
            const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total)
            this.uploadProgress = percentCompleted
          }
        })
        console.log('文件上传成功', response)
        this.uploadProgress = null
      } catch (error) {
        console.error('文件上传失败', error)
        this.uploadProgress = null
      }
    }
  }
}
</script>

在这个例子中,onUploadProgress 回调函数接收一个 progressEvent 对象,通过计算 loadedtotal 的比例来获取上传进度,并实时更新 uploadProgress 数据,从而在模板中显示上传进度。

多文件上传

多文件上传只需稍微修改代码,处理多个文件即可。

<template>
  <div>
    <input type="file" multiple @change="handleFileChange" />
    <button @click="uploadFiles">上传文件</button>
    <div v-if="uploadProgress">
      上传进度: {{ uploadProgress }}%
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      files: [],
      uploadProgress: null
    }
  },
  methods: {
    handleFileChange(event) {
      this.files = Array.from(event.target.files)
    },
    async uploadFiles() {
      if (this.files.length === 0) {
        return
      }
      const formData = new FormData()
      this.files.forEach(file => {
        formData.append('files', file)
      })
      try {
        const response = await this.$http.post('/api/uploadMultiple', formData, {
          headers: {
            'Content-Type':'multipart/form-data'
          },
          onUploadProgress: progressEvent => {
            const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total)
            this.uploadProgress = percentCompleted
          }
        })
        console.log('文件上传成功', response)
        this.uploadProgress = null
      } catch (error) {
        console.error('文件上传失败', error)
        this.uploadProgress = null
      }
    }
  }
}
</script>

这里通过设置 inputmultiple 属性允许选择多个文件,然后将这些文件逐一添加到 FormData 对象中进行上传。同样利用 onUploadProgress 来显示多文件上传的整体进度。

文件下载的最佳实践

文件下载在前端开发中也经常遇到,Vue 实现文件下载可以通过多种方式,下面介绍几种常见的方法。

使用 a 标签下载

对于已知 URL 的文件,可以直接创建一个 a 标签,并设置其 hrefdownload 属性来实现下载。

<template>
  <div>
    <button @click="downloadFile">下载文件</button>
  </div>
</template>

<script>
export default {
  methods: {
    downloadFile() {
      const link = document.createElement('a')
      link.href = '/api/download/file.pdf'
      link.download = 'file.pdf'
      document.body.appendChild(link)
      link.click()
      document.body.removeChild(link)
    }
  }
}
</script>

这种方法简单直接,适用于后端返回静态文件链接的情况。但是如果需要通过网络请求获取文件内容再进行下载,就需要其他方法。

通过 Axios 下载二进制文件

当后端返回的是二进制文件流时,可以使用 Axios 来获取文件内容,并通过 Blob 对象和 URL.createObjectURL 来实现下载。

<template>
  <div>
    <button @click="downloadFile">下载文件</button>
  </div>
</template>

<script>
export default {
  async downloadFile() {
    try {
      const response = await this.$http.get('/api/download', {
        responseType: 'blob'
      })
      const blob = new Blob([response], { type: 'application/pdf' })
      const link = document.createElement('a')
      link.href = URL.createObjectURL(blob)
      link.download = 'file.pdf'
      document.body.appendChild(link)
      link.click()
      document.body.removeChild(link)
      URL.revokeObjectURL(link.href)
    } catch (error) {
      console.error('文件下载失败', error)
    }
  }
}
</script>

在上述代码中,通过设置 Axios 的 responseTypeblob 来告诉 Axios 期望得到二进制响应。然后将响应内容创建为 Blob 对象,再通过 URL.createObjectURL 创建一个临时 URL 供 a 标签下载使用。下载完成后,需要调用 URL.revokeObjectURL 来释放创建的临时 URL 资源。

处理动态文件名

有时候后端返回的文件名是动态的,这时候可以从响应头中获取文件名。

<template>
  <div>
    <button @click="downloadFile">下载文件</button>
  </div>
</template>

<script>
export default {
  async downloadFile() {
    try {
      const response = await this.$http.get('/api/download', {
        responseType: 'blob'
      })
      const disposition = response.headers['content-disposition']
      const matches = /filename=(.*)/.exec(disposition)
      let filename = 'default.pdf'
      if (matches && matches.length > 1) {
        filename = decodeURIComponent(matches[1])
      }
      const blob = new Blob([response], { type: 'application/pdf' })
      const link = document.createElement('a')
      link.href = URL.createObjectURL(blob)
      link.download = filename
      document.body.appendChild(link)
      link.click()
      document.body.removeChild(link)
      URL.revokeObjectURL(link.href)
    } catch (error) {
      console.error('文件下载失败', error)
    }
  }
}
</script>

这里通过解析 content - disposition 响应头中的文件名信息,动态设置下载文件的名称,确保下载的文件名与后端提供的一致。

下载进度显示

类似于文件上传进度显示,文件下载也可以显示进度。Axios 同样支持通过 onDownloadProgress 回调函数来获取下载进度。

<template>
  <div>
    <button @click="downloadFile">下载文件</button>
    <div v-if="downloadProgress">
      下载进度: {{ downloadProgress }}%
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      downloadProgress: null
    }
  },
  async downloadFile() {
    try {
      const response = await this.$http.get('/api/download', {
        responseType: 'blob',
        onDownloadProgress: progressEvent => {
          const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total)
          this.downloadProgress = percentCompleted
        }
      })
      const disposition = response.headers['content-disposition']
      const matches = /filename=(.*)/.exec(disposition)
      let filename = 'default.pdf'
      if (matches && matches.length > 1) {
        filename = decodeURIComponent(matches[1])
      }
      const blob = new Blob([response], { type: 'application/pdf' })
      const link = document.createElement('a')
      link.href = URL.createObjectURL(blob)
      link.download = filename
      document.body.appendChild(link)
      link.click()
      document.body.removeChild(link)
      URL.revokeObjectURL(link.href)
      this.downloadProgress = null
    } catch (error) {
      console.error('文件下载失败', error)
      this.downloadProgress = null
    }
  }
}
</script>

在 Axios 的 get 请求中添加 onDownloadProgress 回调函数,计算下载进度并更新 downloadProgress 数据,从而在模板中显示下载进度。

优化与注意事项

  1. 错误处理:在文件上传和下载过程中,要全面处理各种可能的错误,如网络错误、服务器错误、文件格式不支持等。在响应拦截器和请求处理的 catch 块中,要根据不同的错误类型给出合适的提示信息,提高用户体验。
  2. 安全性:在文件上传时,要对上传的文件进行严格的验证,防止恶意文件上传,例如检查文件类型、大小限制等。在下载时,要确保下载链接的来源可靠,避免用户下载到恶意文件。
  3. 性能优化:对于大文件的上传和下载,可以考虑采用断点续传的方式,减少用户等待时间和网络资源浪费。在上传时,合理设置并发数,避免过多请求导致网络拥塞。
  4. 兼容性:不同浏览器对于文件操作的支持可能存在差异,要进行充分的兼容性测试,特别是在处理 Blob 对象、URL.createObjectURL 等特性时,确保在主流浏览器中都能正常工作。

通过以上关于 Vue 网络请求中文件上传与下载的最佳实践介绍,希望能帮助开发者在实际项目中更高效、更优雅地实现这些功能,提升用户体验和应用的稳定性。无论是简单的单文件上传下载,还是复杂的多文件操作、进度显示等需求,都可以通过上述方法和技巧来实现。