# 实现请求可配置是否展示全局加载动画

​ 很多接口请求都需要展示全局加载动画,每次请求就写一行代码开启动画,请求结束和响应结束就关闭动画,这显得很繁琐,且存在一个问题:那就是当多个请求时,可能会导致其余接口还没请求结束,但是加载动画被前面某个请求结束的接口给提前关闭了

解决方法: 每当请求接口时,累加请求接口数量,响应时累减请求接口数量,当请求数量为0 时 才关闭全局请求动画。

上代码

# 第一步:新增全局加载动画组件

<template>
  <div :class="['xiaobu-loading-mask', show ? 'show' : 'hide']" v-show="show">
    <div class="loading-wrapper">
      <span class="loading-dot loading-dot-spin">
        <i></i>
        <i></i>
        <i></i>
        <i></i>
      </span>
    </div>
  </div>
</template>

<script>
export default {
  name: "Loading",
  unAutoRegister: true,
  computed: {
    show() {
      return this.$store.state.app.showLoading
    }
  }
}
</script>

<style lang="scss" scoped>
  .xiaobu-loading-mask {
    position: fixed;
    left: 0;
    top: 0;
    height: 100%;
    width: 100%;
    background: rgba(255, 255, 255, 0.3);
    user-select: none;
    z-index: 9999;
    overflow: hidden
  }

  .loading-wrapper {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -100%)
  }

  .loading-dot {
    animation: antRotate 1.2s infinite linear;
    transform: rotate(45deg);
    position: relative;
    display: inline-block;
    font-size: 64px;
    width: 37px;
    height: 37px;
    box-sizing: border-box
  }

  .loading-dot i {
    width: 15px;
    height: 15px;
    position: absolute;
    display: block;
    @include background_color("main_color");
    border-radius: 100%;
    transform: scale(.75);
    transform-origin: 50% 50%;
    opacity: .3;
    animation: antSpinMove 1s infinite linear alternate
  }

  .loading-dot i:nth-child(1) {
    top: 0;
    left: 0
  }

  .loading-dot i:nth-child(2) {
    top: 0;
    right: 0;
    -webkit-animation-delay: .4s;
    animation-delay: .4s
  }

  .loading-dot i:nth-child(3) {
    right: 0;
    bottom: 0;
    -webkit-animation-delay: .8s;
    animation-delay: .8s
  }

  .loading-dot i:nth-child(4) {
    bottom: 0;
    left: 0;
    -webkit-animation-delay: 1.2s;
    animation-delay: 1.2s
  }

  @keyframes antRotate {
    to {
      -webkit-transform: rotate(405deg);
      transform: rotate(405deg)
    }
  }

  @-webkit-keyframes antRotate {
    to {
      -webkit-transform: rotate(405deg);
      transform: rotate(405deg)
    }
  }

  @keyframes antSpinMove {
    to {
      opacity: 1
    }
  }

  @-webkit-keyframes antSpinMove {
    to {
      opacity: 1
    }
  }
</style>

# 第二步:在app.vue文件上引入动画组件

<template>
  <div id="app">
    <!-- 加载动画,要在router-view上面,解决双加载动画问题 -->
    <loading></loading>
    <!-- 页面切换容器 -->
    <router-view class="xiaobu-app-container"/>
  </div>
</template>

<script>
import Loading from "@/components/loading"
export default {
  name: "App",
  components: {
    Loading
  }
}
</script>

<style>
#app {
}
</style>

# 第三步:vuex新增showLoading变量,以及设置showLoading值的方法

const state = {
  showLoading: false //展示全局加载动画
}

const mutations = {
  //设置请求动画状态
  SET_LOADING_STATUS: (state, showLoading) => {
    state.showLoading = showLoading
  },
}

const actions = {
}

export default {
  namespaced: true,
  state,
  mutations,
  actions
}

# 第四步:实现累加,以及累减方法

// req-loading.js 文件

import store from "@/store"

let count = 0 // 监听计数中间变量

/**
 * 
 * @param {Number} count http请求列队个数
 */
