前端 + 接口请求实现 vue 动态路由

前端 + 接口请求实现 vue 动态路由

在 Vue 应用中,通过前端结合后端接口请求来实现动态路由是一种常见且有效的权限控制方案。这种方法允许前端根据用户的角色和权限,动态生成和加载路由,而不是在应用启动时就固定所有的路由配置。

实现原理

  1. 定义静态路由配置:

    • 在项目的初始阶段,定义一套完整的路由配置,这些配置包含了所有可能的路由路径和相关的权限信息。
  2. 用户登录与鉴权:

    • 用户登录时,前端向后端发送请求验证用户的身份。
    • 服务器验证成功后,返回一个包含用户信息和权限的数据对象。
  3. 获取用户权限信息:

    • 前端根据登录时获得的令牌(如 JWT),再次向后端请求获取当前用户的权限信息。
    • 权限信息可能包括用户的角色、能够访问的资源等。
  4. 动态生成路由:

    • 前端根据从后端获取的权限信息,动态生成符合用户权限的路由表。
    • 这个过程可以通过递归算法处理路由配置树,根据用户的权限过滤掉无权访问的路由。
  5. 动态添加路由:

    • 使用 Vue Router 的 router.addRoutes(routes) 方法将生成的路由动态添加到路由实例中。
    • 这样只有经过权限验证的路由才会被添加,从而实现了权限控制。
  6. 动态渲染菜单:

    • 左侧菜单通常是基于生成的路由表来渲染的,因此只有用户有权访问的路由才会在菜单中显示。

优点

  1. 安全性:

    • 只有经过验证的用户才能访问其权限范围内的页面。
    • 减少了由于硬编码路由导致的安全漏洞。
  2. 灵活性:

    • 可以根据用户的权限动态调整应用的结构,无需重新部署整个应用即可调整路由。
    • 支持按需加载(懒加载),提高应用性能。
  3. 用户体验:

    • 只展示用户可以访问的菜单项,避免显示无用链接,提高用户体验。
    • 用户界面更加简洁,只显示与其角色相关的功能。
  4. 可维护性:

    • 简化了路由配置,因为不需要为每个角色单独编写路由配置,而是集中管理权限。
    • 更容易扩展和修改权限配置,只需更新后端的权限数据即可。
  5. 开发效率:

    • 开发者只需要关注业务逻辑,而不需要关心每个角色的具体路由配置。
    • 减少了重复工作,提高了开发效率。

示例

在前导航路由钩子 beforeEach 函数里发送接口请求获取路由信息

permission.js

// permission.js
import router from './router'
import store from './store'
import { Message } from 'element-ui'
import { getStore } from '@/utils/store';

const whiteList = ['/login', '/404', '/401'];

router.beforeEach((to, from, next) => {
  let token = getStore('token');

  if (token) {
    /* has token*/
    if (to.path === '/login') {
      next({ path: '/' });
    } else {
      if (store.getters.roles.length === 0) {
        // 判断当前用户是否已拉取完user_info信息
        store.dispatch('GetInfo').then(() => {
          // 获取路由信息
          store.dispatch('GenerateRoutes').then((res) => {
            console.log('--------------', res);
            // 根据roles权限生成可访问的路由表
            router.addRoutes(res) // 动态添加可访问路由表
            next({ ...to, replace: true }) // hack方法 确保addRoutes已完成
          })
        }).catch(err => {
          store.dispatch('LogOut').then(() => {
            Message.error(err)
            next(`/`)
          })
        })
      } else {
        next()
      }
    }
  } else {
    // 没有token
    if (whiteList.indexOf(to.path) !== -1) {
      // 在免登录白名单,直接进入
      next()
    } else {
      next(`/login`) // 否则全部重定向到登录页
    }
  }
})

permission.js 文件需引入到 main.js 里

如果项目 vue-router 版本超过 3.3.0, 需要遍历路由数组再使用 router.addRoute() 方法逐个添加路由

res.forEach( route => {
	router.addRoute(route);
})

假设后端接口返回的路由权限如下

[
  {
    path: '/admin',
    meta: {
      title: "系统管理",
    },
    component: 'Layout',
    children: [
      {
        path: 'user',
        name: 'userIndex',
        meta: {
          title: "用户管理",
        },
        component: '/admin/user/index.vue'
      },
      {
        path: 'role',
        name: 'roleIndex',
        meta: {
          title: "角色管理",
        },
        component: '/admin/role/index.vue',
        children: [
          {
            path: 'add',
            name: 'addRole',
            meta: {
              title: "添加角色",
            },
            component: '/admin/user/index.vue'
          },
          {
            path: 'update',
            name: 'updateRole',
            meta: {
              title: "编辑角色",
            },
            component: '/admin/role/index.vue'
          }
        ]
      }
    ]
  },
  {
    path: '/tableEcho',
    meta: {
      title: "表格管理",
    },
    component: 'Layout',
    children: [
      {
        path: 'test',
        name: 'tableEchoIndex',
        meta: {
          title: "表格测试",
        },
        component: '/tableEcho/index.vue',
        children: [
          {
            path: 'add',
            name: 'addTable',
            hidden: true,
            meta: {
              title: "新增测试",
            },
            component: '/tableEcho/add.vue'
          }
        ]
      },
    ],
  },
]

