需求分析
首先要分析右键菜单需要实现什么功能
- 点击鼠标右键弹出自定义的弹窗
- 实现菜单项的点击
- 自定义菜单项的样式
- 自定义弹窗容器的样式
代码实现
需求搞定之后就是写代码了,下面是基础的代码框架
<template> <div class="yak-content-menu" @contextmenu="showContentMenuFn" @click="hideContextMenuFn"> <slot></slot> <transition> <div v-show="visiable" class="yak-content-menu-wrap" :class="menuWrapClass" :style="{ left: menuPosition.left, top: menuPosition.top }" > <slot name="menu" :menuList="menus"> <!-- 给于用户完整的菜单项控制权限 --> <span class="yak-content-menu-wrap-item" :class="menuItemClass" v-for="item in menus" :key="item.command" @click="menuClick(item)" > {{ item.text }} </span> </slot> </div> </transition> </div> </template> <script lang="ts"> import { ref, reactive, onMounted, onBeforeUnmount, defineComponent, PropType, } from "vue"; interface MenuItem { command: string; text: string; } type Menus = Array<MenuItem>; export default defineComponent({ name: "YakContextmenu", props: { menus: { type: Array as PropType<Menus>, default: () => { return []; }, required: true, }, // 菜单的数组 menuWrapClass: String, // 菜单容器的自定义class menuItemClass: String, // 菜单项的自定义class }, emits: ["menu-click"], setup(props, { emit }) { const wrapEl = ref(); const visiable = ref(false); const menuPosition: any = reactive({ left: 0, top: 0, }); const showContextMenuFn = (ev: any) => { // 1:禁用默认的右键点击事件 // 2:获取当前鼠标的位置 // 3:控制弹窗的显示 }; const hideContextMenuFn = () => { // 隐藏菜单 }; const menuClick = (item: MenuItem) => { // 添加自定义事件 menu-click,方便组件使用 }; return { wrapEl, visiable, menuPosition, menuClick, showContentMenuFn, hideContextMenuFn, }; }, }); </script>
实现 showContextMenuFn
有了上面的框架,现在让我们实现显示菜单的功能
const showContextMenuFn = (ev: any) => { // 禁用默认事件 ev.preventDefault(); // 获取自定义菜单的根元素的位置 const rootPosition = wrapEl.value.getBoundingClientRect(); // 用鼠标所在位置减去根元素的位置,就是弹窗元素相对于根元素的位置 const x = ev.x - rootPosition.left; const y = ev.y - rootPosition.top; menuPosition.top = `${y}px`; menuPosition.left = `${x}px`; // 控制弹窗的显示 visiable.value = true; };
实现隐藏菜单和菜单点击
const hideContextMenuFn = () => { visiable.value = false; // 隐藏菜单 }; const menuClick = (item: MenuItem) => { emit("menu-click", item); // 添加自定义事件 menu-click };
实现的效果
完整的代码
<template> <div class="yak-content-menu" @contextmenu="showContentMenuFn" @click="hideContextMenuFn"> <slot></slot> <transition> <div v-show="visiable" class="yak-content-menu-wrap" :class="menuWrapClass" :style="{ left: menuPosition.left, top: menuPosition.top }" > <slot name="menu" :menuList="menus"> <!-- 给于用户完整的菜单项控制权限 --> <span class="yak-content-menu-wrap-item" :class="menuItemClass" v-for="item in menus" :key="item.command" @click="menuClick(item)" > {{ item.text }} </span> </slot> </div> </transition> </div> </template> <script lang="ts"> import { ref, reactive, onMounted, onBeforeUnmount, defineComponent, PropType, } from "vue"; interface MenuItem { command: string; text: string; } type Menus = Array<MenuItem>; export default defineComponent({ name: "YakContextmenu", props: { menus: { type: Array as PropType<Menus>, default: () => { return []; }, required: true, }, // 菜单的数组 menuWrapClass: String, // 菜单容器的自定义class menuItemClass: String, // 菜单项的自定义class }, emits: ["menu-click"], setup(props, { emit }) { const wrapEl = ref(); const visiable = ref(false); const menuPosition: any = reactive({ left: 0, top: 0, }); const showContextMenuFn = (ev: any) => { // 禁用默认事件 ev.preventDefault(); // 获取自定义菜单的根元素的位置 const rootPosition = wrapEl.value.getBoundingClientRect(); // 用鼠标所在位置减去根元素的位置,就是弹窗元素相对于根元素的位置 const x = ev.x - rootPosition.left; const y = ev.y - rootPosition.top; menuPosition.top = `${y}px`; menuPosition.left = `${x}px`; // 控制弹窗的显示 visiable.value = true; }; const hideContextMenuFn = () => { visiable.value = false; // 隐藏菜单 }; const menuClick = (item: MenuItem) => { emit("menu-click", item); // 添加自定义事件 menu-click }; return { wrapEl, visiable, menuPosition, menuClick, showContentMenuFn, hideContextMenuFn, }; }, }); </script> <style lang="scss"> .yak-content-menu { position: relative; &-wrap { display: inline-block; box-sizing: border-box; background-color: #fff; position: absolute; box-shadow: 0 1px 6px rgb(0 0 0 / 20%); border-color: 1px solid #eee; border-radius: 4px; padding: 5px 0; min-width: 160px; &-item { cursor: pointer; display: block; line-height: 28px; font-size: 14px; padding: 0 24px; text-align: left; &:hover { background-color: #eee; } } } }
仓库地址 Github,如有需求,欢迎提交issue