Files
tyapi-frontend/src/router/index.js
2026-04-25 20:48:08 +08:00

501 lines
15 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { isCurrentOrigin, isPortalDomainConfigReady, isSubPortal, mainPortalOrigin, subPortalOrigin } from '@/constants/portal'
import { useUserStore } from '@/stores/user'
import { createRouter, createWebHistory } from 'vue-router'
import { statisticsRoutes } from './modules/statistics'
// 路由配置
const routes = [
{
path: '/',
redirect: () => (isSubPortal ? '/sub/auth/login' : '/dashboard')
},
{
path: '/auth',
component: () => import('@/layouts/AuthLayout.vue'),
meta: { requiresAuth: false },
children: [
{
path: 'login',
name: 'Login',
component: () => import('@/pages/auth/Login.vue'),
meta: { title: '登录' }
},
{
path: 'register',
name: 'Register',
component: () => import('@/pages/auth/Register.vue'),
meta: { title: '注册' }
},
{
path: 'reset',
name: 'ResetPassword',
component: () => import('@/pages/auth/ResetPassword.vue'),
meta: { title: '重置密码' }
},
{
path: 'test',
name: 'TestStore',
component: () => import('@/pages/auth/TestStore.vue'),
meta: { title: 'Store测试' }
},
{
path: 'response-test',
name: 'ResponseTest',
component: () => import('@/pages/auth/ResponseTest.vue'),
meta: { title: '响应格式测试' }
},
{
path: 'integration-test',
name: 'AuthIntegrationTest',
component: () => import('@/pages/auth/AuthIntegrationTest.vue'),
meta: { title: '认证集成测试' }
}
]
},
{
path: '/sub/auth',
component: () => import('@/layouts/AuthLayout.vue'),
meta: { requiresAuth: false },
children: [
{
path: 'login',
name: 'SubLogin',
component: () => import('@/pages/sub-portal/SubLogin.vue'),
meta: { title: '登录' }
},
{
path: 'register',
name: 'SubRegister',
component: () => import('@/pages/sub-portal/SubRegister.vue'),
meta: { title: '注册' }
},
{
path: 'reset',
name: 'SubResetPassword',
component: () => import('@/pages/auth/ResetPassword.vue'),
meta: { title: '重置密码' }
}
]
},
{
path: '/parent/subordinates',
component: () => import('@/layouts/MainLayout.vue'),
meta: { requiresAuth: true, title: '下属' },
children: [
{
path: '',
name: 'ParentSubordinates',
component: () => import('@/pages/parent/SubordinateManagement.vue'),
meta: { title: '下属' }
}
]
},
{
path: '/products',
component: () => import('@/layouts/MainLayout.vue'),
meta: { requiresAuth: true },
children: [
{
path: '',
name: 'Products',
component: () => import('@/pages/products/index.vue'),
meta: { title: '数据大厅' }
},
{
path: ':id',
name: 'ProductDetail',
component: () => import('@/pages/products/detail.vue'),
meta: { title: '产品详情' }
}
]
},
{
path: '/subscriptions',
component: () => import('@/layouts/MainLayout.vue'),
meta: { requiresAuth: true },
children: [
{
path: '',
name: 'Subscriptions',
component: () => import('@/pages/subscriptions/index.vue'),
meta: { title: '我的订阅' }
}
]
},
{
path: '/finance',
component: () => import('@/layouts/MainLayout.vue'),
meta: { requiresAuth: true },
children: [
{
path: '',
name: 'Finance',
component: () => import('@/pages/finance/Finance.vue'),
meta: { title: '财务管理' }
},
{
path: 'invoice',
name: 'Invoice',
component: () => import('@/pages/finance/Invoice.vue'),
meta: { title: '发票申请' }
},
{
path: 'wallet',
name: 'Wallet',
component: () => import('@/pages/finance/Wallet.vue'),
meta: { title: '余额充值' }
},
{
path: 'wallet/success',
name: 'WalletSuccess',
component: () => import('@/pages/finance/WalletSuccess.vue'),
meta: { title: '充值成功' }
},
{
path: 'wallet/fail',
name: 'WalletFail',
component: () => import('@/pages/finance/WalletFail.vue'),
meta: { title: '充值失败' }
},
{
path: 'wallet/processing',
name: 'WalletProcessing',
component: () => import('@/pages/finance/WalletProcessing.vue'),
meta: { title: '支付处理中' }
},
{
path: 'transactions',
name: 'Transactions',
component: () => import('@/pages/finance/Transactions.vue'),
meta: { title: '消费记录' }
},
{
path: 'recharge-records',
name: 'FinanceRechargeRecords',
component: () => import('@/pages/finance/recharge-records/index.vue'),
meta: { title: '充值记录' }
},
{
path: 'purchase-records',
name: 'FinancePurchaseRecords',
component: () => import('@/pages/finance/purchase-records/index.vue'),
meta: { title: '购买记录' }
}
]
},
{
path: '/apis',
component: () => import('@/layouts/MainLayout.vue'),
meta: { requiresAuth: true },
children: [
{
path: '',
redirect: '/api/management'
},
{
path: 'management',
name: 'ApiManagement',
component: () => import('@/pages/api/ApiManagement.vue'),
meta: { title: 'API管理', icon: 'api' }
},
{
path: 'debugger',
name: 'ApiDebugger',
component: () => import('@/pages/api/ApiDebugger.vue'),
meta: { title: '在线调试', icon: 'bug' }
},
{
path: 'usage',
name: 'ApiUsage',
component: () => import('@/pages/api/Usage.vue'),
meta: { title: '调用记录', icon: 'list' }
},
{
path: 'whitelist',
name: 'ApiWhitelist',
component: () => import('@/pages/api/WhiteList.vue'),
meta: { title: '白名单管理', icon: 'shield' }
}
]
},
{
path: '/profile',
component: () => import('@/layouts/MainLayout.vue'),
meta: { requiresAuth: true },
children: [
{
path: '',
name: 'Profile',
component: () => import('@/pages/profile/Profile.vue'),
meta: { title: '账户中心' }
},
{
path: 'settings',
name: 'Settings',
component: () => import('@/pages/profile/Settings.vue'),
meta: { title: '我的订阅' }
},
{
path: 'certification',
name: 'Certification',
component: () => import('@/pages/certification/index.vue'),
meta: { title: '企业入驻' },
}
]
},
{
path: '/file-upload-test',
component: () => import('@/layouts/MainLayout.vue'),
meta: { requiresAuth: false },
children: [
{
path: '',
name: 'FileUploadTest',
component: () => import('@/pages/FileUploadTest.vue'),
meta: { title: '文件上传组件测试' }
}
]
},
{
path: '/admin',
component: () => import('@/layouts/MainLayout.vue'),
meta: { requiresAuth: true, requiresAdmin: true },
children: [
{
path: '',
redirect: '/admin/products'
},
{
path: 'products',
name: 'AdminProducts',
component: () => import('@/pages/admin/products/index.vue'),
meta: { title: '产品管理' }
},
{
path: 'categories',
name: 'AdminCategories',
component: () => import('@/pages/admin/categories/index.vue'),
meta: { title: '分类管理' }
},
{
path: 'subscriptions',
name: 'AdminSubscriptions',
component: () => import('@/pages/admin/subscriptions/index.vue'),
meta: { title: '订阅管理' }
},
{
path: 'usage',
name: 'AdminUsage',
component: () => import('@/pages/admin/usage/index.vue'),
meta: { title: 'API调用记录管理' }
},
{
path: 'transactions',
name: 'AdminTransactions',
component: () => import('@/pages/admin/transactions/index.vue'),
meta: { title: '消费记录管理' }
},
{
path: 'recharge-records',
name: 'AdminRechargeRecords',
component: () => import('@/pages/admin/recharge-records/index.vue'),
meta: { title: '充值记录管理' }
},
{
path: 'users',
name: 'AdminUsers',
component: () => import('@/pages/admin/users/index.vue'),
meta: { title: '用户管理' }
},
{
path: 'invoices',
name: 'AdminInvoices',
component: () => import('@/pages/admin/invoices/index.vue'),
meta: { title: '发票管理' }
},
{
path: 'articles',
name: 'AdminArticles',
component: () => import('@/pages/admin/articles/index.vue'),
meta: { title: '文章管理' }
},
{
path: 'announcements',
name: 'AdminAnnouncements',
component: () => import('@/pages/admin/announcements/index.vue'),
meta: { title: '公告管理' }
},
{
path: 'statistics',
name: 'AdminStatistics',
component: () => import('@/pages/admin/statistics/SystemStatisticsPage.vue'),
meta: { title: '系统统计' }
},
{
path: 'ui-components',
name: 'AdminUIComponents',
component: () => import('@/pages/admin/ui-components/index.vue'),
meta: { title: '组件管理' }
},
{
path: 'purchase-records',
name: 'AdminPurchaseRecords',
component: () => import('@/pages/admin/purchase-records/index.vue'),
meta: { title: '购买记录管理' }
},
{
path: 'certification-reviews',
name: 'AdminCertificationReviews',
component: () => import('@/pages/admin/certification-reviews/index.vue'),
meta: { title: '企业审核' }
}
]
},
// 统计路由
...statisticsRoutes,
{
path: '/certification/callback/:scene',
component: () => import('@/pages/certification/IframeCallback.vue'),
meta: { requiresAuth: false },
},
{
path: '/:pathMatch(.*)*',
name: 'NotFound',
component: () => import('@/pages/error/NotFound.vue'),
meta: { title: '页面不存在' }
}
]
// 创建路由实例
const router = createRouter({
history: createWebHistory(),
routes,
scrollBehavior(to, from, savedPosition) {
if (savedPosition) {
return savedPosition
} else {
return { top: 0 }
}
}
})
// 路由守卫
router.beforeEach(async (to, from, next) => {
const userStore = useUserStore()
const loginPathByRoute = to.path.startsWith('/sub/') ? '/sub/auth/login' : '/auth/login'
const isSubAuthRoute = to.path.startsWith('/sub/auth')
const isMainAuthRoute = to.path.startsWith('/auth')
const subAuthToMainAuthPath = to.path.replace('/sub/auth', '/auth')
const mainAuthToSubAuthPath = to.path.replace('/auth', '/sub/auth')
// 域名级认证路由隔离:子域只允许 /sub/auth/*,主域禁止 /sub/auth/*
if (isPortalDomainConfigReady) {
const onSubDomain = isCurrentOrigin(subPortalOrigin)
const onMainDomain = isCurrentOrigin(mainPortalOrigin)
if (onSubDomain && isMainAuthRoute) {
next(mainAuthToSubAuthPath)
return
}
if (onMainDomain && isSubAuthRoute) {
next(subAuthToMainAuthPath)
return
}
} else if (isSubPortal && isMainAuthRoute) {
// 子站壳模式下,即使未配置双域名,也只允许进入 /sub/auth/*
next(mainAuthToSubAuthPath)
return
}
// 对于不需要认证的路由(如登录页),不等待初始化,直接放行
const isAuthRoute = to.path.startsWith('/auth') || to.path.startsWith('/sub/auth')
const requiresAuth = to.meta.requiresAuth
// 只有在需要认证的路由上才等待初始化
if (requiresAuth && !userStore.initialized) {
await userStore.init()
} else if (!userStore.initialized) {
// 对于不需要认证的路由,异步初始化但不阻塞
userStore.init().catch(err => {
console.warn('UserStore初始化失败:', err)
})
}
// 设置页面标题
if (to.meta.title) {
document.title = `${to.meta.title} - 天远数据控制台`
}
// 检查是否需要认证
if (requiresAuth && !userStore.isLoggedIn) {
next(loginPathByRoute)
return
}
// 检查管理员权限
if (to.meta.requiresAdmin && !userStore.isAdmin) {
next('/products')
return
}
// 登录态下若未配置主/子域名,降级为单域运行(不强制登出)
// 否则会出现“登录成功后立即被清会话”的问题。
if (userStore.isLoggedIn && !isPortalDomainConfigReady && import.meta.env.PROD) {
console.warn('[router] 未配置主/子域名,当前以单域模式运行,跳过域名隔离跳转')
}
// 域名隔离:子账号登录态必须在子账号专属域名
if (userStore.isLoggedIn && userStore.accountKind === 'subordinate' && subPortalOrigin && !isCurrentOrigin(subPortalOrigin)) {
window.location.replace(`${subPortalOrigin}${to.fullPath}`)
return
}
// 域名隔离:普通/管理员账号不应停留在子账号专属域名
if (userStore.isLoggedIn && userStore.accountKind !== 'subordinate' && subPortalOrigin && isCurrentOrigin(subPortalOrigin)) {
if (mainPortalOrigin) {
window.location.replace(`${mainPortalOrigin}${to.fullPath}`)
return
}
next('/products')
return
}
// 已登录用户访问认证页面,按账号类型重定向到对应首页
if (isAuthRoute && userStore.isLoggedIn) {
next(userStore.accountKind === 'subordinate' ? '/subscriptions' : '/products')
return
}
// 子站壳:禁止进入主站登录/注册路由(可合并子账号入口到 /sub/auth
if (isSubPortal && (to.path.startsWith('/auth/login') || to.path.startsWith('/auth/register'))) {
next(to.path.replace('/auth/', '/sub/auth/'))
return
}
// 主站与「子站壳」共仓:/sub/auth/* 始终可用,子账号注册/登录与主站 /auth 并存,邀请链接不依赖单独构建
// 下属账号不可进入主账号「下属」管理页
if (to.path.startsWith('/parent/subordinates') && userStore.accountKind === 'subordinate') {
next('/subscriptions')
return
}
// 下属账号不允许访问仪表盘
if (to.path.startsWith('/dashboard') && userStore.accountKind === 'subordinate') {
next('/subscriptions')
return
}
next()
})
// 路由后置守卫
router.afterEach(() => {
// 可以在这里添加路由切换后的逻辑
// 比如埋点统计、页面访问记录等
})
export default router