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

Vue网络请求 如何实现全局加载动画与提示框

2022-12-083.7k 阅读

一、Vue 网络请求基础回顾

在 Vue 项目开发中,网络请求是非常常见的操作。我们经常使用的库有 axios,它是一个基于 Promise 的 HTTP 客户端,可用于浏览器和 Node.js。在开始实现全局加载动画与提示框之前,我们先来简单回顾一下如何使用 axios 进行网络请求。

首先,通过 npm 安装 axios:

npm install axios

然后在 Vue 项目中引入并使用,一般我们会在 main.js 中进行全局配置:

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

Vue.prototype.$http = axios

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

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

以上代码展示了一个简单的 GET 请求,通过 await 来处理 Promise,使得异步操作看起来更像同步操作,增强了代码的可读性。

二、实现全局加载动画的思路

  1. 拦截器的作用
    • 要实现全局加载动画,我们需要利用 axios 的拦截器。拦截器可以在请求或响应被 then 或 catch 处理前进行拦截。对于请求拦截器,我们可以在请求发出前显示加载动画;对于响应拦截器,我们可以在响应返回后隐藏加载动画。
    • 以请求拦截器为例,它的基本结构如下:
axios.interceptors.request.use(config => {
  // 在此处显示加载动画
  return config
}, error => {
  return Promise.reject(error)
})
  • 在上述代码中,config 是请求的配置对象,我们可以在返回 config 前进行一些操作,比如显示加载动画。同样,响应拦截器的基本结构如下:
axios.interceptors.response.use(response => {
  // 在此处隐藏加载动画
  return response
}, error => {
  // 在此处隐藏加载动画(如果请求失败)
  return Promise.reject(error)
})
  1. 加载动画的实现方式
    • 加载动画可以通过 CSS 动画或者使用一些 UI 库提供的加载组件来实现。例如,使用 CSS 动画实现一个简单的旋转加载图标:
<style>
  .loading {
      border: 4px solid rgba(0, 0, 0, 0.1);
      width: 30px;
      height: 30px;
      border-radius: 50%;
      border-top-color: #007BFF;
      animation: spin 1s ease-in-out infinite;
      -webkit-animation: spin 1s ease-in-out infinite;
    }

    @keyframes spin {
      to {
        -webkit-transform: rotate(360deg);
        transform: rotate(360deg);
      }
    }
</style>
<div class="loading" v-if="isLoading"></div>
  • 在 Vue 组件中,我们可以通过一个数据属性 isLoading 来控制加载动画的显示与隐藏。例如:
export default {
  data() {
    return {
      isLoading: false
    }
  }
}
  • 结合 axios 拦截器,我们可以在拦截器中控制 isLoading 的值,从而实现全局加载动画的显示与隐藏。

三、在 Vue 中实现全局加载动画

  1. 创建加载动画组件
    • 首先,我们创建一个 Loading.vue 组件,用于显示加载动画。
<template>
  <div class="loading-mask" v-if="isLoading">
    <div class="loading">
      <div class="loading-spinner"></div>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      isLoading: false
    }
  }
}
</script>

<style scoped>
 .loading-mask {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background-color: rgba(0, 0, 0, 0.5);
    display: flex;
    justify-content: center;
    align-items: center;
    z - index: 9999;
  }

 .loading {
    width: 60px;
    height: 60px;
    position: relative;
  }

 .loading - spinner {
    border: 4px solid rgba(0, 0, 0, 0.1);
    width: 100%;
    height: 100%;
    border - radius: 50%;
    border - top - color: #007BFF;
    animation: spin 1s ease - in - out infinite;
    -webkit - animation: spin 1s ease - in - out infinite;
  }

  @keyframes spin {
    to {
      -webkit - transform: rotate(360deg);
      transform: rotate(360deg);
    }
  }
</style>
  1. 在 main.js 中配置拦截器与加载动画
    • main.js 中,我们引入 Loading.vue 组件,并通过 Vue.mixin 来全局混入一个 isLoading 属性和相关方法,同时配置 axios 拦截器。
import Vue from 'vue'
import axios from 'axios'
import Loading from './components/Loading.vue'

// 创建一个 Loading 实例
const loading = new Vue(Loading).$mount()
document.body.appendChild(loading.$el)

