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

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

功能6:动态添加路由记录

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

前言

昨天点击菜单后,没有得到预期结果的页面展示效果。是因为Router里没有对应的路由记录。
从开发工具可以看到当前的路由记录。就只有在src/router/index.js文件里配置的/login/index
在这里插入图片描述

一.操作步骤

1.addRoute方法

是 Vue Router 4.x 中动态添加路由的核心方法,主要用于实现权限路由、按需加载等场景。
方法的入参格式,可以参考路由配置文件里面的首页配置。需要重点关注两个component属性,值要对应到具体的资源对象。

{
    path: '/',
    component: Layout,
    redirect: '/index',
    children: [
      {
        path: 'index',
        component: () => import('@/views/index.vue'),
      }
    ]
  }

2.解析getRouters接口的返回结果

  • “component”: “Layout”

一级折叠菜单,点击会展开。要将Layout字符串,替换成Layout对象

  • “component”: “ParentView”

二级折叠菜单,点击会展开。说明该对象下有children属性。需要特殊处理。修改children里每一个元素的path值。只保留children元素。
例如
“path”: "log"是父对象里的。
“path”: “operlog"是children元素里的。
就将children元素里的path修改为"path”: “log/operlog”

  • “component”: “system/user/index”

菜单项,点击会在内容区显示对应的界面。要将字符串,替换成对应路径下的资源对象,src/views/system/user/index.vue

3.修改layout/index.vue

根据以上分析,写JS方法处理返回结果。

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

import { useRouter } from 'vue-router'
const router = useRouter()

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]

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

