| 1 | <template> |
| 2 | <Provider> |
| 3 | <component |
| 4 | :is="layoutComponent" |
| 5 | id="app-layout" |
| 6 | :class="[ |
| 7 | `theme-${themeName}`, |
| 8 | `layout-${layoutComponentName}`, |
| 9 | `route-${routeName}`, |
| 10 | { 'opacity-0': loading } |
| 11 | ]" |
| 12 | > |
| 13 | <RouterView v-slot="{ Component: RouterComponent }"> |
| 14 | <transition :name="`router-${routerTransition}`" mode="out-in" appear> |
| 15 | <component :is="RouterComponent" id="app-page" :key="routeName + forceRefresh" /> |
| 16 | </transition> |
| 17 | </RouterView> |
| 18 | </component> |
| 19 | |
| 20 | <SplashScreen :show="loading" /> |
| 21 | </Provider> |
| 22 | </template> |
| 23 | |
| 24 | <script lang="ts" setup> |
| 25 | import type { Component } from "vue" |
| 26 | import type { RouteLocationNormalized } from "vue-router" |
| 27 | import type { Layout, RouterTransition, ThemeNameEnum } from "@/types/theme" |
| 28 | import { computed, onBeforeMount, ref, watch } from "vue" |
| 29 | import { useRoute, useRouter } from "vue-router" |
| 30 | import Blank from "@/app-layouts/Blank" |
| 31 | import Provider from "@/app-layouts/common/Provider.vue" |
| 32 | import SplashScreen from "@/app-layouts/common/SplashScreen.vue" |
| 33 | import HorizontalNav from "@/app-layouts/HorizontalNav" |
| 34 | import { useMainStore } from "@/stores/main" |
| 35 | import { useThemeStore } from "@/stores/theme" |
| 36 | import { usePortalSettingsStore } from "./stores/portalSettings" |
| 37 | import { updateFavicon } from "./utils" |
| 38 | |
| 39 | const layoutComponents = { |
| 40 | HorizontalNav, |
| 41 | Blank |
| 42 | } |
| 43 | |
| 44 | const router = useRouter() |
| 45 | const route = useRoute() |
| 46 | const loading = ref(true) |
| 47 | |
| 48 | const portalSettingsStore = usePortalSettingsStore() |
| 49 | const themeStore = useThemeStore() |
| 50 | const mainStore = useMainStore() |
| 51 | |
| 52 | const routeName = computed<string>(() => route?.name?.toString() || "") |
| 53 | const forceRefresh = computed<number>(() => mainStore.forceRefresh) |
| 54 | const forceLayout = ref<Layout | null>(null) |
| 55 | const layout = computed<Layout>(() => themeStore.layout) |
| 56 | const layoutComponentName = computed<Layout>(() => forceLayout.value || layout.value) |
| 57 | const layoutComponent = computed<Component>(() => layoutComponents[layoutComponentName.value]) |
| 58 | const routerTransition = computed<RouterTransition>(() => themeStore.routerTransition) |
| 59 | const themeName = computed<ThemeNameEnum>(() => themeStore.themeName) |
| 60 | |
| 61 | function checkThemeOverrides(currentRoute: RouteLocationNormalized) { |
| 62 | if (currentRoute.meta?.theme?.layout !== undefined) { |
| 63 | forceLayout.value = currentRoute.meta.theme.layout |
| 64 | } else { |
| 65 | forceLayout.value = null |
| 66 | } |
| 67 | } |
| 68 | |
| 69 | // Watch for logo changes and update favicon |
| 70 | watch( |
| 71 | () => portalSettingsStore.portalLogo, |
| 72 | newLogo => { |
| 73 | if (newLogo) { |
| 74 | updateFavicon(newLogo) |
| 75 | } |
| 76 | }, |
| 77 | { immediate: true } |
| 78 | ) |
| 79 | |
| 80 | // Watch for title changes and update document title |
| 81 | watch( |
| 82 | () => portalSettingsStore.portalTitle, |
| 83 | newTitle => { |
| 84 | if (newTitle) { |
| 85 | document.title = newTitle |
| 86 | } |
| 87 | }, |
| 88 | { immediate: true } |
| 89 | ) |
| 90 | |
| 91 | watch(layoutComponentName, () => { |
| 92 | loading.value = false |
| 93 | }) |
| 94 | |
| 95 | router.afterEach(currentRoute => { |
| 96 | checkThemeOverrides(currentRoute) |
| 97 | }) |
| 98 | |
| 99 | onBeforeMount(async () => { |
| 100 | await portalSettingsStore.fetchSettings() |
| 101 | |
| 102 | checkThemeOverrides(route) |
| 103 | |
| 104 | setTimeout(() => { |
| 105 | loading.value = false |
| 106 | }, 500) |
| 107 | }) |
| 108 | </script> |