当前位置: 首页 > news >正文

使用AI一步一步实现若依前端(7)

功能7:路由全局前置守卫

功能6:动态添加路由记录
功能5:侧边栏菜单动态显示
功能4:首页使用Layout布局
功能3:点击登录按钮实现页面跳转
功能2:静态登录界面
功能1:创建前端项目

前言

在若依里,addRoute方法是在前置守卫里被调用的。要能实现在浏览器刷新后,能重新从后端服务器获取数据再动态添加。处理逻辑如下:

浏览器刷新 前置守卫 Pinia 后端API 路由器 浏览器 触发 beforeEach 检查权限数据 数据已丢失 重新获取权限 返回权限数据 addRoute 动态路由 完成路由跳转 浏览器刷新 前置守卫 Pinia 后端API 路由器 浏览器

不能像昨天的代码那样,在layout/index.vue这个地方调用。会导致浏览器刷新后,动态添加的路由记录丢失。

一.操作步骤

1. 全局前置守卫设置

在 Vue Router 中通过 router.beforeEach 注册全局前置守卫,用于实现路由拦截和权限控制:
新建文件:src/permission.js

import router from './router'
import Layout from '@/layout/index.vue'
// 匹配views里面所有的.vue文件
const modules = import.meta.glob('@/views/**/*.vue')

router.beforeEach((to, from, next) => {
    console.log(router.getRoutes())
    if (router.getRoutes().length <= 3) {
        const data1 = {"name": "System","path": "/system","hidden": false,"redirect": "noRedirect","component": "Layout","alwaysShow": true,"meta": {"title": "系统管理","icon": "system","noCache": false,"link": null},"children": [{"name": "User","path": "user","hidden": false,"component": "system/user/index","meta": {"title": "用户管理","icon": "user","noCache": false,"link": null}},{"name": "Role","path": "role","hidden": false,"component": "system/role/index","meta": {"title": "角色管理","icon": "peoples","noCache": false,"link": null}},{"name": "Menu","path": "menu","hidden": false,"component": "system/menu/index","meta": {"title": "菜单管理","icon": "tree-table","noCache": false,"link": null}},{"name": "Dept","path": "dept","hidden": false,"component": "system/dept/index","meta": {"title": "部门管理","icon": "tree","noCache": false,"link": null}},{"name": "Post","path": "post","hidden": false,"component": "system/post/index","meta": {"title": "岗位管理","icon": "post","noCache": false,"link": null}},{"name": "Dict","path": "dict","hidden": false,"component": "system/dict/index","meta": {"title": "字典管理","icon": "dict","noCache": false,"link": null}},{"name": "Config","path": "config","hidden": false,"component": "system/config/index","meta": {"title": "参数设置","icon": "edit","noCache": false,"link": null}},{"name": "Notice","path": "notice","hidden": false,"component": "system/notice/index","meta": {"title": "通知公告","icon": "message","noCache": false,"link": null}},{"name": "Log","path": "log","hidden": false,"redirect": "noRedirect","component": "ParentView","alwaysShow": true,"meta": {"title": "日志管理","icon": "log","noCache": false,"link": null},"children": [{"name": "Operlog","path": "operlog","hidden": false,"component": "monitor/operlog/index","meta": {"title": "操作日志","icon": "form","noCache": false,"link": null}},{"name": "Logininfor","path": "logininfor","hidden": false,"component": "monitor/logininfor/index","meta": {"title": "登录日志","icon": "logininfor","noCache": false,"link": null}}]}]}
        const data2 = { "name": "Monitor", "path": "/monitor", "hidden": false, "redirect": "noRedirect", "component": "Layout", "alwaysShow": true, "meta": { "title": "系统监控", "icon": "monitor", "noCache": false, "link": null }, "children": [{ "name": "Online", "path": "online", "hidden": false, "component": "monitor/online/index", "meta": { "title": "在线用户", "icon": "online", "noCache": false, "link": null } }, { "name": "Job", "path": "job", "hidden": false, "component": "monitor/job/index", "meta": { "title": "定时任务", "icon": "job", "noCache": false, "link": null } }, { "name": "Druid", "path": "druid", "hidden": false, "component": "monitor/druid/index", "meta": { "title": "数据监控", "icon": "druid", "noCache": false, "link": null } }, { "name": "Server", "path": "server", "hidden": false, "component": "monitor/server/index", "meta": { "title": "服务监控", "icon": "server", "noCache": false, "link": null } }, { "name": "Cache", "path": "cache", "hidden": false, "component": "monitor/cache/index", "meta": { "title": "缓存监控", "icon": "redis", "noCache": false, "link": null } }, { "name": "CacheList", "path": "cacheList", "hidden": false, "component": "monitor/cache/list", "meta": { "title": "缓存列表", "icon": "redis-list", "noCache": false, "link": null } }] }
        const data3 = { "name": "Tool", "path": "/tool", "hidden": false, "redirect": "noRedirect", "component": "Layout", "alwaysShow": true, "meta": { "title": "系统工具", "icon": "tool", "noCache": false, "link": null }, "children": [{ "name": "Build", "path": "build", "hidden": false, "component": "tool/build/index", "meta": { "title": "表单构建", "icon": "build", "noCache": false, "link": null } }, { "name": "Gen", "path": "gen", "hidden": false, "component": "tool/gen/index", "meta": { "title": "代码生成", "icon": "code", "noCache": false, "link": null } }, { "name": "Swagger", "path": "swagger", "hidden": false, "component": "tool/swagger/index", "meta": { "title": "系统接口", "icon": "swagger", "noCache": false, "link": null } }] }
        // const data4 = {"name": "Http://ruoyi.vip","path": "http://ruoyi.vip","hidden": false,"component": "Layout","meta": {"title": "若依官网","icon": "guide","noCache": false,"link": "http://ruoyi.vip"}}
        const newRouteRecord = [data1, data2, data3]

        filterAsyncRouter(newRouteRecord)
      
        newRouteRecord.forEach(route => {
          router.addRoute(route) // 动态添加可访问路由表
        })

        next({ ...to, replace: true })
    } else {
        next()
    }
})


