| <script setup lang="ts"> |
| import { useRoute, useRouter } from 'vue-router'; |
| import { ref } from 'vue'; |
| import logo from './assets/logo.png' |
| const router = useRouter(); |
| const route = useRoute(); |
| const menuVisible = ref(false); |
| |
| const menu = [ |
| { |
| name: '仓库', |
| path: '/repo', |
| icon: 'system-log' |
| }, |
| { |
| name: '账号', |
| path: '/account', |
| icon: 'user-list' |
| }, |
| { |
| name: '设置', |
| path: '/setting', |
| icon: 'setting-1' |
| } |
| ]; |
| |
| const logout = () => { |
| window.localStorage.removeItem('isAuthenticated'); |
| router.push('/login'); |
| }; |
| |
| const toggleMenu = () => { |
| menuVisible.value = !menuVisible.value; |
| }; |
| |
| |
| |
| const parseToken = () => { |
| try { |
| const token = localStorage.getItem('token') as string; |
| const [, payload] = token.split('.'); |
| const data = JSON.parse(atob(payload)); |
| return data; |
| } catch (error) { |
| return null; |
| } |
| }; |
| const jwtToken = parseToken(); |
| |
| </script> |
| |
| <template> |
| <template v-if="route.path === '/login'"> |
| <router-view /> |
| </template> |
| <t-layout v-else class="h-screen"> |
| |
| <t-drawer v-model:visible="menuVisible" placement="left" size="232" :footer="false" :header="false" |
| :close-on-overlay-click="true" class="lg:hidden"> |
| <t-menu :value="route.path" theme="dark" class="h-full bg-transparent "> |
| <template #logo> |
| <router-link to="/" class="flex items-center gap-2 p-4"> |
| <img :src="logo" alt="logo" class="w-8 h-8" /> |
| <h1 class="text-xl font-bold text-white">SEED LOG</h1> |
| </router-link> |
| </template> |
| <t-menu-item v-for="item in menu" :key="item.path" :value="item.path" :to="item.path" |
| @click="menuVisible = false" class="menu-item mx-4 my-2 rounded-xl"> |
| <template #icon> |
| <t-icon :name="item.icon" /> |
| </template> |
| {{ item.name }} |
| </t-menu-item> |
| </t-menu> |
| </t-drawer> |
| |
| |
| <t-aside class="sidebar backdrop-blur-lg hidden lg:block"> |
| <t-menu :value="route.path" theme="dark" class="bg-transparent"> |
| <template #logo> |
| <router-link to="/" class="flex items-center gap-2"> |
| <img :src="logo" alt="logo" class="w-10 h-10 transition-transform hover:scale-110" /> |
| <h1 class="text-2xl font-bold text-white tracking-wider">SEED LOG</h1> |
| </router-link> |
| </template> |
| <t-menu-item v-for="item in menu" :key="item.path" :value="item.path" :to="item.path" |
| class="menu-item mx-4 my-2 rounded-xl"> |
| <template #icon> |
| <t-icon :name="item.icon" class="text-xl" /> |
| </template> |
| <span class="font-medium">{{ item.name }}</span> |
| </t-menu-item> |
| </t-menu> |
| </t-aside> |
| |
| |
| <t-layout class="w-full"> |
| <t-header class="header backdrop-blur-xl border-b border-gray-100"> |
| <div class="flex items-center justify-between h-full px-2 sm:px-4 lg:px-8"> |
| |
| <div class="flex items-center gap-2 sm:gap-4 "> |
| <div class="lg:hidden"> |
| <t-button theme="default" variant="text" class=" min-w-[40px]" @click="toggleMenu"> |
| <t-icon name="menu" size="20px" sm:size="24px" /> |
| </t-button> |
| </div> |
| <h1 |
| class="text-base sm:text-xl lg:text-2xl font-bold bg-gradient-to-r from-blue-600 to-indigo-600 bg-clip-text text-transparent truncate max-w-[150px] sm:max-w-full"> |
| {{ jwtToken?.sub }} |
| </h1> |
| </div> |
| <div class="flex items-center gap-2 sm:gap-4"> |
| <t-button theme="danger" @click="logout" class="logout-btn text-sm sm:text-base py-1 px-2 sm:px-4"> |
| <template #icon> |
| <t-icon name="logout" class="text-sm sm:text-base" /> |
| </template> |
| <span>退出</span> |
| </t-button> |
| </div> |
| </div> |
| </t-header> |
| |
| |
| <t-content class="content bg-gray-50/30 flex-1 overflow-y-auto w-full"> |
| <router-view v-slot="{ Component }"> |
| <transition name="fade-slide" mode="out-in"> |
| <keep-alive> |
| <component :is="Component" /> |
| </keep-alive> |
| </transition> |
| </router-view> |
| </t-content> |
| |
| |
| <t-footer class="footer backdrop-blur-sm py-4 text-center text-sm text-gray-600"> |
| <span class="opacity-75">© 微软邮箱管理系统</span> |
| </t-footer> |
| </t-layout> |
| |
| </t-layout> |
| </template> |
| |
| <style scoped> |
| @reference "./assets/base.css"; |
| |
| .sidebar { |
| @apply bg-gradient-to-br from-blue-600 via-blue-700 to-indigo-800; |
| box-shadow: 0 0 20px rgba(0, 0, 0, 0.1); |
| } |
| |
| .header { |
| @apply bg-white/80 sticky top-0 z-10; |
| box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05); |
| } |
| |
| .menu-item { |
| @apply transition-all duration-300 hover:bg-white/15 active:scale-95; |
| @apply flex items-center gap-3 px-4 py-3; |
| } |
| |
| .logout-btn { |
| @apply transition-transform hover:scale-105 active:scale-95; |
| } |
| |
| .fade-slide-enter-active, |
| .fade-slide-leave-active { |
| transition: all 0.3s ease; |
| } |
| |
| .fade-slide-enter-from { |
| opacity: 0; |
| transform: translateY(20px); |
| } |
| |
| .fade-slide-leave-to { |
| opacity: 0; |
| transform: translateY(-20px); |
| } |
| |
| |
| |
| |
| |
| :deep(.t-drawer__body) { |
| @apply p-0; |
| } |
| |
| :deep(.t-drawer__body) { |
| @apply p-0; |
| } |
| |
| :deep(::-webkit-scrollbar) { |
| @apply w-2; |
| } |
| |
| :deep(::-webkit-scrollbar-thumb) { |
| @apply bg-gray-400/30 rounded-full transition-colors hover:bg-gray-400/50; |
| } |
| |
| :deep(::-webkit-scrollbar-track) { |
| @apply bg-transparent; |
| } |
| |
| .content { |
| background-image: radial-gradient(circle at 50% 50%, |
| rgba(255, 255, 255, 0.8) 0%, |
| rgba(240, 240, 250, 0.6) 100%); |
| } |
| |
| .footer { |
| @apply bg-white/60; |
| } |
| |
| |
| @media (max-width: 1024px) { |
| .sidebar { |
| display: none; |
| } |
| } |
| |
| .drawer-menu { |
| @apply bg-gradient-to-br from-blue-600 via-blue-700 to-indigo-800; |
| } |
| </style> |
| |