实现动态路由前提是服务端做好Router路由表的返回数据格式,一般分为两种情况(本人见识少,目前只遇到这两种)一是服务端返回客户端登录用户的权限ID数组,客户端通过权限ID修改对应路由的hidden属性选择是否展示,同时在router.beforeEach中判断好每次路由跳转的目标地址是否符合权限信息,防止通过地址栏输入方式跳转;二是前端配置好login,home等基础通用路由,再由服务端返回完整路由表,通过解析路由数据,动态添加至路由表中。
步骤:从login页跳转home时,获取数据存储在pinia中,beforeEach前置钩子解析pinia中的数据,动态添加至路由表。
(1)配置:vue3 +typeScript+pinia+mock
基础配置如何引入就不过多阐述,进入代码部分。
Router基础路由表
{
path: "/",
redirect: "/login"
},
{
path: "/login",
name: "Login",
meta: { title: "登录", },
component: () => import("../views/login/LoginView.vue"),
},
{
path: "/home",
name: "home",
redirect: "/home/homepage",
component: () => import("../views/home/HomeView.vue"),
children: [
{
path: "/home/homepage",
name: "homepage",
meta: { title: "首页", icon: "icon-shouye", hidden: false, role: ['root', 'user'] },
component: () => import("../views/homepage/index.vue"),
}
]
}
mock数据
router: [
{
path: "/home/echarts",
name: "echarts",
meta: { title: "图表", icon: "icon-tubiao-zhexiantu", hidden: false },
component: "/echarts/index",
},
{
path: "/home/error",
name: "error",
meta: { title: "错误页", icon: "icon-cuowuyemian", hidden: false },
redirect: "/error/404",
component: '/error/index',
children: [
{
path: "/home/error/404",
name: "notfound",
meta: { title: "404", icon: "", hidden: true },
component: "/error/404",
}
]
},
{
path: "/home/table",
name: "table",
meta: { title: "表格", icon: "icon-table", hidden: false },
component: "/table/index",
},
{
path: "/home/edit",
name: "edit",
meta: { title: "编辑", icon: "", hidden: true },
component: "/table/edit",
},
{
path: "/home/guide",
name: "guide",
meta: { title: "国际化", icon: "icon-guojihua", hidden: false },
component: "/guide/index",
},
{
path: "/home/personal",
name: "personal",
meta: { title: "个人中心", icon: "icon-wode", hidden: false },
component: "/personnal/index",
},
{
path: "/home/system",
name: "system",
meta: { title: "系统设置", icon: "icon-xitongshezhi", hidden: false },
component: "/system/index",
}]
(2)获取数据,获取数据采用axios,获取mock定义好的模拟数据。
import { useMenuStore } from '@/stores'
const MenuStore = useMenuStore()
//下方代码本人是定义在login页面的点击登录事件的处理函数中进行执行
const { data: res } = await loginApi(formData)
MenuStore.setMenuList(res.data.router)
// 路由信息存储在pinia中,同时因为pinia中使用persist插件实现数据持久化,
本质也是在local storage中存了备份,persist插件使用具体可以自行搜索
pinia中useMenuStore配置,还需自行配置Store下index.ts,将useMenuStore引入。
import useMenuStore from './modules/menuList';
export { useMenuStore}
import { defineStore } from "pinia";
const useMenuStore = defineStore('useMenuStore', {
state: () => {
return {
menuList: '',
};
},
actions: {
setMenuList(routes: any) {
this.menuList = routes;
},
clearMenuList() {
this.menuList = '';
},
},
persist: {
enabled: true,
strategies: [
{ key: 'routes', storage: localStorage, paths: ['menuList'] },
],
},
});
export default useMenuStore
(3)处理数据,从login页跳转到home页过程中,数据已经获取到了,接下来就是到router.beforeEach中处理相关数据。
import { addRoutes, clerRoutes } from '@/utils/asyncRoute';
// 前置路由守卫,添加动态路由
let registerRouteFresh1 = true; //记录路由动态加载的状态
let registerRouteFresh2 = true;
router.beforeEach(async (to, from, next) => {
if (to.name !== 'Login' && !userStore.token) {
next({ name: 'Login' });
} else {
if (to.name === 'Login') {
userStore.clearToken();
userStore.clearUser();
clerRoutes(menuStore, router);
}
// 从login 页面进入到homepage 如果你的数据是加载login页面获取,该if可不要
if (from.name === 'Login' && registerRouteFresh1) {
addRoutes(menuStore, router);
next({ ...to, replace: true });
//{ ...to, replace: true } 因为动态添加路由时,可能会存在添加未完成时路由发生跳转
此时通过上述代码可重定向到beforeEach执行之前,再次执行加载
registerRouteFresh1 = false;
}
// 本地刷新的时候执行
if (!from.name && registerRouteFresh2) {
addRoutes(menuStore, router);
next({ ...to, replace: true });
registerRouteFresh2 = false;
} else {
next();
}
}
})
通过上面的钩子函数可以实现解析路由数据,添加至路由表的过程,下述讲解方法的具体实现方式
(4)解析添加路由、清除路由的具体实现
解析:router4.0主要通过addRoute的API。
// 添加动态路由
const addRoutes = (menuStore: any, router: any) => {
if (menuStore.menuList && menuStore.menuList.length > 0) {
//首先判断传入的 store中是否时存在数据的,一般在获取数据时就会判断数据是否正确,且pinia增加持久化,故此地也可不要if判断
const routesData = JSON.parse(JSON.stringify(menuStore.menuList));
//深拷贝数据,慎用,上线之后项目会报错,大致是因为循坏加载constructor
const views = import.meta.glob('../views/**/*.vue');
//vue3 新写法,目的是为了后续处理数据中的组件引入路径
recursiveRoutes(routesData, views);
//路由处理函数,下述附代码
routesData.forEach((item: any) => {
//路由数据解析完成,开始添加到本地路由表中
router.addRoute('home', item);
});
}
router.addRoute({
//在所有的路由动态添加完毕后,需再次添加404页面,注意vue3新写法
path: '/:pathMatch(.*)*',
name: '404',
component: () => import('@/views/error/404.vue'),
meta: {
title: '无法找到该页面',
layout: false,
},
});
};
处理数据函数实现。
// 递归遍历路由数据
const recursiveRoutes = (tree: any[], views: any) => {
return tree.map((node) => {
const tempNode = node;
if (tempNode.component) {
// 修改每一个路由的组件引入路径
tempNode.component = views[`../views${tempNode.component}.vue`]
}
if (tempNode.children && tempNode.children.length > 0) {
// 存在嵌套路由时,递归调用该函数处理
recursiveRoutes(tempNode.children, views);
}
return tempNode;
});
};
退出时清除路由函数。
// 清除动态路由
const clerRoutes = (menuStore: any, router: any) => {
if (menuStore.menuList && menuStore.menuList.length > 0) {
menuStore.menuList.forEach((item: any) => {
router.removeRoute(item.name);
});
menuStore.clearMenuList();
//直接调用menus tore中定义好的action函数
}
};
至此,动态路由数据基本就可以成功添加在本地的路由表中了。至于渲染,采用的是element-plus中的el-menu组件,具体见代码:
//思路 :v-if 判断是否存在嵌套模式,不存在直接走 v-else 渲染普通路由
//存在 首先渲染嵌套路由的父路由,通过获取父路由的children 来循环渲染子路由
// list 中存储的是 通过router获取的路由数据,具体见下述script模块
//思路 :v-if 判断是否存在嵌套模式,不存在直接走 v-else 渲染普通路由
//存在 首先渲染嵌套路由的父路由,通过获取父路由的children 来循环渲染子路由
{{ item.meta.title }}
{{ vitem.meta!.title }}
{{ item.meta.title }}