使用elementUI的Loading.service实现弹框全覆盖效果的loading遮罩,并把它写成vue插件,在页面直接使用v-dialogLoading指令去显示与隐藏遮罩。
需求背景:需要实现项目中所有弹框的loading遮罩只显示弹框上,在其他地方不加遮罩。
小声逼逼!!:客户觉得原本的loading遮罩是全页面的会让业务人员觉得弹框底下的页面也在刷新(他们并不想原页面刷新)(我也反复强调过,下面的页面是不刷新的。跟他们说过后给我的答复是有人觉得全屏遮罩太丑了,他们就想要在弹框上loading…),无奈…没办法,硬着头皮干吧…谁让他们是金主爸爸呢
实现方案
最开始想的方案是,改弹窗的css代码,让它达到弹框的大小,欸,这不是轻松愉快的解决了吗?
当我打开它的样式代码后才发现,(傻眼),怎么是个绝对定位,这要设置位置大小,朕应该怎么拿得到呢?每个弹窗的大小位置也有可能不一样啊(汗流浃背)发现问题并不那么简单,快快打开朕的最强Ai,问了下,也是css?还得用上原生事件?这么麻烦?果断pass,不是我不会噢,我真是觉得它给的不好(确信)。罢了,罢了,朕还是自己动手搜搜看吧。
一搜 欸 看到了这个vue使用elementPlus ui框架,如何给Dialog 对话框添加Loading 自定义类名显示
不错不错,但是他是用的vue3+elementPlus Ui,我这个项目是vue2+element,看了下官方文档,欸,这一块好像老的也支持(喜滋滋)这不是轻松愉快就搞定了吗(嘻嘻)
那就赶紧撸起袖子加油干!!
具体实现
看文档,首先得给弹框加一个类名
然后我又是要全局生效,我肯定得自己自定义一个指令来控制它是否生效,所以模板代码就应该是这样:
自定义指定代码如下:
1.新增一个dialog-loading.js文件
// 导入 Element UI 的 Loading 组件,用于创建加载指示器
import { Loading } from 'element-ui';
// 定义一个 Vue 插件对象
export default {
// install 方法是 Vue 插件的入口点,Vue 会在使用 Vue.use() 注册插件时调用它
install(Vue) {
// 在 Vue 的原型上定义一个全局数组,用来存储所有的活动加载实例
Vue.prototype.$activeLoadings = [];
// 向 Vue 添加一个全局混入(mixin),这会影响每一个 Vue 实例
Vue.mixin({
// 混入的数据部分,定义了一个 loadingClassName 变量,用于存储当前加载指示器的类名
data() {
return {
loadingClassName: null,
};
},
// 混入的方法部分
methods: {
// 显示加载指示器的方法
async showLoading() {
// 生成一个基于时间和随机数的唯一类名,确保每次加载指示器的类名都不相同
const uniqueClassName = `loading-${Date.now()}-${Math.floor(Math.random() * 10000)}`;
// 将新生成的类名赋值给实例的 loadingClassName 属性
this.loadingClassName = uniqueClassName;
// 等待 Vue 的 DOM 更新完成
await this.$nextTick();
// 在当前组件的根元素下查找第一个拥有 .loading-class 类名的元素
console.log(this.$el.querySelector('.loading-class'))
// 为找到的元素添加新生成的类名
this.$el.querySelector('.loading-class').classList.add(uniqueClassName);
// 使用 Element UI 的 Loading.service 创建一个新的加载实例,目标为新添加的类名元素,并设置背景色
const loadingInstance = Loading.service({
target: '.' + uniqueClassName,
background: 'rgba(255, 255, 255, 0.9)',
});
// 将新的加载实例信息(类名和实例自身)存入全局数组
this.$activeLoadings.push({ className: uniqueClassName, instance: loadingInstance });
},
// 隐藏加载指示器的方法
async hideLoading() {
// 如果有当前正在使用的 loadingClassName
if (this.loadingClassName) {
// 等待 Vue 的 DOM 更新完成
await this.$nextTick();
// 从全局数组中找到与当前 loadingClassName 匹配的加载实例
const loadingInstance = this.$activeLoadings.find(
(item) => item.className === this.loadingClassName
);
// 如果找到了匹配的实例
if (loadingInstance) {
// 关闭该加载实例
loadingInstance.instance.close();
// 从全局数组中移除已关闭的实例
this.$activeLoadings = this.$activeLoadings.filter(
(item) => item.className !== this.loadingClassName
);
// 从当前组件的根元素中移除 loadingClassName 类名
this.$el.classList.remove(this.loadingClassName);
// 清空 loadingClassName,表示当前没有加载指示器在显示
this.loadingClassName = null;
}
}
},
},
});
// 定义一个名为 dialogLoading 的自定义指令
Vue.directive('dialogLoading', {
// 当指令绑定到元素上时触发
bind(el, binding, vnode) {
// 如果指令的值为真,则显示加载指示器
if (binding.value) {
vnode.context.showLoading();
}
},
// 当指令绑定的值发生变化时触发
update(el, binding, vnode) {
// 如果新旧值不等,则根据新值决定显示或隐藏加载指示器
if (binding.value !== binding.oldValue) {
if (binding.value) {
vnode.context.$nextTick(() => {
vnode.context.showLoading();
});
} else {
vnode.context.$nextTick(() => {
vnode.context.hideLoading();
});
}
}
},
// 当指令与元素解除绑定时触发
unbind(el, binding, vnode) {
// 隐藏加载指示器
vnode.context.hideLoading();
},
});
},
};
2.在main.js里引入
// 自定义弹框loading
import dialogLoading from './common/dialog-loading.js';
Vue.use(dialogLoading);
效果实现
注意
1.this.$el通常指的是Vue组件的根元素,而不是所有子元素。
2.loading的关闭时间应当大于接口的setTimeout时间,不然下一次调用会出现loading的没生效,那是因为上一次的loading还未关闭。
3.target的类名必须是唯一的。