从零开始设计一个右键菜单组件
需求分析
首先要分析右键菜单需要实现什么功能
- 点击鼠标右键弹出自定义的弹窗
- 实现菜单项的点击
- 自定义菜单项的样式
- 自定义弹窗容器的样式
代码实现
需求搞定之后就是写代码了,下面是基础的代码框架
<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
本文来自博客园,作者:_zhiqiu,转载请注明原文链接:https://www.cnblogs.com/guojikun/p/15322749.html