2025-12-10 20:52:44 +08:00
|
|
|
import { createRouter, createWebHistory } from 'vue-router'
|
|
|
|
|
import type { RouteRecordRaw } from 'vue-router'
|
|
|
|
|
import { useAuthStore } from '@/stores/auth'
|
2026-01-16 16:14:12 +08:00
|
|
|
import { useModuleStore } from '@/stores/modules'
|
2025-12-10 20:52:44 +08:00
|
|
|
import { importWithRetry } from '@/utils/importRetry'
|
|
|
|
|
import { log } from '@/utils/logger'
|
2026-02-14 20:06:38 +08:00
|
|
|
import {
|
|
|
|
|
ensureUserLoaded,
|
|
|
|
|
resolveHomeRedirect,
|
|
|
|
|
checkAdminAccess,
|
|
|
|
|
checkModuleAccess
|
|
|
|
|
} from './guards'
|
2025-12-10 20:52:44 +08:00
|
|
|
|
|
|
|
|
const routes: RouteRecordRaw[] = [
|
|
|
|
|
{
|
|
|
|
|
path: '/',
|
|
|
|
|
name: 'Home',
|
|
|
|
|
component: () => importWithRetry(() => import('@/views/public/Home.vue')),
|
|
|
|
|
meta: { requiresAuth: false }
|
|
|
|
|
},
|
|
|
|
|
|
2026-01-29 11:41:55 +08:00
|
|
|
{
|
|
|
|
|
path: '/guide',
|
2026-01-29 13:58:06 +08:00
|
|
|
component: () => importWithRetry(() => import('@/views/public/guide/GuideLayout.vue')),
|
|
|
|
|
meta: { requiresAuth: false },
|
|
|
|
|
children: [
|
|
|
|
|
{
|
|
|
|
|
path: '',
|
|
|
|
|
name: 'GuideOverview',
|
|
|
|
|
component: () => importWithRetry(() => import('@/views/public/guide/Overview.vue')),
|
|
|
|
|
meta: { requiresAuth: false }
|
|
|
|
|
},
|
|
|
|
|
{
|
2026-02-18 18:17:14 +08:00
|
|
|
path: 'architecture',
|
|
|
|
|
name: 'GuideArchitecture',
|
|
|
|
|
component: () => importWithRetry(() => import('@/views/public/guide/ArchitectureGuide.vue')),
|
2026-01-29 13:58:06 +08:00
|
|
|
meta: { requiresAuth: false }
|
|
|
|
|
},
|
|
|
|
|
{
|
2026-02-18 18:17:14 +08:00
|
|
|
path: 'concepts',
|
|
|
|
|
name: 'GuideConcepts',
|
|
|
|
|
component: () => importWithRetry(() => import('@/views/public/guide/ConceptsGuide.vue')),
|
2026-01-29 13:58:06 +08:00
|
|
|
meta: { requiresAuth: false }
|
|
|
|
|
},
|
|
|
|
|
{
|
2026-02-18 18:17:14 +08:00
|
|
|
path: 'strategy',
|
|
|
|
|
name: 'GuideStrategy',
|
|
|
|
|
component: () => importWithRetry(() => import('@/views/public/guide/StrategyGuide.vue')),
|
2026-01-29 13:58:06 +08:00
|
|
|
meta: { requiresAuth: false }
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
path: 'advanced',
|
|
|
|
|
name: 'GuideAdvanced',
|
|
|
|
|
component: () => importWithRetry(() => import('@/views/public/guide/AdvancedGuide.vue')),
|
|
|
|
|
meta: { requiresAuth: false }
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
path: 'faq',
|
|
|
|
|
name: 'GuideFaq',
|
|
|
|
|
component: () => importWithRetry(() => import('@/views/public/guide/GuideFaq.vue')),
|
|
|
|
|
meta: { requiresAuth: false }
|
2026-02-23 15:50:24 +08:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
path: 'modules',
|
|
|
|
|
name: 'GuideModules',
|
|
|
|
|
component: () => importWithRetry(() => import('@/views/public/guide/ModulesGuide.vue')),
|
|
|
|
|
meta: { requiresAuth: false }
|
2026-01-29 13:58:06 +08:00
|
|
|
}
|
|
|
|
|
]
|
2026-01-29 11:41:55 +08:00
|
|
|
},
|
2025-12-10 20:52:44 +08:00
|
|
|
{
|
|
|
|
|
path: '/logo-demo',
|
|
|
|
|
name: 'LogoColorDemo',
|
|
|
|
|
component: () => importWithRetry(() => import('@/views/public/LogoColorDemo.vue')),
|
|
|
|
|
meta: { requiresAuth: false }
|
|
|
|
|
},
|
2026-01-19 03:19:17 +08:00
|
|
|
{
|
|
|
|
|
path: '/auth/callback',
|
|
|
|
|
name: 'AuthCallback',
|
|
|
|
|
component: () => importWithRetry(() => import('@/views/public/AuthCallback.vue')),
|
|
|
|
|
meta: { requiresAuth: false }
|
|
|
|
|
},
|
2025-12-10 20:52:44 +08:00
|
|
|
|
|
|
|
|
{
|
|
|
|
|
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'))
|
|
|
|
|
},
|
2026-01-07 14:55:07 +08:00
|
|
|
{
|
|
|
|
|
path: 'management-tokens',
|
|
|
|
|
name: 'ManagementTokens',
|
2026-02-05 16:32:27 +08:00
|
|
|
component: () => importWithRetry(() => import('@/views/user/ManagementTokens.vue')),
|
|
|
|
|
meta: { module: 'management_tokens' }
|
2026-01-07 14:55:07 +08:00
|
|
|
},
|
2025-12-10 20:52:44 +08:00
|
|
|
{
|
|
|
|
|
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'))
|
|
|
|
|
},
|
2026-03-08 00:05:48 +08:00
|
|
|
{
|
|
|
|
|
path: 'wallet',
|
|
|
|
|
name: 'WalletCenter',
|
|
|
|
|
component: () => importWithRetry(() => import('@/views/user/WalletCenter.vue'))
|
|
|
|
|
},
|
2025-12-10 20:52:44 +08:00
|
|
|
{
|
|
|
|
|
path: 'models',
|
|
|
|
|
name: 'ModelCatalog',
|
|
|
|
|
component: () => importWithRetry(() => import('@/views/user/ModelCatalog.vue'))
|
2026-02-28 11:44:08 +08:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
path: 'async-tasks',
|
|
|
|
|
name: 'UserAsyncTasks',
|
|
|
|
|
component: () => importWithRetry(() => import('@/views/admin/AsyncTasks.vue'))
|
2025-12-10 20:52:44 +08:00
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
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'))
|
|
|
|
|
},
|
2026-03-08 00:05:48 +08:00
|
|
|
{
|
|
|
|
|
path: 'wallets',
|
|
|
|
|
name: 'WalletsManagement',
|
|
|
|
|
component: () => importWithRetry(() => import('@/views/admin/WalletsManagement.vue'))
|
|
|
|
|
},
|
2026-01-07 14:55:07 +08:00
|
|
|
{
|
|
|
|
|
path: 'management-tokens',
|
|
|
|
|
name: 'AdminManagementTokens',
|
2026-02-05 16:32:27 +08:00
|
|
|
component: () => importWithRetry(() => import('@/views/user/ManagementTokens.vue')),
|
|
|
|
|
meta: { module: 'management_tokens' }
|
2026-01-07 14:55:07 +08:00
|
|
|
},
|
2025-12-10 20:52:44 +08:00
|
|
|
{
|
|
|
|
|
path: 'providers',
|
|
|
|
|
name: 'ProviderManagement',
|
|
|
|
|
component: () => importWithRetry(() => import('@/views/admin/ProviderManagement.vue'))
|
|
|
|
|
},
|
2026-02-27 13:54:46 +08:00
|
|
|
{
|
|
|
|
|
path: 'pool',
|
|
|
|
|
name: 'PoolManagement',
|
|
|
|
|
component: () => importWithRetry(() => import('@/views/admin/PoolManagement.vue'))
|
|
|
|
|
},
|
2025-12-10 20:52:44 +08:00
|
|
|
{
|
|
|
|
|
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'))
|
|
|
|
|
},
|
2026-02-04 02:04:54 +08:00
|
|
|
{
|
|
|
|
|
path: 'user-stats',
|
|
|
|
|
name: 'UserStats',
|
|
|
|
|
component: () => importWithRetry(() => import('@/views/admin/UserStats.vue'))
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
path: 'cost-analysis',
|
|
|
|
|
name: 'CostAnalysis',
|
|
|
|
|
component: () => importWithRetry(() => import('@/views/admin/CostAnalysis.vue'))
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
path: 'performance-analysis',
|
|
|
|
|
name: 'PerformanceAnalysis',
|
|
|
|
|
component: () => importWithRetry(() => import('@/views/admin/PerformanceAnalysis.vue'))
|
|
|
|
|
},
|
2025-12-10 20:52:44 +08:00
|
|
|
{
|
|
|
|
|
path: 'system',
|
|
|
|
|
name: 'SystemSettings',
|
|
|
|
|
component: () => importWithRetry(() => import('@/views/admin/SystemSettings.vue'))
|
|
|
|
|
},
|
2026-01-16 16:14:12 +08:00
|
|
|
{
|
|
|
|
|
path: 'modules',
|
|
|
|
|
name: 'ModuleManagement',
|
|
|
|
|
component: () => importWithRetry(() => import('@/views/admin/ModuleManagement.vue'))
|
|
|
|
|
},
|
2026-01-01 02:10:19 +08:00
|
|
|
{
|
|
|
|
|
path: 'email',
|
|
|
|
|
name: 'EmailSettings',
|
|
|
|
|
component: () => importWithRetry(() => import('@/views/admin/EmailSettings.vue'))
|
|
|
|
|
},
|
2026-01-02 16:17:24 +08:00
|
|
|
{
|
|
|
|
|
path: 'ldap',
|
|
|
|
|
name: 'LdapSettings',
|
2026-01-16 16:14:12 +08:00
|
|
|
component: () => importWithRetry(() => import('@/views/admin/LdapSettings.vue')),
|
|
|
|
|
meta: { module: 'ldap' }
|
2026-01-02 16:17:24 +08:00
|
|
|
},
|
2026-01-19 03:19:17 +08:00
|
|
|
{
|
|
|
|
|
path: 'oauth',
|
|
|
|
|
name: 'OAuthSettings',
|
|
|
|
|
component: () => importWithRetry(() => import('@/views/admin/OAuthSettings.vue')),
|
|
|
|
|
meta: { module: 'oauth' }
|
|
|
|
|
},
|
2025-12-10 20:52:44 +08:00
|
|
|
{
|
|
|
|
|
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'))
|
2026-01-31 19:11:25 +08:00
|
|
|
},
|
2026-02-02 03:16:52 +08:00
|
|
|
{
|
|
|
|
|
path: 'async-tasks',
|
|
|
|
|
name: 'AsyncTasks',
|
|
|
|
|
component: () => importWithRetry(() => import('@/views/admin/AsyncTasks.vue'))
|
|
|
|
|
},
|
2026-02-07 12:24:42 +08:00
|
|
|
{
|
|
|
|
|
path: 'proxy-nodes',
|
|
|
|
|
name: 'ProxyNodes',
|
|
|
|
|
component: () => importWithRetry(() => import('@/views/admin/ProxyNodes.vue')),
|
|
|
|
|
meta: { module: 'proxy_nodes' }
|
|
|
|
|
},
|
2026-02-02 03:16:52 +08:00
|
|
|
{
|
|
|
|
|
path: 'gemini-files',
|
|
|
|
|
name: 'GeminiFilesManagement',
|
|
|
|
|
component: () => importWithRetry(() => import('@/views/admin/GeminiFilesManagement.vue'))
|
|
|
|
|
},
|
|
|
|
|
// 保留旧路由兼容性
|
2026-01-31 19:11:25 +08:00
|
|
|
{
|
|
|
|
|
path: 'video-tasks',
|
2026-02-02 03:16:52 +08:00
|
|
|
redirect: '/admin/async-tasks'
|
2025-12-10 20:52:44 +08:00
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
const router = createRouter({
|
|
|
|
|
history: createWebHistory(import.meta.env.BASE_URL),
|
|
|
|
|
routes
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
router.beforeEach(async (to, from, next) => {
|
|
|
|
|
const authStore = useAuthStore()
|
2026-01-16 16:14:12 +08:00
|
|
|
const moduleStore = useModuleStore()
|
2025-12-10 20:52:44 +08:00
|
|
|
|
|
|
|
|
try {
|
2026-02-14 20:06:38 +08:00
|
|
|
const isAuthenticated = await ensureUserLoaded(authStore)
|
2025-12-10 20:52:44 +08:00
|
|
|
|
2026-02-14 20:06:38 +08:00
|
|
|
// 首页重定向
|
|
|
|
|
const homeRedirect = resolveHomeRedirect(to, from, authStore)
|
|
|
|
|
if (homeRedirect !== null) return next(homeRedirect === '' ? undefined : homeRedirect)
|
|
|
|
|
|
|
|
|
|
// 需要认证但未认证
|
2025-12-10 20:52:44 +08:00
|
|
|
const requiresAuth = to.matched.some(record => record.meta.requiresAuth !== false)
|
2026-02-14 20:06:38 +08:00
|
|
|
if (requiresAuth && !isAuthenticated) {
|
2025-12-10 20:52:44 +08:00
|
|
|
sessionStorage.setItem('redirectPath', to.fullPath)
|
|
|
|
|
log.debug('No valid token found, redirecting to home')
|
2026-02-14 20:06:38 +08:00
|
|
|
return next('/')
|
2025-12-10 20:52:44 +08:00
|
|
|
}
|
2026-02-14 20:06:38 +08:00
|
|
|
|
|
|
|
|
// 管理端检查
|
|
|
|
|
const requiresAdmin = to.matched.some(record => record.meta.requiresAdmin)
|
|
|
|
|
if (requiresAdmin) {
|
|
|
|
|
const adminRedirect = await checkAdminAccess(to, authStore, moduleStore)
|
|
|
|
|
if (adminRedirect) return next(adminRedirect)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 非管理端的模块检查
|
|
|
|
|
if (!requiresAdmin) {
|
|
|
|
|
const moduleRedirect = await checkModuleAccess(to, moduleStore)
|
|
|
|
|
if (moduleRedirect) return next(moduleRedirect)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
next()
|
2025-12-10 20:52:44 +08:00
|
|
|
} catch (error) {
|
|
|
|
|
log.error('Router guard error', error)
|
|
|
|
|
// 发生错误时,直接放行,不要乱跳转
|
|
|
|
|
next()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
export default router
|