122 lines
2.5 KiB
Vue
122 lines
2.5 KiB
Vue
<script setup lang="ts">
|
|
import { RouterView } from 'vue-router'
|
|
import ThemeToggle from './components/common/ThemeToggle.vue'
|
|
import LanguageSwitcher from './components/common/LanguageSwitcher.vue'
|
|
import AlertComponent from '@/components/common/AlertComponent.vue'
|
|
import { useAppShell } from '@/composables'
|
|
|
|
const {
|
|
isDarkMode,
|
|
isLoading,
|
|
routeTransitionMode,
|
|
routeTransitionName,
|
|
routeViewKey,
|
|
showGlobalControls
|
|
} = useAppShell()
|
|
</script>
|
|
|
|
<template>
|
|
<div :class="['app-container', isDarkMode ? 'dark' : 'light']">
|
|
<div v-if="showGlobalControls" class="fixed top-4 right-4 z-50 flex items-center space-x-3">
|
|
<LanguageSwitcher />
|
|
<ThemeToggle v-model="isDarkMode" />
|
|
</div>
|
|
<div v-if="isLoading" class="loading-overlay">
|
|
<div class="loading-spinner"></div>
|
|
</div>
|
|
<RouterView v-slot="{ Component }">
|
|
<transition :name="routeTransitionName" :mode="routeTransitionMode">
|
|
<component :is="Component" :key="routeViewKey" />
|
|
</transition>
|
|
</RouterView>
|
|
|
|
<AlertComponent />
|
|
</div>
|
|
</template>
|
|
|
|
<style>
|
|
.app-container {
|
|
min-height: 100vh;
|
|
width: 100%;
|
|
transition: background-color 0.5s ease;
|
|
}
|
|
|
|
.light {
|
|
background:
|
|
linear-gradient(135deg, #dfe9eb 0%, #edf3f2 46%, #f7f8f5 100%),
|
|
linear-gradient(90deg, rgba(103, 135, 145, 0.12), rgba(162, 172, 154, 0.1));
|
|
}
|
|
|
|
.dark {
|
|
background:
|
|
linear-gradient(135deg, #10191e 0%, #17252b 48%, #223036 100%),
|
|
linear-gradient(90deg, rgba(113, 153, 168, 0.12), rgba(117, 143, 132, 0.08));
|
|
}
|
|
|
|
.fade-enter-active,
|
|
.fade-leave-active {
|
|
transition: opacity 0.3s ease;
|
|
}
|
|
|
|
.fade-enter-from,
|
|
.fade-leave-to {
|
|
opacity: 0;
|
|
}
|
|
|
|
.transfer-fade-enter-active,
|
|
.transfer-fade-leave-active {
|
|
transition:
|
|
opacity 0.2s ease,
|
|
transform 0.2s ease;
|
|
}
|
|
|
|
.transfer-fade-enter-from {
|
|
opacity: 0;
|
|
transform: translateY(8px);
|
|
}
|
|
|
|
.transfer-fade-leave-active {
|
|
position: absolute;
|
|
inset: 0;
|
|
width: 100%;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.transfer-fade-leave-to {
|
|
opacity: 0;
|
|
transform: translateY(-6px);
|
|
}
|
|
|
|
.loading-overlay {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
background-color: rgba(0, 0, 0, 0.5);
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
z-index: 9999;
|
|
}
|
|
|
|
.loading-spinner {
|
|
width: 50px;
|
|
height: 50px;
|
|
border: 3px solid #fff;
|
|
border-top: 3px solid #6f98a6;
|
|
border-radius: 50%;
|
|
animation: spin 1s linear infinite;
|
|
}
|
|
|
|
@keyframes spin {
|
|
0% {
|
|
transform: rotate(0deg);
|
|
}
|
|
|
|
100% {
|
|
transform: rotate(360deg);
|
|
}
|
|
}
|
|
</style>
|