2023-04-05 22:41:53 +08:00
|
|
|
|
<template>
|
2023-11-18 15:22:25 +08:00
|
|
|
|
<transition @enter="onEnter" name="el-zoom-in-center">
|
2023-07-06 20:59:22 +08:00
|
|
|
|
<div
|
|
|
|
|
|
aria-hidden="true"
|
|
|
|
|
|
class="el-dropdown__popper el-popper is-light is-pure custom-contextmenu"
|
|
|
|
|
|
role="tooltip"
|
|
|
|
|
|
data-popper-placement="bottom"
|
2023-11-18 15:22:25 +08:00
|
|
|
|
:style="`top: ${state.dropdown.y + 5}px;left: ${state.dropdown.x}px;`"
|
2023-07-06 20:59:22 +08:00
|
|
|
|
:key="Math.random()"
|
2023-11-14 17:36:51 +08:00
|
|
|
|
v-show="state.isShow && !allHide"
|
2023-07-06 20:59:22 +08:00
|
|
|
|
>
|
2023-04-05 22:41:53 +08:00
|
|
|
|
<ul class="el-dropdown-menu">
|
|
|
|
|
|
<template v-for="(v, k) in state.dropdownList">
|
2023-07-06 20:59:22 +08:00
|
|
|
|
<li
|
2023-11-20 12:21:27 +08:00
|
|
|
|
:id="v.clickId"
|
2023-11-14 17:36:51 +08:00
|
|
|
|
v-auth="v.permission"
|
2023-07-06 20:59:22 +08:00
|
|
|
|
class="el-dropdown-menu__item"
|
|
|
|
|
|
aria-disabled="false"
|
|
|
|
|
|
tabindex="-1"
|
|
|
|
|
|
:key="k"
|
2023-11-14 17:36:51 +08:00
|
|
|
|
v-if="!v.affix && !v.isHide(state.item)"
|
|
|
|
|
|
@click="onCurrentContextmenuClick(v)"
|
2023-07-06 20:59:22 +08:00
|
|
|
|
>
|
2023-04-05 22:41:53 +08:00
|
|
|
|
<SvgIcon :name="v.icon" />
|
|
|
|
|
|
<span>{{ v.txt }}</span>
|
|
|
|
|
|
</li>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</ul>
|
2023-11-18 15:22:25 +08:00
|
|
|
|
<div v-if="state.arrowLeft > 0" class="el-popper__arrow" :style="{ left: `${state.arrowLeft}px` }"></div>
|
2023-04-05 22:41:53 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</transition>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts" name="layoutTagsViewContextmenu">
|
|
|
|
|
|
import { computed, reactive, onMounted, onUnmounted, watch } from 'vue';
|
2023-11-14 17:36:51 +08:00
|
|
|
|
import { ContextmenuItem } from './index';
|
2023-11-18 15:22:25 +08:00
|
|
|
|
import SvgIcon from '@/components/svgIcon/index.vue';
|
2023-12-09 16:17:26 +08:00
|
|
|
|
import { useWindowSize } from '@vueuse/core';
|
2023-04-05 22:41:53 +08:00
|
|
|
|
|
|
|
|
|
|
// 定义父组件传过来的值
|
|
|
|
|
|
const props = defineProps({
|
|
|
|
|
|
dropdown: {
|
|
|
|
|
|
type: Object,
|
|
|
|
|
|
default: () => {
|
|
|
|
|
|
return {
|
|
|
|
|
|
x: 0,
|
|
|
|
|
|
y: 0,
|
|
|
|
|
|
};
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
items: {
|
2023-11-14 17:36:51 +08:00
|
|
|
|
type: Array<ContextmenuItem>,
|
2023-11-03 17:09:20 +08:00
|
|
|
|
default: () => [],
|
2023-04-05 22:41:53 +08:00
|
|
|
|
},
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 定义子组件向父组件传值/事件
|
|
|
|
|
|
const emit = defineEmits(['currentContextmenuClick']);
|
|
|
|
|
|
|
2023-12-09 16:17:26 +08:00
|
|
|
|
const { width: vw, height: vh } = useWindowSize();
|
2023-11-18 15:22:25 +08:00
|
|
|
|
|
2023-04-05 22:41:53 +08:00
|
|
|
|
// 定义变量内容
|
|
|
|
|
|
const state = reactive({
|
|
|
|
|
|
isShow: false,
|
2023-11-14 17:36:51 +08:00
|
|
|
|
dropdownList: [] as ContextmenuItem[],
|
2023-04-05 22:41:53 +08:00
|
|
|
|
item: {} as any,
|
|
|
|
|
|
arrowLeft: 10,
|
2023-11-18 15:22:25 +08:00
|
|
|
|
dropdown: {
|
|
|
|
|
|
x: 0,
|
|
|
|
|
|
y: 0,
|
|
|
|
|
|
},
|
2023-04-05 22:41:53 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
2023-11-18 15:22:25 +08:00
|
|
|
|
// 下拉菜单宽高
|
|
|
|
|
|
let contextmenuWidth = 117;
|
|
|
|
|
|
let contextmenuHeight = 117;
|
|
|
|
|
|
// 下拉菜单元素
|
|
|
|
|
|
let ele = null as any;
|
|
|
|
|
|
|
|
|
|
|
|
const onEnter = (el: any) => {
|
|
|
|
|
|
if (ele || el.offsetHeight == 0) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ele = el;
|
|
|
|
|
|
contextmenuHeight = el.offsetHeight;
|
|
|
|
|
|
contextmenuWidth = el.offsetWidth;
|
|
|
|
|
|
setDropdowns(props.dropdown);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const setDropdowns = (dropdown: any) => {
|
|
|
|
|
|
let { x, y } = dropdown;
|
|
|
|
|
|
|
|
|
|
|
|
state.arrowLeft = 10;
|
|
|
|
|
|
|
|
|
|
|
|
// `Dropdown 下拉菜单` 的宽度
|
|
|
|
|
|
if (x + contextmenuWidth > vw.value) {
|
|
|
|
|
|
state.arrowLeft = contextmenuWidth - (vw.value - x);
|
|
|
|
|
|
x = vw.value - contextmenuWidth - 5;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (y + contextmenuHeight > vh.value) {
|
|
|
|
|
|
y = vh.value - contextmenuHeight - 5;
|
|
|
|
|
|
state.arrowLeft = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
state.dropdown.x = x;
|
|
|
|
|
|
state.dropdown.y = y;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2023-11-14 17:36:51 +08:00
|
|
|
|
const allHide = computed(() => {
|
|
|
|
|
|
for (let item of state.dropdownList) {
|
|
|
|
|
|
if (!item.isHide(state.item)) {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2023-04-05 22:41:53 +08:00
|
|
|
|
// 当前项菜单点击
|
2023-11-14 17:36:51 +08:00
|
|
|
|
const onCurrentContextmenuClick = (ci: ContextmenuItem) => {
|
|
|
|
|
|
// 存在点击事件,则触发该事件函数
|
|
|
|
|
|
if (ci.onClickFunc) {
|
|
|
|
|
|
ci.onClickFunc(state.item);
|
|
|
|
|
|
}
|
|
|
|
|
|
emit('currentContextmenuClick', { id: ci.clickId, item: state.item });
|
2023-04-05 22:41:53 +08:00
|
|
|
|
};
|
2023-11-18 15:22:25 +08:00
|
|
|
|
|
2023-04-05 22:41:53 +08:00
|
|
|
|
// 打开右键菜单:判断是否固定,固定则不显示关闭按钮
|
|
|
|
|
|
const openContextmenu = (item: any) => {
|
|
|
|
|
|
state.item = item;
|
|
|
|
|
|
closeContextmenu();
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
state.isShow = true;
|
|
|
|
|
|
}, 10);
|
|
|
|
|
|
};
|
2023-11-18 15:22:25 +08:00
|
|
|
|
|
2023-04-05 22:41:53 +08:00
|
|
|
|
// 关闭右键菜单
|
|
|
|
|
|
const closeContextmenu = () => {
|
|
|
|
|
|
state.isShow = false;
|
|
|
|
|
|
};
|
2023-11-18 15:22:25 +08:00
|
|
|
|
|
2023-04-05 22:41:53 +08:00
|
|
|
|
// 监听页面监听进行右键菜单的关闭
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
|
document.body.addEventListener('click', closeContextmenu);
|
2023-11-14 17:36:51 +08:00
|
|
|
|
state.dropdownList = props.items;
|
2023-04-05 22:41:53 +08:00
|
|
|
|
});
|
2023-11-18 15:22:25 +08:00
|
|
|
|
|
2023-04-05 22:41:53 +08:00
|
|
|
|
// 页面卸载时,移除右键菜单监听事件
|
|
|
|
|
|
onUnmounted(() => {
|
|
|
|
|
|
document.body.removeEventListener('click', closeContextmenu);
|
|
|
|
|
|
});
|
2023-11-18 15:22:25 +08:00
|
|
|
|
|
2023-04-05 22:41:53 +08:00
|
|
|
|
watch(
|
|
|
|
|
|
() => props.dropdown,
|
2023-11-18 15:22:25 +08:00
|
|
|
|
() => {
|
|
|
|
|
|
// 元素置为空,重新在onEnter赋值元素,否则会造成堆栈溢出
|
|
|
|
|
|
ele = null;
|
2023-04-05 22:41:53 +08:00
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
deep: true,
|
|
|
|
|
|
}
|
|
|
|
|
|
);
|
2023-11-18 15:22:25 +08:00
|
|
|
|
|
2023-04-05 22:41:53 +08:00
|
|
|
|
watch(
|
|
|
|
|
|
() => props.items,
|
|
|
|
|
|
(x: any) => {
|
2023-07-06 20:59:22 +08:00
|
|
|
|
state.dropdownList = x;
|
2023-04-05 22:41:53 +08:00
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
deep: true,
|
|
|
|
|
|
}
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 暴露变量
|
|
|
|
|
|
defineExpose({
|
|
|
|
|
|
openContextmenu,
|
|
|
|
|
|
closeContextmenu,
|
|
|
|
|
|
});
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped lang="scss">
|
|
|
|
|
|
.custom-contextmenu {
|
|
|
|
|
|
transform-origin: center top;
|
|
|
|
|
|
z-index: 2190;
|
|
|
|
|
|
position: fixed;
|
|
|
|
|
|
|
2023-12-20 17:29:16 +08:00
|
|
|
|
.el-dropdown-menu__item {
|
|
|
|
|
|
padding: 5px 10px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-04-05 22:41:53 +08:00
|
|
|
|
.el-dropdown-menu__item {
|
|
|
|
|
|
font-size: 12px !important;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
|
|
|
|
|
|
i {
|
|
|
|
|
|
font-size: 12px !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2023-07-06 20:59:22 +08:00
|
|
|
|
</style>
|
2023-11-14 17:36:51 +08:00
|
|
|
|
.
|