vue3后台系统动态路由实现

embedded/2025/1/11 7:03:47/

动态路由的流程:用户登录之后拿到用户信息和token,再去请求后端给的动态路由表,前端处理路由格式为vue路由格式。

1)拿到用户信息里面的角色之后再去请求路由表,返回的路由为tree格式

后端返回路由如下:

前端处理:

共识:动态路由在路由守卫 beforeEach 里面进行处理,每次跳转路由都会走这里。

1.src下新建permission.js文件,main.js中引入

javascript">// main.js
import './permission'

2.permission.js里面重要的一点是:要确保路由已经被添加进去才跳转,否则页面会404或白屏

javascript">import router from "./router";
import { ElMessage } from "element-plus";
import NProgress from "nprogress";
import "nprogress/nprogress.css";
import { getToken } from "@/utils/auth";
import usePermissionStore from "@/store/modules/permission";
NProgress.configure({ showSpinner: false });const whiteList = ["/login", "/register"];const isWhiteList = (path) => {return whiteList.some((pattern) => isPathMatch(pattern, path));
};router.beforeEach((to, from, next) => {NProgress.start();if (getToken()) {/* has token*/if (to.path === "/login") {next({ path: "/" });NProgress.done();} else if (isWhiteList(to.path)) {next();} else {// 如果已经请求过路由表,直接进入const hasRefresh = usePermissionStore().hasRefreshif (!hasRefresh) {next()}else{try {// getRoutes 方法用来获取动态路由usePermissionStore().getRoutes().then(routes => {           const hasRoute = router.hasRoute(to.name)routes.forEach(route => {router.addRoute(route) // 动态添加可访问路由表})if (!hasRoute) {// 如果该路由不存在,可能是动态注册的路由,它还没准备好,需要再重定向一次到该路由next({ ...to, replace: true }) // 确保addRoutes已完成} else {next()}}).catch((err)=>{next(`/login?redirect=${to.path}`)})} catch (error) {ElMessage.error(error || 'Has Error')next(`/login?redirect=${to.path}`)NProgress.done()}}}} else {// 没有tokenif (isWhiteList(to.path)) {// 在免登录白名单,直接进入next();} else {next(`/login?redirect=${to.fullPath}`) // 否则全部重定向到登录页NProgress.done();}}
});router.afterEach(() => {NProgress.done();
});

3.store/modules/permission.js

javascript">async getRoutes() {this.hasRefresh = false;const roleId = JSON.parse(localStorage.getItem("user")).roldId;return new Promise((resolve, reject)=>{if (roleId) {getRouters({ roleId: roleId }).then((res) => {let routes = [];routes = generaRoutes(routes, res.data);console.log('routes',routes);this.setRoutes(routes)this.setSidebarRouters(routes)resolve(routes);});} else {this.$router.push(`/login`);}})  }//添加动态路由
setRoutes(routes) {this.addRoutes = routes;this.routes = constantRoutes.concat(routes);
},// 设置侧边栏路由
setSidebarRouters(routes) {this.sidebarRouters = routes;
}
javascript">// 匹配views里面所有的.vue文件
const modules = import.meta.glob("./../../views/**/*.vue");//将后端给的路由处理成vue路由格式,这个方法不是固定的,根据后端返回的数据做处理
//这段代码是若依框架里的,原来的代码不支持三级路由,我改了下
function generaRoutes(routes, data, parentPath = "") {data.forEach((item) => {if (item.isAccredit == true) {if (item.category.toLowerCase() == "moudle" ||item.category.toLowerCase() == "menu") {const fullPath = parentPath ? `${parentPath}/${item.path}` : item.path;const menu = {path:item.category.toLowerCase() == "moudle"? "/" + item.path: item.path,name: item.path,component:item.category.toLowerCase() == "moudle"? Layout: loadView(`${fullPath}/index`),hidden: false,children: [],meta: {icon: item.icon,title: item.name,},};if (item.children) {generaRoutes(menu.children, item.children, fullPath);}routes.push(menu);}}});return routes;
}export const loadView = (view) => {let res;for (const path in modules) {const dir = path.split("views/")[1].split(".vue")[0];// 将路径转换为数组以便逐级匹配const pathArray = dir.split('/');const viewArray = view.split('/');if (pathArray.length === viewArray.length && pathArray.every((part, index) => part === viewArray[index])) {res = () => modules[path]();break; // 找到匹配项后退出循环}}return res;
};

2)登录接口里后端返回路由表,返回的路由格式为对象数组,不为tree格式

这种情况下需要将后端返回的路由处理成tree格式后,再处理成vue的路由格式,我是分两步处理的。(有来技术框架基础上改的)

后端返回路由如下:这个数据格式比较简陋,但没关系,只要能拿到url或path就没问题

1.登录逻辑里面将数据处理成tree格式,store/modules/user.ts

  const menuList = useStorage<TreeNode[]>("menuList", [] as TreeNode[]);function login(loginData: LoginData) {return new Promise<void>((resolve, reject) => {AuthAPI.login(loginData).then((data) => {const { accessToken, info, menus, welcome } = data;setToken("Bearer" + " " + accessToken); // Bearer eyJhbGciOiJIUzI1NiJ9.xxx.xxxmenuList.value = transRouteTree(menus);// 生成路由和侧边栏usePermissionStoreHook().generateRoutes(menuList.value);resolve();}).catch((error) => {reject(error);});});}// 将后端返回的路由转为tree结构function transRouteTree(data: RouteNode[]): TreeNode[] {if (!data || !Array.isArray(data)) {return [];}const map: { [id: number]: TreeNode } = {};const roots: TreeNode[] = [];data.forEach((node) => {if (!node || typeof node !== "object") {return [];}map[node.id] = {path: node.url ? node.url : "/",component: node.url ? node.url + "/index" : "Layout",name: node.url,meta: {title: node.menuName,icon: "system",hidden: false,alwaysShow: false,params: null,},children: [],};if (node.parentId === 0) {roots.push(map[node.id]);} else {if (map[node.parentId]) {map[node.parentId].children.push(map[node.id]);}}});return roots;}

2.src下的permission.ts 

router.beforeEach(async (to, from, next) => {NProgress.start();const isLogin = !!getToken(); // 判断是否登录if (isLogin) {if (to.path === "/login") {// 已登录,访问登录页,跳转到首页next({ path: "/" });} else {const permissionStore = usePermissionStore();// 判断路由是否加载完成if (permissionStore.isRoutesLoaded) {console.log(to, "to000");if (to.matched.length === 0) {// 路由未匹配,跳转到404next("/404");} else {// 动态设置页面标题const title = (to.params.title as string) || (to.query.title as string);if (title) {to.meta.title = title;}next();}} else {try {// 生成动态路由const list = userStore.menuList || [];await permissionStore.generateRoutes(list);next({ ...to, replace: true });} catch (error) {// 路由加载失败,重置 token 并重定向到登录页await useUserStore().clearUserData();redirectToLogin(to, next);NProgress.done();}}}} else {// 未登录,判断是否在白名单中if (whiteList.includes(to.path)) {next();} else {// 不在白名单,重定向到登录页redirectToLogin(to, next);NProgress.done();}}});// 后置守卫,保证每次路由跳转结束时关闭进度条router.afterEach(() => {NProgress.done();});// 重定向到登录页
function redirectToLogin(to: RouteLocationNormalized, next: NavigationGuardNext) {const params = new URLSearchParams(to.query as Record<string, string>);const queryString = params.toString();const redirect = queryString ? `${to.path}?${queryString}` : to.path;next(`/login?redirect=${encodeURIComponent(redirect)}`);
}

3.store/modules/permission.ts

  /*** 生成动态路由*/function generateRoutes(data: RouteVO[]) {return new Promise<RouteRecordRaw[]>((resolve) => {const dynamicRoutes = transformRoutes(data);routes.value = constantRoutes.concat(dynamicRoutes); // 侧边栏dynamicRoutes.forEach((route: RouteRecordRaw) => router.addRoute(route));isRoutesLoaded.value = true;resolve(dynamicRoutes);});}/*** 转换路由数据为组件*/
const transformRoutes = (routes: RouteVO[]) => {const asyncRoutes: RouteRecordRaw[] = [];routes.forEach((route) => {const tmpRoute = { ...route } as RouteRecordRaw;// 顶级目录,替换为 Layout 组件if (tmpRoute.component?.toString() == "Layout") {tmpRoute.component = Layout;} else {// 其他菜单,根据组件路径动态加载组件const component = modules[`../../views${tmpRoute.component}.vue`];if (component) {tmpRoute.component = component;} else {tmpRoute.component = modules["../../views/error-page/404.vue"];}}if (tmpRoute.children) {tmpRoute.children = transformRoutes(route.children);}asyncRoutes.push(tmpRoute);});return asyncRoutes;
};

http://www.ppmy.cn/embedded/152940.html

相关文章

用c实现C++类(八股)

在 C 语言中&#xff0c;虽然没有内建的面向对象编程&#xff08;OOP&#xff09;特性&#xff08;如封装、继承、多态&#xff09;&#xff0c;但通过一些编程技巧&#xff0c;我们仍然可以模拟实现这些概念。下面将用通俗易懂的方式&#xff0c;逐步介绍如何在 C 中实现封装、…

8 事件等待

临界区&自旋锁 这两个章节在”多核同步“篇已经学习过了,需要了解的可以自行查看对应章节。 线程等待与唤醒 我们在之前的课程里面了解了如何自己实现临界区以及什么是Windows自旋锁,这两种同步方案在线程无法进入临界区时都会让当前线程进入等待状态。 一种是通过Sl…

Pytest安装Allure生成自动化测试报告

Date: 2025.01.09 16:33:01 author: lijianzhan Allure 是一个强大的测试报告框架&#xff0c;能够生成美观且详细的测试报告。它与 pytest 结合使用&#xff0c;可以帮助你更好地展示测试结果、分析测试数据&#xff0c;并提高测试的可读性和可维护性。以下是关于如何在 Pytho…

在 Vue 3 集成 e签宝电子合同签署功能

实现 Vue 3 e签宝电子合同签署功能&#xff0c;需要使用 e签宝提供的实际 SDK 或 API。 e签宝通常提供针对不同平台&#xff08;如 Web、Android、iOS&#xff09;的 SDK&#xff0c;而 Web 端一般通过 WebView 或直接使用嵌入式 iframe 来加载合同签署页面。 下面举个 &…

JavaSE(十五)——认识进程与多线程入门

文章目录 认识进程进程的概念进程的特性进程的状态进程的优先级进程的描述进程的调度 并发与并行认识线程线程的概念Java中线程的表示认识Thread类线程的创建 线程的控制和管理线程启动-start()线程休眠-sleep()线程中断-interrupt()线程插队-join()线程让步-yield() 线程的状态…

DARPA 着眼于新的量子传感技术研究

前言 DARPA 正专注于推进量子传感器的研究&#xff0c;以应对定位、导航和授时&#xff08;PNT&#xff09;以及军事应用中的情报、监视和侦察&#xff08;ISR&#xff09;方面的挑战。最新一项名为“鲁棒量子传感器”&#xff08;RoQS&#xff09;的新计划旨在提高能够探测地…

代码随想录day03

203 链表基础操作 class Solution { public:ListNode* removeElements(ListNode* head, int val) {while (head!NULL&&head->valval){ListNode* temphead;headhead->next;delete temp;}ListNode* curhead;while (cur!NULL&&cur->next!NULL){if(cur-…

Python基于YOLOv8和OpenCV实现车道线和车辆检测

使用YOLOv8&#xff08;You Only Look Once&#xff09;和OpenCV实现车道线和车辆检测&#xff0c;目标是创建一个可以检测道路上的车道并识别车辆的系统&#xff0c;并估计它们与摄像头的距离。该项目结合了计算机视觉技术和深度学习物体检测。 1、系统主要功能 车道检测&am…