Initial commit: One Pipe System

完整的管理系统,包含账户管理、卡片管理、套餐管理、财务管理等功能模块。

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
sexygoat
2026-01-22 16:35:33 +08:00
commit 222e5bb11a
495 changed files with 145440 additions and 0 deletions

View File

@@ -0,0 +1,333 @@
import type { Router, RouteLocationNormalized, NavigationGuardNext } from 'vue-router'
import NProgress from 'nprogress'
import { useSettingStore } from '@/store/modules/setting'
import { useUserStore } from '@/store/modules/user'
import { useMenuStore } from '@/store/modules/menu'
import { setWorktab } from '@/utils/navigation'
import { setPageTitle, setSystemTheme } from '../utils/utils'
import { menuService } from '@/api/menuApi'
import { registerDynamicRoutes } from '../utils/registerRoutes'
import { AppRouteRecord } from '@/types/router'
import { RoutesAlias } from '../routesAlias'
import { menuDataToRouter } from '../utils/menuToRouter'
import { asyncRoutes } from '../routes/asyncRoutes'
import { loadingService } from '@/utils/ui'
import { useCommon } from '@/composables/useCommon'
import { useWorktabStore } from '@/store/modules/worktab'
import {
isInWhiteList,
hasRoutePermission,
isTokenValid,
buildLoginRedirect
} from './permission'
// 是否已注册动态路由
const isRouteRegistered = ref(false)
// 临时开发模式:跳过所有权限验证(开发静态页面时使用)
const DEV_MODE_SKIP_AUTH = true
/**
* 路由全局前置守卫
* 处理进度条、获取菜单列表、动态路由注册、404 检查、工作标签页及页面标题设置
*/
export function setupBeforeEachGuard(router: Router): void {
router.beforeEach(
async (
to: RouteLocationNormalized,
from: RouteLocationNormalized,
next: NavigationGuardNext
) => {
try {
// 开发模式:直接放行,无需登录
if (DEV_MODE_SKIP_AUTH) {
const settingStore = useSettingStore()
if (settingStore.showNprogress) {
NProgress.start()
}
setSystemTheme(to)
setPageTitle(to)
// 开发模式下,首次访问时注册所有路由
if (!isRouteRegistered.value) {
const menuList = asyncRoutes.map((route) => menuDataToRouter(route))
const menuStore = useMenuStore()
menuStore.setMenuList(menuList)
registerDynamicRoutes(router, menuList)
isRouteRegistered.value = true
// 重新导航到目标路由
next({ path: to.path, query: to.query, hash: to.hash, replace: true })
return
}
// 设置工作标签页
setWorktab(to)
next()
return
}
await handleRouteGuard(to, from, next, router)
} catch (error) {
console.error('路由守卫处理失败:', error)
next('/exception/500')
}
}
)
}
/**
* 处理路由守卫逻辑
*/
async function handleRouteGuard(
to: RouteLocationNormalized,
from: RouteLocationNormalized,
next: NavigationGuardNext,
router: Router
): Promise<void> {
const settingStore = useSettingStore()
const userStore = useUserStore()
// 处理进度条
if (settingStore.showNprogress) {
NProgress.start()
}
// 设置系统主题
setSystemTheme(to)
// 处理登录状态
if (!(await handleLoginStatus(to, userStore, next))) {
return
}
// 处理动态路由注册
if (!isRouteRegistered.value && userStore.isLogin) {
await handleDynamicRoutes(to, router, next)
return
}
// 处理已知的匹配路由
if (to.matched.length > 0) {
setWorktab(to)
setPageTitle(to)
next()
return
}
// 尝试刷新路由重新注册
if (userStore.isLogin) {
isRouteRegistered.value = false
await handleDynamicRoutes(to, router, next)
return
}
// 如果以上都不匹配跳转到404
next(RoutesAlias.Exception404)
}
/**
* 处理登录状态
*/
async function handleLoginStatus(
to: RouteLocationNormalized,
userStore: ReturnType<typeof useUserStore>,
next: NavigationGuardNext
): Promise<boolean> {
const { isLogin, accessToken, info } = userStore
// 如果访问的是白名单路由,直接放行
if (isInWhiteList(to.path) || to.meta.noLogin) {
// 如果已登录且访问登录页,重定向到首页
if (isLogin && to.path === RoutesAlias.Login) {
next(RoutesAlias.Home || '/')
return false
}
return true
}
// 检查是否已登录
if (!isLogin || !accessToken) {
userStore.logOut()
next(buildLoginRedirect(to.fullPath))
return false
}
// 检查 Token 是否有效
if (!isTokenValid(accessToken)) {
console.warn('Token 已过期,需要重新登录')
userStore.logOut()
next(buildLoginRedirect(to.fullPath))
return false
}
// 检查页面级权限
if (!hasRoutePermission(to, info)) {
console.warn('无权限访问该页面:', to.path)
next(RoutesAlias.Exception403 || '/exception/403')
return false
}
return true
}
/**
* 处理动态路由注册
*/
async function handleDynamicRoutes(
to: RouteLocationNormalized,
router: Router,
next: NavigationGuardNext
): Promise<void> {
try {
await getMenuData(router)
next({
path: to.path,
query: to.query,
hash: to.hash,
replace: true
})
} catch (error) {
console.error('动态路由注册失败:', error)
next('/exception/500')
}
}
/**
* 获取菜单数据
* @param router 路由实例
*/
async function getMenuData(router: Router): Promise<void> {
try {
if (useCommon().isFrontendMode.value) {
await processFrontendMenu(router) // 前端控制模式
} else {
await processBackendMenu(router) // 后端控制模式
}
} catch (error) {
handleMenuError(error)
}
}
/**
* 处理前端控制模式的菜单逻辑
*/
async function processFrontendMenu(router: Router): Promise<void> {
const closeLoading = loadingService.showLoading()
try {
const menuList = asyncRoutes.map((route) => menuDataToRouter(route))
const userStore = useUserStore()
const roles = userStore.info.roles
if (!roles || roles.length === 0) {
console.warn('用户角色信息不存在,清除登录状态')
closeLoading()
userStore.logOut()
throw new Error('获取用户角色失败')
}
const filteredMenuList = filterMenuByRoles(menuList, roles)
await new Promise((resolve) => setTimeout(resolve, 300))
await registerAndStoreMenu(router, filteredMenuList, closeLoading)
} catch (error) {
closeLoading()
throw error
}
}
/**
* 处理后端控制模式的菜单逻辑
*/
async function processBackendMenu(router: Router): Promise<void> {
const closeLoading = loadingService.showLoading()
try {
const { menuList } = await menuService.getMenuList()
await registerAndStoreMenu(router, menuList, closeLoading)
} catch (error) {
closeLoading()
throw error
}
}
/**
* 注册路由并存储菜单数据
*/
async function registerAndStoreMenu(
router: Router,
menuList: AppRouteRecord[],
closeLoading: () => void
): Promise<void> {
if (!isValidMenuList(menuList)) {
closeLoading()
throw new Error('获取菜单列表失败,请重新登录')
}
const menuStore = useMenuStore()
menuStore.setMenuList(menuList)
registerDynamicRoutes(router, menuList)
isRouteRegistered.value = true
const worktabStore = useWorktabStore()
worktabStore.validateWorktabs(router)
// 刷新后只保留固定标签页和当前标签页
worktabStore.initializeAfterRefresh()
closeLoading()
}
/**
* 处理菜单相关错误
*/
function handleMenuError(error: unknown): void {
console.error('菜单处理失败:', error)
// 确保 loading 被关闭
loadingService.hideLoading()
useUserStore().logOut()
throw error instanceof Error ? error : new Error('获取菜单列表失败,请重新登录')
}
/**
* 根据角色过滤菜单
*/
const filterMenuByRoles = (menu: AppRouteRecord[], roles: string[]): AppRouteRecord[] => {
return menu.reduce((acc: AppRouteRecord[], item) => {
const itemRoles = item.meta?.roles
const hasPermission = !itemRoles || itemRoles.some((role) => roles?.includes(role))
if (hasPermission) {
const filteredItem = { ...item }
if (filteredItem.children?.length) {
filteredItem.children = filterMenuByRoles(filteredItem.children, roles)
}
acc.push(filteredItem)
}
return acc
}, [])
}
/**
* 验证菜单列表是否有效
*/
function isValidMenuList(menuList: AppRouteRecord[]): boolean {
return Array.isArray(menuList) && menuList.length > 0
}
/**
* 重置路由相关状态
*/
export function resetRouterState(router: Router): void {
isRouteRegistered.value = false
// 清理动态注册的路由
router.getRoutes().forEach((route) => {
if (route.meta?.dynamic) {
router.removeRoute(route.name as string)
}
})
// 清空菜单数据
const menuStore = useMenuStore()
menuStore.setMenuList([])
}