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