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>

效果:

相关推荐
恋猫de小郭40 分钟前
Flutter Zero 是什么?它的出现有什么意义?为什么你需要了解下?
android·前端·flutter
崔庆才丨静觅7 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60618 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了8 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅8 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅8 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅9 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment9 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅9 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊9 小时前
jwt介绍
前端