最近编写vue3动态路由的功能遇到了一些问题,处理好了,总结出来,希望能帮助到你。
- 正片开始
先写好本地缓存菜单的方法(存储、删除、获取)
// utils/menu.js
const 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.js
import { 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(); // 清空 state
clearToken(); // 清除 token
clearMenuList(); // 清除 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`这段不是固定的哈要根据你的文件格式来自定义,否则在上面导出文件数组中匹配不到会返回 undefined
meta: 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(.)” 当刷新界面时,动态添加的路由会清空,地址栏的路由路径找不到就会报不影响代码正常跑但是不好看。
// router/index.js 配置路由模式,设置路由拦截等等
// import NProgress from "@/config/nprogress";
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 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,文章最后附上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 }) 一直匹配不到存在的路由死循环
- 进度条nprogress配置
// config/nprogress.js
import NProgress from "nprogress";
import "nprogress/nprogress.css";
NProgress.configure({
easing: "ease", // 动画方式
speed: 500, // 递增进度条的速度
showSpinner: true, // 是否显示加载ico
trickleSpeed: 200, // 自动递增间隔
minimum: 0.3 // 初始化时的最小百分比
});
export default NProgress;
- 到这就差不多了,因该可以用了,啰嗦了点