动态路由生成实现方案

以下是具体实现步骤:

1. 创建路由工具文件

首先,需要创建一个路由工具文件 src/renderer/src/router/utils.ts,用于将菜单列表转换为路由配置。请复制以下代码并保存到该文件中:

import { RouteRecordRaw } from 'vue-router'

/**
 * 递归将菜单列表转换为路由配置
 * @param menuList 菜单列表
 * @returns 路由配置数组
 */
export function generateRoutes(menuList: any[]): RouteRecordRaw[] {
  const routes: RouteRecordRaw[] = []

  menuList.forEach(menu => {
    const route: RouteRecordRaw = {
      path: menu.path || '',
      name: menu.name || '',
      meta: {
        title: menu.title || '',
        icon: menu.icon || '',
        requiresAuth: menu.requiresAuth !== false
      }
    }

    // 处理组件路径
    if (menu.component) {
      // 假设组件路径格式为 '@renderer/view/xxx/index.vue'
      route.component = () => import(`@renderer/view/${menu.component}/index.vue`)
    }

    // 处理子菜单
    if (menu.children && menu.children.length > 0) {
      route.children = generateRoutes(menu.children)
    }

    routes.push(route)
  })

  return routes
}

/**
 * 将生成的路由添加到路由实例
 * @param router 路由实例
 * @param routes 生成的路由配置
 */
export function addRoutesToRouter(router: any, routes: RouteRecordRaw[]) {
  routes.forEach(route => {
    router.addRoute(route)
  })
}

2. 修改 home/index.vue 文件

接下来,需要修改 src/renderer/src/view/home/index.vue 文件,在获取菜单列表后动态生成路由:

// ... existing code ...
import { useRouter } from 'vue-router'
import { generateRoutes, addRoutesToRouter } from '@renderer/router/utils'

const router = useRouter()
const userStore = useUserStore()

const getRouters = async () => {
    const res = await getRoutersApi(userStore.roles[0].rolePerm)
    console.log('菜单列表:', res)

    // 假设res.data包含菜单列表
    if (res.data && res.data.length > 0) {
        // 生成路由
        const dynamicRoutes = generateRoutes(res.data)
        // 添加到路由实例
        addRoutesToRouter(router, dynamicRoutes)
        console.log('动态路由生成成功:', dynamicRoutes)
    }
}
// ... existing code ...

3. 优化路由守卫

为了确保动态路由加载完成后再进行路由跳转,可以修改 src/renderer/src/router/index.ts 中的路由守卫:

// ... existing code ...
import { ref } from 'vue'

// 标记动态路由是否已加载
const isDynamicRoutesLoaded = ref(false)

// 路由守卫
router.beforeEach(async (to, from, next) => {
  const userStore = useUserStore()
  // 检查是否需要登录
  if (to.meta.requiresAuth) {
    // 检查登录状态
    const isAuthenticated = await userStore.checkAuth()
    if (isAuthenticated) {
      // 如果动态路由未加载,则加载动态路由
      if (!isDynamicRoutesLoaded.value) {
        // 这里需要调用获取菜单列表的接口
        const res = await getRoutersApi(userStore.roles[0].rolePerm)
        if (res.data && res.data.length > 0) {
          const dynamicRoutes = generateRoutes(res.data)
          addRoutesToRouter(router, dynamicRoutes)
          isDynamicRoutesLoaded.value = true
          // 重新导航到目标路由
          next({ ...to, replace: true })
          return
        }
      }
      next()
    } else {
      next('/login')
    }
  } else {
    // 不需要登录的页面
    if (to.path === '/login' && await userStore.checkAuth()) {
      // 如果已登录,重定向到首页
      next('/')
    } else {
      next()
    }
  }
})
// ... existing code ...
注意事项
  1. 上述代码假设您的菜单列表结构包含 pathnametitlecomponent 和 children 等字段。您可能需要根据实际的接口返回结构进行调整。

  2. 需要在 router/index.ts 中导入 generateRoutes 和 addRoutesToRouter 函数,以及 getRoutersApi 接口。

  3. 动态路由加载逻辑可能需要根据您的项目实际情况进行调整,例如添加错误处理、加载状态等。

  4. 确保在使用路由守卫时正确处理异步操作,避免出现路由跳转异常。

侧边栏菜单渲染实现

为 menus.vue 文件添加模板部分,实现侧边栏菜单的渲染。以下是完整的实现代码:

