2025-11-10 00:59:40 +08:00
|
|
|
|
<script setup>
|
|
|
|
|
|
import { RouterView } from 'vue-router'
|
|
|
|
|
|
import { ref, onMounted } from 'vue'
|
|
|
|
|
|
import SidebarNav from './components/SidebarNav.vue'
|
|
|
|
|
|
import TopNav from './components/TopNav.vue'
|
|
|
|
|
|
import { theme } from 'ant-design-vue'
|
|
|
|
|
|
import SvgSprite from '@/components/icons/SvgSprite.vue'
|
2025-11-12 22:45:29 +08:00
|
|
|
|
import { useUserStore } from '@/stores/user'
|
|
|
|
|
|
import { getToken } from '@gold/utils/token-manager'
|
2025-11-10 00:59:40 +08:00
|
|
|
|
|
|
|
|
|
|
function readCssVar(name) {
|
|
|
|
|
|
return getComputedStyle(document.documentElement).getPropertyValue(name).trim() || undefined
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const themeToken = ref({
|
|
|
|
|
|
algorithm: theme.darkAlgorithm,
|
|
|
|
|
|
token: {
|
|
|
|
|
|
colorPrimary: '#3B82F6',
|
|
|
|
|
|
colorInfo: '#1A66E0',
|
|
|
|
|
|
colorBgBase: '#0D0D0D',
|
|
|
|
|
|
colorBgContainer: '#1A1A1A',
|
|
|
|
|
|
colorTextBase: '#F2F2F2',
|
|
|
|
|
|
colorTextSecondary: '#CCCCCC',
|
|
|
|
|
|
colorBorder: '#333333',
|
|
|
|
|
|
borderRadius: 6,
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2025-11-12 22:45:29 +08:00
|
|
|
|
onMounted(async () => {
|
2025-11-10 00:59:40 +08:00
|
|
|
|
// 运行时从 :root 读取,若存在则覆盖默认值
|
|
|
|
|
|
const next = { ...themeToken.value.token }
|
|
|
|
|
|
next.colorPrimary = readCssVar('--color-primary') || next.colorPrimary
|
|
|
|
|
|
next.colorInfo = readCssVar('--color-blue') || next.colorInfo
|
|
|
|
|
|
next.colorBgBase = readCssVar('--color-bg') || next.colorBgBase
|
|
|
|
|
|
next.colorBgContainer = readCssVar('--color-surface') || next.colorBgContainer
|
|
|
|
|
|
next.colorTextBase = readCssVar('--color-text') || next.colorTextBase
|
|
|
|
|
|
next.colorTextSecondary = readCssVar('--color-text-secondary') || next.colorTextSecondary
|
|
|
|
|
|
next.colorBorder = readCssVar('--color-border') || next.colorBorder
|
|
|
|
|
|
themeToken.value = { algorithm: theme.darkAlgorithm, token: next }
|
2025-11-12 22:45:29 +08:00
|
|
|
|
|
|
|
|
|
|
// 检查登录状态:如果有token但store中未标记为登录,则恢复登录状态
|
|
|
|
|
|
const userStore = useUserStore()
|
|
|
|
|
|
|
|
|
|
|
|
// 等待store从本地存储恢复完成(最多等待500ms)
|
|
|
|
|
|
let waitCount = 0
|
|
|
|
|
|
while (!userStore.isHydrated && waitCount < 50) {
|
|
|
|
|
|
await new Promise(resolve => setTimeout(resolve, 10))
|
|
|
|
|
|
waitCount++
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const token = getToken()
|
|
|
|
|
|
if (token) {
|
|
|
|
|
|
// 如果有token但未登录,可能是刷新页面,需要恢复登录状态
|
|
|
|
|
|
if (!userStore.isLoggedIn) {
|
|
|
|
|
|
userStore.isLoggedIn = true
|
|
|
|
|
|
// 尝试获取用户信息
|
|
|
|
|
|
try {
|
|
|
|
|
|
await userStore.fetchUserInfo()
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('初始化用户信息失败:', error)
|
|
|
|
|
|
}
|
|
|
|
|
|
} else if (!userStore.nickname && !userStore.userId) {
|
|
|
|
|
|
// 如果已登录但没有用户信息,尝试获取
|
|
|
|
|
|
try {
|
|
|
|
|
|
await userStore.fetchUserInfo()
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('获取用户信息失败:', error)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-11-10 00:59:40 +08:00
|
|
|
|
})
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<template>
|
|
|
|
|
|
<a-config-provider :theme="themeToken">
|
|
|
|
|
|
<div class="app-shell">
|
|
|
|
|
|
<SvgSprite />
|
|
|
|
|
|
<TopNav />
|
|
|
|
|
|
<div class="app-body">
|
|
|
|
|
|
<SidebarNav />
|
|
|
|
|
|
<div class="app-content">
|
|
|
|
|
|
<main class="content-scroll">
|
|
|
|
|
|
<keep-alive>
|
|
|
|
|
|
<RouterView />
|
|
|
|
|
|
</keep-alive>
|
|
|
|
|
|
</main>
|
|
|
|
|
|
<footer class="py-6 text-xs text-center text-gray-500">
|
|
|
|
|
|
v0.1 · API 正常 · © 2025 金牌内容大师
|
|
|
|
|
|
</footer>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</a-config-provider>
|
|
|
|
|
|
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
|
.app-shell {
|
|
|
|
|
|
min-height: 100vh;
|
|
|
|
|
|
background: var(--color-bg);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 顶部固定,下面主体需要留出空间 */
|
|
|
|
|
|
.app-body {
|
|
|
|
|
|
padding-top: 70px; /* 与 TopNav 高度对齐 */
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: 220px 1fr; /* 左侧固定宽度侧边栏 */
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.app-content {
|
|
|
|
|
|
min-height: calc(100vh - 70px);
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.content-scroll {
|
|
|
|
|
|
flex: 1 1 auto;
|
|
|
|
|
|
overflow: auto; /* 右侧内容区域滚动 */
|
|
|
|
|
|
padding: 0 16px 0 16px;
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|