SPA应用在登录状态无刷新状态实时执行权更新
最近有空,撸了一下曾经工作中一直想优化的功能:SPA应用在登录状态无刷新状态实时执行权更新。
因为之前项目中的用户前端权限有三种(页面菜单、按钮增删改查、接口数据权限),这次先优化第一种页面菜单。
demo的技术栈是:ts5.4、vue3、vue-router4、vite5.4、pinia2,项目目录如下:

其中全量路由列表常量保存在@/constants/router/Routers.ts里:
export const Routers = [ { path: '/parentA', name: 'parentA', component: () => import('@/views/ParentA/ParentAView.vue'), children: [ { path: 'childA', name: 'parentAChildA', component: () => import('@/views/ParentA/ParentAChildAView.vue'), meta: {} }, { path: 'childB', name: 'parentAChildB', component: () => import('@/views/ParentA/ParentAChildBView.vue'), meta: {} } ] }, { path: '/parentB', name: 'parentB', component: () => import('@/views/ParentB/ParentBView.vue'), children: [ { path: 'childA', name: 'parentBChildA', component: () => import('@/views/ParentB/ParentBChildAView.vue'), meta: {} }, { path: 'childB', name: 'parentBChildB', component: () => import('@/views/ParentB/ParentBChildBView.vue'), meta: {} } ] } ]
更新路由hook核心代码:src/hooks/routers.ts
import type { Router, RouteRecordRaw } from 'vue-router'
import { Routers as TotalTemplateRouters } from '@/constants/router/Routers'
let menus: any[] = []
function removeRoutePro(routerInstance: Router, currentRoutes: RouteRecordRaw[], name: string) {
routerInstance.removeRoute(name);
const index = currentRoutes.findIndex((route) => route.name === name);
index >= 0 && currentRoutes.splice(index, 1);
}
function mergeRouterTree(
routerInstance: Router,
samelevelTemplateRoutes: RouteRecordRaw[],
currentRoutes: RouteRecordRaw[],
currentRouteParentRoute?: RouteRecordRaw
) {
;(samelevelTemplateRoutes as RouteRecordRaw[])?.forEach?.((TemplateRoute) => {
const TemplateRouteChildren = TemplateRoute.children || [];
const currentRoute = currentRoutes.find((currentRoute) => currentRoute.name === TemplateRoute.name)
const currentRouteMenu = menus.find((menu) => menu.name === TemplateRoute.name)
/**
* *********************** 一、如果没有菜单权限,执行删除****************************
*/
if (!currentRouteMenu) {
TemplateRoute.name && removeRoutePro(routerInstance, currentRoutes, TemplateRoute.name as string);
return
}
/**
* *********************** 二、如果有菜单权限 *************************************
*/
// 1) 当前路由树中不存在此路由,执行新增
if (!currentRoute) {
// 如果有children就继续递归
const children = [...TemplateRouteChildren];
const templateRouteCopy = {...TemplateRoute, children};
if (currentRouteParentRoute?.name) {
routerInstance.addRoute(currentRouteParentRoute.name, templateRouteCopy)
} else {
routerInstance.addRoute(templateRouteCopy)
}
mergeRouterTree(routerInstance, TemplateRouteChildren, children, templateRouteCopy);
return
}
// 2) 当前路由树中存在此路由,执行更新children
// 如果有children就继续递归
TemplateRouteChildren?.length > 0 &&
mergeRouterTree(routerInstance, TemplateRouteChildren, currentRoute.children || [], currentRoute);
})
}
// 需要测试parentA=>childrenB更新到parentA=>childrenC的情况下childrenB是否被消除。
function updateRouterTree(routerInstance: Router) {
mergeRouterTree(
routerInstance,
TotalTemplateRouters,
routerInstance.getRoutes()
)
/**
* ********如果当前访问页面的route在更新routeTree之后被删除了,需要replace到无权限/404页面。************
*/
// code*************
}
export const useRouters = async (routerInstance: Router, _menus: any[] = []) => {
menus = _menus
updateRouterTree(routerInstance)
return { }
}
后续通过在合适的时间点去调用useRouters(routerInstance, menus);方法更新Router树,其中请求后台获取的menus数据的结构通常是扁平的:
// 用例:
useRouters(routerInstance, [ { path: '/parentA', name: 'parentA', }, { path: 'childB', name: 'parentAChildB', meta: {} }, { path: '/parentB', name: 'parentB', }, { path: 'childB', name: 'parentBChildB', meta: {} } ]); // Router树更新为: [ '/', '/parentA', '/parentA/childB', '/parentB', '/parentB/childB'] useRouters(routerInstance, [ { path: '/parentA', name: 'parentA', }, { path: 'childA', name: 'parentAChildA', meta: {} }, { path: '/parentB', name: 'parentB', }, { path: 'childA', name: 'parentBChildA', meta: {} } ]); // Router树更新为: [ '/', '/parentA', '/parentA/childA', '/parentB', '/parentB/childA'] // 通过merge的形式去掉了第一次新增的'parentAChildB'和'parentBChildB'子路由
用例中使用mock数据调用了两次useRouters方法,
第一次Router树更新为: [ '/', '/parentA', '/parentA/childB', '/parentB', '/parentB/childB']
第二次Router树更新为: [ '/', '/parentA', '/parentA/childA', '/parentB', '/parentB/childA']
useRouters通过merge的形式去掉了第一次新增的'parentAChildB'和'parentBChildB'子路由,访问这两个路由会重定向到404页面,
如果需要区分“访问路由资源404”和“缺失访问路由权限”可以进一步完善在updateRouterTree方法逻辑,并配合在404页面中补全逻辑。

浙公网安备 33010602011771号