全局loading 当发起 ajax 请求时,在页面上显示一个加载框(Loading 效果),然后等数据返回后自动将其隐藏。

(1)在项目中创建一个 http.js,里面内容是对 Axios 进行二次封装,代码如下:

  • 基本原理是通过 axios 提供的请求拦截和响应拦截的接口,控制 loading 的显示或者隐藏。同时在请求失败时还会自动弹出消息提示框显示错误信息。
  • loding 效果这里采用的是 Element UI 中提供的 Loading 组件来实现。而错误消息提示框则用的是 Element UI 中的 Message 组件来实现。
  • 内部有个计数器,确保同一时刻如果有多个请求的话,不会同时出现多个 loading,而是只有 1 个。并且在所有请求结束后才会隐藏 loading。
  • 使用了 lodash 的 debounce 防抖。因为有时会碰到在一次请求完毕后又立刻又发起一个新的请求的情况(比如删除一个表格条目后立刻进行刷新)。这种情况会造成连续 loading 两次,并且中间有一次极短的闪烁。通过防抖可以让 300ms 间隔内的 loading 便合并为一次,避免闪烁的情况。
  • 默认所有请求都会自动有 loading 效果。如果某个请求不需要 loading 效果,可以在请求 header 中 showLoading 设置为 false。
  • 默认的 loading 效果时全屏的(覆盖在 body 上)。如果某个请求是需要在某个指定元素上显示 loading 效果,可以将请求 header 中 loadingTarget 设置为该元素的选择符。
import axios from 'axios';
import { Message,Loading } from 'element-ui';
import _ from 'lodash';
  
const http = axios.create({
    baseURL:process.env.BASE_URL, //设置请求的base url
    timeout:40000 //超时时长
});
  
//loading对象
let loading;
  
//当前正在请求的数量
let needLoadingRequestCount = 0;
  
//显示loading
function showLoading(target) {
  // 后面这个判断很重要,因为关闭时加了抖动,此时loading对象可能还存在,
  // 但needLoadingRequestCount已经变成0.避免这种情况下会重新创建个loading
  if (needLoadingRequestCount === 0 && !loading) {
    loading = Loading.service({
      lock: true,
      text: "Loading...",
      background: 'rgba(255, 255, 255, 0.5)',
      target: target || "body"
    });
  }
  needLoadingRequestCount++;
}
  
//隐藏loading
function hideLoading() {
  needLoadingRequestCount--;
  needLoadingRequestCount = Math.max(needLoadingRequestCount, 0); //做个保护
  if (needLoadingRequestCount === 0) {
    //关闭loading
    toHideLoading();
  }
}
  
//防抖:将 300ms 间隔内的关闭 loading 便合并为一次。防止连续请求时, loading闪烁的问题。
var toHideLoading = _.debounce(()=>{
      loading.close();
      loading = null;
    }, 300);
  
//添加请求拦截器
http.interceptors.request.use(config => {
  //判断当前请求是否设置了不显示Loading
  if(config.headers.showLoading !== false){
    showLoading(config.headers.loadingTarget);
  }
  return config;
}, err => {
  //判断当前请求是否设置了不显示Loading
  if(config.headers.showLoading !== false){
    hideLoading();
  }
  Message.error('请求超时!');
  return Promise.resolve(err);
});
  
//响应拦截器
http.interceptors.response.use(
    response => {
      //判断当前请求是否设置了不显示Loading(不显示自然无需隐藏)
      if(response.config.headers.showLoading !== false){
        hideLoading();
      }
      return response;
    },
    error => {
      //判断当前请求是否设置了不显示Loading(不显示自然无需隐藏)
      if(error.config.headers.showLoading !== false){
        hideLoading();
      }
      if(error.response && error.response.data && error.response.data.message) {
        var jsonObj = JSON.parse(error.response.data.message);
        Message.error(jsonObj.message);
      }else{
        Message.error(error.message);
      }
      return Promise.reject(error);
    }
);
  
export default http;

(2)接着在 main.js 中将该组件引入,并定义成原型属性方便使用。

import http from './utils/http.js';
Vue.prototype.axios = http;

(3)如果在请求 header 中传递个 showLoading:false 参数,则该请求不会有 loading 效果。

this.axios.get("/api/getDeviceDatas",{headers: {'showLoading': false}})
 .then(response => {
     this.tableData = response.data;
 })

(4)如果将请求 header 中 loadingTarget 设置为页面上某个元素的选择符,则 loading 效果会只显示在个元素区域上。比如我们下面代码我们只让 loading 遮罩显示在弹出框的内容区域上。

this.axios.get("/api/controlApp?method=" + method, {headers: {'loadingTarget': '#dialogContent'}})
 .then(response => {
     // 重新加载数据
     this.loadAppData();
     this.$message.success('执行成功!');
 })

首屏loading

1.创建一个Loading.vue文件

<template>
    <div id="vue-loading">
        <div class="loader"></div>
    </div>
</template>

<script>
export default {
    name: 'loading',
    data () {
        return {
            show: false,
        }
    },

}
</script>

2:创建一个loading.js文件

import LoadingComponent from '@/components/Loading.vue'

let Loading = {}

// 避免重复install,设立flag

Loading.installed = false

Loading.install = function (Vue) {

    if (Loading.installed) return

    Vue.prototype.$loading = {}

    Vue.prototype.$loading.show = () => {

        // 如果页面有loading则不继续执行

        if (document.querySelector('#vue-loading')) return

        // 1、创建构造器,定义loading模板

        let LoadingTip = Vue.extend(LoadingComponent)

        // 2、创建实例,挂载到文档以后的地方

        let tpl = new LoadingTip().$mount().$el

        // 3、把创建的实例添加到body中

        document.body.appendChild(tpl)

        // 阻止遮罩滑动

        document.querySelector('#vue-loading').addEventListener('touchmove', function (e) {
            e.stopPropagation()
            e.preventDefault()
        })
        Loading.installed = true

    }
    Vue.prototype.$loading.hide = () => {
        let tpl = document.querySelector('#vue-loading')
        document.body.removeChild(tpl)
    }

}
export default Loading

3.在main.js文件里边引入文件

import Loading from './components/Loading.js'
Vue.use(Loading); // 注册在Vue实例上

4.显示,隐藏loading

this.$loading.show()  //显示loading
this.$loading.hide()  //关闭loading

5.当然可以写在请求前拦截和返回结果后

//添加请求拦截器
service.interceptors.request.use(function (config) {
	this.$loading.show()  // 请求前显示loading
    // 在发送请求之前做些什么
    if (getToken()) {
        config.headers.token = getToken()
        config.headers['client-language'] = window.sessionStorage.getItem("language")
    }
    return config;
}, function (error) {
    // 对请求错误做些什么
    this.$loading.hide()  // 请求报错也需要关闭loading
    return Promise.reject(error);
});
//添加响应拦截器
service.interceptors.response.use(function (response) {
    this.$loading.hide()  // 数据回来之后关闭loading
    // 对响应数据做点什么
    return response;
}, function (error) {
    // 对响应错误做点什么
    this.$loading.hide()  // 请求报错也需要关闭loading
    return Promise.reject(error);
});