最近编写vue3动态路由的功能遇到了一些问题,处理好了,总结出来,希望能帮助到你。
- 正片开始
先写好本地缓存菜单的方法(存储、删除、获取)
// utils/menu.jsconst getMenuList = () => {return JSON.parse(localStorage.getItem('menu_list'));
};const setMenuList = (menuList) => {localStorage.setItem('menu_list', JSON.stringify(menuList));
};const clearMenuList = () => {localStorage.removeItem('menu_list');
};export { getMenuList, setMenuList, clearMenuList };
- pinia (vuex) 里创建一个全局变量存储菜单数组 && 在 actions方法里创建登录和退出登录的异步方法
// store/modules/user.jsimport { defineStore } from "pinia";
import { setMenuList, clearMenuList, getMenuList } from "@/utils/menu";const useUserStore = defineStore("user", {state: () => {return {token: undefined,userMenuPermission: [] // 菜单数组初始我们设为空数组,等下需要他做判断}},getters: {userInfo(state) {return { ...state };},},actions: {// 设置用户信息setInfo(partial) {this.$patch(partial);},// 重置用户信息resetInfo() {this.$reset();},// 获取用户信息info() {// 这可判断一下 本地存储的菜单是否存在,存在就给 userMenuPermission赋值就行不掉接口,我这没有整,你们按照自己需求来 getUserInfo().then((res) => {this.setInfo(res.data); // 将用户信息设置给state,但是注意需要key相同setMenuList(res.data.userMenuPermission) // 缓存菜单数组generateRoutes(this.userMenuPermission) // 添加动态路由});},// 账号密码登录async loginPwd(form, pubKey) {// ...省略await setToken(res.data.token); // 保存token 存储手法和菜单的一样},// 退出登录logout() {userLogin.logout().then((res) => {this.logoutCallBack();});},// 退出登录清空token和用户信息logoutCallBack() {this.resetInfo(); // 清空 stateclearToken(); // 清除 tokenclearMenuList(); // 清除 MenuList},},
});
export default useUserStore; // 暴露模块
// store/index.js
import { createPinia } from "pinia";
import useUserStore from "./modules/user";const pinia = createPinia();export { useUserStore };
export default pinia;
- 处理路由配置
// router/modules/dynamicRouter.js 处理菜单数组和动态添加路由import { DEFAULT_LAYOUT } from "./staticRouter"; // layout (布局)
import router from "@/router/index"; // 1. 引入views下的所有文件 (使用vite创建的需要这样动态导入地址)
const modules = import.meta.glob('@/views/**/*.vue')/*** router.addRoute(route: RouteRecord):动态添加路由* router.removeRoute(name: string | symbol):动态删除路由* router.hasRoute(name: string | symbol): 判断路由是否存在* router.getRoutes(): 获取路由列表*/// 根据菜单信息递归生成路由(记得递归一定要出口)
export function generateRoutes(menuList = []) {const routes = [];menuList.forEach((menu) => {let routeItem = {};//如果是目录if (menu.meta.type == 1) {routeItem = {//路由名称name: menu.name,//路由地址path: menu.path,//组件路径component: DEFAULT_LAYOUT, // 是目录就为布局路径children: [],meta: menu.meta};} else {//如果是菜单routeItem = {//路由名称name: menu.name,//路由地址path: menu.path,//组件路径component: modules[`/src/views${menu.component}/index.vue`], // 为菜单的地址就要根据后端返回的地址来拼接,`/src/views${menu.component}/index.vue`这段不是固定的哈要根据你的文件格式来自定义,否则在上面导出文件数组中匹配不到会返回 undefinedmeta: menu.meta,children: [],};}if (menu.children && menu.children.length > 0) {routeItem.children = generateRoutes(menu.children);}if (!router.hasRoute(routeItem.name)) { // 防止添加了相同的菜单router.addRoute('layout', routeItem)}routes.push(routeItem); // 这个是拼接好处理的路由数组});return routes; // 根据需要返回即可
}
上面可能会遇到的问题:
modules[/src/views${menu.component}/index.vue
] 会匹配不到你后端返回的路径,你要检查一下拼接格式是否正确,可打印出来和 const modules = import.meta.glob(‘@/views/**/*.vue’)数组里的对比查看问题
// router/modules/staticRouter.js 放置一些静态路由资源比如404、login等页面/*** * layout (布局)*/
export const DEFAULT_LAYOUT = () => import('@/layout/default-layout.vue');/*** staticRouter (静态路由)*/
export const staticRouter = [{path: '/',redirect: '/dashboard',},{path: '/login',name: 'login',component: () => import('@/views/login/index.vue'),meta: {requiresAuth: false,},},
];export const staticMenuRouter = [{name: "dashboard",path: "/dashboard",component: DEFAULT_LAYOUT,redirect: "/dashboard/index",meta: {title: "首页",sort: 1,icon: "icon-dashboard",},children: [{path: "index",name: "index",component: () => import("@/views/dashboard/index.vue"),meta: {title: "工作台",affix: true, // 用于判断TagsView是否一直固定hidden: true},},],}
]/*** errorRouter (错误页面路由)*/
export const errorRouter = [{path: "/403",name: "403",component: () => import("@/views/exception/403.vue"),meta: {title: "403页面"}},{path: "/404",name: "404",component: () => import("@/views/exception/404.vue"),meta: {title: "404页面"}},{path: "/500",name: "500",component: () => import("@/views/exception/500.vue"),meta: {title: "500页面"}},// 解决刷新页面,路由警告{path: "/:pathMatch(.*)*",component: () => import("@/views/exception/404.vue")}
];
上面可能会遇到的问题:
没有设置 path: “/:pathMatch(.)” 当刷新界面时,动态添加的路由会清空,地址栏的路由路径找不到就会报不影响代码正常跑但是不好看。
// routerindex.js 配置路由模式,设置路由拦截等等import { createRouter, createWebHistory, createWebHashHistory } from 'vue-router';
import { staticRouter, errorRouter, staticMenuRouter } from "@/router/modules/staticRouter";
import { useUserStore } from '@/store';
import { isLogin } from '@/utils/cache/auth'; // 判断token是否存在// const userStore = useUserStore();const title = 'xxxxxx系统'// 白名单
let whiteList = ["login", '404'];export function getPageTitle(pageTitle) { // 拼接每个路由页面的名称显示在浏览器if (pageTitle) {return `${pageTitle} - ${title}`}return `${title}`
}// 设置路由模式,导入静态的路由
const router = createRouter({history: createWebHashHistory(),routes: [...staticRouter, ...errorRouter, ...staticMenuRouter],strict: false, // strict 为 true 时,将不会匹配带有尾部斜线的路由// sensitive: false, // 为 true 时,将区分大小写scrollBehavior: () => ({ left: 0, top: 0 }) // 设置页面滚动行为(始终滚动到顶部)
});// 路由前置首位
router.beforeEach(async (to, from, next) => {const userStore = useUserStore();// 1.NProgress 开始// NProgress.start(); // 一个加载动画,需要的朋友可以去看咋使用的 npm install --save nprogress// 2.判断是访问登陆页,有 Token 就在当前页面,没有 Token 重置路由到登陆页if (to.path.toLocaleLowerCase() === '/login') {if (isLogin()) return next(from.fullPath);resetRouter(); // 没有token 就重置路由return next(); // 放行去登录页}// 3.判断访问页面是否在路由白名单地址(静态路由)中,如果存在直接放行if (whiteList.includes(to.path)) return next();// 4.判断是否有 Token,没有重定向到 login 页面if (!isLogin()) return next({ path: '/login', replace: true });// 5.我们判断 vuex里面的设置的菜单列表是否为空,为空就重新获取列表添加路由if (!userStore.userMenuPermission.length) {await userStore.info();return next({ ...to, replace: true }); // 在已有的路由里自己匹配,replace: true 允许用户手贱点回退}// 8.正常访问页面next();});// 重置路由
const resetRouter = () => {const userStore = useUserStore();userStore.userMenuPermission.forEach(route => {const { name } = route;if (name && router.hasRoute(name)) router.removeRoute(name);});
};// 全局后置守卫
router.afterEach((to, from) => {// 动态设置浏览器标题document.title = getPageTitle(to.meta.title)// NProgress.done();
})export default router;
上面可能会遇到的问题:
刷新界面后重新获取路由动态添加路由,要是异步的
否则会出现 next({ …to, replace: true }) 一直匹配不到存在的路由死循环
- 到这就差不多了,因该可以用了,啰嗦了点