vuex 处理数据

store/index.vue

// store/index.vue
import Vue from 'vue'
import Vuex from 'vuex'
import { routes, dynamicRoutes } from "@/router";
import { login, getInfo, logout, getRouters } from "@/api/user";
import { setStore, clearStore } from '@/utils/store';
import Layout from '@/Layout/index.vue'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    routes,
    token: "",
    roleType: "",
    roles: [],
    permissions: [],
    sidebarRouters: [],

  },
  getters: {
    token: state => state.token,
    roles: state => state.roles,
    permissions: state => state.permissions,
    sidebarRouters: state => state.sidebarRouters,
  },
  mutations: {
    SET_TOKEN: (state, token) => {
      state.token = token;
    },
    SET_USERINFO: (state, user) => {
      state.userInfo = user;
    },
    SET_ROLETYPE: (state, roleType) => {
      state.roleType = roleType;
    },
    SET_ROLES: (state, roles) => {
      state.roles = roles;
    },
    SET_PERMISSIONS: (state, permissions) => {
      state.permissions = permissions;
    },
    SET_ROUTE: (state, sidebarRouters) => {
      state.sidebarRouters = sidebarRouters;
    },
  },
  actions: {
    Login({ commit }, userInfo) {
      return new Promise((resolve, reject) => {
        login(userInfo).then(res => {
          setToken(res.data.token);
          setStore('token', res.data.token);
          commit('SET_TOKEN', res.data.token);
          resolve();
        }).catch(error => {
          reject(error);
        })
      })
    },
    // 获取用户信息
    GetInfo({ commit }) {
      return new Promise((resolve, reject) => {
        getInfo().then(res => {
          console.log('res::: ', res);
          if (res.data.code === 0 || 200) {
            const user = res.data.sysUser;
            const roleType = res.data.roleType;
            commit('SET_USERINFO', user);

            // roleType 用户所用的权限级别 1 普通用户  2 项目经理  3 部门管理员  4 综合部管理员  5  部门领导  -1 项目运维管理员
            setStore('ROLE_TYPE', roleType);
            if (res.data.roles) { // 验证返回的roles是否为真
              commit('SET_ROLES', res.data.roles);
              commit('SET_PERMISSIONS', res.data.permissions);
            } else {
              commit('SET_ROLES', ['ROLE_DEFAULT']);
            }
            resolve();
          } else {
            reject(error);
          }
        }).catch(error => {
          reject(error);
        })
      })
    },
    GenerateRoutes({ commit }) {
      return new Promise((resolve, reject) => {
        // 向后端请求路由数据
        getRouters().then(res => {
          if (res.data.code === 0 || 200) {
            const sdata = JSON.parse(JSON.stringify(res.data.routes));
            console.log('sdata::: ', sdata);

            let newRouters = filterAsyncRouter(sdata);

            // 连接公共路由
            const sidebarRoutes = routes.concat([...newRouters]);
            commit('SET_ROUTE', sidebarRoutes);
            resolve(sidebarRoutes);
          } else {
            reject(error);
          }
        }).catch(error => {
          reject(error);
        })
      })
    },
    // 退出系统
    LogOut({ commit, state }) {
      return new Promise((resolve, reject) => {
        logout(state.token).then(() => {
          commit('SET_TOKEN', '')
          commit('SET_ROLES', [])
          commit('SET_PERMISSIONS', [])
          clearStore('token');
          clearStore('userInfo')
          resolve()
        }).catch(error => {
          reject(error)
        })
      })
    },
  },
  modules: {
  }
})

const loadView = (view) => {
  // 路由懒加载
  return () => import(`@/views${view}`);
};

function filterAsyncRouter(routes) {
  return routes.filter((route) => {
    if (Array.isArray(route.children) && route.children.length > 0) {
      // 如果该路由含有子路由时,递归调用该函数
      route.children = filterAsyncRouter(route.children);
    }

    if (route.component) {
      // Layout ParentView 组件特殊处理
      if (route.component === "Layout") {
        route.component = Layout;
      } else {
        // 路由组件懒加载
        route.component = loadView(route.component);
      }
    }

    return true;
  })
}

由于接口返回的 component 是字符串, 需手动封装函数转换成组件

公共路由如下

router/index.js

