【Vue】NavBar 顶部弹窗点击弹窗外部区域关闭弹窗实现
需求场景
很常见的功能,NavBar 顶部菜单按钮点击显示自定义的弹窗,【点击页面空白区域关闭弹窗】,类似 el-Popover 弹出框的效果。点击区域外自动关闭并且联动其他弹框,并且同时只能存在一个。
代码实现
template 部分
scrtpt 部分
完整代码
2022-7-11 更新完整代码
点击查看代码
<template>
<div :class="classObj" class="app-wrapper">
<div
v-if="device === 'mobile' && sidebar.opened"
class="drawer-bg"
@click="handleClickOutside"
/>
<div class="main-container">
<navbar
@handleAvatarClick="handleAvatarClick"
@handleCompanySeclect="handleCompanySeclect"
@handleSearchInputChange="handleSearchInputChange"
@handleMessageClick="handleMessageClick"
@handleShareClick="handleShareClick"
/>
<app-main />
</div>
<!--弹窗部分 -->
<AvatarModal
class="avtar-modal modal-zIndex"
ref="Avatar"
v-if="showModalType === 'Avatar'"
@closeModal="handleCloseModal"
@removeEventPageClick="removeEventPageClick"
@addEventPageClick="addEventPageClick"
/>
<ShareModal
class="share-modal modal-zIndex"
ref="Share"
v-if="showModalType === 'Share'"
@closeModal="handleCloseModal"
@removeEventPageClick="removeEventPageClick"
@addEventPageClick="addEventPageClick"
/>
<CompanySelectModal
class="company-select-modal"
ref="Company"
v-if="showModalType === 'Company'"
/>
<SearchModal
class="search-modal modal-zIndex"
ref="Search"
v-if="showModalType === 'Search'"
/>
<MessageModal
class="message-modal modal-zIndex"
ref="Message"
v-if="showModalType === 'Message'"
@closeModal="handleCloseModal"
/>
</div>
</template>
<script>
import { Navbar, AppMain, TagsView, HeadNavbar } from './components'
import ResizeMixin from './mixin/ResizeHandler'
import AvatarModal from './components/ModalComponents/AvatarModal.vue'
import CompanySelectModal from './components/ModalComponents/CompanySelectModal.vue'
import SearchModal from './components/ModalComponents/SearchModal.vue'
import MessageModal from './components/ModalComponents/MessageModal.vue'
import ShareModal from './components/ModalComponents/ShareModal.vue'
export default {
name: 'Layout',
components: {
Navbar,
AppMain,
TagsView,
HeadNavbar,
AvatarModal,
CompanySelectModal,
SearchModal,
MessageModal,
ShareModal,
},
mixins: [ResizeMixin],
data() {
return {
showModalType: '', // 控制弹框的显示类型
}
},
computed: {
sidebar() {
return this.$store.state.app.sidebar
},
device() {
return this.$store.state.app.device
},
classObj() {
return {
hideSidebar: !this.sidebar.opened,
openSidebar: this.sidebar.opened,
withoutAnimation: this.sidebar.withoutAnimation,
mobile: this.device === 'mobile',
}
},
},
beforeDestroy() {
document.removeEventListener('click', this.bodyCloseModal)
},
methods: {
handleClickOutside() {
this.$store.dispatch('closeSideBar', {
withoutAnimation: false,
})
},
handleAvatarClick() {
this.isShowModal('Avatar')
},
handleCompanySeclect() {
this.isShowModal('Company')
},
handleSearchInputChange() {
this.isShowModal('Search')
},
handleMessageClick() {
this.isShowModal('Message')
},
handleShareClick() {
this.isShowModal('Share')
},
// 控制弹窗显隐
isShowModal(type) {
// 移除页面监听事件(防止用户通过点击 btn 产生 bug)
this.removeEventPageClick()
if (this.showModalType === type) {
this.showModalType = ''
} else {
this.showModalType = type
}
// 触发监听
this.addEventPageClick()
},
// 触发页面监听
addEventPageClick() {
setTimeout(() => {
document.addEventListener('click', this.bodyCloseModal)
}, 100)
},
// 移除页面监听
removeEventPageClick() {
document.removeEventListener('click', this.bodyCloseModal)
},
// 点击外部区域关闭
bodyCloseModal(e) {
let self = this
if (
this.showModalType &&
this.$refs[this.showModalType] &&
!this.$refs[this.showModalType].$el.contains(e.target)
) {
if (self.showModalType) {
self.showModalType = ''
self.removeEventPageClick()
}
}
},
// 主动关闭
handleCloseModal() {
this.showModalType = ''
},
},
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
@import '~@/styles/mixin.scss';
.app-wrapper {
@include clearfix;
position: relative;
height: 100%;
width: 100%;
&.mobile.openSidebar {
position: fixed;
top: 0;
}
}
.drawer-bg {
background: #000;
opacity: 0.3;
width: 100%;
top: 0;
height: 100%;
position: absolute;
z-index: 999;
}
.modal-zIndex {
z-index: 2000;
}
.avtar-modal {
position: fixed;
right: 5PX;
top: 60PX;
}
.company-select-modal {
position: fixed;
right: 196PX;
top: 60PX;
}
.search-modal {
position: fixed;
right: 406PX;
top: 60PX;
}
.message-modal {
position: fixed;
right: 75PX;
top: 60PX;
}
.share-modal {
position: fixed;
right: 126PX;
top: 60PX;
}
</style>