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页面中补全逻辑。

posted @ 2024-08-10 23:32  筑潇  阅读(4)  评论(0编辑  收藏  举报