Files
one-pipe-system/src/router/guards/permission.ts
sexygoat 237eeed87a
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 6m54s
修复:原来的统一去了404, 现在加了403, 和部分bug
2026-03-03 16:26:04 +08:00

217 lines
5.6 KiB
TypeScript
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 type { RouteLocationNormalized } from 'vue-router'
import type { UserInfo } from '@/types/api'
import { useUserStore } from '@/store/modules/user'
/**
* 不需要登录的路由白名单
*/
export const LOGIN_WHITE_LIST = [
'/auth/login',
'/auth/register',
'/auth/forget-password',
'/exception/403',
'/exception/404',
'/exception/500'
]
/**
* 检查路由是否在白名单中
*/
export const isInWhiteList = (path: string): boolean => {
return LOGIN_WHITE_LIST.some((whitePath) => {
if (whitePath.endsWith('*')) {
// 支持通配符匹配
const prefix = whitePath.slice(0, -1)
return path.startsWith(prefix)
}
return path === whitePath
})
}
/**
* 检查用户是否有权限访问路由
* 根据文档要求:菜单访问基于 URL按钮访问基于权限标识
*/
export const hasRoutePermission = (
route: RouteLocationNormalized,
userInfo: Partial<UserInfo>
): boolean => {
const { roles = [], permissions = [] } = userInfo
// 如果是超级管理员,直接通过
if (userInfo.user_type === 1) {
return true
}
// 默认始终可访问的路由(不需要菜单权限)
const defaultAllowedPaths = ['/dashboard']
const isDefaultRoute = defaultAllowedPaths.some(
(allowedPath) => route.path === allowedPath || route.path.startsWith(allowedPath + '/')
)
// 检查菜单访问权限(基于 URL
const userStore = useUserStore()
const userMenus = userStore.menus || []
// 如果是默认路由,跳过菜单权限检查
if (!isDefaultRoute && userMenus.length > 0) {
const hasMenuAccess = checkMenuAccess(route.path, userMenus)
if (!hasMenuAccess) {
return false
}
}
// 如果路由没有设置额外的权限要求,直接通过
if (!route.meta?.roles && !route.meta?.permissions) {
return true
}
// 检查角色权限
if (route.meta.roles) {
const routeRoles = route.meta.roles as string[]
const hasRole = routeRoles.some((role) => roles.includes(role as any))
if (!hasRole) {
return false
}
}
// 检查操作权限
if (route.meta.permissions) {
const routePermissions = route.meta.permissions as string[]
const hasPermission = routePermissions.some((permission) => {
if (permissions.includes('*:*:*')) {
return true
}
return permissions.some((userPermission) => {
if (userPermission.endsWith('*')) {
const prefix = userPermission.slice(0, -1)
return permission.startsWith(prefix)
}
return userPermission === permission
})
})
if (!hasPermission) {
return false
}
}
return true
}
/**
* 递归检查菜单访问权限(基于 URL
*/
function checkMenuAccess(path: string, menus: any[]): boolean {
for (const menu of menus) {
// 检查当前菜单的 URL 是否匹配(支持动态参数)
if (menu.url && matchMenuPath(path, menu.url)) {
console.log(`[菜单权限检查] 路径 ${path} 匹配菜单 ${menu.url}`)
return true
}
// 递归检查子菜单
if (menu.children && menu.children.length > 0) {
if (checkMenuAccess(path, menu.children)) {
return true
}
}
}
console.log(`[菜单权限检查] 路径 ${path} 未找到匹配的菜单`)
return false
}
/**
* 匹配菜单路径,支持动态参数
* @param actualPath 实际访问路径,如 /account-management/enterprise-customer/customer-accounts/3000
* @param menuPath 菜单定义路径,如 /account-management/enterprise-customer/customer-accounts/:id
*/
function matchMenuPath(actualPath: string, menuPath: string): boolean {
// 移除查询参数
const cleanActualPath = actualPath.split('?')[0]
const cleanMenuPath = menuPath.split('?')[0]
// 如果完全匹配,直接返回
if (cleanActualPath === cleanMenuPath) {
return true
}
// 将路径分割成段
const actualSegments = cleanActualPath.split('/').filter(Boolean)
const menuSegments = cleanMenuPath.split('/').filter(Boolean)
// 段数必须相同
if (actualSegments.length !== menuSegments.length) {
return false
}
// 逐段比较
for (let i = 0; i < menuSegments.length; i++) {
const menuSegment = menuSegments[i]
const actualSegment = actualSegments[i]
// 如果是动态参数(以 : 开头),跳过比较
if (menuSegment.startsWith(':')) {
continue
}
// 如果不是动态参数,必须完全匹配
if (menuSegment !== actualSegment) {
return false
}
}
return true
}
/**
* 检查 Token 是否有效
* 简单检查,真实项目中应该验证 JWT 或者调用后端接口
*/
export const isTokenValid = (token: string): boolean => {
if (!token) return false
// Mock Token 格式: mock_token_{key}_{timestamp}
if (token.startsWith('mock_token_')) {
// Mock Token 永不过期(开发环境)
return true
}
// 真实 Token 可以在这里添加 JWT 解析和过期检查
// 例如:
// try {
// const decoded = jwt_decode(token)
// const isExpired = decoded.exp * 1000 < Date.now()
// return !isExpired
// } catch {
// return false
// }
return true
}
/**
* 获取重定向路径
* 登录后跳转到之前访问的页面
*/
export const getRedirectPath = (route: RouteLocationNormalized): string => {
const redirect = route.query.redirect as string
if (redirect && !isInWhiteList(redirect)) {
return redirect
}
return '/'
}
/**
* 构建登录重定向 URL
*/
export const buildLoginRedirect = (currentPath: string): string => {
if (isInWhiteList(currentPath)) {
return '/auth/login'
}
return `/auth/login?redirect=${encodeURIComponent(currentPath)}`
}