likes
comments
collection
share

Vue + Element Plus 实现权限管理系统(四):动态添加路由

作者站长头像
站长
· 阅读数 39

在权限系统开发中,根据后端返回的菜单列表动态添加路由是非常常见的需求,它可以实现根据用户权限动态加载可访问的页面。在上一篇文章中,我们已经了解到如何渲染侧边栏菜单。本篇文章我们将重点介绍如何优化动态路由的添加过程。

将菜单列表转换为路由格式

学过 vue 的都知道,vue 路由包含 name,path,component 等属性,其中 component 属性是一个函数返回一个模块,比如

{
      path: "/",
      name: "Layout",
      component: () =>
        import(/* webpackChunkName: "Layout" */ "../layout/index.vue"),
}

但是后端返回的却是一个字符串,如aa/bb,所以我们需要一个函数将后端返回的菜单列表处理成路由格式,在 utils 下新建filterRoute.ts

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

export const loadView = (view: any) => {
  let res;
  for (const path in modules) {
    const dir = path.split("views/")[1].split(".vue")[0];
    if (dir === view) {
      res = () => modules[path]();
    }
  }
  return res;
};
export const filterRoute = (data: any) => {
  data.forEach((item: any) => {
    if (item.children?.length > 0) {
      delete item.component;
      filterRoute(item.children);
    } else {
      item.component = loadView(item.component);
      // item.redirect = "/404";
    }
  });
  return data;
};

其中,import.meta.glob("../views/**/*.vue")可以匹配到 views 下所有后缀为.vue的文件,然后通过对比后端返回的 component 与views/后面的路径来生成 vue 路由中component所需要的格式。同时 filterRoute 函数中如果菜单是父菜单,则其不能有 component 属性。

根据以上规则可以看出,如果菜单 component 配置了aa/bb则我们需要在 views 目录下创建aa/bb.vue文件才能匹配当前组件路径

引入 Pinia

pinia 是一个很好用的状态管理器,它可以方便的管理全局的状态,首先安装pinia

npm i pinia

然后在main.ts中注册

import { createPinia } from "pinia";
const app = createApp(App);

const pinia = createPinia();
app.use(pinia).use(router).mount("#app");

新建store/index文件用于存放 pinia 管理的状态,同时这里我们定义了获取菜单的方法GenerateRoutes

import { defineStore } from "pinia";
import { getMenuList } from "@/http/menu/index";
import router from "@/router";
import { MenuVo } from "@/http/menu/types/menu.vo";
type StoreState = {
  isCollapse: boolean,
  menuList: MenuVo[],
};
export default defineStore("home", {
  state: (): StoreState => {
    return {
      isCollapse: false,
      menuList: [],
    };
  },
  actions: {
    async GenerateRoutes() {
      const { data } = await getMenuList({});
      this.menuList = data;
      return data;
    },
  },
});

动态添加路由

在路由配置文件中router/index.ts,我们先定义好公共路由页面

import { createRouter, createWebHashHistory, RouteRecordRaw } from "vue-router";
import home from "@/store";
import { nextTick } from "vue";
import { filterRoute } from "@/utils/filterRoute";
const router = createRouter({
  history: createWebHashHistory(),
  scrollBehavior(to, from, savedPosition) {
    if (savedPosition) {
      return savedPosition;
    } else {
      return { top: 0 };
    }
  },
  routes: [
    {
      path: "/",
      name: "Layout",
      component: () =>
        import(/* webpackChunkName: "Layout" */ "../layout/index.vue"),
    },
    {
      path: "/:pathMatch(.*)*",
      component: () => import("../views/404.vue"),
    },
    {
      path: "/login",
      name: "login",
      component: () =>
        import(/* webpackChunkName: "login" */ "../views/login.vue"),
    },
  ],
});
export default router;

然后在路由守卫beforeEach中使用addRoute进行动态路由添加,同时定义一个白名单路由列表,这里暂时只有一个login页面,表示直接放行

import home from "@/store";
import { nextTick } from "vue";
import { filterRoute } from "@/utils/filterRoute";
...
const writeLists = ['login']
router.beforeEach(async (to, from, next) => {
  if (writeLists.includes(to.name)) {
    next();
    return;
  }
  await nextTick();
  const homeStore = home();
  if (homeStore.menuList.length) {
    next();
    return;
  }
  const data = await homeStore.GenerateRoutes();
  const routers = filterRoute(data);
  routers.forEach((route: RouteRecordRaw) => {
    router.addRoute("Layout", route);
  });

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

Tips

  • 这里在获取 pinia 中数据时,先执行了await nextTick();是因为此时如果直接拿 pinia 中数据是拿不到的,因为 pinia 还没有初始化完毕
  • next({ ...to, replace: true })再次加载当前路由,即重新执行beforeEach,因为第一次进入 beforeEach 函数中的时候还没有加载动态路由,所以我们需要在添加完路由后重新加载当前路由以触发 beforeEach 钩子函数

路由跳转

我们模拟修改一下菜单表中子菜单 2.1 的 component 为test2/index

Vue + Element Plus 实现权限管理系统(四):动态添加路由

相应的我们则需要在 views 下新建test2/index.vue

<template>
    <div>test2</div>
</template>

<script lang='ts' setup>
</script>

来到layout/components/sidebar.vue中处理菜单选择事件

Vue + Element Plus 实现权限管理系统(四):动态添加路由

其中 getPath 中d参数代表当前菜单的父菜单和子菜单 index 的数组,即父菜单的 path 和子菜单 path,如['aa','bb'],刚好根据它们进行相应的路由跳转

最后点击子菜单2.1就会发现这个页面被渲染出来了

Vue + Element Plus 实现权限管理系统(四):动态添加路由

写在最后

最后动动小手给个赞吧👍

转载自:https://juejin.cn/post/7259644132577656893
评论
请登录