Vue.mixin({
  data() {
    return {
      isLoading: false
    }
  },
  methods: {
    showLoading() {
      this.isLoading = true
      loading.isLoading = true
    },
    hideLoading() {
      this.isLoading = false
      loading.isLoading = false
    }
  }
})

axios.interceptors.request.use(config => {
  this.$options.methods.showLoading.call(this)
  return config
}, error => {
  this.$options.methods.hideLoading.call(this)
  return Promise.reject(error)
})

axios.interceptors.response.use(response => {
  this.$options.methods.hideLoading.call(this)
  return response
}, error => {
  this.$options.methods.hideLoading.call(this)
  return Promise.reject(error)
})

Vue.prototype.$http = axios
  • 在上述代码中,我们首先创建了 Loading.vue 的实例,并将其挂载到 document.body 上。然后通过 Vue.mixin 混入了 isLoading 属性以及 showLoadinghideLoading 方法。在 axios 的请求和响应拦截器中,分别调用 showLoadinghideLoading 方法来控制加载动画的显示与隐藏。

四、实现全局提示框的思路

  1. 提示框的类型与用途
    • 全局提示框通常用于向用户展示一些重要信息,比如请求成功或失败的提示。常见的提示框类型有成功提示、失败提示、警告提示等。
    • 对于网络请求相关的提示,成功提示可以告知用户数据已成功获取或操作已成功完成;失败提示则用于展示请求失败的原因,帮助用户了解问题所在。
  2. 提示框的实现方式
    • 与加载动画类似,我们可以通过创建一个提示框组件,并结合 Vue 的混入和事件总线来实现全局提示框。提示框组件可以通过接收不同的参数来显示不同类型的提示信息。
    • 例如,我们可以定义一个 Toast.vue 提示框组件,它接收 message(提示信息)和 type(提示类型,如 successerrorwarning)两个属性。
<template>
  <div class="toast" :class="`toast - ${type}`" v - if="isVisible">
    {{message}}
  </div>
</template>

<script>
export default {
  data() {
    return {
      isVisible: false,
      message: '',
      type:'success'
    }
  },
  methods: {
    show(message, type ='success') {
      this.message = message
      this.type = type
      this.isVisible = true
      setTimeout(() => {
        this.isVisible = false
      }, 3000)
    }
  }
}
</script>

<style scoped>
 .toast {
    position: fixed;
    bottom: 20px;
    left: 50%;
    transform: translateX(-50%);
    background - color: rgba(0, 0, 0, 0.8);
    color: white;
    padding: 10px 20px;
    border - radius: 5px;
    z - index: 9999;
    opacity: 0;
    transition: opacity 0.3s ease - in - out;
  }

 .toast - success {
    background - color: #28a745;
  }

 .toast - error {
    background - color: #dc3545;
  }

 .toast - warning {
    background - color: #ffc107;
  }

 .toast.show {
    opacity: 1;
  }
</style>
  • 在上述代码中,show 方法用于显示提示框,通过 setTimeout 在 3 秒后自动隐藏提示框。

五、在 Vue 中实现全局提示框

  1. 创建提示框组件
    • 我们已经创建了 Toast.vue 组件,接下来在 main.js 中配置它。
  2. 在 main.js 中配置提示框
    • main.js 中,我们引入 Toast.vue 组件,并通过 Vue.mixin 混入显示提示框的方法。
import Vue from 'vue'
import axios from 'axios'
import Loading from './components/Loading.vue'
import Toast from './components/Toast.vue'

// 创建 Loading 实例
const loading = new Vue(Loading).$mount()
document.body.appendChild(loading.$el)

// 创建 Toast 实例
const toast = new Vue(Toast).$mount()
document.body.appendChild(toast.$el)

Vue.mixin({
  data() {
    return {
      isLoading: false
    }
  },
  methods: {
    showLoading() {
      this.isLoading = true
      loading.isLoading = true
    },
    hideLoading() {
      this.isLoading = false
      loading.isLoading = false
    },
    showToast(message, type ='success') {
      toast.show(message, type)
    }
  }
})

axios.interceptors.request.use(config => {
  this.$options.methods.showLoading.call(this)
  return config
}, error => {
  this.$options.methods.hideLoading.call(this)
  this.$options.methods.showToast.call(this, '请求失败', 'error')
  return Promise.reject(error)
})