<template>
  <el-menu
    :default-active="activeMenu"
    class="el-menu-vertical-demo"
    @select="handleMenuSelect"
    background-color="#f5f5f5"
    text-color="#333"
    active-text-color="#409EFF"
    :collapse="isCollapse"
  >
    <!-- 递归渲染菜单 -->
    <template v-for="menu in menuList" :key="menu.path">
      <!-- 有子菜单的项 -->
      <el-sub-menu v-if="menu.children && menu.children.length > 0" :index="menu.path">
        <template #title>
          <el-icon :size="18"><component :is="menu.meta.icon || 'Menu'"></component></el-icon>
          <span>{{ menu.meta.title }}</span>
        </template>
        <!-- 递归渲染子菜单 -->
        <template v-for="subMenu in menu.children" :key="subMenu.path">
          <el-sub-menu v-if="subMenu.children && subMenu.children.length > 0" :index="subMenu.path">
            <template #title>
              <el-icon :size="18"><component :is="subMenu.meta.icon || 'Menu'"></component></el-icon>
              <span>{{ subMenu.meta.title }}</span>
            </template>
            <!-- 第三级菜单 -->
            <el-menu-item
              v-for="thirdMenu in subMenu.children"
              :key="thirdMenu.path"
              :index="thirdMenu.path"
            >
              <el-icon :size="18"><component :is="thirdMenu.meta.icon || 'Menu'"></component></el-icon>
              <span>{{ thirdMenu.meta.title }}</span>
            </el-menu-item>
          </el-sub-menu>
          <!-- 无子菜单的项 -->
          <el-menu-item v-else :index="subMenu.path">
            <el-icon :size="18"><component :is="subMenu.meta.icon || 'Menu'"></component></el-icon>
            <span>{{ subMenu.meta.title }}</span>
          </el-menu-item>
        </template>
      </el-sub-menu>
      <!-- 无子菜单的项 -->
      <el-menu-item v-else :index="menu.path">
        <el-icon :size="18"><component :is="menu.meta.icon || 'Menu'"></component></el-icon>
        <span>{{ menu.meta.title }}</span>
      </el-menu-item>
    </template>
  </el-menu>
</template>

<script setup lang="ts">
import { ref, onMounted, computed } from 'vue'
import { getRoutersApi } from '@renderer/api/user'
import { useUserStore } from '@renderer/store/user'
import { useRouter, useRoute } from 'vue-router'
import { generateRoutes, addRoutesToRouter } from '@renderer/router/routerChange'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'

const userStore = useUserStore()
const router = useRouter()
const route = useRoute()

// 菜单列表
const menuList = ref<any[]>([])
// 菜单折叠状态
const isCollapse = ref(false)
// 当前激活的菜单
const activeMenu = computed(() => {
  return route.path
})

// 获取路由
const getRouters = async () => {
  const data = await getRoutersApi(userStore.roles[0].rolePerm)
  // 生成路由
  const dynamicRoutes = generateRoutes(data)
  // 添加到路由实例
  addRoutesToRouter(router, dynamicRoutes)
  menuList.value = dynamicRoutes
  console.log('动态路由生成成功:', dynamicRoutes)
}

// 菜单选择事件处理
const handleMenuSelect = (index: string) => {
  router.push(index)
}

onMounted(() => {
  getRouters()
})
</script>

<style lang="scss" scoped>
.el-menu-vertical-demo {
  height: 100vh;
  width: 200px;
  position: fixed;
  left: 0;
  top: 30px; // 与顶部窗口控制栏高度对齐
  overflow-y: auto;
}

// 菜单折叠时的样式
.el-menu--collapse {
  width: 64px;
}

// 自定义滚动条样式
.el-menu-vertical-demo::-webkit-scrollbar {
  width: 6px;
}

.el-menu-vertical-demo::-webkit-scrollbar-thumb {
  background-color: #ddd;
  border-radius: 3px;
}

.el-menu-vertical-demo::-webkit-scrollbar-track {
  background-color: #f5f5f5;
}
</style>

实现说明

  1. 模板部分

    • 使用 Element Plus 的 <el-menu> 组件创建垂直菜单

    • 通过 v-for 遍历 menuList 数据渲染菜单项

    • 对有子菜单的项使用 <el-sub-menu>,无子菜单的项使用 <el-menu-item>

    • 递归渲染子菜单,支持多级菜单结构

    • 使用动态组件 <component :is="menu.meta.icon"> 渲染菜单图标

  2. 脚本部分

    • 导入并注册所有 Element Plus 图标

    • 添加 activeMenu 计算属性,根据当前路由高亮对应的菜单项

    • 实现 handleMenuSelect 方法,处理菜单点击事件并跳转路由

    • 保留原有的 getRouters 方法获取和处理菜单数据

  3. 样式部分

    • 设置菜单高度为全屏,宽度为 200px

    • 固定菜单在左侧,顶部留出 30px 给窗口控制栏

    • 添加滚动条样式美化

    • 配置菜单折叠时的宽度为 64px

使用说明

  1. 确保已在项目中正确引入 Element Plus 和图标库

  2. 将上述代码替换到 menus.vue 文件中

  3. 菜单数据会从后端 API 获取并动态渲染

  4. 点击菜单项会自动跳转到对应的路由

  5. 菜单会根据当前路由自动高亮

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值