Zzhangcheng修复缺陷
fb208af0创建于 2024年6月11日历史提交
<template>
  <transition name="context-menu">
    <div class="context-menu" v-show="show" :style="style" @mousedown.stop @contextmenu.prevent>
      <slot>
        <ul v-if="props.list.length">
          <li v-for="(item, index) in props.list" :key="index" @click="handleMenuItemClick(item)">
            {{ item.label }}
          </li>
        </ul>
      </slot>
    </div>
  </transition>
</template>
<script lang="ts" setup name="ContextMenu">
  import { getCurrentInstance } from 'vue';

  interface Menu {
    label: string;
    click: () => any;
  }
  const { proxy } = getCurrentInstance();
  const style = ref({});
  const props = withDefaults(
    defineProps<{
      offset: { top: number; left: number };
      show: boolean;
      list?: Menu[];
    }>(),
    {
      offset: () => ({
        left: 0,
        top: 0,
      }),
      show: false,
      list: () => [],
    },
  );
  const emit = defineEmits<{
    (e: 'update:show', value: boolean): void;
  }>();

  watch(
    () => props.show,
    (value) => {
      value && nextTick(setPosition);
    },
  );

  const handleMenuItemClick = (menu: Menu) => {
    menu.click();
    clickDocumentHandler();
  };
  const clickDocumentHandler = () => {
    props.show && emit('update:show', false);
  };
  const setPosition = () => {
    let docHeight = document.documentElement.clientHeight;
    let docWidth = document.documentElement.clientWidth;
    let menuHeight = proxy.$el.getBoundingClientRect().height;
    let menuWidth = proxy.$el.getBoundingClientRect().width;

    // Increase the spacing between clicks and menus, which is more aesthetically pleasing
    const gap = 3;
    let topover = props.offset.top + menuHeight + gap >= docHeight ? menuHeight + gap : -gap;
    let leftover = props.offset.left + menuWidth + gap >= docWidth ? menuWidth + gap : -gap;
    style.value = {
      left: `${props.offset.left - leftover}px`,
      top: `${props.offset.top - topover}px`,
    };
  };

  onMounted(() => {
    document.body.appendChild(proxy.$el);
    document.addEventListener('mousedown', clickDocumentHandler);
    window.addEventListener('resize', clickDocumentHandler);
  });
  onBeforeUnmount(() => {
    let popperElm = proxy.$el;
    if (popperElm && popperElm.parentNode === document.body) {
      document.body.removeChild(popperElm);
    }
    document.removeEventListener('mousedown', clickDocumentHandler);
    window.removeEventListener('resize', clickDocumentHandler);
  });
</script>
<style lang="scss" scoped>
  .context-menu {
    z-index: 2300;
    display: block;
    position: fixed;
    color: initial;
    box-shadow: 0px 0px 12px rgba(0, 0, 0, 0.12);
    &-enter,
    &-leave-to {
      opacity: 0;
    }
    &-enter-active,
    &-leave-active {
      transition: opacity 0.15s;
    }
    ul {
      padding: 3px 0;
      margin: 0;
      list-style-type: none;
      border-radius: 4px;
      background-color: var(--el-bg-color-overlay);
    }
    li {
      padding: 2px 12px;
      line-height: 20px;
      display: flex;
      align-items: center;
      white-space: nowrap;
      list-style: none;
      margin: 0;
      cursor: pointer;
      outline: 0;
      &.disabled {
        color: var(--color-text-4);
        cursor: not-allowed;
      }
      &:not(.disabled):hover {
        background-color: var(--color-fill-2);
      }
    }
  }
</style>