vue實現右鍵菜單

自己寫的右鍵菜單組件 可以直接安裝 npm i @xxllxx/vue-context-menu


.

代碼目錄結構

├── src
    ├── components
        └── context-menu
        	├── index.js    // 註冊
    		├── utils.js    
	        ├── index.vue   // 菜單box
	        └── item.vue 	// 菜單項
	

代碼

index.js

// src/components/context-menu/index.js
import contextMenu from './index.vue'
import contextMenuItem from './item.vue'

const install = (Vue, config) => {
  Vue.component(config.name || contextMenu.name, contextMenu)
  Vue.component(config.itemName || contextMenuItem.name, contextMenuItem)
}

if (typeof window !== 'undefined' && window.Vue) {
  install(window.Vue)
}

export default {
  install,
  contextMenu,
  contextMenuItem
}

utils.js

// src/components/context-menu/utils.js
export function getElementOffset(element) {
  let offset = { left: 0, top: 0 }
  let current = element.offsetParent

  offset.left += element.offsetLeft
  offset.top += element.offsetTop

  while (current !== null) {
    offset.left += current.offsetLeft
    offset.top += current.offsetTop
    current = current.offsetParent
  }
  return offset
}
// 清空菜單
export function clearContextMenu() {
  if (document.querySelector('.context-menu-box')) {
    let element = document.querySelector('.context-menu-box')
    element.remove()
  }
}

index.vue

// src/components/context-menu/index.vue
<template>
  <div
    v-once
    style="display: block;z-index: 9999;pointer-events: visible"
    class="context-menu-box"
    key="context_menu"
  >
    <ul
      ref="menu"
      class="list-context-menu-wrapper"
      data-hide-modal="0"
      style="left: -1000px; top: -1000px; "
    >
      <slot v-once>
        <li style="width:120px" @click="emptyClick" class="empty-text">空菜單</li>
      </slot>
    </ul>
  </div>
</template>
<script>
import { getElementOffset, clearContextMenu } from './utils'
export default {
  name: 'context-menu',
  props: {
    width: {
      type: String,
      default: ''
    },
    offset: {
      type: Object,
      default: null
    },
    mode: {
      type: String,
      default: 'contextmenu'
    }
  },
  data() {
    return {
      modeList: ['contextmenu', 'click', 'all']
    }
  },
  computed: {
    trigger: function() {
      if (this.modeList.find(t => t == this.mode)) return this.mode
      return 'contextmenu'
    }
  },
  mounted() {
    this.$nextTick(() => {
      let el = this.$el
      let parent = this.$el.parentElement
      let menu = this.$refs['menu']
      let that = this
      let offset = this.offset
      if (parent) {
        if (this.width) {
          menu.style.width = this.width
        }
        parent.removeChild(el)

        parent.addEventListener('click', event => {
          if (this.trigger == 'contextmenu') clearContextMenu()
        })

        // 菜單禁用右鍵菜單
        menu.addEventListener('contextmenu', e => {
          // e.stopPropagation()
          e.preventDefault()
        })

        const func = function(event) {
          event.preventDefault()
          event.stopPropagation()

          clearContextMenu()

          document.body.insertBefore(el, document.body.firstChild)

          let x = 0
          let y = 0
          if (offset && !isNaN(offset.x) && !isNaN(offset.y)) {
            let temp = getElementOffset(parent)
            x = temp.left + offset.x
            y = temp.top + offset.y
          } else {
            //獲取鼠標視口位置
            x = document.body.offsetWidth - menu.offsetWidth <= event.clientX ? event.clientX - menu.offsetWidth : event.clientX
            y = 0
            if (document.body.offsetHeight - menu.offsetHeight <= event.clientY) y = event.clientY - menu.offsetHeight
            else y = event.clientY
            if (y < 0 && document.body.offsetHeight > menu.offsetHeight) {
              y = (document.body.offsetHeight - menu.offsetHeight) / 2
            }
          }
          menu.style.left = x + 'px'
          menu.style.top = y + 'px'
        }
        // 添加菜單事件 
        switch (this.trigger) {
          case 'contextmenu':
            parent.addEventListener('contextmenu', func)
            break
          case 'click':
            parent.addEventListener('click', func)
            break
          case 'all':
            parent.addEventListener('contextmenu', func)
            parent.addEventListener('click', func)
            break
          default:
            parent.addEventListener('contextmenu', func)
            break
        }
        //#region 刪除菜單
        // 其他地方左鍵時取消已打開的菜單
        document.addEventListener(
          'click',
          e => {
            clearContextMenu()
          },
          true
        )
        // 其他地方右鍵時取消已打開的菜單
        document.addEventListener(
          'contextmenu',
          e => {
            // 菜單右鍵不取消菜單
            if (!e.path.find(t => t.tagName === 'DIV' && t.className === 'context-menu-box')) clearContextMenu()
          },
          true
        )
        //#endregion
      }
    })
  },
  methods: {
    emptyClick(event) {
      event.stopPropagation()
    }
  }
}
</script>
<style lang="scss">
.context-menu-box {
  position: absolute;

  user-select: none;
  .list-context-menu-wrapper {
    position: absolute;
    padding: 8px 0;
    z-index: 999;
    background: #fff;
    box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.12);
    border-radius: 3px;
    border: 1px solid #e4e4e4;
    box-sizing: content-box;
    margin: 0px;

    white-space: nowrap;
    li {
      list-style: none;
    }

    .menu-item-space {
      height: 40px;
      line-height: 40px;
    }

    .empty-text {
      height: 60px;
      margin: 0px auto;
      padding: 0 24px;
      display: flex;
      justify-content: center;
      align-items: center;
      color: rgba(0, 0, 0, 0.4);
    }

    .menu-item {
      &:hover {
        background: rgba(0, 0, 0, 0.02);
      }
      &:active {
        background: rgba(0, 0, 0, 0.04);
      }

      &:focus {
        outline: -webkit-focus-ring-color auto 1px;
      }
      text-align: left;
      font-size: 14px;
      color: rgba(0, 0, 0, 0.88);
      font-weight: normal;
      font-style: normal;
      font-stretch: normal;
      letter-spacing: normal;
      display: block;
      cursor: pointer;
      -webkit-tap-highlight-color: rgba(255, 255, 255, 0);
      padding: 0 24px;
      // 加圖標默認樣式
	  img,
      svg {
        display: inline-block;
        width: 14px;
        height: 14px;
        vertical-align: middle;
        margin-right: 9px;
      }
    }

    .seperator {
      height: 1px;
      padding: 8px 0;

      &::before {
        content: '';
        display: block;
        height: 1px;
        background: rgba(0, 0, 0, 0.04);
      }
    }
  }
}
</style>

item.vue

// src/components/context-menu/item.vue
<template>
  <li
    :class="isEmpty? 'menu-item menu-item-space':'seperator'"
    :aria-label="label"
    tabindex="1"
    @click="menuClick"
  >
    <slot>{{label}}</slot>
  </li>
</template>
<script>
export default {
  name: 'context-menu-item',
  props: {
    label: {
      type: String,
      default: ''
    }
  },
  computed: {
    isEmpty: function() {
      if (this.label || this.$slots.default) return true
      return false
    }
  },
  created() {},
  methods: {
    menuClick() {
      if (this.label) this.$emit('click')
    }
  }
}
</script>

使用

全局註冊

// src/main.js
import Vue from 'vue'
//...
import context from '@/components/context-menu'
// 全局註冊  
Vue.use(context)
// 重命名組件名稱 默認 context-menu 和 context-menu-item
// Vue.use(context, { name: 'contextMenu', itemName: 'contextMenuItem' })

//...
//...

context-menu 屬性方法

參數 說明 類型 可選值 默認值
mode 觸發方式 String click/contextmenu/all contextmenu
width 菜單寬度 String 自適應
offset 固定菜單位置,根據父級的偏移量 Object {x:120,y:20} null

context-menu-item 屬性

參數 說明 類型 可選值 默認值
lable 顯示文字 —— ——
@click 觸發事件 Function —— ——

例子

直接添加到需要右鍵菜單的元素下

// src/view/home.vue
<template>
  <div>
    <div class="homeItem">
      1
      <context-menu width="120px">
        <context-menu-item @click="item2">
      	  <!-- 自定義摸版 -->
          <img src="/svg/tag.svg" />
          標籤
        </context-menu-item>
        <context-menu-item @click="item2">
          <img src="/svg/wallet.svg" />
          錢包
        </context-menu-item >
        <context-menu-item @click="item1">
          <img src="/svg/package.svg" />
          包裹
        </context-menu-item >
        <context-menu-item @click="item1">
          <img src="/svg/logistics.svg" />
          物流
        </context-menu-item>
      </context-menu >
    </div>
    <div class="homeItem">
      2
      <context-menu width="120px">
        <context-menu-item @click="item2">2</context-menu-item>
      </context-menu>
    </div>
  </div>
</template>

<script>
export default {
  name: 'home',
  methods: {
    item1() {
      alert(1)
    },
    item2() {
      alert(2)
    },
    item3() {
      alert(3)
    }
  }
}
</script>
<style scoped>
.homeItem {
  background: aqua;
  width: 120px;
  margin-top: 20px;
  height: 120px;
}
.menu-item img {
  margin-top: -1px;
}
</style>

效果

自定義摸版

默認

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章