/**
 * 异步路由过滤器 - 核心路由配置处理器
 * 功能: 
 * 1. 递归处理路由配置树,动态加载Vue组件
 * 2. 特殊处理Layout组件和ParentView结构
 * 3. 规范化路由配置结构
 * 
 * @param {Array} asyncRouterArr - 原始异步路由配置数组
 * @returns {Array} 处理后的标准化路由配置数组
 * 
 * 处理逻辑:
 * 1. 遍历路由配置,处理子路由配置
 * 2. 动态加载组件(转换字符串路径为真实组件)
 * 3. 递归处理嵌套子路由
 * 4. 清理空children和redirect属性
 */
const filterAsyncRouter = (asyncRouterArr) => {
    asyncRouterArr.filter(routeMap => {
      // 处理子路由
      if (routeMap.children) {
        routeMap.children = filterChildrenForRouter(routeMap.children);
      }
      
      if (routeMap.component) {
        // Layout 组件特殊处理
        if (routeMap.component === 'Layout') {
          routeMap.component = Layout
        } else {
          routeMap.component = loadView(routeMap.component)
        }
      }
  
      // 递归处理子路由
      if (routeMap.children?.length) {
        filterAsyncRouter(routeMap.children);
      } else {
        delete routeMap.children;
        delete routeMap.redirect;
      }
  
      return true;
    });
  }
    
  /**
   * 子路由结构转换器 - 路由层级扁平化处理器
   * 功能:
   * 1. 处理ParentView类型的路由结构
   * 2. 合并嵌套子路由路径
   * 3. 将多级路由转换为扁平结构
   * 
   * @param {Array} childrenArr - 原子路由配置数组
   * @returns {Array} 转换后的扁平化子路由数组
   * 
   * 处理逻辑:
   * 1. 当遇到ParentView组件时,将其子路由提升到当前层级
   * 2. 合并父级路径到子路由path
   * 3. 保留普通路由配置
   */
  const filterChildrenForRouter = (childrenArr) => {
    let children = [];
    
    childrenArr.forEach(el => {
      if (el.children?.length && el.component === 'ParentView') {
        children.push(...el.children.map(c => ({
          ...c,
          path: `${el.path}/${c.path}`
        })));
        return;
      }
      
      children.push(el);
    });
    
    return children;
  }
  
  /**
   * 动态组件加载器 - 模块解析器
   * 功能:
   * 根据组件路径字符串动态加载Vue组件
   * 
   * @param {string} view - 组件路径字符串(例: "system/user/index")
   * @returns {Component} Vue组件
   * 
   * 处理逻辑:
   * 1. 遍历预编译的模块集合(modules)
   * 2. 匹配views目录下的对应组件文件
   * 3. 返回组件异步加载函数
   */
  const loadView = (view) => {
    let res;
    for (const path in modules) {
      const dir = path.split('views/')[1].split('.vue')[0];
      if (dir === view) {
        res = () => modules[path]();
      }
    }
    return res;
  }

2.全局注册

在main.js里把前置守卫引入。

import './permission' 

3.删除layout里的添加逻辑

将添加路由记录的逻辑移到前置守卫里。

<script setup>
import { ElContainer, ElAside } from 'element-plus'
import Sidebar from './components/Sidebar.vue'
import Navbar from './components/Navbar.vue'
import AppMain from './components/AppMain.vue'

