vue3后台系统动态路由实现

news2025/1/12 13:12:24

动态路由的流程:用户登录之后拿到用户信息和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;
};

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何开启苹果手机(IOS)系统的开发者模式?

如何开启开发者模式&#xff1f; 一、打开设置二、隐私与安全性三、找到开发者模式四、开启开发者模式------------------------------------------------------------如果发现没有开发者模式的选项一、电脑下载爱思助手二、连接手机三、工具箱——虚拟定位——打开虚拟定位——…

国产编辑器EverEdit - 扩展脚本:在当前文件目录下新建同类型文件

1 扩展脚本&#xff1a;在当前文件目录下新建同类型文件 1.1 应用场景 用户在进行编程语言学习时&#xff0c;比如&#xff1a;Python&#xff0c;经常做完一个小练习后&#xff0c;又需要新建一个文件&#xff0c;在新建文件的时候&#xff0c;不但要选择文件类型&#xff0c…

011:利用大津算法完成图片分割

本文为合集收录&#xff0c;欢迎查看合集/专栏链接进行全部合集的系统学习。 合集完整版请参考这里。 上一篇文章介绍了大津算法可以完成图片的前景和背景分割。 总的来说&#xff0c;大津算法的核心思想就两个&#xff1a; 数学上&#xff0c;通过确定一个像素阈值&#xf…

Jenkins触发器--在其他项目执行后构建

前言&#xff1a; jenkins中有多种触发器可用&#xff0c;可以方便的控制构建的启动 这里简单介绍下项目后构建的配置方法 1. 解释&#xff1a; Build after other projects are built Set up a trigger so that when some other projects finish building, a new build is…

PowerApps助力PowerBI实现数据写回

原文发布日期: 2019-08-01 06:03:50 0000 注&#xff1a;本文旨在介绍Power BI如何利用PowerApps实现用户在前端对数据源进行增删查改&#xff0c;关于此&#xff0c;你也可以在Google上找到更详细但较零散的资料 正文 在SSAS多维数据集中&#xff0c;开发者可以给数据开启&q…

oracle 19c安装

文章目录 一 环境配置1、更换yum源2、文件配置 二 oracle环境配置1、下载依赖包2、创建用户和用户组3、创建目录并赋予权限4、修改资源限制参数5、修改内核参数6、配置安全7、配置Oracle环境变量 三 安装Oracle数据库四 创建Oracle实例五 启动数据库 一 环境配置 1、更换yum源…

LabVIEW启动时Access Violation 0xC0000005错误

问题描述 在启动LabVIEW时&#xff0c;可能出现程序崩溃并提示以下错误&#xff1a;Error 0xC0000005 (Access Violation) ​ Access Violation错误通常是由于权限不足、文件冲突或驱动问题引起的。以下是解决此问题的全面优化方案&#xff1a; 解决步骤 1. 以管理员身份运行…

xilinx平台使用多个 FIFO 拼接

Xilinx FIFO IP 输入 的最大位宽 是 1024 bit &#xff0c;当需要缓存的数据是 1280bit 又或者是 1536等 。怎么办呢&#xff1f; 有一个办法就是拆数据&#xff0c;将1280拆5个 256bit输入&#xff0c;也就是可以使用 5个 256位宽输入的FIFO拼接起来。&#xff08;其它位宽也…

Ceph分布式存储集群,不仅仅是一个简单的对象存储解决方案

Ceph 作为 OpenStack 的存储后端 块存储&#xff08;Cinder 后端&#xff09; Ceph 的 RBD&#xff08;RADOS Block Device&#xff09;模块作为 OpenStack Cinder 服务的后端&#xff0c;为虚拟机提供块级别的存储资源。RBD 支持快照、克隆和恢复等功能&#xff0c;能够满足虚…

SD ComfyUI工作流 老照片修复上色

文章目录 老照片修复上色SD模型Node节点工作流程开发与应用效果展示老照片修复上色 该工作流专门设计用于老照片的修复和上色,通过一系列高级的图像处理技术,包括深度图预处理、面部修复、上色和图像放大等步骤,来恢复老照片的质量并增加色彩。首先,工作流加载老照片并进行…

Jmeter-压测时接口如何按照顺序执行

Jmeter-压测时接口如何按照顺序执行-临界部分控制器 在进行压力测试时&#xff0c;需要按照顺序进行压测&#xff0c;比如按照接口1、接口2、接口3、接口4 进行执行 查询结果是很混乱的&#xff0c;如果请求次数少&#xff0c;可能会按照顺序执行&#xff0c;但是随着次数增加…

Mysql--运维篇--日志管理(连接层,SQL层,存储引擎层,文件存储层)

MySQL提供了多种日志类型&#xff0c;用于记录不同的活动和事件。这些日志对于数据库的管理、故障排除、性能优化和安全审计非常重要。 一、错误日志 (Error Log) 作用&#xff1a; 记录MySQL服务器启动、运行和停止期间遇到的问题和错误信息。 查看&#xff1a; 默认情况下…

【2025 Rust学习 --- 13 闭包:Rust的Lambda】

Rust的Lambda — 闭包 对整型向量进行排序很容易&#xff1a; integers.sort(); 遗憾的是&#xff0c;当我们想对一些数据进行排序时&#xff0c;它们几乎从来都不是整型向量。例 如&#xff0c;对某种记录型数据来说&#xff0c;内置的 sort 方法一般不适用&#xff1a; st…

鸿蒙面试 2025-01-09

鸿蒙分布式理念&#xff1f;&#xff08;个人认为理解就好&#xff09; 鸿蒙操作系统的分布式理念主要体现在其独特的“流转”能力和相关的分布式操作上。在鸿蒙系统中&#xff0c;“流转”是指涉多端的分布式操作&#xff0c;它打破了设备之间的界限&#xff0c;实现了多设备…

一个基于Spring Boot的智慧养老平台

以下是一个基于Spring Boot的智慧养老平台的案例代码。这个平台包括老人信息管理、健康监测、紧急呼叫、服务预约等功能。代码结构清晰&#xff0c;适合初学者学习和参考。 1. 项目结构 src/main/java/com/example/smartelderlycare├── controller│ ├── ElderlyCon…

Taro+react 开发第一节创建 带有redux状态管理的项目

Taro 项目基于 node&#xff0c;请确保已具备较新的 node 环境&#xff08;>16.20.0&#xff09;&#xff0c;推荐使用 node 版本管理工具 nvm 来管理 node&#xff0c;这样不仅可以很方便地切换 node 版本&#xff0c;而且全局安装时候也不用加 sudo 了。 1.安装 npm inf…

云商城--基础数据处理和分布式文件存储

第2章 基础数据处理和分布式文件存储 1.分布式文件存储系统Ceph学习 ​ 1).掌握Ceph架构 ​ 2).掌握Ceph组件 ​ 3).搭建Ceph集群(了解) 2.Ceph使用 ​ 1).基于Ceph实现文件上传 ​ 2).基于Ceph实现文件下载 3.SKU、SPU管理 ​ 1).掌握SKU和SPU关系 ​ 2).理解商品发…

Vue.js:现代前端开发的灵活框架

大家好&#xff01;我是 [数擎 AI]&#xff0c;一位热爱探索新技术的前端开发者&#xff0c;在这里分享前端和 Web3D、AI 技术的干货与实战经验。如果你对技术有热情&#xff0c;欢迎关注我的文章&#xff0c;我们一起成长、进步&#xff01; 开发领域&#xff1a;前端开发 | A…

初学者关于对机器学习的理解

一、机器学习&#xff1a; 1、概念&#xff1a;是指从有限的观测数据中学习(或“猜 测”)出具有一般性的规律&#xff0c;并利用这些规律对未知数据进行预测的方法.机器学 习是人工智能的一个重要分支&#xff0c;并逐渐成为推动人工智能发展的关键因素。 2、使用机器学习模型…

小程序textarea组件键盘弹起会遮挡住输入框

<textarea value"{{remark}}" input"handleInputRemark" ></textarea> 如下会有遮挡&#xff1a; 一行代码搞定 cursor-spacing160 修改后代码 <textarea value"{{remark}}" input"handleInputRemark" cursor-spacin…