index.vue 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. <script setup lang="ts">
  2. import { useMediaQuery } from '@vueuse/core'
  3. import { isEmpty } from 'lodash-es'
  4. import { NScrollbar } from 'naive-ui'
  5. import { computed, provide, watch, ref } from 'vue'
  6. import texturePng from '@/assets/texture.png'
  7. import { EmptyPlaceholder } from '@/components'
  8. import { useComponentThemeOverrides } from '@/composable/useComponentThemeOverrides'
  9. import { mediaQueryInjectionKey, layoutInjectionKey } from '@/injection'
  10. import { usePreferencesStore } from '@/stores'
  11. import { useTabsStore } from '@/stores'
  12. import AsideLayout from './aside/index.vue'
  13. import MobileLeftAside from './aside/mobile/MobileLeftAside.vue'
  14. import MobileRightAside from './aside/mobile/MobileRightAside.vue'
  15. import Tabs from './component/Tabs.vue'
  16. import FooterLayout from './footer/index.vue'
  17. import HeaderLayout from './header/index.vue'
  18. import MainLayout from './main/index.vue'
  19. import type { LayoutSlideDirection } from '@/injection'
  20. defineOptions({
  21. name: 'Layouts',
  22. })
  23. const tabsStore = useTabsStore()
  24. const preferencesStore = usePreferencesStore()
  25. const { scrollbarInMainLayout } = useComponentThemeOverrides()
  26. const mediaQuery = {
  27. sm: useMediaQuery('(max-width: 640px)'),
  28. md: useMediaQuery('(max-width: 768px)'),
  29. lg: useMediaQuery('(max-width: 1024px)'),
  30. xl: useMediaQuery('(max-width: 1280px)'),
  31. xxl: useMediaQuery('(max-width: 1536px)'),
  32. }
  33. const layoutSlideDirection = ref<LayoutSlideDirection>(null)
  34. const shouldRefreshRoute = ref(false)
  35. const layoutTranslateOffset = computed(() => {
  36. return layoutSlideDirection.value === 'right'
  37. ? preferencesStore.preferences.menu.maxWidth || 0
  38. : layoutSlideDirection.value === 'left'
  39. ? -62
  40. : 0
  41. })
  42. function setLayoutSlideDirection(direction: LayoutSlideDirection) {
  43. layoutSlideDirection.value = direction === layoutSlideDirection.value ? null : direction
  44. }
  45. watch(
  46. () => mediaQuery.sm.value,
  47. (isSm) => {
  48. if (isSm) {
  49. preferencesStore.modify({
  50. menu: {
  51. collapsed: false,
  52. },
  53. })
  54. setLayoutSlideDirection(null)
  55. }
  56. },
  57. )
  58. provide(mediaQueryInjectionKey, mediaQuery)
  59. provide(layoutInjectionKey, {
  60. shouldRefreshRoute,
  61. layoutSlideDirection,
  62. setLayoutSlideDirection,
  63. })
  64. </script>
  65. <template>
  66. <div
  67. class="relative flex h-svh overflow-hidden"
  68. :style="{ backgroundImage: `url(${texturePng})` }"
  69. >
  70. <MobileLeftAside v-if="mediaQuery.sm.value" />
  71. <div
  72. class="relative flex h-full w-full flex-col border-naive-border transition-[border-color,rounded,transform] max-sm:rounded-xl sm:transform-none!"
  73. :class="{
  74. 'overflow-hidden rounded-xl border': mediaQuery.sm.value && layoutTranslateOffset,
  75. }"
  76. :style="
  77. mediaQuery.sm.value && layoutSlideDirection
  78. ? {
  79. transform: `translate(${layoutTranslateOffset}px) scale(0.88)`,
  80. }
  81. : null
  82. "
  83. >
  84. <HeaderLayout />
  85. <div class="flex flex-1 overflow-hidden">
  86. <AsideLayout v-if="!mediaQuery.sm.value" />
  87. <div class="relative flex flex-1 flex-col overflow-x-hidden">
  88. <Transition
  89. type="transition"
  90. enter-active-class="transition-[grid-template-rows]"
  91. leave-active-class="transition-[grid-template-rows]"
  92. enter-from-class="grid-rows-[0fr]"
  93. leave-to-class="grid-rows-[0fr]"
  94. enter-to-class="grid-rows-[1fr]"
  95. leave-from-class="grid-rows-[1fr]"
  96. >
  97. <div
  98. v-if="
  99. !mediaQuery.sm.value &&
  100. !isEmpty(tabsStore.tabs) &&
  101. preferencesStore.preferences.showTabs
  102. "
  103. class="grid shrink-0 items-baseline overflow-hidden"
  104. >
  105. <Tabs />
  106. </div>
  107. </Transition>
  108. <NScrollbar
  109. class="transition-[padding]"
  110. :class="{
  111. 'pb-4': mediaQuery.sm.value && layoutSlideDirection,
  112. }"
  113. container-class="main-container"
  114. :theme-overrides="scrollbarInMainLayout"
  115. >
  116. <MainLayout />
  117. </NScrollbar>
  118. <EmptyPlaceholder
  119. :show="isEmpty(tabsStore.tabs)"
  120. description="空标签页"
  121. size="huge"
  122. >
  123. <template #icon>
  124. <div class="flex items-center justify-center">
  125. <span class="iconify ph--rectangle" />
  126. </div>
  127. </template>
  128. </EmptyPlaceholder>
  129. <Transition
  130. type="transition"
  131. enter-active-class="transition-[grid-template-rows]"
  132. leave-active-class="transition-[grid-template-rows]"
  133. enter-from-class="grid-rows-[0fr]"
  134. leave-to-class="grid-rows-[0fr]"
  135. enter-to-class="grid-rows-[1fr]"
  136. leave-from-class="grid-rows-[1fr]"
  137. >
  138. <div
  139. v-if="!mediaQuery.sm.value && preferencesStore.preferences.showFooter"
  140. class="grid shrink-0 items-baseline overflow-hidden"
  141. >
  142. <FooterLayout />
  143. </div>
  144. </Transition>
  145. </div>
  146. </div>
  147. </div>
  148. <MobileRightAside v-if="mediaQuery.sm.value" />
  149. </div>
  150. </template>