前言
在上篇文章中写了如何实现弹出框,最后也留下了一个问题:在tab栏中点击哪里,弹出框就出现在哪里,这个是怎么实现的?
在此之前我们先思考:在浏览器中右键,通常会出现什么?
prevent和$event
在这里我们的需求是,在右键tab导航栏时,弹出选项框。但实际上在右键点击时,会弹出浏览器菜单。
我们之前在实现tab的关闭时,讲了 @click.stop 阻止点击事件冒泡。这里为了右键时不弹出浏览器的菜单,使用 @contextmenu.prevent。
click指的是左键点击事件,contextmenu指的是右键点击事件。prevent的作用就是阻止原生事件,这里指的就是右键不再弹出浏览器菜单,而是触发绑定的新事件。我们看看新事件的方法:
@contextmenu.prevent="onContextmenu(item, $event)"
在tab的父标签中绑定了右键事件,阻止右键菜单并且调用onContextmenu方法。
onContextmenu传入了两个参数,item指得就是路由,这样就可以将tab与弹出框的标签绑定;vue中通过v-on绑定事件处理函数, $event 参数可以访问原生事件对象,其中包含了事件发生时的所有信息和参数,在这里指的是右键点击事件,我们看一下它的属性。
其中clientX和clientY是鼠标事件触发时的鼠标相对于浏览器窗口的位置,通过这两个属性就可以解决开头提到的在tab栏中点击哪里,弹出框就出现在哪里这个问题。
我们看看onContextmenu实现逻辑。
onContextmenu
在tabs.vue中,一共为弹出框一共定义了五个功能标签。
contextmenuItems: [
{name: 'refresh', label: '重新加载', icon: 'fa fa-refresh'},
{name: 'close', label: '关闭标签', icon: 'fa fa-times'},
{name: 'fullScreen', label: '当前标签全屏', icon: 'el-icon-FullScreen'},
{name: 'closeOther', label: '关闭其他标签', icon: 'fa fa-minus'},
{name: 'closeAll', label: '关闭全部标签', icon: 'fa fa-stop'},
],
onContextmenu没有实现上面的功能,只是作为一个入口,将tab对应的router和鼠标坐标传递给弹出框组件的contextmenuRef函数。
const onContextmenu = (menu: RouteLocationNormalized, event: MouseEvent) => {
// 禁用刷新,只有打开的tab才能刷新
state.contextmenuItems[0].disabled = route.path !== menu.path
// 当只有一个tab时,禁用关闭其他和关闭全部
state.contextmenuItems[4].disabled = state.contextmenuItems[3].disabled = navTabs.state.tabsView.length == 1 ? true : false
const {clientX, clientY} = event
contextmenuRef.value.onShowContextmenu(menu, {
x: clientX,
y: clientY
})
}
contextmenuRef是弹出框组件的引用,使用ref实现的。
<Contextmenu ref="contextmenuRef" :items="state.contextmenuItems" @contextmenuItemClick="onContextmenuItem"/>
1. 实现弹出框坐标位置
onShowContextmenu是在弹出框组件中定义的,接收tabs中onContextmenu传入的路由、坐标参数。onShowContextmenu只有三行代码。
const onShowContextmenu = (menu: RouteLocationNormalized, axis: Axis) => {
state.menu = menu
state.axis = axis
state.show = true
}
将路由、坐标赋值给state变量,设置show为ture。
Axis是自定义的接口,里面只有x、y两个属性。弹出框使用v-show绑定了show变量决定是否弹出,所以在onShowContextmenu被调用时,将show设置为true,这样就弹出框就能展示。
而代表坐标位置的Axis变量,被弹出框的style属性(即css)绑定。
<div
class="el-popper is-pure is-light el-dropdown__popper ba-contextmenu"
:style="`top:${state.axis.y + 5}px; left:${state.axis.x - 14}px; width:${props.width}px`"
:key="Math.random()"
v-show="state.show"
aria-hidden="false"
data-popper-placement="bottom"
>
利用top和left修改弹出框的位置,就能实现在tab栏中点击哪里,弹出框就出现在哪里。
2. 弹出框关闭
在弹出框组件中,除了定义onShowContextmenu在tabs中调用,用来触发显示弹出框,还定义了onHideContextmenu用来关闭弹出框。
const onHideContextmenu = () => {
state.show = false
}
就一行简单的代码,将show设置为false即可。那么,想一下弹出框在什么时候会隐藏呢?是不是鼠标左键点击弹出框以外的位置就会隐藏。
import {useEventListener} from '@vueuse/core'
onMounted(() => {
useEventListener(document, 'click', onHideContextmenu)
})
使用的是 @vueuse/core的useEventListener实现的,用来监听document的click事件,监听到则触发onHideContextmenu来关闭弹出框。
3. 标签禁用
disabled是在tabs.vue中定义contextmenuItems时设定的属性,在渲染弹出框的时候,就会使用此属性,来判断在某些情况下哪些标签会被禁用。
标签禁用的情况有两种:
- 只有当前打开的tab才能刷新,此刻如果右键点击其他tab,显示弹出框的时候要禁用
- 当只有一个tab时,关闭其他页面、关闭所有页面功能要禁用
所以在onContextMenu中添加下面两行代码,每次弹出框弹出之前都会先完成5个标签disabled属性的初始化:
// 禁用刷新,只有打开的tab才能刷新
state.contextmenuItems[0].disabled = route.path !== menu.path && navTabs.state.tabsView.length > 1
// 当只有一个tab时,禁用关闭其他和关闭全部
state.contextmenuItems[4].disabled = state.contextmenuItems[3].disabled = navTabs.state.tabsView.length == 1 ? true : false
tabsView是一个存放路由的列表,tabs的渲染就是遍历tabsView。这里加一个大于1的判断原因是:在首次访问时,是通过getFirstRoute获取路由渲染的第一个tab(控制台),这里没有触发route.push跳转,route.path与控制台的path就不相等,重新加载就会被禁用,如果这里要加length判断,避免禁用。下面是没有加length判断的情况:
BuildAdmin在实现重新加载禁用时,就没做length的判断。在第七篇写tab及滑动块实现时,因为一些技术问题,就用了和BuildAdmin不一样的方法进行实现的。所以后面涉及tab的部分需要做一些适当的修改。
在弹出框组件渲染标签时,将class与disabled绑定。
<li class="el-dropdown-menu__item" :class="item.disabled ? 'is-disabled' : ''" >
这里使用了三目运算符,当class为is-disabled时,ElementPlus会自动渲染css。
ElementPlus自动将cursor设置为not-allowed,color设置为内部定义的禁用颜色变量 --el-text-color-disabled( #c0c4cc) ,这样就实现了标签禁用。
结语
本篇文章主要讲了弹出框的两个知识点:弹出位置和标签禁用,都是对ElementPlus和vue简单的使用。