// router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import Layout from '@/Layout/index.vue'

Vue.use(VueRouter)

// 公共路由
export const routes = [
  {
    path: '/',
    name: 'redirect',
    component: Layout,
    hidden: true, // 隐藏菜单
    redirect: "/homePage", // 用户在地址栏输入 '/' 时会自动重定向到 /homePage 页面
  },
  {
    path: '/homePage',
    component: Layout,
    redirect: "/homePage/index",
    meta: {
      title: "首页",
    },
    children: [
      {
        path: 'index',
        name: 'homePageIndex',
        meta: {
          title: "首页",
        },
        component: () => import('@/views/homePage/index.vue')
      }
    ]
  },
  {
    path: '/login',
    component: () => import('@/views/login.vue'),
    hidden: true
  },
  {
    path: '/404',
    component: () => import('@/views/error/404.vue'),
    hidden: true
  },
  {
    path: '/401',
    component: () => import('@/views/error/401.vue'),
    hidden: true
  },
]

const router = new VueRouter({
  base: process.env.BASE_URL,
  routes
})

export default router

文件结构如下
在这里插入图片描述
页面菜单渲染

在这里插入图片描述

左侧菜单实现参考链接: Elemnt-UI + 递归组件实现后台管理系统左侧菜单

前端单独实现动态路由参考连接: 前端单独实现 vue 动态路由

总结

通过以上步骤,你可以实现一个完整的动态路由权限管理系统:

  • 后端接口返回路由配置:获取用户的权限信息及路由信息。
  • 动态加载组件:使用异步组件方式加载指定路径的组件。
  • 动态添加路由:根据权限信息动态添加路由。
  • 路由守卫:使用 router.addRoutes()router.addRoute() 添加路由。

这样可以确保应用根据用户的权限动态加载相应的路由,增强安全性与灵活性。

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

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

相关文章

路由器的固定ip地址是啥意思?固定ip地址有什么好处

‌在当今数字化时代,‌路由器作为连接互联网的重要设备,‌扮演着举足轻重的角色。‌其中,‌路由器的固定IP地址是一个常被提及但可能让人困惑的概念。‌下面跟着虎观代理小二一起将深入探讨路由器的固定IP地址的含义,‌揭示其背后…

用Unity2D制作一个人物,实现移动、跳起、人物静止和动起来时的动画:下(人物动画)

上个博客我们做出了人物的动画机和人物移动跳跃,接下来我们要做出人物展现出来的动画了 我们接下来就要用到动画机了,双击我们的动画机,进入到这样的页面,我这是已经做好的页面,你们是没有这些箭头的 依次像我一样连接…

【Python】Windows下python的下载安装及使用

文章目录 下载安装检测 使用环境搭建下载PycharmPycharm安装 下载 进入官网下载:https://www.python.org/ 点击下载 64位电脑下载该项 安装 勾选 添加至环境变量 使用自定义安装 检测 安装成功后,打开命令提示符窗口(winR,输入cmd回车…

红海云 × 紫光同芯 | 数字化驱动芯片领军企业人力资源管理新升级

紫光同芯微电子有限公司(以下简称“紫光同芯”)是新紫光集团汽车电子与智能芯片板块的核心企业。专注于汽车电子与安全芯片领域,累计出货超过230亿颗,为亚洲、欧洲、美洲、非洲的二十多个国家和地区提供产品和服务。 为进一步提升…

VSC++: 十转十六进制

