You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Vue3自定义上下文菜单组件:通过内部HTML定义菜单项需求

Vue 3 上下文菜单组件实现方案

你想要开发的这种“直接在组件内部定义菜单项”的上下文菜单,插槽(Slots)恰恰是最适合的解决方案,结合子组件就能完全实现你设想的使用方式,不用通过prop传入列表。下面是具体实现步骤和代码:

1. 拆分组件结构

我们需要两个核心组件:

  • ContextMenu:负责菜单的容器、右键触发显示/隐藏、位置定位等核心逻辑
  • MenuItem:封装单个菜单项的样式和点击事件,支持自定义名称和点击回调

2. 实现 MenuItem 子组件

这个组件负责渲染单个菜单项,接收name属性显示文本,同时向外触发点击事件:

<template>
  <div class="menu-item" @click="handleClick">
    <!-- 支持插槽自定义内容,默认显示name属性 -->
    <slot>{{ name }}</slot>
  </div>
</template>

<script setup>
const props = defineProps({
  name: {
    type: String,
    required: true
  }
})

const emit = defineEmits(['click'])

const handleClick = () => {
  emit('click')
}
</script>

<style scoped>
.menu-item {
  padding: 8px 16px;
  cursor: pointer;
  transition: background-color 0.2s;
}

.menu-item:hover {
  background-color: #f5f5f5;
}
</style>

3. 实现 ContextMenu 父组件

这个组件负责上下文菜单的显示逻辑,通过插槽渲染内部的所有MenuItem:

<template>
  <div 
    ref="menuRef"
    class="context-menu"
    :style="{ top: `${position.y}px`, left: `${position.x}px` }"
    v-show="visible"
  >
    <!-- 插槽用于渲染传入的所有菜单项 -->
    <slot></slot>
  </div>
</template>

<script setup>
import { ref, onMounted, onUnmounted } from 'vue'

const menuRef = ref(null)
const visible = ref(false)
const position = ref({ x: 0, y: 0 })

// 右键触发菜单显示
const handleContextMenu = (e) => {
  e.preventDefault()
  position.value = { x: e.clientX, y: e.clientY }
  visible.value = true
}

// 点击外部关闭菜单
const handleClickOutside = (e) => {
  if (menuRef.value && !menuRef.value.contains(e.target)) {
    visible.value = false
  }
}

onMounted(() => {
  document.addEventListener('contextmenu', handleContextMenu)
  document.addEventListener('click', handleClickOutside)
})

onUnmounted(() => {
  document.removeEventListener('contextmenu', handleContextMenu)
  document.removeEventListener('click', handleClickOutside)
})
</script>

<style scoped>
.context-menu {
  position: fixed;
  background-color: white;
  border: 1px solid #eee;
  border-radius: 4px;
  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
  z-index: 9999;
}
</style>

4. 使用方式

完全符合你设想的用法,不同场景下直接在ContextMenu内部定义对应的菜单项:

<template>
  <!-- 文章列表场景 -->
  <div class="article-list">
    <div v-for="article in articles" :key="article.id" class="article-item">
      {{ article.title }}
      <ContextMenu>
        <MenuItem name="复制" @click="copyArticle(article)"/>
        <MenuItem name="删除" @click="deleteArticle(article)"/>
      </ContextMenu>
    </div>
  </div>

  <!-- 用户列表场景 -->
  <div class="user-list">
    <div v-for="user in users" :key="user.id" class="user-item">
      {{ user.name }}
      <ContextMenu>
        <MenuItem name="编辑用户" @click="editUser(user)"/>
        <MenuItem name="删除用户" @click="deleteUser(user)"/>
      </ContextMenu>
    </div>
  </div>
</template>

<script setup>
import ContextMenu from './ContextMenu.vue'
import MenuItem from './MenuItem.vue'

const articles = ref([
  { id: 1, title: 'Vue组件开发技巧' },
  { id: 2, title: 'JavaScript异步编程' }
])

const users = ref([
  { id: 1, name: '张三' },
  { id: 2, name: '李四' }
])

const copyArticle = (article) => {
  console.log('复制文章:', article)
}
const deleteArticle = (article) => {
  console.log('删除文章:', article)
}
const editUser = (user) => {
  console.log('编辑用户:', user)
}
const deleteUser = (user) => {
  console.log('删除用户:', user)
}
</script>

方案说明

  • 不需要通过prop传入菜单项列表,完全通过插槽传递内容,和你想要的使用方式一致
  • MenuItem组件可以灵活扩展,比如添加图标、禁用状态等,只需修改子组件即可
  • 上下文菜单的显示/隐藏、位置定位逻辑封装在ContextMenu中,复用性强

内容的提问来源于stack exchange,提问作者Fritz

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 09:10:37