App.vue 1.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758
  1. <template>
  2. <div :class="['web-app', layoutClass]">
  3. <template v-if="isAppLayout">
  4. <div class="app-shell">
  5. <TopNavBar @logout="onLogout" />
  6. <div :class="['app-body', { 'no-sidebar': !hasSidebar }]">
  7. <SideNav />
  8. <main class="content">
  9. <router-view />
  10. </main>
  11. </div>
  12. </div>
  13. </template>
  14. <router-view v-else />
  15. <ToastHost />
  16. </div>
  17. </template>
  18. <script setup>
  19. import { computed, watch } from 'vue'
  20. import { useRoute, useRouter } from 'vue-router'
  21. import { useAuth } from '@/store/auth'
  22. import { useMenu } from '@/store/menu'
  23. import { useSyncDocumentTitle } from '@/composables/useSyncDocumentTitle'
  24. import TopNavBar from '@/components/TopNavBar.vue'
  25. import SideNav from '@/components/SideNav.vue'
  26. import ToastHost from '@/components/common/ToastHost.vue'
  27. const route = useRoute()
  28. const router = useRouter()
  29. const { logout } = useAuth()
  30. const { state: menuState, sidebarDepth, reset: resetMenu, syncActiveFromPath } = useMenu()
  31. // 跟随路由同步浏览器 title
  32. useSyncDocumentTitle()
  33. // meta.layout: 'blank' => 全屏独立布局(登录页);其他 => 带侧边栏的业务布局
  34. const isAppLayout = computed(() => route.meta?.layout !== 'blank')
  35. const layoutClass = computed(() => (isAppLayout.value ? 'layout-app' : 'layout-blank'))
  36. // 仅当菜单存在最后一层时显示侧边栏(深度 = 1 时不显示)
  37. const hasSidebar = computed(() => sidebarDepth.value > 0)
  38. // 路由变化时,把一级菜单的 active 状态切到当前路径所属的菜单组
  39. watch(
  40. () => route.path,
  41. (path) => {
  42. if (menuState.loaded) syncActiveFromPath(path)
  43. },
  44. { immediate: true },
  45. )
  46. function onLogout() {
  47. resetMenu()
  48. logout()
  49. router.replace('/login')
  50. }
  51. </script>