vue3 封装右键菜单组件

实现思路

在 Vue 中封装右键菜单组件时,可以通过 addEventListener 监听 contextmenu 事件,也可以直接在标签上绑定 @contextmenu事件。由于我们在 Vue 中封装组件,应当充分利用框架的优势,使用 @contextmenu 语法来让代码更简洁和易于维护。

封装组件

由于不同区域需要显示不同的菜单项,因此该组件的菜单项由外部传入。

defineExpose将变量、方法暴露出去,使得父组件能够访问这些暴露的内容

menu.vue

xml 复制代码
<template>
  <div class="menu" v-if="show" ref="menuRef">
    <div
      class="menu-item"
      v-for="(item, index) in operation"
      :key="index"
      @click="handleClick(item.name)"
    >
      {
  
  { item.name }}
    </div>
  </div>
</template>
<script lang="ts" setup>
//友情提示:如果使用的不是naive-ui可以直接将有关naive-ui的代码注释掉
import { useMessage } from 'naive-ui'
import { nextTick, ref } from 'vue'
//使用naive-ui的消息提示框,要在根组件使用 <n-message-provider></>n-message-provider>
let message = useMessage()
//点击菜单项
const handleClick = (name) => {
  message.info(name)
  show.value = false
}
// 接收父组件传入的菜单项
defineProps({
  operation: {
    type: Array,
    default: () => [],
  },
})
//用来显示、隐藏菜单
const show = ref(false)
let menuRef = ref()
//获取并设置菜单的位置
const setPosition = (x, y) => {
  nextTick(() => {
    let dom = menuRef.value
    dom.style.left = x + 'px'
    dom.style.top = y + 'px'
    dom.style.height = 'fit-content'
    let height = dom.style.height
    dom.style.height = height
  })
}
//暴露数据
defineExpose({
  show,
  setPosition,
})
</script>
<style lang="scss" scoped>
.menu {
  width: 100px;
  padding-top: 10px;
  padding-bottom: 10px;
  border-radius: 10px;
  background: #ffffff;
  height: 0px;
  position: absolute;
  z-index: 1000;
  .menu-item {
    font-size: 16px;
    width: 100%;
    text-align: center;
    padding-top: 4px;
    padding-bottom: 4px;
    cursor: pointer;
    transition: 0.5s;
  }
  .menu-item:hover {
    background: #e2e2e2;
    transition: 0.5s;
  }
}
</style>

使用组件

在父组件使用组件 在页面中,我们通常会划分多个区域,右键点击不同的区域时展示不同的菜单。在这种情况下,需要在事件处理函数中阻止浏览器的默认右键菜单弹出。此外,如果区域内嵌套了其他区域,还需要阻止事件冒泡,确保事件只在当前区域内处理,从而避免影响到其他区域的右键菜单。

index.vue

xml 复制代码
<template>
  <div class="contextMenu" @click="displayNoneMenu()">
    <div
      class="box-1 box"
      @contextmenu="handleContextMenu($event, 'operationRef')"
    >
      <div class="text">操作区</div>
      <Menu ref="operationRef" :operation="operation"></Menu>
      <div
        class="box-1-1"
        @contextmenu.stop="handleContextMenu($event, 'operation2Ref')"
      >
        <div class="text">操作分区</div>
        <Menu ref="operation2Ref" :operation="operation2"></Menu>
      </div>
    </div>
    <div
      class="box-2 box"
      @contextmenu="handleContextMenu($event, 'settingRef')"
    >
      <div class="text">设置区</div>
      <Menu ref="settingRef" :operation="setting"></Menu>
    </div>
    <div class="box-3 box" @contextmenu="handleContextMenu($event, 'infoRef')">
      <div class="text">信息区</div>
      <Menu ref="infoRef" :operation="info"></Menu>
    </div>
    <div class="box-4 box" @contextmenu="handleContextMenu($event, 'toolRef')">
      <div class="text">工具区</div>
      <Menu ref="toolRef" :operation="tool"></Menu>
    </div>
  </div>
