1.创建下拉菜单组件
打开contextmenu.vue编辑
<template>
<transition name="el-zoom-in-bottom">
<div class="el-dropdown__popper el-popper is-light is-pure o-contextmenu"
data-popper-placement="bottom"
:style="`top: ${state.position.y}px;left: ${state.position.x}px;width: ${width}px;`"
:key = "comKey"
v-show="state.isShow"
ref="contextmenuRef"
>
<ul class="el-dropdown-menu"
:style="`width: ${width}px;`">
<template v-for="(v, k) in state.dropdownList">
<li class="el-dropdown-menu__item"
:key="k"
v-if="v.show(state.name)"
@click="onItemClick(v)" >
<span>{{ $t(v.txt) }}</span>
</li>
</template>
</ul>
<div class="el-popper__arrow" :style="{ left: `${state.arrowLeft}px` }"></div>
</div>
</transition>
</template>
<script setup lang="ts">
import { computed, reactive, onMounted, onUnmounted, watch } from 'vue';
// 定义父组件传过来的值
const props = defineProps({
list: {//菜单列表数据
type: Array<Object>,
default: () => {
return [];
},
},
handleCommand:{//点击操作项处理
type: Function,
},
comKey:{
type: String,
default:"",
},
width: {//菜单宽度
type: Number,
default:80,
},
});
// 定义变量内容
const state = reactive({
isShow: false,
dropdownList: props.list,
position:{x:0,y:0},
name: '',//右键点击标签路由名字
arrowLeft: 30,
});
// 当前项菜单点击
const onItemClick = (item: Object) => {
if(props.handleCommand){
props.handleCommand(state.name, item);
}
};
// 打开右键菜单
const openContextmenu = (item: Object) => {
let width = props.cwidth*0.5;
closeContextmenu();
state.position.x = item.x;
state.position.y = item.y;
state.name = item.name;
//小箭头位置调整
state.arrowLeft = item.left>= width ? width : item.left*0.5 -5;
if(state.arrowLeft<=5){ state.arrowLeft = 5;}
setTimeout(() => {
state.isShow = true;
}, 10);
};
// 关闭右键菜单
const closeContextmenu = () => {
state.isShow = false;
};
// 监听页面监听进行右键菜单的关闭
onMounted(() => {
document.body.addEventListener('click', closeContextmenu);
});
// 页面卸载时,移除右键菜单监听事件
onUnmounted(() => {
document.body.removeEventListener('click', closeContextmenu);
});
// 暴露变量
defineExpose({
openContextmenu,
closeContextmenu,
});
</script>
<style scoped lang="scss">
.o-contextmenu {
transform-origin: center top;
z-index: 3000;
position: fixed;
.el-dropdown-menu__item {
font-size: 12px !important;
white-space: nowrap;
i {
font-size: 12px !important;
}
}
}
</style>
2.tabs中调用contextmenu组件
<template>
<el-tabs v-model="$route.name"
type="card"
@tab-remove="removeTab"
@tab-click="handleClick"
@contextmenu.prevent= "onContextmenu"
>
<el-tab-pane
v-for="item in tabs"
:key="item.name"
:label="item.title"
:name="item.name"
:closable="item.name!==MENU_DEFAULT_NAME"
>
</el-tab-pane>
</el-tabs>
<Contextmenu ref="contextmenuRef"
comKey="tab-contextmenu"
:width="100"
:list="dropdownList"
:handleCommand="handleCommand"/>
</template>
<script setup lang="ts">
import { defineAsyncComponent, ref, onBeforeMount, watch } from 'vue'
import { storeToRefs } from 'pinia';
import { useTabStore } from '@/stores/tab'
import {useRouter,useRoute} from 'vue-router'
import { MENU_DEFAULT_NAME } from '@/utils/constant'
// 引入组件
const Contextmenu = defineAsyncComponent(() => import('./contextmenu.vue'));
const router = useRouter()
const route = useRoute()
const useTab = useTabStore()
const { tabs } = storeToRefs(useTab)
const contextmenuRef = ref();
/**
*
* 下拉菜单列表数据
*
**/
const dropdownList=[
{ id: 0, txt: 'contextmenu.closeCurrent', show: (name)=>{
return name != MENU_DEFAULT_NAME;
}
},
{ id: 1, txt: 'contextmenu.closeOther', show: (name)=>{
return useTab.hasOther(name);
}
},
{ id: 2, txt: 'contextmenu.closeLeft', show: (name)=>{
return useTab.hasLeft(name);
}
},
{ id: 3, txt: 'contextmenu.closeRight', show: (name)=>{
return useTab.hasRight(name);
}
},
{ id: 4, txt: 'contextmenu.closeAll', show: (name)=>{
return true;
}
},
];
/**
*
* 下拉菜单项点击处理
*
**/
const handleCommand = async (name: string, item: Object) => {
let found ;
let needRefreshRouter = false;
switch (item.id) {
case 0:
// 关闭当前
found = useTab.remove(name);
needRefreshRouter = router.name == name;
break;
case 1:
// 关闭其它
found = useTab.removeOther(name)
needRefreshRouter = router.name != name;
break;
case 2:
// 关闭左边
found = useTab.removeLeft(name)
needRefreshRouter = !useTab.has(router.name);
break;
case 3:
// 关闭右边
found = useTab.removeRight(name)
needRefreshRouter = !useTab.has(router.name);
break;
case 4:
// 关闭全部
found = useTab.removeAll();
needRefreshRouter = name != MENU_DEFAULT_NAME;
break;
}
if(found){
if(needRefreshRouter){
router.push(found.path)
}
}
};
/**
*
* 右键下拉菜单
*
**/
const onContextmenu = (e: PointerEvent) =>{
if(!!e.target.id){
const v= {name:e.target.id.replace('tab-',''), x:e.clientX, y: e.clientY, left:e.srcElement.clientWidth-e.offsetX};
contextmenuRef.value.openContextmenu(v);
}else{
contextmenuRef.value.closeContextmenu();
}
}
/**
* 点击标签处理
*
**/
const handleClick = (tab:any) => {
const found = router.getRoutes().find(v=>v.name== tab.props.name);
//设置选中标签
if(found){
router.push(found.path)
}
}
/**
* 移除标签处理
*
**/
const removeTab = (tab:string) => {
contextmenuRef.value.closeContextmenu();
const found = useTab.remove(tab);
//关闭标签为当前路由时,需要重新设置选中标签
if(route.name == tab){
router.push(found.path)
}
}
onBeforeMount(() => {
useTab.change(route);
})
watch(
()=>router.currentRoute.value,
()=>{
useTab.change(route);
},
);
</script>
<style lang="scss" scoped>
.headbar-container {
z-index: 10;
height: var(--gl-headbar-height);
background-color: var(--gl-headbar-background-color);
box-shadow: var(--el-box-shadow-light);
}
</style>
3.store处理
import {defineStore} from 'pinia';
import { MENU_DEFAULT_NAME } from '@/utils/constant';
/**
*
* @methods tab信息
*/
export const useTabStore = defineStore('tab',{
persist: true,
state: (): ArrayState<Meta> => ({
datas: [],
}),
getters:{
tabs(state){
return this.datas;
}
},
actions: {
/**
* 切换标签
* param route
*
**/
change(route:any) {
if (this.datas.every(item => item.name !== route.name)) {
this.datas.push(
{
id:route.id,
name:route.name,
path:route.path,
title:route.meta.title,
}
);
}
},
/**
* 关闭标签
* param name
*
**/
remove(name:string){
const length = this.datas.length;
const removeIndex = this.datas.findIndex(item=>item.name == name);
this.datas.splice(removeIndex,1);
return this.datas[removeIndex==length-1? removeIndex-1 : removeIndex];
},
/**
* 关闭其他标签
* param name
*
**/
removeOther(name:string){
this.datas = this.datas.filter(item=>item.name === name || item.name === MENU_DEFAULT_NAME);
return this.datas.length==2 ? this.datas[1] : this.datas[0];
},
/**
* 关闭左边标签
* param name
*
**/
removeLeft(name:string){
const removeIndex = this.datas.findIndex(item=>item.name == name);
this.datas.splice(1, removeIndex-1);
return this.datas[1];
},
/**
* 关闭右边标签
* param name
*
**/
removeRight(name:string){
const removeIndex = this.datas.findIndex(item=>item.name == name);
this.datas.splice(removeIndex+1);
return this.datas[this.datas.length-1];
},
/**
* 关闭全部标签(保留首页)
*
**/
removeAll(){
//保留一个
this.datas.splice(1);
return this.datas[0];
},
/**
*
* 指定节点是否存在右边节点
*
**/
hasRight(name: string){
const index = this.datas.findIndex(item=>item.name == name);
return index != this.datas.length-1;
},
/**
*
* 指定节点是否存在左边节点(除了首页)
*
**/
hasLeft(name: string){
const index = this.datas.findIndex(item=>item.name == name);
return index >1;
},
/**
*
* 除了指定节点和首页 是否存在其他节点
*
**/
hasOther(name: string){
const index = this.datas.findIndex(item=>item.name == name);
return (index==0 && this.datas.length>1) || (index>0 && this.datas.length>2);
},
/**
*
* 是否存在指定节点
*
**/
has(name: string){
const index = this.datas.findIndex(item=>item.name == name);
return index >-1;
},
clear(){
this.datas = [];
},
},
});