mirror of
https://github.com/fawney19/Aether.git
synced 2026-01-03 00:02:28 +08:00
208 lines
6.7 KiB
TypeScript
208 lines
6.7 KiB
TypeScript
|
|
import { createRouter, createWebHistory } from 'vue-router'
|
||
|
|
import type { RouteRecordRaw } from 'vue-router'
|
||
|
|
import { useAuthStore } from '@/stores/auth'
|
||
|
|
import { importWithRetry } from '@/utils/importRetry'
|
||
|
|
import { log } from '@/utils/logger'
|
||
|
|
|
||
|
|
const routes: RouteRecordRaw[] = [
|
||
|
|
{
|
||
|
|
path: '/',
|
||
|
|
name: 'Home',
|
||
|
|
component: () => importWithRetry(() => import('@/views/public/Home.vue')),
|
||
|
|
meta: { requiresAuth: false }
|
||
|
|
},
|
||
|
|
|
||
|
|
{
|
||
|
|
path: '/logo-demo',
|
||
|
|
name: 'LogoColorDemo',
|
||
|
|
component: () => importWithRetry(() => import('@/views/public/LogoColorDemo.vue')),
|
||
|
|
meta: { requiresAuth: false }
|
||
|
|
},
|
||
|
|
|
||
|
|
{
|
||
|
|
path: '/dashboard',
|
||
|
|
component: () => importWithRetry(() => import('@/layouts/MainLayout.vue')),
|
||
|
|
meta: { requiresAuth: true },
|
||
|
|
children: [
|
||
|
|
{
|
||
|
|
path: '',
|
||
|
|
name: 'Dashboard',
|
||
|
|
component: () => importWithRetry(() => import('@/views/shared/Dashboard.vue'))
|
||
|
|
},
|
||
|
|
{
|
||
|
|
path: 'api-keys',
|
||
|
|
name: 'MyApiKeys',
|
||
|
|
component: () => importWithRetry(() => import('@/views/user/MyApiKeys.vue'))
|
||
|
|
},
|
||
|
|
{
|
||
|
|
path: 'announcements',
|
||
|
|
name: 'Announcements',
|
||
|
|
component: () => importWithRetry(() => import('@/views/user/Announcements.vue'))
|
||
|
|
},
|
||
|
|
{
|
||
|
|
path: 'usage',
|
||
|
|
name: 'MyUsage',
|
||
|
|
component: () => importWithRetry(() => import('@/views/shared/Usage.vue'))
|
||
|
|
},
|
||
|
|
{
|
||
|
|
path: 'endpoint-status',
|
||
|
|
name: 'EndpointStatus',
|
||
|
|
component: () => importWithRetry(() => import('@/views/shared/HealthMonitor.vue'))
|
||
|
|
},
|
||
|
|
{
|
||
|
|
path: 'settings',
|
||
|
|
name: 'Settings',
|
||
|
|
component: () => importWithRetry(() => import('@/views/user/Settings.vue'))
|
||
|
|
},
|
||
|
|
{
|
||
|
|
path: 'models',
|
||
|
|
name: 'ModelCatalog',
|
||
|
|
component: () => importWithRetry(() => import('@/views/user/ModelCatalog.vue'))
|
||
|
|
}
|
||
|
|
]
|
||
|
|
},
|
||
|
|
{
|
||
|
|
path: '/admin',
|
||
|
|
component: () => importWithRetry(() => import('@/layouts/MainLayout.vue')),
|
||
|
|
meta: { requiresAuth: true, requiresAdmin: true },
|
||
|
|
children: [
|
||
|
|
{
|
||
|
|
path: 'dashboard',
|
||
|
|
name: 'AdminDashboard',
|
||
|
|
component: () => importWithRetry(() => import('@/views/shared/Dashboard.vue'))
|
||
|
|
},
|
||
|
|
{
|
||
|
|
path: 'users',
|
||
|
|
name: 'Users',
|
||
|
|
component: () => importWithRetry(() => import('@/views/admin/Users.vue'))
|
||
|
|
},
|
||
|
|
{
|
||
|
|
path: 'keys',
|
||
|
|
name: 'ApiKeys',
|
||
|
|
component: () => importWithRetry(() => import('@/views/admin/ApiKeys.vue'))
|
||
|
|
},
|
||
|
|
{
|
||
|
|
path: 'providers',
|
||
|
|
name: 'ProviderManagement',
|
||
|
|
component: () => importWithRetry(() => import('@/views/admin/ProviderManagement.vue'))
|
||
|
|
},
|
||
|
|
{
|
||
|
|
path: 'models',
|
||
|
|
name: 'ModelManagement',
|
||
|
|
component: () => importWithRetry(() => import('@/views/admin/ModelManagement.vue'))
|
||
|
|
},
|
||
|
|
{
|
||
|
|
path: 'health-monitor',
|
||
|
|
name: 'HealthMonitor',
|
||
|
|
component: () => importWithRetry(() => import('@/views/shared/HealthMonitor.vue'))
|
||
|
|
},
|
||
|
|
{
|
||
|
|
path: 'usage',
|
||
|
|
name: 'Usage',
|
||
|
|
component: () => importWithRetry(() => import('@/views/shared/Usage.vue'))
|
||
|
|
},
|
||
|
|
{
|
||
|
|
path: 'system',
|
||
|
|
name: 'SystemSettings',
|
||
|
|
component: () => importWithRetry(() => import('@/views/admin/SystemSettings.vue'))
|
||
|
|
},
|
||
|
|
{
|
||
|
|
path: 'audit-logs',
|
||
|
|
name: 'AuditLogs',
|
||
|
|
component: () => importWithRetry(() => import('@/views/admin/AuditLogs.vue'))
|
||
|
|
},
|
||
|
|
{
|
||
|
|
path: 'cache-monitoring',
|
||
|
|
name: 'CacheMonitoring',
|
||
|
|
component: () => importWithRetry(() => import('@/views/admin/CacheMonitoring.vue'))
|
||
|
|
},
|
||
|
|
{
|
||
|
|
path: 'ip-security',
|
||
|
|
name: 'IPSecurity',
|
||
|
|
component: () => importWithRetry(() => import('@/views/admin/IPSecurity.vue'))
|
||
|
|
},
|
||
|
|
{
|
||
|
|
path: 'announcements',
|
||
|
|
name: 'AnnouncementManagement',
|
||
|
|
component: () => importWithRetry(() => import('@/views/user/Announcements.vue'))
|
||
|
|
}
|
||
|
|
]
|
||
|
|
}
|
||
|
|
]
|
||
|
|
|
||
|
|
const router = createRouter({
|
||
|
|
history: createWebHistory(import.meta.env.BASE_URL),
|
||
|
|
routes
|
||
|
|
})
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 判断错误是否为网络错误
|
||
|
|
*/
|
||
|
|
function isNetworkError(error: any): boolean {
|
||
|
|
return !error.response || error.message?.includes('Network') || error.message?.includes('timeout')
|
||
|
|
}
|
||
|
|
|
||
|
|
router.beforeEach(async (to, from, next) => {
|
||
|
|
const authStore = useAuthStore()
|
||
|
|
|
||
|
|
try {
|
||
|
|
// 如果有token但没有用户信息,尝试获取用户信息
|
||
|
|
if (authStore.token && !authStore.user) {
|
||
|
|
try {
|
||
|
|
await authStore.fetchCurrentUser()
|
||
|
|
} catch (error: any) {
|
||
|
|
// 区分网络错误和认证错误
|
||
|
|
if (isNetworkError(error)) {
|
||
|
|
log.warn('Network error while fetching user info, keeping session', { error: error?.message })
|
||
|
|
} else if (error.response?.status === 401) {
|
||
|
|
log.info('Authentication failed, clearing session')
|
||
|
|
authStore.logout()
|
||
|
|
} else {
|
||
|
|
log.warn('Failed to fetch user info, but keeping session', { error: error?.message })
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 检查整个路由匹配记录链中的 meta
|
||
|
|
const requiresAuth = to.matched.some(record => record.meta.requiresAuth !== false)
|
||
|
|
const requiresAdmin = to.matched.some(record => record.meta.requiresAdmin)
|
||
|
|
|
||
|
|
// 如果需要认证但没有token,跳转到首页
|
||
|
|
if (requiresAuth && !authStore.token) {
|
||
|
|
sessionStorage.setItem('redirectPath', to.fullPath)
|
||
|
|
log.debug('No valid token found, redirecting to home')
|
||
|
|
next('/')
|
||
|
|
} else if (to.path === '/' && authStore.isAuthenticated && (to.query.returnTo || from.path.startsWith('/dashboard') || from.path.startsWith('/admin') || from.path === '/')) {
|
||
|
|
// 已登录用户如果是从dashboard返回首页、刷新首页、或者有returnTo参数,允许访问首页
|
||
|
|
next()
|
||
|
|
} else if (authStore.isAuthenticated && to.path === '/' && !to.query.returnTo) {
|
||
|
|
// 已登录用户首次访问首页(非返回/刷新场景),根据角色跳转到对应仪表盘
|
||
|
|
const isAdmin = authStore.user?.role === 'admin'
|
||
|
|
const redirectPath = sessionStorage.getItem('redirectPath')
|
||
|
|
if (redirectPath && redirectPath !== '/') {
|
||
|
|
sessionStorage.removeItem('redirectPath')
|
||
|
|
next(redirectPath)
|
||
|
|
} else {
|
||
|
|
next(isAdmin ? '/admin/dashboard' : '/dashboard')
|
||
|
|
}
|
||
|
|
} else if (requiresAdmin) {
|
||
|
|
// 需要管理员权限的页面
|
||
|
|
const isAdmin = authStore.user?.role === 'admin'
|
||
|
|
if (!isAdmin) {
|
||
|
|
log.warn('Non-admin user attempted to access admin page, redirecting to user dashboard')
|
||
|
|
next('/dashboard')
|
||
|
|
} else {
|
||
|
|
next()
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
next()
|
||
|
|
}
|
||
|
|
} catch (error) {
|
||
|
|
log.error('Router guard error', error)
|
||
|
|
// 发生错误时,直接放行,不要乱跳转
|
||
|
|
next()
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
export default router
|