menu-item.vue 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. <script lang="ts" setup>
  2. import type { MenuItemProps, MenuItemRegistered } from '../interface';
  3. import { computed, onBeforeUnmount, onMounted, reactive, useSlots } from 'vue';
  4. import { useNamespace } from '@vben-core/composables';
  5. import { VbenIcon, VbenMenuBadge, VbenTooltip } from '@vben-core/shadcn-ui';
  6. import { useMenu, useMenuContext, useSubMenuContext } from '../hooks';
  7. interface Props extends MenuItemProps {}
  8. defineOptions({ name: 'MenuItem' });
  9. const props = withDefaults(defineProps<Props>(), {
  10. disabled: false,
  11. });
  12. const emit = defineEmits<{ click: [MenuItemRegistered] }>();
  13. const slots = useSlots();
  14. const { b, e, is } = useNamespace('menu-item');
  15. const nsMenu = useNamespace('menu');
  16. const rootMenu = useMenuContext();
  17. const subMenu = useSubMenuContext();
  18. const { parentMenu, parentPaths } = useMenu();
  19. const active = computed(() => props.path === rootMenu?.activePath);
  20. const menuIcon = computed(() =>
  21. active.value ? props.activeIcon || props.icon : props.icon,
  22. );
  23. const isTopLevelMenuItem = computed(
  24. () => parentMenu.value?.type.name === 'Menu',
  25. );
  26. const collapseShowTitle = computed(
  27. () =>
  28. rootMenu.props?.collapseShowTitle &&
  29. isTopLevelMenuItem.value &&
  30. rootMenu.props.collapse,
  31. );
  32. const showTooltip = computed(
  33. () =>
  34. rootMenu.props.mode === 'vertical' &&
  35. isTopLevelMenuItem.value &&
  36. rootMenu.props?.collapse &&
  37. slots.title,
  38. );
  39. const item: MenuItemRegistered = reactive({
  40. active,
  41. parentPaths: parentPaths.value,
  42. path: props.path || '',
  43. });
  44. /**
  45. * 菜单项点击事件
  46. */
  47. function handleClick() {
  48. if (props.disabled) {
  49. return;
  50. }
  51. rootMenu?.handleMenuItemClick?.({
  52. parentPaths: parentPaths.value,
  53. path: props.path,
  54. });
  55. emit('click', item);
  56. }
  57. onMounted(() => {
  58. subMenu?.addSubMenu?.(item);
  59. rootMenu?.addMenuItem?.(item);
  60. });
  61. onBeforeUnmount(() => {
  62. subMenu?.removeSubMenu?.(item);
  63. rootMenu?.removeMenuItem?.(item);
  64. });
  65. </script>
  66. <template>
  67. <li
  68. :class="[
  69. rootMenu.theme,
  70. b(),
  71. is('active', active),
  72. is('disabled', disabled),
  73. is('collapse-show-title', collapseShowTitle),
  74. ]"
  75. role="menuitem"
  76. @click.stop="handleClick"
  77. >
  78. <VbenTooltip
  79. v-if="showTooltip"
  80. :content-class="[rootMenu.theme]"
  81. side="right"
  82. >
  83. <template #trigger>
  84. <div :class="[nsMenu.be('tooltip', 'trigger')]">
  85. <VbenIcon :class="nsMenu.e('icon')" :icon="menuIcon" fallback />
  86. <slot></slot>
  87. <span v-if="collapseShowTitle" :class="nsMenu.e('name')">
  88. <slot name="title"></slot>
  89. </span>
  90. </div>
  91. </template>
  92. <slot name="title"></slot>
  93. </VbenTooltip>
  94. <div v-show="!showTooltip" :class="[e('content')]">
  95. <VbenMenuBadge
  96. v-if="rootMenu.props.mode !== 'horizontal'"
  97. class="right-2"
  98. v-bind="props"
  99. />
  100. <VbenIcon :class="nsMenu.e('icon')" :icon="menuIcon" fallback />
  101. <slot></slot>
  102. <slot name="title"></slot>
  103. </div>
  104. </li>
  105. </template>