
import { defineStore } from 'pinia'
export const usePermissionStore = defineStore('permission', {
state: () => ({
permissionData: null, // 存储原始权限数据
authorizedRoutes: [], // 过滤后的前端路由
}),
actions: {
setPermissionData(data) {
this.permissionData = data
},
setAuthorizedRoutes(routes) {
this.authorizedRoutes = routes
},
clearSessionData() {
this.permissionData = null
this.authorizedRoutes = []
},
},
})
export default usePermissionStore
router/index
import 'nprogress/nprogress.css'
import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router'
import { baseRoutes } from './baseRoutes'
import PageLayout from '@/layout/page-layout.vue'
import { staticRoutes } from './staticRoutes'
import { dynamicRoutes } from './dynamicRoutes'
import usePermissionStore from '@/store/modules/permission'
import { toRaw } from 'vue'
// 需要 PageLayout 的路由
const layoutRoutes: RouteRecordRaw[] = [
{
path: '/',
name: 'root',
component: PageLayout,
children: [
...staticRoutes, // 静态路由始终注册
// 动态路由初始为空,后续通过 addRoute 添加
],
},
]
const router = createRouter({
history: createWebHistory(),
routes: [...baseRoutes, ...layoutRoutes],
scrollBehavior() {
return { top: 0 }
},
})
export function mergeDynamicRoutes() {
const store = usePermissionStore()
const authorizedRoutes = store.authorizedRoutes || []
// 查找 root 路由
const rootRoute = router.getRoutes().find((route) => route.name === 'root')
if (!rootRoute) {
console.error('未找到 root 路由')
return
}
// 检查并合并动态路由到 rootRoute.children
const existingPaths = new Set(rootRoute.children.map((child) => child.path))
const newChildren = authorizedRoutes.filter((item) => !existingPaths.has(item.path))
rootRoute.children = [...(rootRoute.children || []), ...newChildren]
// 更新路由表
newChildren.forEach((item) => {
const routeIdentifier = item.path
if (!router.hasRoute(routeIdentifier)) {
console.log(`正在添加路由: ${item.path}`)
router.addRoute('root', item)
} else {
console.log(`路由已存在: ${item.path}`)
}
})
// 先移除可能已存在的404路由
if (router.hasRoute('notFound')) {
router.removeRoute('notFound')
}
router.addRoute({
path: '/:pathMatch(.*)*',
name: 'notFound',
component: () => import('@/views/not-found/index.vue'),
})
console.log('合并后的 rootRoute.children:', rootRoute)
}
export default router
// src/router/guard/index.ts
import NProgress from 'nprogress'
import type { Router } from 'vue-router'
import { setupTokenGuard } from './token'
import { setupWhiteListGuard } from './whiteList'
import usePermissionStore from '@/store/modules/permission'
import { usePermissionRoutes } from '@/hooks/useAuthPermission'
export function setupRouterGuard(router: Router) {
router.beforeEach(async (to, from, next) => {
NProgress.start()
const guards = [setupWhiteListGuard, setupTokenGuard]
try {
const results = await Promise.all(guards.map((guard) => guard(to, from, next)))
if (results.includes(false)) {
return
}
const store = usePermissionStore()
// 这里很重要,登录成功后调用这个
if (store.authorizedRoutes.length === 0) {
const { fetchPermissionRoutes } = usePermissionRoutes()
await fetchPermissionRoutes()
next({ ...to }) // 避免刷新404
return
}
next()
} catch (error) {
console.error('Router guard error:', error)
next('/login')
}
})
router.afterEach(() => {
NProgress.done()
})
}
// src/hooks/usePermissionRoutes.ts
import { getPermissionsUserApi } from '@/api/permission'
import usePermissionStore from '@/store/modules/permission'
import { extractPathsFromPermissionTree, filterAuthorizedRoutes } from '@/utils/routerUtils'
import { dynamicRoutes } from '@/router/dynamicRoutes'
import { mergeDynamicRoutes } from '@/router'
/**
* 获取用户权限路由的Hook
*/
export function usePermissionRoutes() {
const store = usePermissionStore()
const fetchPermissionRoutes = async () => {
try {
const res = await getPermissionsUserApi()
const data = res.data
// 1. 存储原始权限数据
store.setPermissionData(data)
// 2. 提取有效路径,这里需要注意路由怎么返回的
// 情况差不多的话,前端表如果有嵌套路由,那么详情页别带/
const permissionPaths = extractPathsFromPermissionTree(data.permissionTree)
// 3. 过滤出有权限的动态路由
const authorizedRoutes = filterAuthorizedRoutes(dynamicRoutes, permissionPaths)
// 4. 存储过滤后的路由
store.setAuthorizedRoutes(authorizedRoutes)
// 合并路由
mergeDynamicRoutes()
} catch (error) {
console.error('获取权限失败:', error)
}
}
return {
fetchPermissionRoutes,
}
}
// src/utils/routerUtils.ts
import type { RouteRecordRaw } from 'vue-router'
export function extractPathsFromPermissionTree(permissionTree: any[]): string[] {
const paths: string[] = []
const traverse = (nodes: any[]) => {
nodes.forEach((node) => {
// 处理MENU_DIR类型且webPath不为空的节点(对应路由父路径)
if ((node.type === 'MENU_DIR' || node.type === 'MENU') && node.webPath) {
paths.push(node.webPath)
}
// 递归处理子节点
if (node.children && node.children.length > 0) {
traverse(node.children)
}
})
}
traverse(permissionTree)
return paths
}
/**
* 过滤动态路由,只保留有权限的路由
*/
export function filterAuthorizedRoutes(allRoutes: RouteRecordRaw[], permissionPaths: string[]): RouteRecordRaw[] {
return allRoutes.filter((route) => {
// 去掉路径末尾的斜杠,确保匹配一致性
const normalizedRoutePath = route.path.replace(/\/$/, '')
return permissionPaths.some((path) => {
const normalizedPermissionPath = path.replace(/\/$/, '')
return normalizedRoutePath === normalizedPermissionPath
})
})
}