</template>
<script lang="ts" setup>
import Menu from './menu.vue'
import { ref } from 'vue'
//不同的菜单
let operation = [
  {
    name: '添加',
  },
  {
    name: '删除',
  },
  {
    name: '编辑',
  },
]
let operation2 = [
  {
    name: '查看详情',
  },
  {
    name: '查看用户',
  },
]
let setting = [
  {
    name: '修改属性',
  },
  {
    name: '更新',
  },
]
let info = [
  {
    name: '查看日志',
  },
  {
    name: '显示数据',
  },
]
let tool = [
  {
    name: '复制',
  },
  {
    name: '粘贴',
  },
  {
    name: '删除',
  },
]
 
// 给子组件绑定ref 获取组件实例
let operationRef = ref()
let operation2Ref = ref()
let settingRef = ref()
let infoRef = ref()
let toolRef = ref()
// 缓存当前显示的菜单
let currentMenuRef = ref()
//点击任何区域,隐藏菜单
const displayNoneMenu = () => {
  if (currentMenuRef.value) {
    currentMenuRef.value.show = false
  }
}
//右键事件
const handleContextMenu = (e, ref_) => {
  //阻止浏览器默认事件
  e.preventDefault()
  if (currentMenuRef.value) {
//在显示下次菜单前,先隐藏上一次的菜单。
    currentMenuRef.value.show = false 
  }
  let menuRef = null
  switch (ref_) {
    case 'operationRef':
      menuRef = operationRef.value
      break
    case 'operation2Ref':
      menuRef = operation2Ref.value
      break
    case 'settingRef':
      menuRef = settingRef.value
      break
    case 'infoRef':
      menuRef = infoRef.value
      break
    case 'toolRef':
      menuRef = toolRef.value
      break
  }
// 通过获取到的组件实例,设置菜单的显示和位置
  menuRef.show = true
  menuRef.setPosition(e.offsetX, e.offsetY)
  currentMenuRef.value = menuRef
}
</script>
<style lang="scss" scoped>
.contextMenu {
  height: 100%;
  width: 100%;
  background: palegreen;
  border-radius: 10px;
  display: flex;
  flex-flow: wrap;
  .box {
    position: relative;
  }
  .box-1 {
    width: 40%;
    height: 40%;
    background: #25a4bb;
    .box-1-1 {
      width: 50%;
      height: 50%;
      background: coral;
    }
  }
  .box-2 {
    width: 60%;
    height: 40%;
    background: #cacaca;
  }
  .box-3 {
    width: 60%;
    height: 60%;
    background: palevioletred;
  }
  .box-4 {
    width: 40%;
    height: 60%;
    background: paleturquoise;
  }
  .text {
    font-weight: 600;
    font-size: 20px;
    color: #333333;
    text-align: center;
    font-family: 'Avenir', Helvetica, Arial, sans-serif;
    margin-top: 10px;
  }
}
</style>

效果:

相关推荐
百万蹄蹄向前冲39 分钟前
Trae分析Phaser.js游戏《洋葱头捡星星》
前端·游戏开发·trae
朝阳5811 小时前
在浏览器端使用 xml2js 遇到的报错及解决方法
前端
GIS之路1 小时前
GeoTools 读取影像元数据
前端
ssshooter2 小时前
VSCode 自带的 TS 版本可能跟项目TS 版本不一样
前端·面试·typescript
你的人类朋友2 小时前
【Node.js】什么是Node.js
javascript·后端·node.js
Jerry3 小时前
Jetpack Compose 中的状态
前端
dae bal3 小时前
关于RSA和AES加密
前端·vue.js
柳杉3 小时前
使用three.js搭建3d隧道监测-2
前端·javascript·数据可视化
lynn8570_blog4 小时前
低端设备加载webp ANR
前端·算法
LKAI.4 小时前
传统方式部署(RuoYi-Cloud)微服务
java·linux·前端·后端·微服务·node.js·ruoyi