vue3后台系统动态路由实现

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

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

后端返回路由如下:

前端处理:

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

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

// main.js
import './permission'

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

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().hasRefresh
      if (!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 {
    // 没有token
    if (isWhiteList(to.path)) {
      // 在免登录白名单,直接进入
      next();
    } else {
      next(`/login?redirect=${to.fullPath}`) // 否则全部重定向到登录页
      NProgress.done();
    }
  }
});

router.afterEach(() => {
  NProgress.done();
});

3.store/modules/permission.js

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;
}
// 匹配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.xxx
          menuList.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) {
            // 路由未匹配,跳转到404
            next("/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;
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/952742.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【DAPM杂谈之二】实践是检验真理的标准

本文主要分析DAPM的设计与实现 内核的版本是&#xff1a;linux-5.15.164&#xff0c;下载链接&#xff1a;Linux内核下载 主要讲解有关于DAPM相关的知识&#xff0c;会给出一些例程并分析内核如何去实现的 /**************************************************************…

【Qt】事件、qt文件

目录 Qt事件 QEvent QMouseEvent QWheelEvent QKeyEvent QTimerEvent Qt文件 QFile QFileInfo Qt事件 在Qt中用一个对象表示一个事件&#xff0c;这些事件对象都继承自抽象类QEvent。事件和信号的目的是一样的&#xff0c;都是为了响应用户的操作。有两种产生事件的方…

线形回归与小批量梯度下降实例

1、准备数据集 import numpy as np import matplotlib.pyplot as pltfrom torch.utils.data import DataLoader from torch.utils.data import TensorDataset######################################################################### #################准备若干个随机的x和…

消息队列使用中防止消息丢失的实战指南

消息队列使用中防止消息丢失的实战指南 在分布式系统架构里&#xff0c;消息队列起着举足轻重的作用&#xff0c;它异步解耦各个业务模块&#xff0c;提升系统整体的吞吐量与响应速度。但消息丢失问题&#xff0c;犹如一颗不定时炸弹&#xff0c;随时可能破坏系统的数据一致性…

【优选算法篇】:深入浅出位运算--性能优化的利器

✨感谢您阅读本篇文章&#xff0c;文章内容是个人学习笔记的整理&#xff0c;如果哪里有误的话还请您指正噢✨ ✨ 个人主页&#xff1a;余辉zmh–CSDN博客 ✨ 文章所属专栏&#xff1a;优选算法篇–CSDN博客 文章目录 一.位运算一.位运算概述二.常见的位运算操作符三.常见的位运…

创业AI Agents系统深度解析

Agents 近日&#xff0c;AI领域的知名公司Anthropic发布了一份题为《构建高效的智能代理》的报告。该报告基于Anthropic过去一年与多个团队合作构建大语言模型&#xff08;LLM&#xff09;智能代理系统的经验&#xff0c;为开发者及对该领域感兴趣的人士提供了宝贵的洞见。本文…

【Spring Boot】Spring 事务探秘:核心机制与应用场景解析

前言 &#x1f31f;&#x1f31f;本期讲解关于spring 事务介绍~~~ &#x1f308;感兴趣的小伙伴看一看小编主页&#xff1a;GGBondlctrl-CSDN博客 &#x1f525; 你的点赞就是小编不断更新的最大动力 &#x1f386;那么废话不多说直…

centos7.6 安装nginx 1.21.3与配置ssl

1 安装依赖 yum -y install gcc zlib zlib-devel pcre-devel openssl openssl-devel2 下载Nginx wget http://nginx.org/download/nginx-1.21.3.tar.gz3 安装目录 mkdir -p /data/apps/nginx4 安装 4.1 创建用户 创建用户nginx使用的nginx用户。 #添加www组 # groupa…

夯实前端基础之HTML篇

知识点概览 HTML部分 1. DOM和BOM有什么区别&#xff1f; DOM&#xff08;Document Object Model&#xff09; 当网页被加载时&#xff0c;浏览器会创建页面的对象文档模型&#xff0c;HTML DOM 模型被结构化为对象树 用途&#xff1a; 主要用于网页内容的动态修改和交互&…

Elasticsearch:向量数据库基础设施类别的兴衰

过去几年&#xff0c;我一直在观察嵌入技术如何从大型科技公司的 “秘密武器” 转变为日常开发人员工具。接下来发生的事情 —— 向量数据库淘金热、RAG 炒作周期以及最终的修正 —— 教会了我们关于新技术如何在更广泛的生态系统中找到一席之地的宝贵经验。 更多有关向量搜索…

【华为云开发者学堂】基于华为云 CodeArts CCE 开发微服务电商平台

实验目的 通过完成本实验&#xff0c;在 CodeArts 平台完成基于微服务的应用开发&#xff0c;构建和部署。 ● 理解微服务应用架构和微服务模块组件 ● 掌握 CCE 平台创建基于公共镜像的应用的操作 ● 掌握 CodeArts 平台编译构建微服务应用的操作 ● 掌握 CodeArts 平台部署微…

计科高可用服务器架构实训(防火墙、双机热备,VRRP、MSTP、DHCP、OSPF)

一、项目介绍 需求分析&#xff1a; &#xff08;1&#xff09;总部和分部要求网络拓扑简单&#xff0c;方便维护&#xff0c;网络有扩展和冗余性&#xff1b; &#xff08;2&#xff09;总部分财务部&#xff0c;人事部&#xff0c;工程部&#xff0c;技术部&#xff0c;提供…

【C++入门】详解合集

目录 &#x1f495;1.C中main函数内部———变量的访问顺序 &#x1f495;2.命名空间域 namespace &#x1f495;3.命名空间域&#xff08;代码示例&#xff09;&#xff08;不要跳&#xff09; &#x1f495;4.多个命名空间域的内部重名 &#x1f495;5.命名空间域的展开 …

预编译SQL

预编译SQL 预编译SQL是指在数据库应用程序中&#xff0c;SQL语句在执行之前已经通过某种机制&#xff08;如预编译器&#xff09;进行了解析、优化和准备&#xff0c;使得实际执行时可以直接使用优化后的执行计划&#xff0c;而不需要每次都重新解析和编译。这么说可能有一些抽…

qemu搭建虚拟的aarch64环境开发ebpf

一、背景 需求在嵌入式环境下进行交叉编译&#xff0c;学习ebpf相关技术&#xff0c;所以想搭建一个不依赖硬件环境的学习环境。 本文使用的环境版本&#xff1a; 宿主机&#xff1a; Ubuntu24.02 libbpf-bootstrap源码&#xff1a; https://github.com/libbpf/libbpf-boots…

深度学习从入门到实战——卷积神经网络原理解析及其应用

卷积神经网络CNN 卷积神经网络前言卷积神经网络卷积的填充方式卷积原理展示卷积计算量公式卷积核输出的大小计算感受野池化自适应均值化空洞卷积经典卷积神经网络参考 卷积神经网络 前言 为什么要使用卷积神经网络呢&#xff1f; 首先传统的MLP的有什么问题呢&#xff1f; - …

2015年西部数学奥林匹克几何试题

2015/G1 圆 ω 1 \omega_1 ω1​ 与圆 ω 2 \omega_2 ω2​ 内切于点 T T T. M M M, N N N 是圆 ω 1 \omega_1 ω1​ 上不同于 T T T 的不同两点. 圆 ω 2 \omega_2 ω2​ 的两条弦 A B AB AB, C D CD CD 分别过 M M M, N N N. 证明: 若线段 A C AC AC, B D BD …

《Spring Framework实战》14:4.1.4.5.自动装配合作者

欢迎观看《Spring Framework实战》视频教程 自动装配合作者 Spring容器可以自动连接协作bean之间的关系。您可以通过检查ApplicationContext的内容&#xff0c;让Spring自动为您的bean解析协作者&#xff08;其他bean&#xff09;。自动装配具有以下优点&#xff1a; 自动装配…

JVM之垃圾回收器概述(续)的详细解析

ParNew(并行) Par 是 Parallel 并行的缩写&#xff0c;New 是只能处理的是新生代 并行垃圾收集器在串行垃圾收集器的基础之上做了改进&#xff0c;采用复制算法&#xff0c;将单线程改为了多线程进行垃圾回收&#xff0c;可以缩短垃圾回收的时间 对于其他的行为&#xff08;…

有一台服务器可以做哪些很酷的事情

有一台服务器可以做哪些很酷的事情 今天我也来简单分享一下&#xff0c;这几年来&#xff0c;我用云服务器做了哪些有趣的事情。 服务器推荐 1. 个人博客 拥有个人服务器&#xff0c;你可以完全掌控自己的网站或博客。 与使用第三方托管平台相比&#xff0c;你能自由选择网站…