之前开发H5活动页时,产品同学往页面加入了十几种弹窗,而且弹窗还存在优先级关系,管理弹窗弹出的顺序是个大难题。为了方便管理弹窗,我通过弹窗队列管理弹窗,弹窗可以一个接一个地弹出,用户体验非常友好!
1.数据结构-队列
在介绍弹窗队列之前,我们必须了解数据结构-队列
。队列是一种特殊的线性表,特殊之处在于它只允许在表的前端进行删除操作,而在表的后端进行插入操作。所以队列遵循先进先出(FIFO)原则。
回到弹窗的需求,弹窗是逐个弹出的,先添加的弹出的弹出优先级更高,所以很适合使用队列管理。下面是一个弹窗队列的原理图。
2.基于vue3+vuex实现全局弹窗队列
2.1.使用vuex管理队列
因为弹窗顺序存在优先级,所以采用优先队列
管理弹窗。往队列插入弹窗时,会检查优先级,往合适的位置插入弹窗。
对外暴露两个方法:
addModal
:往队列添加弹窗组件实例nextModal
:弹出队列头部的弹窗
import { createStore } from 'vuex'
const store = createStore({
state: {
queue: [], // 弹窗队列
visible: false, // 是否已经有弹窗展示
order: ['modal2', 'modal3' ,'modal1'], // 权重
},
mutations: {
/**
* 新增要弹出的弹窗
* @param {Object} info 组件的实例、类型等信息
* @param {Object} info.instance 组件的实例
* @param {String} info.type 组件的权重名,对应order
* @param {Boolean} info.show 是否立即展示
*/
add(state, info) {
const { queue, order } = state;
const orderIndex = order.findIndex(ceil => ceil === info.type);
// 找到合适的位置插入
for (let i = 0; i < queue.length; i++) {
const curModal = queue[i];
const curOrderIndex = order.findIndex(ceil => ceil === curModal.type);
if (curOrderIndex > orderIndex) {
queue.splice(i - 1, 0, info);
return;
} else if (curModal.type === info.type) {
queue.splice(i, 0, info);
return;
}
}
queue.push(info);
},
// 弹出第一个弹窗
pop(state) {
const queue = state.queue;
if (queue.length > 0) {
const { instance } = queue.shift();
instance.show();
state.visible = true;
} else {
state.visible = false;
}
}
},
actions: {
/**
* 往队列添加弹窗,并弹出队列的第一个弹窗
* @param {Object} info 组件的实例、类型等信息
* @param {Object} info.instance 组件的实例
* @param {String} info.type 组件的权重名,对应order
* @param {Boolean} info.show 是否立即展示
*/
addModal(ctx, info = {}) {
ctx.commit('add', info);
if (!ctx.state.visible && info.show) {
ctx.commit('pop');
}
},
// 下一个弹窗
nextModal(ctx) {
ctx.commit('pop');
}
}
})
export default store
2.2.弹窗组件
2.2.1弹窗蒙层
不同的弹窗有公共的蒙层和关闭按钮,可以抽取为一个公共组件mask.vue
。点击关闭按钮时,弹出下一个弹窗。
<template>
<div class="mask" v-if="visible">
<div class="content animate__bounceIn">
<img src="../assets/关闭.png" class="close" alt="" @click="close">
<slot></slot>
</div>
</div>
</template>
<script setup>
import { useStore } from "vuex";
defineProps(['visible'])
const emit = defineEmits(['update:visible'])
const store = useStore();
// 关闭弹窗
function close() {
// 关闭当前弹窗
emit('update:visible', false)
// 弹出下一个弹窗
store.dispatch('nextModal')
}
</script>
<style lang="scss" scoped>
.mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba($color: #000000, $alpha: 0.7);
display: flex;
justify-content: center;
align-items: center;
.content {
padding: 20px;
background-color: #fff;
border-radius: 12px;
position: relative;
.close {
position: absolute;
right: 0;
top: -40px;
width: 30px;
height: 30px;
}
}
}
.animate__bounceIn {
animation-duration: calc(1s * 0.75);
animation-name: bounceIn;
}
@keyframes bounceIn {
from, 20%, 40%, 60%, 80%, to {
animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
}
0% {
opacity: 0;
transform: scale3d(0.3, 0.3, 0.3);
}
20% {
transform: scale3d(1.1, 1.1, 1.1);
}
40% {
transform: scale3d(0.9, 0.9, 0.9);
}
60% {
opacity: 1;
transform: scale3d(1.03, 1.03, 1.03);
}
80% {
transform: scale3d(0.97, 0.97, 0.97);
}
to {
opacity: 1;
transform: scale3d(1, 1, 1);
}
}
</style>
2.2.2公共的展示弹窗方法
所有弹窗通过show方法展示弹窗,因此抽取作为组合式函数modalvisible.js
import { ref } from 'vue'
export function useModalVisible() {
const visible = ref(false)
// 显示弹窗
function show() {
visible.value = true
}
return { visible, show }
}
2.2.3弹窗组件
<template>
<Mask v-model:visible="visible">
<div class="container">
我是弹窗1
</div>
</Mask>
</template>
<script setup>
import Mask from './mask.vue'
import { useModalVisible } from '../common/modalvisible'
const { visible, show } = useModalVisible()
defineExpose({
show
})
</script>
<style lang="scss" scoped>
.container {
width: 200px;
height: 200px;
display: flex;
justify-content: center;
align-items: center;
}
</style>
2.2.4弹出弹窗
使用addModal
方法往弹窗队列添加弹窗的实例,即可实现弹出弹窗
<template>
<button @click="testShow" style="margin-right: 10px;">展示弹窗</button>
<button @click="testOrder">测试排序</button>
<Modal1 ref="modal1"></Modal1>
<Modal2 ref="modal2"></Modal2>
<Modal3 ref="modal3"></Modal3>
</template>
<script setup>
import { useStore } from "vuex";
import { ref } from 'vue'
import Modal1 from './components/modal1.vue';
import Modal2 from './components/modal2.vue';
import Modal3 from './components/modal3.vue';
const store = useStore();
const modal1 = ref(null)
const modal2 = ref(null)
const modal3 = ref(null)
function testShow() {
store.dispatch('addModal', {
instance: modal1,
type: 'modal1',
show: true
})
setTimeout(() => {
store.dispatch('addModal', {
instance: modal2,
type: 'modal2',
show: true
})
}, 500);
setTimeout(() => {
store.dispatch('addModal', {
instance: modal3,
type: 'modal3',
show: true
})
}, 1000);
}
function testOrder() {
store.dispatch('addModal', {
instance: modal3,
type: 'modal3',
show: false
})
store.dispatch('addModal', {
instance: modal2,
type: 'modal2',
show: false
})
setTimeout(() => {
store.dispatch('addModal', {
instance: modal1,
type: 'modal1',
show: true
})
}, 300);
}
</script>
<style scoped>
</style>