【vue无限下级动态路由菜单】


前言

通过vue、elementplus、route实现无限下级动态菜单树

应用场景:实现多级菜单列表,使功能分类更加清晰易懂。
实现原理:通过递归动态注册route路由和elementplus的el-menu组件实现树形菜单。


提示:以下是本篇文章正文内容,下面案例可供参考

一、项目结构

1.菜单结构

在这里插入图片描述

2.组件结构

在这里插入图片描述

二、vuex动态注册路由

1.vuex动态注册路由

代码如下(示例):

import { defineStore } from "pinia";
import { RouteRecordRaw } from "vue-router";
import { ref } from "vue"
import router, { Layout, Directory } from "@/router";
import { listRoutes } from "@/api/menu";

const modules = import.meta.glob("../../views/**/**.vue");

export const useRouterStore = defineStore("router", () => {
    const routes: ref<RouteRecordRaw[]> = [];
    const hasRole = ref(false);

    // actions
    function setRoutes(newRoutes: RouteRecordRaw[]) {
        routes.value = newRoutes;
        hasRole.value = true;
    }

    // 生成动态路由
    function generateRoutes() {
        return new Promise<RouteRecordRaw[]>((resolve, reject) => {
            listRoutes().then((res) => {
                const routes = recurRoutes(res.data);
                routes.forEach(route => {
                    router.addRoute(route)
                })
                setRoutes(routes);
                resolve(routes);
            }).catch((err) => {
                console.log('err',err)
            })
        })
    }

    // 递归引入路由组件
    const recurRoutes = (routes: RouteRecordRaw[]) => {
        const newRoute: RouteRecordRaw[] = [];
        routes.forEach((route) => {
            if (route.component?.toString() == "Layout") {
                // 一级目录
                route.component = Layout;
            } else if (route.component?.toString() == "Directory") {
                // 其他目录
                route.component = Directory;
            } else {
                const component = modules[`../../views/${route.component}.vue`];
                route.component = component;
            }
            if (route.children) {
                route.children = recurRoutes(route.children);
            }
            newRoute.push(route);
        })
        return newRoute;
    }

    return { routes, setRoutes, generateRoutes };

})

2.动态路由注册

路由静态注册时使用的一下方法,无法在运行时动态注册路由。

// 静态路由
export const constantRoutes: RouteRecordRaw[] = [
    {
        path: '/',
        name: '/',
        component: Layout,
        children: [
            {
                path: '/shopForm',
                name: 'shopForm',
                component: () => import('@/views/shop/shopForm/index.vue')
            }
        ]
    },
    {
        path: '/login',
        name: 'login',
        component: () => import('@/views/login/index.vue')
    }
];

可以使用vite的 glob 方法实现动态注册

// 引入vite的
const modules = import.meta.glob("../../views/**/**.vue");
const component = modules[`../../views/${route.component}.vue`];

如果框架搭建异常导致无法使用vite的 glob 方法,可使用以下方法代替。
大致原理为:通过后端返回路由数据中的组件路径,配合前端写上的静态路径拼接,以此达到文件引入的效果。

const component = import('../../views/' + route.component.valueOf() + '.vue');

3.配置路由守卫

在router的index.ts中,配置路由守卫,将vuex中的动态路由数据,与router配置的静态路由合并,形成完整的菜单路由。

/**
 * 路由守卫
 */
router.beforeEach(async (to, from, next) => {
    let token = localStorage.getItem('token');
    if (to.path === '/login') {
        // 前往等登录页
        next();
    } else if (!token) {
        // 尚未登录
        next({path: "/login"})
    } else if (to.path === '/' || to.path === '') {
        // 前往首页,添加路由
        const routerStore = useRouterStore();
        await routerStore.generateRoutes();
        next()
    } else if (router.getRoutes().length == 3) {	// 3为router中配置的静态路由数量,因为动态获取路由列表数量的方法出行问题,所以暂时写成固定的
        // 没有添加动态路由
        const routerStore = useRouterStore();
        await routerStore.generateRoutes();
        console.log('router', router.getRoutes())
        next({path: to.path})
    } else {
        // 正常放行
        next()
    }
})

4.页面刷新后动态路由失效问题

每次页面刷新时,已注册的动态路由和vuex中的动态路由,都会被浏览器清空。
所以路由守卫会判断当前路由列表中的路由数量,是包含了vuex中的动态路由,还是只有router配置的静态路由。
如果路由列表中没有vuex中的动态路由,则调用vuex的方法重新请求后端接口,注册动态路由。

在这里插入图片描述

三、树形菜单

1.封装树形菜单组件

实现原理:利用递归的逻辑方法判断当前遍历的数据,是否含有子菜单。
如果是含有子菜单的,那当前遍历的数据属于目录,反之则为菜单。

<script lang="ts" setup>
import MenuTree from "@/components/MenuTree/index.vue";

const props = defineProps({
  routes: {
    type: Object,
    required: true
  }
})
</script>