const data1 = {"name": "System","path": "/system","hidden": false,"redirect": "noRedirect","component": "Layout","alwaysShow": true,"meta": {"title": "系统管理","icon": "system","noCache": false,"link": null},"children": [{"name": "User","path": "user","hidden": false,"component": "system/user/index","meta": {"title": "用户管理","icon": "user","noCache": false,"link": null}},{"name": "Role","path": "role","hidden": false,"component": "system/role/index","meta": {"title": "角色管理","icon": "peoples","noCache": false,"link": null}},{"name": "Menu","path": "menu","hidden": false,"component": "system/menu/index","meta": {"title": "菜单管理","icon": "tree-table","noCache": false,"link": null}},{"name": "Dept","path": "dept","hidden": false,"component": "system/dept/index","meta": {"title": "部门管理","icon": "tree","noCache": false,"link": null}},{"name": "Post","path": "post","hidden": false,"component": "system/post/index","meta": {"title": "岗位管理","icon": "post","noCache": false,"link": null}},{"name": "Dict","path": "dict","hidden": false,"component": "system/dict/index","meta": {"title": "字典管理","icon": "dict","noCache": false,"link": null}},{"name": "Config","path": "config","hidden": false,"component": "system/config/index","meta": {"title": "参数设置","icon": "edit","noCache": false,"link": null}},{"name": "Notice","path": "notice","hidden": false,"component": "system/notice/index","meta": {"title": "通知公告","icon": "message","noCache": false,"link": null}},{"name": "Log","path": "log","hidden": false,"redirect": "noRedirect","component": "ParentView","alwaysShow": true,"meta": {"title": "日志管理","icon": "log","noCache": false,"link": null},"children": [{"name": "Operlog","path": "operlog","hidden": false,"component": "monitor/operlog/index","meta": {"title": "操作日志","icon": "form","noCache": false,"link": null}},{"name": "Logininfor","path": "logininfor","hidden": false,"component": "monitor/logininfor/index","meta": {"title": "登录日志","icon": "logininfor","noCache": false,"link": null}}]}]}
const data2 = { "name": "Monitor", "path": "/monitor", "hidden": false, "redirect": "noRedirect", "component": "Layout", "alwaysShow": true, "meta": { "title": "系统监控", "icon": "monitor", "noCache": false, "link": null }, "children": [{ "name": "Online", "path": "online", "hidden": false, "component": "monitor/online/index", "meta": { "title": "在线用户", "icon": "online", "noCache": false, "link": null } }, { "name": "Job", "path": "job", "hidden": false, "component": "monitor/job/index", "meta": { "title": "定时任务", "icon": "job", "noCache": false, "link": null } }, { "name": "Druid", "path": "druid", "hidden": false, "component": "monitor/druid/index", "meta": { "title": "数据监控", "icon": "druid", "noCache": false, "link": null } }, { "name": "Server", "path": "server", "hidden": false, "component": "monitor/server/index", "meta": { "title": "服务监控", "icon": "server", "noCache": false, "link": null } }, { "name": "Cache", "path": "cache", "hidden": false, "component": "monitor/cache/index", "meta": { "title": "缓存监控", "icon": "redis", "noCache": false, "link": null } }, { "name": "CacheList", "path": "cacheList", "hidden": false, "component": "monitor/cache/list", "meta": { "title": "缓存列表", "icon": "redis-list", "noCache": false, "link": null } }] }
const data3 = { "name": "Tool", "path": "/tool", "hidden": false, "redirect": "noRedirect", "component": "Layout", "alwaysShow": true, "meta": { "title": "系统工具", "icon": "tool", "noCache": false, "link": null }, "children": [{ "name": "Build", "path": "build", "hidden": false, "component": "tool/build/index", "meta": { "title": "表单构建", "icon": "build", "noCache": false, "link": null } }, { "name": "Gen", "path": "gen", "hidden": false, "component": "tool/gen/index", "meta": { "title": "代码生成", "icon": "code", "noCache": false, "link": null } }, { "name": "Swagger", "path": "swagger", "hidden": false, "component": "tool/swagger/index", "meta": { "title": "系统接口", "icon": "swagger", "noCache": false, "link": null } }] }
// const data4 = {"name": "Http://ruoyi.vip","path": "http://ruoyi.vip","hidden": false,"component": "Layout","meta": {"title": "若依官网","icon": "guide","noCache": false,"link": "http://ruoyi.vip"}}
const menuData = [data1, data2, data3]
</script>

<template>
  <el-container class="h-screen">
    <el-aside width="200px">
      <Sidebar :menu-data="menuData"/>
    </el-aside>
    
    <el-container>
      <el-header height="48px">
        <Navbar />
      </el-header>
      <AppMain />
    </el-container>
  </el-container>
</template>

