index.vue 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. <script setup lang="ts">
  2. import hotkeys from 'hotkeys-js'
  3. import { useSlots } from '@/slots'
  4. import Logo from '../Logo/index.vue'
  5. defineOptions({
  6. name: 'MainSidebar',
  7. })
  8. const settingsStore = useSettingsStore()
  9. const menuStore = useMenuStore()
  10. const { switchTo } = useMenu()
  11. onMounted(() => {
  12. hotkeys('alt+`', (e) => {
  13. if (settingsStore.settings.menu.enableHotkeys && ['side', 'head'].includes(settingsStore.settings.menu.mode)) {
  14. e.preventDefault()
  15. switchTo(menuStore.actived + 1 < menuStore.allMenus.length ? menuStore.actived + 1 : 0)
  16. }
  17. })
  18. hotkeys('alt+shift+`', (e) => {
  19. if (settingsStore.settings.menu.enableHotkeys && ['side', 'head'].includes(settingsStore.settings.menu.mode)) {
  20. e.preventDefault()
  21. switchTo(menuStore.actived - 1 >= 0 ? menuStore.actived - 1 : menuStore.allMenus.length - 1)
  22. }
  23. })
  24. })
  25. onUnmounted(() => {
  26. hotkeys.unbind('alt+`')
  27. hotkeys.unbind('alt+shift+`')
  28. })
  29. </script>
  30. <template>
  31. <Transition name="main-sidebar">
  32. <div v-if="settingsStore.settings.menu.mode === 'side' || (settingsStore.mode === 'mobile' && settingsStore.settings.menu.mode !== 'single')" class="main-sidebar-container">
  33. <component :is="useSlots('main-sidebar-top')" />
  34. <div class="sidebar-logo flex-center h-16 border-b border-border/50 px-4 text-3">
  35. AI HUB
  36. </div>
  37. <component :is="useSlots('main-sidebar-after-logo')" />
  38. <FaScrollArea :scrollbar="false" mask gradient-color="var(--g-main-sidebar-bg)" class="menu flex-1">
  39. <!-- 侧边栏模式(含主导航) -->
  40. <div class="w-full flex flex-col of-hidden py-1 transition-all -mt-2">
  41. <template v-for="(item, index) in menuStore.allMenus" :key="index">
  42. <div
  43. class="menu-item relative px-2 py-1 transition-all" :class="{
  44. active: index === menuStore.actived,
  45. }"
  46. >
  47. <div
  48. v-if="item.children && item.children.length !== 0" class="group menu-item-container relative h-full w-full flex cursor-pointer items-center justify-between gap-1 rounded-lg py-4 text-[var(--g-main-sidebar-menu-color)] transition-all hover-(bg-[var(--g-main-sidebar-menu-hover-bg)] text-[var(--g-main-sidebar-menu-hover-color)]) px-2!" :class="{
  49. 'text-[var(--g-main-sidebar-menu-active-color)]! bg-[var(--g-main-sidebar-menu-active-bg)]!': index === menuStore.actived,
  50. }" :title="typeof item.meta?.title === 'function' ? item.meta?.title() : item.meta?.title" @click="switchTo(index)"
  51. >
  52. <div class="w-full inline-flex flex-1 flex-col items-center justify-center gap-[2px]">
  53. <FaIcon v-if="item.meta?.icon" :name="item.meta?.icon" class="menu-item-container-icon transition-transform group-hover-scale-120" />
  54. <span class="w-full flex-1 truncate text-center text-sm transition-height transition-opacity transition-width">
  55. {{ typeof item.meta?.title === 'function' ? item.meta?.title() : item.meta?.title }}
  56. </span>
  57. </div>
  58. </div>
  59. </div>
  60. </template>
  61. </div>
  62. </FaScrollArea>
  63. <!-- <component :is="useSlots('main-sidebar-after-menu')" /> -->
  64. <div class="flex-center px-4 py-3">
  65. <AccountButton only-avatar :button-variant="settingsStore.settings.menu.mode === 'side' ? 'secondary' : 'ghost'" class="size-12 p-2" />
  66. </div>
  67. <component :is="useSlots('main-sidebar-bottom')" />
  68. </div>
  69. </Transition>
  70. </template>
  71. <style scoped>
  72. .main-sidebar-container {
  73. position: relative;
  74. z-index: 10;
  75. display: flex;
  76. flex-direction: column;
  77. width: var(--g-main-sidebar-width);
  78. color: var(--g-main-sidebar-menu-color);
  79. background-color: var(--g-main-sidebar-bg);
  80. box-shadow: 1px 0 0 0 hsl(var(--border)), -1px 0 0 0 hsl(var(--border));
  81. transition: background-color 0.3s, color 0.3s, box-shadow 0.3s;
  82. .sidebar-logo {
  83. background-color: var(--g-main-sidebar-bg);
  84. transition: background-color 0.3s;
  85. }
  86. .menu {
  87. :deep(.menu-item) {
  88. .menu-item-container {
  89. padding-block: 8px;
  90. color: var(--g-main-sidebar-menu-color);
  91. &:hover {
  92. color: var(--g-main-sidebar-menu-hover-color);
  93. background-color: var(--g-main-sidebar-menu-hover-bg);
  94. }
  95. .menu-item-container-icon {
  96. font-size: 20px !important;
  97. }
  98. }
  99. &.active .menu-item-container {
  100. color: var(--g-main-sidebar-menu-active-color) !important;
  101. background-color: var(--g-main-sidebar-menu-active-bg) !important;
  102. }
  103. }
  104. }
  105. }
  106. /* 主侧边栏动画 */
  107. .main-sidebar-enter-active,
  108. .main-sidebar-leave-active {
  109. transition: 0.3s;
  110. }
  111. .main-sidebar-enter-from,
  112. .main-sidebar-leave-to {
  113. transform: translateX(calc(var(--g-main-sidebar-width) * -1));
  114. }
  115. </style>