vue实现自定义全局右键菜单_vue.js

前段时间公司在做一个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触发了。