自定义右键菜单组件 (vue3)---针对右键菜单高度过高被遮挡问题的改进(23.11.3)
vue3自定义右键菜单组件,display:none 和固定定位 以及一些细节处理
·
自定义右键菜单组件
宽固定 高由传入的数组右键菜单决定
利用display:none 决定右键菜单的显示问题
<template>
<ul
class="context-container"
:style="{ top: clientY + 'px', left: clientX + 'px', display: show ? 'flex' : 'none' }"
ref="contextRef"
>
<li
v-for="(item, index) in listArr"
@click="onChangeMenu(item, index)"
:class="item[propsObj.disabled] ? 'disabled' : ''"
>
<SvgIcon :name="item[propsObj.icon] || item[propsObj.elIcon]" class="svg" />
<span>{{ item[propsObj.label] }}</span>
</li>
</ul>
</template>
传入参数主要是以下
const props = defineProps({
list: { //右键菜单内容数组
type: Array,
default: () => []
},
propsObj: {//数组里的项-对象的key对应的参数字段,可以自定义 默认如下(模仿elementplus 里的props)
type: Object,
default: () => {
return {
label: 'label', //文字字段
value: 'value',//每一项唯一值
disabled: 'disabled', //该项是否可点
icon: 'icon', //该邮件菜单的icon 引入项目本地的svg 具体用法查svg 组件用法
elIcon: 'elIcon' //如果是el-icon,有所区别
}
}
},
modelValue: {//控制显示的参数
type: Boolean,
default: false,
required: true
},
position: {//控制右键菜单位置的参数 右键点击的点 分别对应 event.clientX event.clientY
type: Object,
default: () => {
return {
x: 0,
y: 0
}
}
}
})
const emits = defineEmits(['change', 'update:modelValue'])
//change事件 点击右键菜单项时触发的事件
其他js代码
const show = computed({
get() {
console.log('show', props.modelValue)
return props.modelValue
},
set(newVal) {
emits('update:modelValue', newVal)
}
})
//list 转入做一下处理
const listArr = computed(() => {
return props.list.map((item) => {
if (item[props.propsObj.elIcon]) {
item[props.propsObj.elIcon] = 'ele-' + item[props.propsObj.elIcon]
}
return { ...item }
})
})
const contextRef = ref()
//右键菜单位置是css fixed固定的
//计算右键菜单的宽高 及视口 保证右键菜单不出视口
//这里由于这个组件没有二级分类右键,宽度是固定的,所有相对来说,没有高度考虑情况复杂
const clientX = computed(() => {
const width = document.documentElement.clientWidth
let eleWidth = 150
let X = props.position.x
return width - X < eleWidth ? width - eleWidth : X
})
//const clientY = computed(() => {
// let height = document.documentElement.clientHeight
//let eleHeight = props.list.length * 24
//let Y = props.position.y
// return height - Y < eleHeight ? height - (height - Y) - eleHeight : Y
})
//注释部分没有考虑到如果右键菜单过高,右键菜单在鼠标点击位置的上方或下方仍然超出可视区域的情况
//原来的逻辑默认是右键菜单在鼠标点击位置的上方,如果此时右键菜单的高度大于鼠标点击位置距离可视区下面的高度,就会导致右键菜单部分被遮住;之前是直接将右键菜单放在鼠标上方,但是这里也会有问题:右键菜单的高度大于鼠标点击位置距离可视区上面的高度,右键菜单被遮住;所以这里增加了一些判断,如果有这种情况,就以右键菜单的高度的一半放在鼠标点击位置,居中
const clientY = computed(() => {
let height = document.documentElement.clientHeight
let length = props.isRefresh ? props.list.length + 1 : props.list.length
let eleHeight = length * 25
let Y = props.position.y
if (eleHeight >= height) {
if (eleHeight > height) {
ElMessage.error('右键菜单高度超过可视区域高度,部分会被遮挡')
}
return 0
} else {
const getHeight = () => {
let halfEleHeight = eleHeight / 2
if (halfEleHeight < Y && halfEleHeight < height - Y) {
return Y - halfEleHeight
} else if (halfEleHeight > Y) {
return 0
} else if (halfEleHeight > height - Y) {
return height - eleHeight
}
}
return height - Y >= eleHeight
? Y
: Y < eleHeight
? getHeight()
: height - (height - Y) - eleHeight
}
})
const onChangeMenu = (item, index) => {
if (item[props.propsObj.disabled]) return //如果是不可点击的 不用传事件出去
let obj = {
...item
}
if (obj[props.propsObj.elIcon]) {
obj[props.propsObj.elIcon] = obj[props.propsObj.elIcon].split('-')[1]
} //为了传出去的item 与 传进来的list里item 保持一致
emits('change', obj, index)
close()
}
//关闭右键菜单
const close = () => {
emits('update:modelValue', false)
}
//阻止浏览器右键菜单默认事件
const preventDefault = (event) => {
event.preventDefault()
let clientX = event.clientX
let clientY = event.clientY
//一个页面可以出现多个右键组件 当A、B组件都引用了右键组件,A里面右键菜单已点击出现,又点击了B组件右键,此时A组件里的右键菜单应消失;(一个页面,只有一个右键菜单出现) 这里主要是利用事件的冒泡 最终会冒泡到最上面document ,当前最新的事件对象event判断下与传入的position数据如果不符,说明不是最新的 可以关闭了
if (clientY != props.position.y || clientX != props.position.x) {
close()
}
}
onMounted(() => {
console.log('ref', contextRef.value)
document.addEventListener('click', close) //如果右键菜单出现时 点击了页面,右键菜单消失
document.addEventListener('scroll', close) //页面滚动,右键菜单消失
document.addEventListener('contextmenu', preventDefault) //阻止浏览器右键菜单默认事件
})
onBeforeMount(() => {
document.removeEventListener('click', close)
document.removeEventListener('scroll', close)
document.removeEventListener('contextmenu', preventDefault)
})
defineExpose({
contextRef
})
.context-container {
display: flex;
flex-direction: column;
position: fixed; //固定定位
opacity: 1;
pointer-events: auto;
width: 150px;
border-radius: 2px;
z-index: 2022; //层级设定高点,以免被覆盖
background-color: #fff;
> li {
height: 28px;
line-height: 28px;
padding: 0 12px;
font-size: 12px;
color: #333333;
cursor: pointer;
&:first-child {
border-bottom: 1px solid #fafafa;
}
&:hover {
background-color: @bg;
}
&.disabled {
cursor: not-allowed;
color: #999999;
&:hover {
background-color: #fff;
}
}
.svg {
margin-right: 5px;
color: #4e5769;
font-size: 14px !important;
margin-right: 8px;
vertical-align: middle;
}
}
}
更多推荐
已为社区贡献2条内容
所有评论(0)