import { onMounted } from 'vue'
onMounted(() => {
  const newRouteRecord = JSON.parse(JSON.stringify(menuData));
  filterAsyncRouter(newRouteRecord)

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

/**
 * 异步路由过滤器 - 核心路由配置处理器
 * 功能: 
 * 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;
}
</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>

4.修改MenuItem.vue

让el-menu-item的index属性的值和路由记录的path对应,才能正确的跳转。


<template>
    <template v-if="hasChildren">
        <el-sub-menu :index="item.path">
            <template #title>
                <el-icon v-if="item.meta?.icon">
                    <component :is="item.meta.icon" />
                </el-icon>
                <span>{{ item.meta?.title }}</span>
            </template>
            <template v-for="child in item.children" :key="child.path">
                <MenuItem :item="child" :level="level + 1" :base-path="resolvePath(item.path)"/>
            </template>
        </el-sub-menu>
    </template>

    <template v-else>
        <el-menu-item :index="resolvePath(item.path)">
            <el-icon v-if="item.meta?.icon">
                <component :is="item.meta.icon" />
            </el-icon>
            <span>{{ item.meta?.title }}</span>
        </el-menu-item>
    </template>
</template>

<script setup>
import { defineProps, computed } from 'vue';

const props = defineProps({
    item: {
        type: Object,
        required: true
    },
    level: {
        type: Number,
        default: 0
    },
    basePath: {
        type: String,
        default: ''
    }
});

const hasChildren = computed(() => {
    if (props.level >= 2) {
        if (props.item.children) {
            console.error('菜单层级超过限制,最多允许两层子菜单');
            return false;
        }
        return false;
    }
    return props.item.children && props.item.children.length > 0;
});

const resolvePath = (routePath) => {
    if (props.basePath.length === 0) {
        return routePath
    };
    return getNormalPath(props.basePath + '/' + routePath)
}

/**
 * 路径标准化处理器
 * 功能: 规范化URL/文件路径格式,确保路径符合统一格式标准
 * 核心处理逻辑:
 * 1. 处理空值及无效路径
 * 2. 转换双斜杠为单斜杠
 * 3. 去除路径末尾的斜杠
 * 
 * @param {string} p - 原始路径字符串
 * @returns {string} 标准化后的路径
 */
const getNormalPath = (p) => {
    // 空值安全处理:当传入空值/undefined字符串时直接返回原值
    if (p.length === 0 || !p || p == 'undefined') {
        return p
    };

    // 双斜杠转换:替换路径中的双斜杠为单斜杠
    let res = p.replace('/\/+/g', '/')

    // 末尾斜杠清理:当标准化后路径以斜杠结尾时移除末尾斜杠
    if (res[res.length - 1] === '/') {
        return res.slice(0, res.length - 1)
    }
    return res;
}
</script>

5.给用户管理新建对应页面

根据用户管理的信息"component": "system/user/index"
新建文件src/views/system/user/index.vue

<script setup lang="ts">

</script>

<template>
  <div>
用户管理
  </div>
</template>

<style scoped lang="scss">
</style>

二.功能验证

运行项目,浏览器访问http://localhost:5173/index
点击用户管理,Layout不变,内容区显示变成用户管理页面。
在这里插入图片描述

三.知识点拓展

1. 动态路由注册(addRoute)

router.addRoute(route) // 动态添加路由

作用:运行时动态添加路由配置
特点
• 不需要重启应用即可生效
• 常用于权限管理系统
• 可以嵌套添加父子路由
参数结构

{
  path: '/user',
  component: UserComponent,
  children: [...]
}

2. 组件递归渲染

<!-- MenuItem组件内调用自身 -->
<MenuItem :item="child" :level="level + 1"/>

实现原理
• 组件在自己的模板中调用自身
• 通过props传递不同层级的参数
• 需要设置终止条件(示例中通过level >=2 限制层级)
应用场景:树形菜单、多级导航等嵌套结构

3. 动态组件加载

// 动态加载视图组件
const loadView = (view) => {
  return () => import(`@/views/${view}.vue`)
}

核心机制
• 使用import()实现代码分割
• 返回一个返回Promise的工厂函数
• Vue会自动处理异步加载状态

4. 模块批量导入(重点)

const modules = import.meta.glob('@/views/**/*.vue')

功能解析
import.meta.glob:Vite提供的特殊导入方法
'@/views//*.vue’**:匹配views目录下所有vue文件
返回值:键值对集合,键为文件路径,值为动态导入函数
生成结构示例

{
  './views/user/index.vue': () => import('./views/user/index.vue'),
  './views/about.vue': () => import('./views/about.vue')
}

优势
• 实现真正的按需加载
• 自动处理文件路径映射
• 支持热模块替换(HMR)

5. 路由元信息(Meta)

meta: {
  title: '用户管理',
  icon: 'user',
  noCache: false
}

使用方式
• 通过route.meta访问配置信息
• 配合v-if控制菜单显示逻辑
• 可以通过全局守卫读取路由元信息
典型应用
• 页面标题管理
• 菜单图标配置
• 缓存控制

6. 组合式API应用

import { useRouter } from 'vue-router'

const router = useRouter()

特性
useRouter提供路由实例
• 在setup()中直接使用
• 配合<script setup>语法更简洁
对比选项式API优势
• 更好的类型推导
• 更灵活的逻辑组合
• 更清晰的依赖关系

7. 异步组件处理

component: () => import('@/views/index.vue')

工作原理
• 访问路由时才会加载组件
• 自动生成独立的chunk文件
• 支持加载状态处理(loading/error)
优化效果
• 减少首屏加载体积
• 提升页面加载速度
• 自动代码分割

8. 路由路径处理

const resolvePath = (routePath) => {
  return basePath + '/' + routePath
}

核心要点
• 处理嵌套路由的路径拼接
• 使用正则表达式标准化路径
• 防止出现//等异常路径
注意事项
• 绝对路径与相对路径的区别
• 动态参数路径的特殊处理
• 与路由配置的严格对应

四.思考

1.在侧边栏点击用户管理,显示用户管理的界面时,点击浏览器的刷新,会出现什么现象呢?

2.直接在浏览器输入http://localhost:5173/system/user,会显示什么?


http://www.kler.cn/a/583340.html

相关文章:

  • 【每日学点HarmonyOS Next知识】路由栈问题、图片圆角、颜色资源转十六进制字符串、数据集变化崩溃、组件声明周期
  • Qt | 屏幕截图实现
  • deepseek的regflow安装mac版本
  • 【反无人机目标检测数据集】MIDGARD:关于基于机器学习的微型无人机视觉相对定位的训练数据集
  • 简述你对 Spring MVC 的理解
  • ubuntu-drivers-common 包功能详解
  • 每天一篇《目标检测》文献(三)
  • Python----数据可视化(Pyecharts一:介绍安装,全局配置,系列配置)
  • Vue 组件通信 - 子传父
  • ctfhub-web-SSRF通过攻略
  • ffmpeg实用技巧:使用ffmpeg命令行从视频文件中提取帧画面并保存为图片
  • 桂链:什么是区块链账本?
  • 为什么 HTTP GET 方法不使用请求体?
  • [笔记.AI]KAG(知识增强生成 Knowledge Augmented Generation)
  • Vue:class与style绑定
  • 【科研绘图系列】python绘制分组点图(grouped dot plot)
  • LiveGBS流媒体平台GB/T28181常见问题-视频流安全控制HTTP接口鉴权勾选流地址鉴权后401Unauthorized如何播放调用接口流地址校验
  • 从Spring容器中获取bean
  • C#实现本地Deepseek模型及其他模型的对话v1.4
  • Python Flask 从 HTTP 请求中解包参数