axios.interceptors.response.use(response => {
  this.$options.methods.hideLoading.call(this)
  this.$options.methods.showToast.call(this, '请求成功','success')
  return response
}, error => {
  this.$options.methods.hideLoading.call(this)
  this.$options.methods.showToast.call(this, '请求失败', 'error')
  return Promise.reject(error)
})

Vue.prototype.$http = axios
  • 在上述代码中,我们创建了 Toast.vue 的实例并挂载到 document.body 上。通过 Vue.mixin 混入了 showToast 方法,在 axios 的响应拦截器中,根据请求的结果调用 showToast 方法显示相应的提示信息。

六、优化与注意事项

  1. 多次请求处理
    • 当有多个网络请求同时进行时,加载动画的显示与隐藏需要进行合理处理。例如,我们可以通过一个计数器来记录当前正在进行的请求数量。在请求拦截器中计数器加 1,在响应拦截器中计数器减 1,只有当计数器为 0 时才隐藏加载动画。
    • 以下是修改后的代码示例:
import Vue from 'vue'
import axios from 'axios'
import Loading from './components/Loading.vue'
import Toast from './components/Toast.vue'

// 创建 Loading 实例
const loading = new Vue(Loading).$mount()
document.body.appendChild(loading.$el)

// 创建 Toast 实例
const toast = new Vue(Toast).$mount()
document.body.appendChild(toast.$el)

Vue.mixin({
  data() {
    return {
      requestCount: 0
    }
  },
  methods: {
    showLoading() {
      if (this.requestCount === 0) {
        loading.isLoading = true
      }
      this.requestCount++
    },
    hideLoading() {
      this.requestCount--
      if (this.requestCount === 0) {
        loading.isLoading = false
      }
    },
    showToast(message, type ='success') {
      toast.show(message, type)
    }
  }
})

axios.interceptors.request.use(config => {
  this.$options.methods.showLoading.call(this)
  return config
}, error => {
  this.$options.methods.hideLoading.call(this)
  this.$options.methods.showToast.call(this, '请求失败', 'error')
  return Promise.reject(error)
})

axios.interceptors.response.use(response => {
  this.$options.methods.hideLoading.call(this)
  this.$options.methods.showToast.call(this, '请求成功','success')
  return response
}, error => {
  this.$options.methods.hideLoading.call(this)
  this.$options.methods.showToast.call(this, '请求失败', 'error')
  return Promise.reject(error)
})

Vue.prototype.$http = axios
  1. 提示框文案优化
    • 对于提示框的文案,应该根据具体的业务场景进行优化。例如,在请求失败时,根据不同的错误状态码显示不同的提示信息。如果是 404 错误,可以提示“资源未找到”;如果是 500 错误,可以提示“服务器内部错误,请稍后重试”等。
    • 以下是修改后的响应拦截器代码示例:
axios.interceptors.response.use(response => {
  this.$options.methods.hideLoading.call(this)
  this.$options.methods.showToast.call(this, '请求成功','success')
  return response
}, error => {
  this.$options.methods.hideLoading.call(this)
  let errorMessage = '请求失败'
  if (error.response) {
    switch (error.response.status) {
      case 404:
        errorMessage = '资源未找到'
        break;
      case 500:
        errorMessage = '服务器内部错误,请稍后重试'
        break;
      default:
        errorMessage = `请求失败,状态码: ${error.response.status}`
    }
  }
  this.$options.methods.showToast.call(this, errorMessage, 'error')
  return Promise.reject(error)
})
  1. 样式与兼容性
    • 在实现加载动画和提示框时,要注意样式的兼容性。对于 CSS 动画,不同的浏览器可能有不同的表现,需要添加相应的浏览器前缀。例如,-webkit - transform 用于 WebKit 内核的浏览器(如 Safari)。
    • 同时,提示框和加载动画的样式应该与整个项目的风格保持一致,以提供良好的用户体验。

通过以上步骤,我们在 Vue 项目中实现了全局加载动画与提示框,使得网络请求过程更加友好和可视化,提高了用户体验。在实际项目中,可以根据具体需求对代码进行进一步的优化和扩展。