今天在开发TinyEngine华为低代码引擎开源项目时,我遇到了一个细节的问题:原本是点击标题显示菜单,再次点击标题或点击空白区域关闭菜单。中国没错的,然后呢,这里我手动实现了一个二级菜单,二级菜单内部点击是会切换该选项后面跟着的图标的,我希望点击完之后只是图标变化,但点击菜单内部时菜单也会关闭,虽然也能用,但很影响用户体验,这不是我期望的行为。
简单的描述一下问题就是:
在项目中,我实现了一个点击标题显示菜单的功能,并在点击空白区域时关闭菜单。然而,点击菜单内部时菜单也会关闭,这导致用户体验不佳。
这里先贴一下问题代码段
模板部分
<template>
<div class="top-panel-logo">
<h1 class="logo-wrap" @click.stop="handleTitleClick">
<div class="menu-icon-wrapper">
<svg-icon name="menu"></svg-icon>
<span class="menu-title">{{ state.appName }}</span>
</div>
</h1>
<div v-if="state.showMenu" class="main-menu">
<ul>
<li @click="handleClick({ code: 'publishApp' })">
<span class="menu-item">发布应用</span>
</li>
<li @mouseenter="state.showSubMenu = true" @mouseleave="state.showSubMenu = false" >
<span class="menu-item">显示设置</span>
<icon-right></icon-right>
</li>
</ul>
</div>
</div>
<div
v-if="state.showMenu && state.showSubMenu"
class="sub-menu"
@mouseenter="state.showSubMenu = true"
@mouseleave="state.showSubMenu = false"
>
<ul>
<li v-for="(item, index) in subMenus" :key="index" @click="changeShowState(item)">
<span class="menu-item">{{ item.name }}</span>
<span v-show="item.isShow">√</span><!--二级选项状态标识-->
</li>
</ul>
</div>
<!--省略其他内容-->
</div>
</template>
脚本部分
<script setup>
import ...省略
const state = reactive({
showMenu: false,
showSubMenu: false,
});
const subMenus = ref([
{ name: '左侧活动栏', code: 'changeLeft', isShow: true },
{ name: '右侧活动栏', code: 'changeRight', isShow: true },
{ name: '底部活动栏', code: 'changeBottom', isShow: true }
])
const handleCloseMenu = () => {
state.showMenu = false
state.showSubMenu = false
window.removeEventListener('click', handleCloseMenu)
}
const handleTitleClick = () => {
state.showMenu = !state.showMenu
if (state.showMenu) {
window.addEventListener('click', handleCloseMenu)
} else {
window.removeEventListener('click', handleCloseMenu)
}
}
onUnmounted(() => {
window.removeEventListener('click', handleCloseMenu)
})
</script>
在点击标题时会切换 state.showMenu
的值并在 window
上添加或移除 click
事件监听器,这里是为了在点击空白区域时关闭菜单。
但是!!!!这样会导致点击菜单内部时也会关闭菜单!(在增加显示设置菜单时其实这个是没有影响的,因为增加该需求前,这里原本只有一个发布页面按钮,点击时会弹出悬浮操作框,此时关闭操作栏的逻辑也是没有问题的,但这实际上是触发了window上的事件实现的)
所以问题的根本原因在于 window
上的 click
事件监听器会在点击菜单内部时触发,从而调用 handleCloseMenu
方法关闭菜单
❗💥我们需要一种方法来阻止事件冒泡,使得点击菜单内部时不会触发 window
上的 click
事件。
解决方法
可以使用 Vue.js 的事件修饰符 @click.stop
来阻止事件冒泡,从而防止点击菜单内部时关闭菜单
解决方案步骤
在菜单的
@click
事件上添加.stop
修饰符:<div v-if="state.showMenu" class="main-menu" @click.stop> <ul> <li @click="handleClick({ code: 'publishApp' })"> <span class="menu-item">发布应用</span> </li> <!-- 其他菜单项 --> </ul> </div>
这样点击菜单内部时事件不会冒泡到
window
,就不会触发handleCloseMenu
方法在子菜单的
@click
事件上添加.stop
修饰符:如果有子菜单同样需要在子菜单的
@click
事件上添加.stop
修饰符:<div v-if="state.showMenu && state.showSubMenu" class="sub-menu" @mouseenter="state.showSubMenu = true" @mouseleave="state.showSubMenu = false" @click.stop > <ul> <li v-for="(item, index) in subMenus" :key="index" @click="changeShowState(item)"> <span class="menu-item">{{ item.name }}</span> <span v-show="item.isShow">√</span> </li> </ul> </div>
这样点击子菜单内部时,事件同样不会冒泡到
window
,就不会触发handleCloseMenu
方法
完。