Files
tyapi-frontend/src/router/index.js

501 lines
15 KiB
JavaScript
Raw Normal View History

2026-04-25 20:48:08 +08:00
import { isCurrentOrigin, isPortalDomainConfigReady, isSubPortal, mainPortalOrigin, subPortalOrigin } from '@/constants/portal'
2025-11-24 16:06:44 +08:00
import { useUserStore } from '@/stores/user'
import { createRouter, createWebHistory } from 'vue-router'
import { statisticsRoutes } from './modules/statistics'
// 路由配置
const routes = [
{
path: '/',
2026-04-25 11:59:14 +08:00
redirect: () => (isSubPortal ? '/sub/auth/login' : '/dashboard')
2025-11-24 16:06:44 +08:00
},
{
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: '认证集成测试' }
}
]
},
2026-04-25 11:59:14 +08:00
{
path: '/sub/auth',
component: () => import('@/layouts/AuthLayout.vue'),
meta: { requiresAuth: false },
children: [
{
path: 'login',
name: 'SubLogin',
component: () => import('@/pages/sub-portal/SubLogin.vue'),
2026-04-25 20:48:08 +08:00
meta: { title: '登录' }
2026-04-25 11:59:14 +08:00
},
{
path: 'register',
name: 'SubRegister',
component: () => import('@/pages/sub-portal/SubRegister.vue'),
2026-04-25 20:48:08 +08:00
meta: { title: '注册' }
2026-04-25 11:59:14 +08:00
},
{
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: '下属' }
}
]
},
2025-11-24 16:06:44 +08:00
{
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: '充值记录' }
2025-12-22 18:32:34 +08:00
},
{
path: 'purchase-records',
name: 'FinancePurchaseRecords',
component: () => import('@/pages/finance/purchase-records/index.vue'),
meta: { title: '购买记录' }
2025-11-24 16:06:44 +08:00
}
]
},
{
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: '文章管理' }
},
2025-12-06 13:53:58 +08:00
{
path: 'announcements',
name: 'AdminAnnouncements',
component: () => import('@/pages/admin/announcements/index.vue'),
meta: { title: '公告管理' }
},
2025-11-24 16:06:44 +08:00
{
path: 'statistics',
name: 'AdminStatistics',
component: () => import('@/pages/admin/statistics/SystemStatisticsPage.vue'),
meta: { title: '系统统计' }
2025-12-19 16:57:49 +08:00
},
{
path: 'ui-components',
name: 'AdminUIComponents',
component: () => import('@/pages/admin/ui-components/index.vue'),
meta: { title: '组件管理' }
2025-12-22 18:32:34 +08:00
},
{
path: 'purchase-records',
name: 'AdminPurchaseRecords',
component: () => import('@/pages/admin/purchase-records/index.vue'),
meta: { title: '购买记录管理' }
2026-03-17 17:19:00 +08:00
},
{
path: 'certification-reviews',
name: 'AdminCertificationReviews',
component: () => import('@/pages/admin/certification-reviews/index.vue'),
meta: { title: '企业审核' }
2025-11-24 16:06:44 +08:00
}
]
},
// 统计路由
...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()
2026-04-25 11:59:14 +08:00
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
}
2025-11-24 16:06:44 +08:00
2025-12-04 12:44:54 +08:00
// 对于不需要认证的路由(如登录页),不等待初始化,直接放行
2026-04-25 11:59:14 +08:00
const isAuthRoute = to.path.startsWith('/auth') || to.path.startsWith('/sub/auth')
2025-12-04 12:44:54 +08:00
const requiresAuth = to.meta.requiresAuth
// 只有在需要认证的路由上才等待初始化
if (requiresAuth && !userStore.initialized) {
2025-11-24 16:06:44 +08:00
await userStore.init()
2025-12-04 12:44:54 +08:00
} else if (!userStore.initialized) {
// 对于不需要认证的路由,异步初始化但不阻塞
userStore.init().catch(err => {
console.warn('UserStore初始化失败:', err)
})
2025-11-24 16:06:44 +08:00
}
// 设置页面标题
if (to.meta.title) {
document.title = `${to.meta.title} - 天远数据控制台`
}
// 检查是否需要认证
2025-12-04 12:44:54 +08:00
if (requiresAuth && !userStore.isLoggedIn) {
2026-04-25 11:59:14 +08:00
next(loginPathByRoute)
2025-11-24 16:06:44 +08:00
return
}
// 检查管理员权限
if (to.meta.requiresAdmin && !userStore.isAdmin) {
next('/products')
return
}
2026-04-25 20:48:08 +08:00
// 登录态下若未配置主/子域名,降级为单域运行(不强制登出)
// 否则会出现“登录成功后立即被清会话”的问题。
if (userStore.isLoggedIn && !isPortalDomainConfigReady && import.meta.env.PROD) {
console.warn('[router] 未配置主/子域名,当前以单域模式运行,跳过域名隔离跳转')
2026-04-25 11:59:14 +08:00
}
// 域名隔离:子账号登录态必须在子账号专属域名
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
}
2025-11-24 16:06:44 +08:00
next('/products')
return
}
2026-04-25 11:59:14 +08:00
// 已登录用户访问认证页面,按账号类型重定向到对应首页
if (isAuthRoute && userStore.isLoggedIn) {
2026-04-25 20:48:08 +08:00
next(userStore.accountKind === 'subordinate' ? '/subscriptions' : '/products')
2026-04-25 11:59:14 +08:00
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
}
2025-11-24 16:06:44 +08:00
next()
})
// 路由后置守卫
router.afterEach(() => {
// 可以在这里添加路由切换后的逻辑
// 比如埋点统计、页面访问记录等
})
export default router