<template>
  <!-- 循环遍历菜单 -->
  <template v-for="(route, index) in props.routes" :index="index">
    <!-- 判断菜单是否还有下级 -->
    <el-sub-menu :index="route.path" v-if="route.children">
      <!-- 有下级渲染为目录 -->
      <template #title>
        <svg-icon :icon-class="route.meta.icon" />
        <span>{{ route.meta.title }}</span>
      </template>

      <!-- 递归子集 -->
      <menu-tree :routes="route.children"></menu-tree>
    </el-sub-menu>

    <!-- 没有下级渲染为菜单 -->
    <el-menu-item :index="route.path" v-else>
      <svg-icon :icon-class="route.meta.icon" />
      <span>{{ route.meta.title }}</span>
    </el-menu-item>
  </template>
</template>

<style lang="scss" scoped>
.el-header {
  height: 60px;
  background-color: #545c64;
  .logo {
    height: 60px;
  }
  h2,.logout {
    text-align: center;
    height: 80px;
    line-height: 60px;
    color: #e3e2e2;
  }
}
.el-container {
  // 侧边栏、头部固定,只滚动内容
  height: calc(100vh - 60px);
}
.el-aside {
  width: 200px;
  .el-menu {
    height: calc(100vh - 60px);
    width: 200px;
  }
}
.svg-icon {
  padding: 10px;
}
</style>

2.页面嵌套的原因和解决方案

目录路由地址封装成组件引入,一级目录路由组件为Layout,其他下级目录路由组件为Directory。
注:无限下级路由的关键之一在于下级目录的路由组件页面,该页面中需要再次嵌套router-view,否则路由跳转时下级菜单时会出现页面嵌套现象。

在这里插入图片描述

下级目录路由组件跳转文件

<template>
  <router-view></router-view>
</template>

<script>
export default {
  name: "index"
}
</script>

<style scoped>

</style>

四、数据库表结构

在这里插入图片描述


总结

下面将其中的几个关键技术点以及解决方案梳理一下:

  1. 运行时动态注册路由。可以使用vite的 glob 方法实现,如果没有集成 vite,可以配合后端存储的组件路径,和前端的文件路径拼接达到动态注册路由的效果。
  2. 页面刷新动态路由失效。vuex存在刷新会失效的情况,所以只能在路由守卫中判断,看当前路由列表是否有注册动态路由,没有的话调用vuex重新将路由注册,以此解决刷新失效问题。
  3. 页面样式嵌套。一级目录的组件路径封装为Layout,及首页路径;其余的二级、三级等目录的组件,都统一封装为一个另外使用了router-view的页面,通过路由嵌套解决页面嵌套的问题。
  4. 递归。在实现无限下级动态菜单这个功能中,递归这个方法被多次使用,不管是路由的动态注册,还是el-menu的菜单渲染,甚至后端处理菜单列表时,都用到了递归的逻辑。初次接触递归时会觉得很复杂、难以理解,最简单的方法就是将循环多些几层,通过总结其中的规律来封装方法递归的逻辑。

以上就是今天要讲的内容,本文仅仅简单介绍了使用递归逻辑以及动态路由实现无限下级菜单的方法,可以将后台管理系统的目录更加清晰的划分层级,让用户拥有更好的体验。

要实现动态路由菜单,需要以下几个步骤: 1. 定义路由配置 首先,需要定义路由配置。可以将路由配置存储在一个数组或者数据库中,并动态生成路由。例如: ``` const routes = [ { path: '/home', name: 'Home', component: Home }, { path: '/about', name: 'About', component: About }, // ... ] ``` 2. 获取路由配置 然后,需要获取路由配置。这可以通过异步请求后端接口获取路由配置数据,或者将路由配置存储在 Vuex 中。例如: ``` const store = new Vuex.Store({ state: { routes: [] // 存储路由配置 }, mutations: { setRoutes(state, routes) { state.routes = routes } }, actions: { async fetchRoutes({ commit }) { // 异步请求获取路由配置 const response = await axios.get('/api/routes') commit('setRoutes', response.data) } } }) ``` 3. 动态生成路由 接下来,需要动态生成路由。这可以通过在路由配置数组中循环,调用 `router.addRoute` 方法来实现。例如: ``` const router = createRouter({ history: createWebHistory(), routes: [] }) store.watch( state => state.routes, (routes) => { router.getRoutes().forEach(route => { // 删除旧路由 router.removeRoute(route.name) }) routes.forEach(route => { // 添加新路由 router.addRoute(route.name, { path: route.path, name: route.name, component: () => import(`@/views/${route.component}.vue`) }) }) }, { immediate: true } ) ``` 4. 渲染菜单 最后,需要根据路由配置来渲染菜单。可以使用 `v-for` 循环渲染动态路由菜单。例如: ``` <template> <div> <ul> <li v-for="route in routes" :key="route.name"> <router-link :to="route.path">{{ route.name }}</router-link> </li> </ul> <router-view /> </div> </template> <script> import { mapState } from 'vuex' export default { computed: { ...mapState(['routes']) } } </script> ``` 通过以上步骤,就可以实现动态路由菜单了。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值