void 十转十六进制(int 数) {//缘由https://ask.csdn.net/questions/1089023string 十六模 "0123456789ABCDEF", 进制 "";int j 0;cout << 数 << ends; if (!数)cout << "0";while (数)进制.push_back(十六模[数 % 16]), j…

LCS—最长公共子序列

最长公共子序列问题就是求出两个字符串的LCS长度&#xff0c;是一道非常经典的面试题目&#xff0c;因为它的解法是典型的二维动态规划。 比如输入 str1 "babcde", str2 "acbe"&#xff0c;算法应该输出3&#xff0c;因为 str1 和 str2 的最长公共子序列…

【大模型基础】P2 Bag-of-Words

目录 词袋模型 概述词袋模型 实例第1步 构建语料库第2步 对句子进行分词第3步 创建词汇表第4步 转换词袋表示第5步 计算余弦相似度 词袋模型的局限性 词袋模型 概述 词袋模型&#xff0c;Bag-of-Words&#xff0c;是一种简单的文本表示方法&#xff0c;也是 NLP 中的一个经典模…

[数据集][目标检测]血细胞检测数据集VOC+YOLO格式2757张4类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;2757 标注数量(xml文件个数)&#xff1a;2757 标注数量(txt文件个数)&#xff1a;2757 标注…

因MathType导致word复制粘贴失败,显示:运行时错误‘53’

问题&#xff1a;运行时错误‘53’&#xff1a;文件未找到&#xff1a;MathPage.WLL 解决方法&#xff1a;打开MathType所在文件夹 右击MathType图标->点击“打开文件所在位置”->找到MathPage.WLL文件。 然后&#xff0c;把这个文件复制到该目录下&#xff1a;C:\Progr…

jenkins工具的介绍和gitlab安装

使用方式 替代手动&#xff0c;自动化拉取、集成、构建、测试&#xff1b;是CI/CD持续集成、持续部署主流开发模式中重要工具&#xff1b;必须组件 jenkins-gitlab&#xff0c;代码公共仓库服务器&#xff08;至少6G内存&#xff09;&#xff1b;jenkins-server&#xff0c;需…

论文解读:利用大模型进行基于上下文的OCR校正

论文地址&#xff1a;https://arxiv.org/pdf/2408.17428 背景概述 研究问题&#xff1a;这篇文章要解决的问题是如何利用预训练的语言模型&#xff08;LMs&#xff09;来改进光学字符识别&#xff08;OCR&#xff09;的质量&#xff0c;特别是针对报纸和期刊等复杂布局的文档。…

Jmeter_循环获取请求接口的字段,并写入文件

通过JSON提取器、计数器、beanshell&#xff0c;循环读取邮箱接口的返回字段&#xff0c;筛选出flag为3的收件人&#xff0c;并写入csv文件。 1、调用接口&#xff0c;获取所有的邮件$.data.total.count&#xff1b; 2、beanshell后置处理total转换成页码&#xff0c;这里是227…

纵切车床和走心机的区别

纵切车床和走心机在机床加工领域中各自扮演着重要的角色&#xff0c;它们在多个方面存在显著的差异。下面&#xff0c;我将从基本概念、工作原理、应用领域以及加工能力等方面来详细阐述这两者的区别。 一.基本概念 ‌纵切车床‌&#xff1a;纵切车床&#xff0c;也被称为自动纵…

NFTScan | 09.02~09.08 NFT 市场热点汇总

欢迎来到由 NFT 基础设施 NFTScan 出品的 NFT 生态热点事件每周汇总。 周期&#xff1a;2024.09.02~ 2024.09.08 NFT Hot News 01/ 数据&#xff1a;NFT 8 月销售额跌破 4 亿美元&#xff0c;创年内新低 9 月 2 日&#xff0c;数据显示&#xff0c;8 月 NFT 的月销售额仅为 …

ozon免费选品工具,OZON免费选品神器

在跨境电商的浩瀚海洋中&#xff0c;寻找那片属于自己的盈利蓝海&#xff0c;是每个商家梦寐以求的目标。随着俄罗斯电商市场的迅速崛起&#xff0c;Ozon平台以其庞大的用户基数和不断增长的市场份额&#xff0c;成为了众多跨境卖家眼中的“香饽饽”。然而&#xff0c;面对琳琅…

vue-router + el-menu

1. el-menu的router属性 在el-menu中有一属性&#xff1a;router&#xff0c;默认是false 1.1 使用默认配置&#xff0c;即false 这时候需要自己在点击子菜单的时候进行导航&#xff0c;在el-menu添加方法&#xff0c;里边有三个参数 index: 选中菜单项的 index,indexPath…

jmeter之ForEach控制器使用

ForEach控制器作用&#xff1a; 一般和用户自定义变量或者正则表达式提取器配合使用&#xff0c;读取返回结果中一系列相关的变量值&#xff0c;该控制器下的取样器都会被执行一次或多次&#xff0c;每次读取不同的变量值(类似python当中的for语句,用来遍历操作) 本节代码已上…

uniapp数据缓存和发起网络请求

数据缓存 uni.onStorageSync同步的方式将数据存储到本地缓存 <template><button click"onStorageSync()">存储数据</button> </template><script setup>const onStorageSync () > {// 存储数据uni.setStorageSync(username, 张三)…

Spring Cloud Sleuth+Zipkin服务链路追踪

前言&#xff1a;为什么要用链路追踪 微服务架构是一个分布式架构&#xff0c;按照规则划分服务单元&#xff0c;一个分布式系统往往有很多个服务单元。服务单元数量多&#xff0c;业务复杂&#xff0c;出现错误和异常往往很难定位问题。主要体现在&#xff0c;一个请求可能需要…

(一)模式识别——基于SVM的道路分割实验(附资源)

写在前面&#xff1a;本报告所有代码公开在附带资源中&#xff0c;无法下载代码资源的伙伴私信留下邮箱&#xff0c;小编24小时内回复 一、实验目的 1、实验目标 学习掌握SVM&#xff08;Support Vector Machine&#xff09;算法思想&#xff0c;利用MATLAB的特定工具箱和库函…