likes
comments
collection
share

vue 封装一个右键快捷菜单简洁组件

作者站长头像
站长
· 阅读数 11

业务场景

数组生成的标签,使用右键打开菜单,快捷删除目标选中的标签、或者关闭除选中之外的剩余标签,或者全部关闭

实现思路

右键时屏蔽原生事件,把自定义右键菜单浮框封装为组件,传入事件中$event中鼠标的x,y轴位置来显示

开始封装浮框菜单组件

组件接收3个参数

visible // 显示控制

position 坐标轴位置

mark 父组件传入的数据标识,用于操作数据

当点击菜单按钮时,发送回调事件给父组件,由父组件决定数据操作

<template>
  <div ref="rightMenu" v-show="visible" :style="{ left: left + 'px', top: top + 'px' }" class="rightMenu">
    <span @click="clickMenu('close')">关闭</span>
    <span @click="clickMenu('closeOther')">关闭其他</span>
    <span @click="clickMenu('closeAll')">全部关闭</span>
  </div>
</template>
<script>
export default {
  props: {
    visible: { // 显示控制
      type: Boolean,
      required: true,
    },
    position: { // 坐标轴位置
      type: Object,
      default: () => ({ clientX: 0, clientY: 0 })
    },
    mark: { // 父组件传入的数据标识,用于操作数据
      type: [String, Number],
      default: ''
    }
  },
  data() {
    return {
      left: 0, // x轴坐标位置,默认为0,浮框显示时再计算位置
      top: 0, // y轴坐标位置,默认为0,浮框显示时再计算位置
      menu: { // 浮框菜单高宽信息
        width: 0,
        height: 0
      }
    }
  },
  watch: {
    // 监听body点击以及鼠标滚轮事件
    visible(newVal) {
      if (newVal) { // 显示
        document.documentElement.addEventListener('click', this.closeMenu)
        document.documentElement.addEventListener('wheel', this.closeMenu)
      } else { // 隐藏
        document.documentElement.removeEventListener('click', this.closeMenu)
        document.documentElement.removeEventListener('wheel', this.closeMenu)
      }
    },
    position(newVal) {
      this.setPosition(newVal)
    }
  },
  methods: {
    // 设置位置信息
    async setPosition({ clientX, clientY }) {
       let positionX = clientX // x轴坐标
      let positionY = clientY // y轴坐标

      await this.$nextTick() // dom更新后再获取ref

      this.getRightMenu() // 获取浮框菜单高宽信息

      // 这里获取窗口屏幕高度不要使用 document.body 不等于 游览器窗口高度,会造成计算错误
      if (positionY + this.menu.height > window.innerHeight) { // y轴超出边界处理 (y轴上边距坐标+元素高度 > 窗口屏幕高度)
        positionY = document.documentElement.offsetHeight - this.menu.height
      }
      if (positionX + this.menu.width > window.innerWidth) { // x轴超出边界处理(x轴左边距坐标+元素宽度 > 窗口屏幕宽度)
        positionX = document.documentElement.offsetWidth - this.menu.width
      }
      this.left = positionX
      this.top = positionY
    },
    // 获取浮框菜单高宽信息
    getRightMenu() {
      console.log(this.$refs.rightMenu)
      this.menu.width = this.$refs.rightMenu.offsetWidth // 选择器宽度(如果是vue组件要加上$el)
      this.menu.height = this.$refs.rightMenu.offsetHeight // 选择器高度(如果是vue组件要加上$el)
    },
    // 关闭右键菜单浮框
    closeMenu() {
      this.$emit('update:visible', false)
      this.$emit('update:position', { clientX: 0, clientY: 0 })
      this.menu = { // 选择器高宽信息
        width: 0,
        height: 0
      }
    },
    // 点击操作按钮
    clickMenu(target) {
      this.$emit(target, this.mark) // 传回事件名和数据标识

      // 点击后会自动关闭菜单,已在打开时绑定事件 document.documentElement.addEventListener('click', this.closeMenu)
    }
  }
}
</script>
<style lang="less" scoped>
.rightMenu{
  display: flex;
  flex-direction: column;
  position: fixed;
  background-color: #fff;
  box-shadow: 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .04);
  border-radius: 6px;
  padding: 5px;
  z-index: 9999;
  span{
    border-bottom: 1px solid #EBEEF5;
    padding: 10px 20px;
    text-align: center;
    cursor: pointer;
  }
  span:hover{
    background: #eee;
  }
  span:last-child{
    border-bottom: 0;
  }
}
</style>

在进行位置的高宽计算,绑定事件时要注意,不要使用document.bodybody是根据文档内容大小撑开的高宽,导致没有占满全屏,发生错误

应当使用 document.documentElement,指整个文档流的根节点,其占据的大小应当等于浏览器视口的大小。

在vue中使用

在item子元素使用 contextmenu 右键事件并加上修饰符阻止默认事件

根据不同的回调事件进行数据操作

<template>
  <div id="app">
    <div class="box">
      <el-tag
        v-for="item in tags"
        :key="item.id"
        @contextmenu.prevent.native="openMenu(item, $event)"
      >
      {{ item.title }}
      </el-tag>
    </div>
    <RightMenu
      :visible.sync="menu.visible"
      :position.sync="menu.position"
      :mark="menu.mark"
      @close="close"
      @closeOther="closeOther"
      @closeAll="closeAll"
    />
  </div>
</template>
<script>
import RightMenu from '@/components/RightMenu/index.vue'
export default {
  name: "App",
  components: { RightMenu },
  data() {
    return {
      tags: [
        {
          id: 1,
          title: '标签1'
        },
        {
          id: 2,
          title: '标签2'
        },
        {
          id: 3,
          title: '标签3'
        },
        {
          id: 4,
          title: '标签4'
        },
        {
          id: 5,
          title: '标签5'
        },
      ],
      menu: {
        visible: false,
        position: {
          clientX: 0,
          clientY: 0
        }
      }
    };
  },
  methods: {
    // 打开菜单组件
    openMenu(item, e) {
      this.menu = {
        visible: true,
        mark: item.id, // 把当前id传入
        position: {
          clientX: e.clientX,
          clientY: e.clientY
        }
      }
    },
    // 关闭当前
    close(id) {
      const index = this.tags.findIndex(item => item.id === id)
      index !== -1 && this.tags.splice(index, 1)
    },
    // 关闭其他
    closeOther(id) {
      const index = this.tags.findIndex(item => item.id === id)
      const currentItem = [this.tags[index]]
      this.tags = currentItem
    },
    // 关闭所有
    closeAll() {
      this.tags = []
    }
  }
};
</script>
<style lang="less">
#app{
  display: flex;
  align-items: center;
}
.box{
  display: flex;
  .el-tag{
    margin-right: 20px;
    font-size: 22px;
    padding: 10px;
    height: auto;
    cursor: pointer;
  }
}
</style>

实现效果

vue 封装一个右键快捷菜单简洁组件

vue 封装一个右键快捷菜单简洁组件

转载自:https://juejin.cn/post/7253610755127705657
评论
请登录