乘风原创程序

  • vue实现自定义全局右键菜单
  • 2022/4/8 9:52:36
  • 前段时间公司在做一个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触发了。