基于Vue+ts的前端单例模式的全局遮罩层代码示例

一、背景

前端在与后端接口交互时,像保存、修改以及发布及批量操作时,尤其是响应时间较的操作时,要把中间等待过程反馈给用户。否则,在体验上容易给用户造成一定的困惑,甚至造成数据异常。

1、每个功能单独处理:

1、请求接口前,开启loading
2、接口响应完成(成功或失败)后,关闭loading

<a-button :loading="isLoading" type="primary">保存</a-button>
<script lang="ts" setup>
const isLoading = ref(false)
const handleClick = async () => {
    
    
    isLoading.value = true;
    const res = await getData();
    isLoading.value = false;
};
</script>

但是这样,需要在每个操作中都进行,loading的开启与关闭,整个操作有大量的重复代码。

2、在request和response统一拦截

有的人可能想到了,能不能在axios中统一处理,在请求和响应(request和response)作统一拦截,这不实为一个好的方案。
但是也会有一个问题,这样拦截后,所有查询接口也会触发loading的开启,像分页查询还可以理解。
像前端详情类或复杂点的页面,其实是局部加载的,一进页面第一屏正常显示,其他信息分步进行加载,用户在滚动页面时,不需要感知到这个过程,既满足了页面的响应,又不影响用户体验。
如果这时候,查询也触发loading,反而让用户感觉这页面怎么回事,打开后还在转圈,性能不行啊。

二、新的尝试

1、设计思路

  • (1)创建一个全局loading
  • (2)在api层,定义后台接口时,需要触发loading的操作中,增加标识
  • (3)在axios 中,request拦截标识开启loading, response中统一关闭
    这样做的话,对于使用层来说,只需要处理一个地方,就是在定义接口时,多了一行代码
headers:{
    
    loading:true}

这样既得到了预期效果,也减少了代码的处理

2、效果示例

在这里插入图片描述

3、代码示例

(1)创建全局loading

可以是自定义的一个div遮罩层,也可以是 element的 ElLoading,这里我拿自定义的div层来做示例

<template>  
  <a-button @click="handleLoad">signLoad</a-button>
  <a-button @click="handleClose">close</a-button>
  <a-button @click="handleMatch">handleMatch</a-button>
  <div id="loading-mask">
    <div class="loading-wrapper">
      <span class="loading-dot loading-dot-spin">
        <i></i>
        <i></i>
        <i></i>
        <i></i>
      </span>
      <span id="loadText">数据加载中,请稍候!</span>
    </div>
  </div>
</template>
<script lang="ts" setup>
//引用全局遮罩控制类
import Singleton from './singleLoading' 
//模拟开启loading
function handleLoad() {
    
    
  Singleton.getInstance().show()
  setTimeout(() => {
    
    
    handleClose()
  }, 1500)
}
function handleClose() {
    
    
  Singleton.getInstance().close()
}
//验证对象实例
function handleMatch() {
    
    
  let a = Singleton.getInstance()
  let b = Singleton.getInstance()
  console.log('log内容match', a, b, a === b)
}
<style lang="less" scoped>
#loading-mask {
    
    
  position: fixed;
  top: 0;
  left: 0;
  z-index: 9999;
  display: none;
  width: 100%;
  height: 100%;
  overflow: hidden;
  background: rgba(0, 0, 0, 0.5);
  user-select: none;
}

.loading-wrapper {
    
    
  position: absolute;
  top: 60%;
  left: 50%;
  width: 130px;
  text-align: center;
  transform: translate(-50%, -100%);
}

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

.loading-dot i {
    
    
  position: absolute;
  display: block;
  width: 22px;
  height: 22px;
  background-color: #1890ff;
  border-radius: 100%;
  transform: scale(0.75);
  transform-origin: 50% 50%;
  opacity: 0.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: 0.4s;
  animation-delay: 0.4s;
}

.loading-dot i:nth-child(3) {
    
    
  right: 0;
  bottom: 0;
  -webkit-animation-delay: 0.8s;
  animation-delay: 0.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;
  }
}
#loadText {
    
    
  position: relative;
  top: 20px;
  display: inline-block;
  width: 120px;
  width: 130px;
  color: #fff;
  text-align: center;
}
</style>

(2)单例模式的全局遮罩类

/**
 * 全局loading遮罩控制类
 */
class SingletonLoading {
    
    
  // 静态实例变量,用于存储唯一的实例
  private static instance: SingletonLoading | null = null
  // 全局遮罩dom对象loading-mask
  private dom: any 

  // 私有构造函数,防止外部通过 new SingletonLoading() 创建实例
  private constructor() {
    
    
    // 可以在这里添加初始化代码
    this.dom = document.getElementById('loading-mask')    
  }

  // 静态方法,用于获取实例
  public static getInstance(): SingletonLoading {
    
    
    if (!SingletonLoading.instance) {
    
    
      SingletonLoading.instance = new SingletonLoading()
    }
    return SingletonLoading.instance
  }

  /**
   * 显示全局loading
   * @param text 提示文本,可为空
   */
  show(): void {
    
    
    this.dom ? (this.dom.style.display = 'block') : ''   
  }
  /** 关闭全局loading */
  close(): void {
    
    
    this.dom ? (this.dom.style.display = 'none') : ''
  }
}

export default SingletonLoading

3、loading的开启:api接口层处理,只需要加一行代码

模拟代码示例:

export async function SaveForm(data = {
     
     }) {
    
    
  loading.show();
  return request({
    
    
    url: `/SaveFormApi`,
    method: 'post',
    data,
    headers:{
    
    loading:true}
  });
}

4、loading的关闭,在response响应统一关闭即可

代码示例:

import Singleton from './singleLoading' 
const loading=Singletion.getInstance();
在request中开启:if(headers?.loading){
    
    loading.show()}
在response中统一处理:loading.close();