<style>
.el-header {
  --el-header-padding: 0;
  height: auto;
}
</style>

二.功能验证

运行项目,浏览器访问http://localhost:5173/system/user。
在这里插入图片描述
点击浏览器刷新,页面也能正常显示。

三.知识点拓展

1. 全局前置守卫(Router Navigation Guards)

作用:在路由跳转前进行权限验证和路由处理
典型应用
• 登录状态验证
• 动态路由加载
• 页面访问权限控制
核心方法

router.beforeEach((to, from, next) => {
  // 逻辑处理
  next() // 必须调用next()继续路由流程
})

原理
当发生路由导航时,该守卫会按照注册顺序依次执行。通过next()控制流程:
next():继续后续守卫或路由跳转
next(false):中断当前导航
next('/path'):重定向到指定路径

2. 动态路由加载(addRoute)

核心方法

router.addRoute({
  path: '/system',
  component: Layout,
  children: [...]
})

特点
• 支持嵌套路由动态添加
• 路由信息可存储在Vuex/Pinia中
• 浏览器刷新后需要重新加载

3. 组件懒加载(Code Splitting)

实现方式

// 静态导入
import Home from '@/views/Home.vue'

// 动态导入(推荐)
component: () => import('@/views/Home.vue')

优化原理
• Webpack将动态导入的组件单独打包
• 访问时按需加载,提升首屏速度
• 结合import.meta.glob实现批量导入:

const modules = import.meta.glob('@/views/**/*.vue')
// 生成类似:
{
  './views/About.vue': () => import('./views/About.vue'),
  './views/Home.vue': () => import('./views/Home.vue')
}

4. 路由重定向处理

三种实现方式

// 方式1:静态重定向
redirect: '/dashboard'

// 方式2:命名路由重定向 
redirect: { name: 'Home' }

// 方式3:动态函数重定向
redirect: to => {
  return '/custom-path'
}

特殊处理
代码中出现的redirect: "noRedirect"是若依框架的特殊标识,用于:
• 标识不需要重定向的父级路由
• 配合菜单系统处理面包屑导航
• 控制侧边栏菜单的展开状态

5. 路由生命周期管理

关键时间点

  1. 用户刷新页面 → 触发全局守卫
  2. 检查现有路由配置 → 判断是否需要重新加载
  3. 调用API获取最新路由 → 动态添加路由
  4. 使用next({ ...to, replace: true })重试当前路由

注意事项
• 需要处理路由重复添加的情况
• 注意浏览器历史记录的管理
• 建议使用路由白名单机制(如登录页免验证)

四.思考

目前在permission.js和layout/index.vue这两个文件里,都有getRouters接口的返回结果,数据冗余。要将这个数据放在一个什么地方,能让permission.js和layout/index.vue这两个文件都方便获取呢?

相关文章:

  • Flutter 基础组件 Text 详解
  • 工作记录 2017-01-05
  • 算法面试题深度解析:LeetCode 2012.数组元素的美丽值求和计算与多方案对比
  • 【深度学习】读写文件
  • LiveGBS流媒体平台GB/T28181常见问题-视频流安全控制HTTP接口鉴权勾选流地址鉴权后401Unauthorized如何播放调用接口流地址校验
  • Web开发-PHP应用TP框架MVC模型路由访问模版渲染安全写法版本漏洞
  • echarts geo3D地图标记点自定义照片
  • Android StrictMode 使用与原理深度解析
  • Android : Camera之CHI API
  • DeFi基石ERC4626标准实现一个金库合约
  • [GHCTF 2025](>﹏<)
  • Manus AI:多语言手写识别的技术革命与未来图景
  • Java 大视界 -- Java 大数据在智能医疗药品研发数据分析与决策支持中的应用(126)
  • linux下的网络抓包(tcpdump)介绍
  • 网络基础概述2
  • 入门到入土,Java学习 day16(算法1)
  • 【2025】AWVS安装保姆级教程(最新25.1.2可用)
  • HEC-HMS水文建模全解析:气候变化与极端水文、离散化流域单元‌精准刻画地表径流、基流与河道演进过程‌
  • STM32全系大阅兵(2)
  • 七层协议攻防实战:从HTTP慢速攻击到DNS隧道检测
  • 巴基斯坦称对印度发起军事行动
  • 虚假认定实质性重组、高估不良债权价值,原中国华融资产重庆分公司被罚180万元
  • 保证断电、碰撞等事故中车门系统能够开启!隐藏式门把手将迎来强制性国家标准
  • 丰田汽车:美国关税或导致4、5月损失1800亿日元,新财年净利润下滑三成
  • 中消协点名新能源汽车行业:定金退款争议频发
  • 著名国际关系理论家、“软实力”概念提出者约瑟夫•奈逝世