vue3实现一个通用的右键菜单组件

发布时间 2023-10-20 10:58:21作者: 秃头的铲屎官

1、新建一个名为ContextMenu.vue的文件

<template>
    <div ref="containerRef">
      <slot></slot>
      <Teleport to="body">
        <Transition @beforeEnter="handleBeforeEnter" @enter="handleEnter" @afterEnter="handleAfterEnter">
          <div v-if="showMenu" class="context-menu" :style="{ left: x + 'px', top: y + 'px' }">
            <div class="menu-list">
              <!-- 添加菜单的点击事件 -->
              <div @click="handleClick(item)" class="menu-item" v-for="(item, i) in menu" :key="item.label">
                {{ item.label }}
              </div>
            </div>
          </div>
        </Transition>
      </Teleport>
    </div>
  </template>
  <script setup>
  import { ref } from 'vue';
  import useContextMenu from './useContextMenu';
  const props = defineProps({
    menu: {
      type: Array,
      default: () => [],
    },
  });
  const containerRef = ref(null);
  const emit = defineEmits(['select']);
  const { x, y, showMenu } = useContextMenu(containerRef);
  // 菜单的点击事件
  function handleClick(item) {
    // 选中菜单后关闭菜单
    showMenu.value = false;
    // 并返回选中的菜单
    emit('select', item);
  }
  
  function handleBeforeEnter(el) {
    el.style.height = 0;
  }
  
  function handleEnter(el) {
    el.style.height = 'auto';
    const h = el.clientHeight;
    el.style.height = 0;
    requestAnimationFrame(() => {
      el.style.height = h + 'px';
      el.style.transition = '.5s';
    });
  }
  
  function handleAfterEnter(el) {
    el.style.transition = 'none';
  }
  </script>
<style lang="scss" scoped>
.context-menu{
    position: absolute;  
    .menu-list{
      padding:0 10px;
      box-shadow: 0 0 2px 2px #f1f1f1;
      background: #fff;
      .menu-item{
        padding: 4px 5px;
        text-align: center;
        cursor: pointer;
        &:hover{
          color: #0073e5;
        }
      }
    }
    
}
</style>
  

2、新建useContextMenu.ts文件

import { onMounted, onUnmounted, ref } from "vue";
export default function (containerRef) {
  const showMenu = ref(false);
  const x = ref(0);
  const y = ref(0);
  const handleContextMenu = (e) => {
    e.preventDefault();
    e.stopPropagation();
    showMenu.value = true;
    x.value = e.clientX;
    y.value = e.clientY;
  };
  function closeMenu() {
    showMenu.value = false;
  }
  onMounted(() => {
    const div = containerRef.value;
    div.addEventListener("contextmenu", handleContextMenu);
    window.addEventListener("click", closeMenu, true);
    window.addEventListener("contextmenu", closeMenu, true);
  });
  onUnmounted(() => {
    const div = containerRef.value;
    div.removeEventListener("contextmenu", handleContextMenu);
    window.removeEventListener("click", closeMenu, true);
    window.removeEventListener("contextmenu", closeMenu, true);
  });
  return {
    showMenu,
    x,
    y,
  };
}

3、使用方式如下:

<ContextMenu :menu="[
            { label: '添加' },
            { label: '编辑' },
            { label: '删除' },
            { label: '查看' },
            { label: '复制' },
          ]" @select="select($event.label)">
            <div class="edit-mian-box">
              <div class="edit-box">11111</div>
            </div>
</ContextMenu>

 

效果图如下: