vue3 右键多级菜单

200 阅读2分钟

vue3 右键多级菜单展示

vue3 右键多级菜单展示,一共两个文件,子菜单循环展示,只需要将菜单数据传入即可展示,里面有的样式是用element,自行修改。

<!--
  @文件名: contextmenuCom.vue
  @来源:YongPing.Wang|2023/11/24 15:10
  @描述:右键
-->
<template>
  <transition enter-active-class="animate__animated animate__fadeIn animate__faster">
    <div v-show="isShow" ref="contextmenuComRef" class="contextmenuCom">
      <contextmenu-item-com :menuList="menuList" @contextmenuHandle="handleContextmenu" />
    </div>
  </transition>
</template>

<script lang="ts" setup>
  import { nextTick, onMounted, onUnmounted, ref } from 'vue'
  import { getUuidUnit } from 'lg-js-tools'
  import ContextmenuItemCom from '@/components/engineerDraw/components/contextmenuItemCom.vue'

  const emits = defineEmits(['contextmenuClose', 'contextmenuHandle'])
  const props = defineProps({
    domRef: Object,
  })

  const menuList = ref<any>([])
  const isShow = ref(false)
  const contextmenuComRef = ref()

  const init = (event: any, data: any) => {
    menuList.value = data.map((item: any) => {
      item.key = getUuidUnit()
      if (item.children) {
        item.children.forEach((child: any) => {
          child.key = getUuidUnit()
        })
      }
      return item
    })
    isShow.value = true
    nextTick(() => {
      const menu: HTMLDivElement | null = contextmenuComRef.value
      if (menu) {
        const menuWidth = menu.offsetWidth + 5
        const menuHeight = menu.offsetHeight + 5
        const chaY = document.body.clientHeight - event.clientY
        const chaX = document.body.clientWidth - event.clientX

        // 防止菜单太靠底,根据可视高度调整菜单出现位置
        if (chaY < menuHeight) {
          menu.style.top = event.clientY - menuHeight + 'px'
        } else {
          menu.style.top = event.clientY + 5 + 'px'
        }
        if (chaX < menuWidth) {
          menu.style.left = event.clientX - menuWidth + 'px'
        } else {
          props.domRef?.setIsShow(event.clientX)
          menu.style.left = event.clientX + 5 + 'px'
        }
      }
    })
  }

  // 关闭
  const close = () => {
    menuList.value = []
    isShow.value = false
    emits('contextmenuClose')
  }

  // 获取状态
  const getIsShow = () => {
    return isShow.value
  }

  // 点击菜单
  const handleContextmenu = (data: any) => {
    emits('contextmenuHandle', data)
    close()
  }

  onMounted(() => {
    document.addEventListener('mousedown', close)
  })
  onUnmounted(() => {
    document.removeEventListener('mousedown', close)
  })

  defineExpose({
    init,
    getIsShow,
  })
</script>

<style lang="scss" scoped>
  .contextmenuCom {
    font-family: custom-harmony-sans-regular;
    position: fixed;
    padding: 0 6px;
    font-size: 13px;
    border-radius: 4px;
    box-sizing: border-box;
    white-space: nowrap;
    z-index: 1000;
    background-color: var(--el-color-white);
    box-shadow: var(--el-box-shadow);
  }
</style>
<!--
@文件名: contextmenuItemCom.vue
@来源:YongPing.Wang|2024/11/06 09:51
@描述: 
-->
<template>
  <template v-for="item in props.menuList" :key="item.key">
    <div class="contextmenu__item" @mouseleave="mouseoutHandle(item)">
      <div :class="[item.value === 'children' ? '' : 'link']" class="name LG-btn" @click="handleContextmenu(item)" @mouseover="mouseoverHandle(item)" @mouseup.stop="" @mousedown.stop="">
        {{ item.name }}
        <span v-if="item.value === 'children'" class="value">
          <el-icon><arrow-right /></el-icon>
        </span>
      </div>

      <div v-if="item.children && item.children.length" :id="item.key" class="childrenBox">
        <div class="children">
          <contextmenu-item-com :menuList="item.children" @contextmenuHandle="handleContextmenu" />
        </div>
      </div>
    </div>
  </template>
</template>
<script lang="ts" setup>
  import { ArrowRight } from '@element-plus/icons-vue'

  const props = defineProps({
    menuList: Array,
  })
  const emits = defineEmits(['contextmenuHandle'])

  // 二级菜单
  const mouseoverHandle = (item: any) => {
    let dom = document.getElementById(`${item.key}`)
    if (dom) {
      dom.style.display = 'block'
    }
  }
  const mouseoutHandle = (item: any) => {
    let dom = document.getElementById(`${item.key}`)
    if (dom) {
      dom.style.display = 'none'
    }
  }

  // 点击菜单
  const handleContextmenu = (data: any) => {
    if ((data.children && data.children.length) || data.value === 'children') {
      return
    }
    emits('contextmenuHandle', data)
  }
</script>
<style lang="scss" scoped>
  .contextmenu__item {
    position: relative;
    display: flex;
    align-items: center;
    padding: 6px 8px 6px 4px;

    .name {
      display: flex;
      align-items: center;
      position: relative;
      width: 100%;

      .value {
        width: 15px;
        font-size: 13px;
        margin-bottom: -5px;
        position: absolute;
        right: -15px;
      }
    }

    .childrenBox {
      background-color: transparent;
      display: none;
      position: absolute;
      left: calc(100% + 5px);
      top: 0;
      z-index: 1;
      padding-left: 5px;

      .children {
        border-radius: 4px;
        background-color: var(--el-color-white);
        box-shadow: var(--el-box-shadow);
        padding: 0 4px;
      }
    }
  }

  .contextmenu__item:not(:last-child) {
    border-bottom: 1px solid var(--el-border-color-light);
  }

  .contextmenu__item:first-child {
    border-top-left-radius: 4px;
    border-top-right-radius: 4px;
  }

  .contextmenu__item:last-child {
    border-bottom-left-radius: 4px;
    border-bottom-right-radius: 4px;
  }

  .link:hover {
    color: var(--el-color-primary);
  }
</style>