Skip to content

动态路由

TechUI 的动态路由机制是后台管理系统的核心。它采用 后端控制策略,即路由表并非在前端硬编码,而是根据当前登录用户的权限,由后端返回菜单数据,前端再结合本地的组件注册表,动态生成 Vue Router 路由配置。

工作原理

  1. 组件注册:前端维护一份 register.js,将路由标识(字符串)映射到真实的 Vue 组件文件。
  2. 获取菜单:用户登录后,从后端或本地数据库获取菜单树数据。
  3. 路由匹配:通过 routerPackag 函数,将菜单数据中的 label 与组件注册表匹配。
  4. 动态挂载:使用 router.addRoute 将匹配成功的组件挂载到名为 layout 的父路由下。
  5. 全局拦截:在 router.beforeEach 中处理页面刷新、鉴权、Tab 页签添加等逻辑。

核心流程解析

动态路由的逻辑主要集中在全局 Provider 组件中,仅当 isActAdminFeatures 为真时激活。

数据持久化与同步

为了防止页面刷新导致 Vuex/Pinia 状态丢失,TechUI 实现了双重持久化机制:

  • 用户信息 (UserInfo): 通过 watch 监听 $tState.ADMIN.userInfo。一旦变化,使用 tStoreCrypto(加密存储)同步到 Session/Local Storage。同时,如果发现 Token 或 ID 丢失,会自动触发登出。
  • 菜单数据 (Menu): 通过 watch 监听 $tState.ADMIN.menu。一旦获取到菜单数据,会将其写入 IndexedDB (TuiDB)。这确保了即使用户刷新页面,也能从本地数据库快速恢复庞大的菜单结构,而无需再次请求后端。

路由组装 (routerPackag)

routerPackag 是将业务数据转换为路由配置的核心函数。

javascript
// 伪代码逻辑演示
async function routerPackag(routerData){
  for(let i=0; i<routerData.length; i++){
    // 1. 解构后端数据
    let { label, icon, title, keepAlive, ... } = routerData[i];
    
    // 2. 在注册表中查找组件
    let comp = $tState.ADMIN.componentRegister[label];

    // 3. 如果不是父节点且组件存在
    if(!isParent && comp){
      // 4. 动态添加路由到 'layout' 父节点
      router.addRoute("layout", {
        path: '/' + label, // 默认路径为 /label
        name: label,       // 路由名称
        component: comp,   // 对应的 Vue 组件
        meta: { ... }      // 注入 meta 信息
      });
    }
  }
}

关键点:

  • 所有动态路由都是 layout 路由的子路由。这意味着它们都会渲染在 TuiAdminLayout<router-view> 中。
  • 路由的 namepath 均基于 label 生成,确保唯一性。

全局路由拦截 (Guard)

router.beforeEach 承担了“守门员”的角色,处理了复杂的初始化逻辑:

阶段一:状态恢复

  • 用户恢复:如果状态树中没有 UserID,尝试从本地存储读取 Token 恢复登录态。
  • 菜单恢复:如果状态树中没有菜单数据,尝试从 IndexedDB (TuiDB) 中读取缓存菜单。

阶段二:动态加载

  • 初始化检查:检查 $ARouterInited 标记。
  • 执行加载:如果未初始化,调用 routerPackag($AMenu.value) 执行路由挂载。
  • 状态翻转:挂载完成后,将 $ADMIN.value.routerInited 设为 true,并重新触发路由跳转(next({ ...to }))以确保新路由生效。

阶段三:业务处理

  • Tab 管理:如果目标路由有效且设置了 label,会自动调用 tabAdd(to) 将其加入顶部页签栏。
  • 缓存管理:如果 meta.keepAlive 为真,会自动将其 name 加入 $AKeepAlive 列表。
  • 鉴权失败:如果上述检查均未通过(无 Token、无 ID),则强制重定向至 /login

后端数据结构规范

为了配合动态路由,后端返回的菜单数据(或本地 Mock 数据)应符合以下 JSON 结构:

json
[
  {
    "label": "dashboard",      // 核心字段:对应 register.js 中的键名
    "title": "仪表盘",         // 页面标题
    "icon": "ti-dashboard",    // 菜单图标
    "isParent": false,         // 是否为父级菜单目录
    "keepAlive": true,         // 是否开启页面缓存
    "hideInMenu": false,       // 是否在侧边栏隐藏
    "hideInTab": false,        // 是否在 Tab 栏隐藏
    "parentId": "root"         // 父节点 ID
  }
]

常见问题

Q: 为什么刷新页面后显示 404?A: 通常是因为动态路由尚未加载完成。TechUI 的 beforeEach 逻辑中处理了这种情况:在刷新时,它会先从 IndexedDB 恢复菜单并重新注册路由,然后再执行跳转。如果出现 404,请检查 IndexedDB 是否成功写入了 menus 数据。

Q: 新增页面如何配置?A: 需要两步:

  1. 在前端 register.js 中引入 .vue 文件并导出。
  2. 在后端(或 Mock 数据)中添加对应的菜单项,确保 labelregister.js 中的键名一致。