function showLoading(count) {
  // 当http请求列队个数为0时关闭loading
  if (count === 0) {
    store.commit("app/SET_LOADING_STATUS", false)
  } else {
    store.commit("app/SET_LOADING_STATUS", true)
  }
}

export function addHttpLoading() {
  count++
  showLoading(count)
}

// http请求列队计数减一
export function removeHttpLoading() {
  count--
  showLoading(count)
}


# 第五步:在axios请求文件上使用

import axios from "axios"
import Message from "@/plugins/reset-message.js"
import {addHttpLoading, removeHttpLoading} from "./req-loading"
import {getToken} from "@/utils/cookie"
import store from "@/store"

// 创建axios实例
const service = axios.create({
  baseURL: process.env.VUE_APP_BASE_URL,
  timeout: process.env.VUE_APP_TIME_OUT // 请求超时时间
})

// request拦截器
service.interceptors.request.use(
  config => {
    addHttpLoading()
    const token = getToken()
    if (token) {
      config.headers.Authorization = "Bearer " + token
    }
    return config
  },
  error => {
    Promise.reject(error)
  }
)

// respone拦截器
service.interceptors.response.use(
  async response => {
    removeHttpLoading()
    const res = response.data
    // 如果自定义代码不是200,则判断为错误。
    if (response.status !== 200) {
      Message({
        message: res.message || "Error",
        type: "error",
        duration: 5 * 1000
      })
      return Promise.reject(new Error(res.message || "Error"))
    } else {
      // code为200 或 返回的是文件流 直接返回
      if (res.code === 200 || response.config.responseType === "blob") {
        return res
      }
      Message({
        message: res.message || "Error",
        type: "error",
        duration: 5 * 1000
      })
      if (res.code === 401) {
        await store.dispatch("user/logout")
        window.location.reload()
      }
      return Promise.reject(new Error(res.message || "Error"))
    }
  },
  error => {
    removeHttpLoading()
    if (error.name === "AxiosError") {
      Message.error(error.message)
    }
    return Promise.reject(new Error(error))
  }
)

export default service
  

这样即可实现有请求就会带上动画,所有请求结束才关闭动画

# 第六步:实现可配置是否要展示加载动画

在axios请求中的config添加变量

/**
 * @description: 登录
 * @param {*}
 * @return {*}
 * @author: syx
 */
export function login(data = {}, config = {
	showLoading: true
}) {
  return axiosRequest.post("/xiaobu-admin/login", data, config)
}

在第五步的文件中,新增判断

// 请求拦截中判断 
service.interceptors.request.use(
  config => {
    if (config.showLoading) {
      addHttpLoading()
    }
    return config
  },
  error => {
    Promise.reject(error)
  }
)
// 响应拦截中判断
service.interceptors.response.use(
  async response => {
    if (response.config.showLoading) {
      removeHttpLoading()
    }
    const res = response.data
    return res
  },
  error => {
    // 有配置showLoading为true的接口,请求异常才会减少请求接口
	if (error.config&&error.config.showLoading) {
    	removeHttpLoading()
    }
    if (error.name === "AxiosError") {
      Message.error(error.message)
    }
    return Promise.reject(new Error(error))
  }
)

# 第七步:以上6步即可实现可配置的展示加载动画,但是存在一个问题,就是可能会出现双加载动画的问题。

image-20230114213153806

解决方法: 采用css解决此问题,思路就是 出现全局加载动画的时候 里面的动画添加display:none样式。

以element的loading为例。其加载动画类名为 el-loading-mask。

全局添加CSS代码

.xiaobu-loading-mask.show  + .xiaobu-app-container .el-loading-mask{
  display: none;
}

这就是为什么 loading组件要放在router-view前面的原因

WARNING

element的loading动画不能放在body元素下。放在body的话只能通过js判断了,能用css解决的问题就不要用js,所以为了避免出现问题,请不要将loading的body设置为true。

# 总结

​ 此篇文章为axios请求优化其一,关注微信公众号【爆米花小布】不迷路。如有问题,可私信。

史上最牛逼的axios请求优化 (opens new window)