前段时间公司在做一个webide项目,其中有对文件树的各种操作,主要通过右键菜单实现,今天就来记录一下怎么在vue项目中实现全局的自定义右键菜单。效果如图所示:
注意:
需要在项目中找到页面整体布局的组件,比如项目使用home.vue作为整个项目的公共布局,将根元素定位设置成relative,使右键菜单相对于其进行定位。
本文的右键菜单主要使用vuex实现
一、vuex中定义全局状态用于管理右键菜单
export default { ? ? /** ? ? ?* menucontent格式: ? ? ?* [ ? ? ?* ? ? ?{ ? ? ?* ? ? ? ? ?name: '新建文件', ? // 操作名 ? ? ?* ? ? ? ? ?method: 'createdirectory', ?// 需要执行的对应组件中的方法 ? ? ?* ?? ??? ??? ?disabled: true?? ??? ?// 是否禁用,可以根据页面具体逻辑进行调整 ? ? ?* ? ? ?} ? ? ?* ] ?? ? *? ?? ? *? ?? ? * 整体右键菜单采用绝对定位,所以clientx、clienty代表是left和top定位 ? ? ?*/ ? ? state: { ? ? ? ? menucontent: [], ? ?// 右键菜单内容 ? ? ? ? clientx: '', ? ?// left ? ? ? ? clienty: '', ? ?// top ? ? ? ? displaycontextmenu: false ? // 是否展示右键菜单 ? ? }, ? ? mutations: { ? ? ? ? set_context_menu: (state, payload) => { ? ? ? ? ? ? state.menucontent = payload.menucontent; ? ? ? ? ? ? state.clientx = payload.clientx; ? ? ? ? ? ? state.clienty = payload.clienty; ? ? ? ? ? ? state.displaycontextmenu = payload.displaycontextmenu; ? ? ? ? }, ? ? } }
二、定义公共组件contextmenu.vue
<template> ? ? <div class="context-menu" v-show="contextmenu.displaycontextmenu"? ? ? ? ? :style="{'left': contextmenu.clientx + 'px', 'top': contextmenu.clienty + 'px'}"> ? ? ? ? <ul> ? ? ? ? ? ? <li v-for="(item, i) in contextmenu.menucontent" :key="i" :class="item.disabled ? 'disabled' : ''"? ? ? ? ? ? ? ? ? @click="emitevent(item.method)"> ? ? ? ? ? ? ? ? {{item.name}} ? ? ? ? ? ? </li> ? ? ? ? </ul> ? ? </div> </template> <script> import { mapstate } from 'vuex'; import bus from '@/views/common/bus'; export default { ? ? name: 'contextmenu', ? ? data(){ ? ? ? ? return { ? ? ? ? } ? ? }, ? ? computed: { ? ? ? ? ...mapstate(['contextmenu']) ? ? }, ? ? methods: { ? ? ? ? emitevent(type){ ? ? ? ? ? ? bus.$emit('operatedirectory', type) ? ? ? ? } ? ? } } </script> <style lang="scss" scoped> ? ? .context-menu { ? ? ? ? position: absolute; ? ? ? ? background: #fff; ? ? ? ? color: #34495e; ? ? ? ? min-width: 100px; ? ? ? ? box-shadow: 0px 2px 4px 0px rgba(0, 0, 0, 0.2); ? ? ? ? border-radius: 2px; ? ? ? ? cursor: pointer; ? ? ? ? z-index: 1002; ? ? ? ? &>ul { ? ? ? ? ? ? text-align: left; ? ? ? ? ? ? padding: 5px 10px; ? ? ? ? ? ? &>li { ? ? ? ? ? ? ? ? padding: 3px 4px; ? ? ? ? ? ? ? ? font-size: 12px; ? ? ? ? ? ? ? ? list-style: none; ? ? ? ? ? ? ? ? height: 24px; ? ? ? ? ? ? ? ? line-height: 24px; ? ? ? ? ? ? ? ? &:hover { ? ? ? ? ? ? ? ? ? ? background: #edf6ff; ? ? ? ? ? ? ? ? } ? ? ? ? ? ? } ? ? ? ? ? ? .disabled { ? ? ? ? ? ? ? ? color: #888585; ? ? ? ? ? ? ? ? pointer-events: none; ? ? ? ? ? ? } ? ? ? ? } ? ? } </style>
三、在组件中使用
import { mapstate } from 'vuex'; // ... computed: { ?? ?...mapstate(['contextmenu']) }, created(){ ?? ?bus.$on('operatedirectory', (param) => { ?? ?// 点击右键菜单时,应触发组件内的同名方法,首先应判断该方法是否在本组件内存在,存在则调用 ?? ??? ?if(this[param]){ ?? ??? ??? ?this[param](); ?? ??? ?} ?? ?}); }, // ... methods: { ?? ?showcontextmenu(event, data) { ? ? ? ? event.preventdefault();?? ??? ?// 阻止浏览器的默认事件 ? ? ? ? const menucontent = { ? ? ? ? ? ? menucontent: [ ? ? ? ? ? ? { ? ? ? ? ? ? ? ? icon: "el-icon-upload2", ? ? ? ? ? ? ? ? name: "运行", ? ? ? ? ? ? ? ? method: "run", ? ? ? ? ? ? }, ? ? ? ? ? ? { ? ? ? ? ? ? ? ? icon: "el-icon-refresh", ? ? ? ? ? ? ? ? name: "编辑", ? ? ? ? ? ? ? ? method: "editconfig", ? ? ? ? ? ? }, ? ? ? ? ? ? { ? ? ? ? ? ? ? ? icon: "el-icon-refresh", ? ? ? ? ? ? ? ? name: "添加", ? ? ? ? ? ? ? ? method: "addcommond", ? ? ? ? ? ? }, ? ? ? ? ? ? { ? ? ? ? ? ? ? ? icon: "el-icon-refresh", ? ? ? ? ? ? ? ? name: "删除", ? ? ? ? ? ? ? ? method: "deleteconfig", ? ? ? ? ? ? }, ? ? ? ? ? ? ], ? ? ? ? ? ? clientx: event.clientx, ? ? ? ? ? ? clienty: event.clienty, ? ? ? ? ? ? displaycontextmenu: true, ? ? ? ? }; ? ? ? ? this.$store.commit("set_context_menu", menucontent); ? ? ? ? // 再次点击页面,右键菜单消失 ? ? ? ? document.onclick = (event) => { ? ? ? ? ? ? this.$store.commit("set_context_menu", { ? ? ? ? ? ? ? ? displaycontextmenu: false, ? ? ? ? ? ? }); ? ? ? ? }; ? ? }, ? ? run(){ ?? ??? ?// ... ?? ?}, ?? ?editconfig(){ ?? ??? ?// ... ?? ?}, ?? ?addcommond(){ ?? ??? ?// ... ?? ?}, ?? ?deleteconfig(){ ?? ??? ?// ... ?? ?} }
总结
这种可以实现全局右键菜单,并且支持自定义右键内容,但是vue3.0对event bus的取消会导致不可用。
目前有一种优化方法:项目中对应会使用组件库,例如element ui,在定义contextmenu.vue的时候,使用dialog,将内容定义在popover 弹出框中,当组件使用右键菜单时,使用子组件的方式使用contextmenu.vue,点击右键菜单内容时就不需要使用emit触发了。