前端实现动态路由(前端控制全部路由,后端返回用户角色)

news2024/12/23 4:31:25

在这里插入图片描述

优缺点

优点:

  • 不用后端帮助,路由表维护在前端
  • 逻辑相对比较简单,比较容易上手
  • 权限少的系统用前端鉴权更加方便

缺点:

  • 线上版本每次修改权限页面,都需要重新打包项目
  • 大型项目不适用
  • 如果需要在页面中增加角色并且控制可以访问的页面,则不能用前端鉴权

具体思路

1、前端定义静态路由和动态路由,创建vue实例的时候vue-router挂载静态路由(登录等不需要权限的页面)
2、登录时获取用户信息,存入vuex中,存储token
3、在路由拦截器中,通过token去获取用户角色,拿角色去获取所有可访问的路由
4、调用router.addrouters(store.state.addRouters)添加可访问的路由
5、退出时清空用户信息,清空角色,清空路由

步骤一:前端定义静态路由和动态路由

router/index.js

import Vue from "vue"
import VueRouter from "vue-router"
import Layout from "@/layout"

Vue.use(VueRouter)

// 解决重复点击路由报错的BUG
// 下面这段代码主要解决这个问题 :Uncaught (in promise) Error: Redirected when going from "/login" to "/index" via a navigation guard.
const originalPush = VueRouter.prototype.push
VueRouter.prototype.push = function push(location) {
  return originalPush.call(this, location).catch((err) => err)
}

// 定义好静态路由
export const constantRoutes = [
  {
    path: "/login",
    name: "login",
    component: () => import("../views/login"),
    hidden: true,
  },
]

// 定义动态路由,以及每个页面对应的roles(写在meta中,不写代表都可以访问)
export const asyncRoutes = [
  {
    id: 1,
    name: "/",
    path: "/",
    component: Layout,
    redirect: "/index",
    hidden: false,
    children: [
      {
        name: "index",
        path: "/index",
        meta: { title: "index" },
        component: () => import("@/views/index"),
      },
    ],
  },
  {
    id: 2,
    name: "/form",
    path: "/form",
    component: Layout,
    redirect: "/form/index",
    hidden: false,
    children: [
      {
        name: "/form/index",
        path: "/form/index",
        meta: { title: "form" },
        component: () => import("@/views/form"),
      },
    ],
  },
  {
    id: 3,
    name: "/example",
    path: "/example",
    component: Layout,
    redirect: "/example/tree",
    meta: { title: "example" },
    hidden: false,
    children: [
      {
        name: "/tree",
        path: "/example/tree",
        meta: { title: "tree" },
        component: () => import("@/views/tree"),
      },
      {
        name: "/copy",
        path: "/example/copy",
        meta: { title: "copy" },
        component: () => import("@/views/tree/copy"),
      },
    ],
  },
  {
    id: 4,
    name: "/table",
    path: "/table",
    component: Layout,
    redirect: "/table/index",
    hidden: false,
    meta: { roles: ["admin"] },
    children: [
      {
        name: "/table/index",
        path: "/table/index",
        meta: { title: "table", roles: ["admin"] },
        component: () => import("@/views/table"),
      },
    ],
  },
  {
    id: 5,
    name: "/admin",
    path: "/admin",
    component: Layout,
    redirect: "/admin/index",
    hidden: false,
    meta: { roles: ["admin"] },
    children: [
      {
        name: "/admin/index",
        path: "/admin/index",
        meta: { title: "admin", roles: ["admin"] },
        component: () => import("@/views/admin"),
      },
    ],
  },
  {
    id: 6,
    name: "/people",
    path: "/people",
    component: Layout,
    redirect: "/people/index",
    hidden: false,
    meta: { roles: ["admin", "common_user"] },
    children: [
      {
        name: "/people/index",
        path: "/people/index",
        meta: { title: "people", roles: ["admin", "common_user"] },
        component: () => import("@/views/people"),
      },
    ],
  },
  {
    id: 7,
    name: "/404",
    path: "/404",
    component: () => import("@/views/404"),
  },
  // 注意404页面要放到最后
  { path: "*", redirect: "/404", hidden: true },
]

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

export default router

这里我们根据 vue-router官方推荐 的方法通过meta标签来标示改页面能访问的权限有哪些。如meta: { role: [‘admin’,‘super_editor’] }表示该页面只有admin和超级编辑才能有资格进入。

注意事项:这里有一个需要非常注意的地方就是 404 页面一定要最后加载,如果放在constantRoutes一同声明了404,后面的所以页面都会被拦截到404

步骤二:登录时获取用户信息,存入vuex中,存储token

login/index.vue

methods: {
    login () {
      this.$refs.userForm.validate((valid) => {
        if (valid) {
          // 模拟登录接口去请求用户数据
          setTimeout(() => {
            // 这里的res就是模拟后台返回的用户数据(不包含用户角色,一般角色是由单独的一个接口返回)
            const res = dynamicUserData.filter((item) => item.username === this.user.username)[0]
            console.log(res)
            // 存储用户的信息及token到vuex,并做sessionStorage持久化处理
            this.$store.commit('User/saveUserInfo', res)
            Message({ type: 'success', message: "登录成功", showClose: true, duration: 3000 })
            this.$router.push({ path: "/index" })
          }, 1000)
        } else return false
      })
    }
  }

附:vuex持久化处理

import Vue from 'vue'
import Vuex from 'vuex'
import User from './modules/user'
import permission from './modules/permission'
import createPersistedState from 'vuex-persistedstate'
Vue.use(Vuex)

export default new Vuex.Store({
  state: {},
  mutations: {},
  actions: {},
  modules: {
    User,
    permission,
  },
  plugins: [
    createPersistedState({
      storage: window.sessionStorage, // 可选sessionStorage localStorage
      reducer(val) {
        return {
          User: val.User,
        }
      },
    }),
  ],
})

步骤【三四】:在路由拦截器中,通过token去获取用户角色,拿角色去获取所有可访问的路由,调用router.addrouters(store.state.addRouters)添加可访问的路由

路由钩子逻辑:

是否为白名单页面
  是:   直接进入
  不是: 判断是否有token
  			无token:跳转到login登录页
  			有token: 判断用户是否有角色权限表
  						有权限表:直接进入
  						无权限表:调接口获取用户角色,并存储到vuex
  								根据返回的角色和路由表每个页面的需要的权限对比,生成可访问的路由表
  								使用router.addRouters()添加路由

路由导航守卫:

import router from "./index"
import NProgress from "nprogress" // progress bar
import store from "@/store"
import menu from "@/mock/menu.js"

NProgress.configure({ showSpinner: false }) // NProgress Configuration

// 白名单页面直接进入
const whiteList = ["/login"]

router.beforeEach((to, from, next) => {
  NProgress.start()
  // 白名单页面,不管是否有token,是否登录都直接进入
  if (whiteList.indexOf(to.path) !== -1) {
    next()
    return false
  }
  // 有token(代表了有用户信息,但是不确定有没有角色权限数组)
  if (store.state.User.token) {
    // 判断当前用户是否有角色权限数组, 是登录状态则一定有路由,直接放行,不是登录状态则去获取路由菜单登录
    // 刷新时hasRoles会重置为false,重新去获取 用户的角色列表
    const hasRoles = store.state.permission.roles && store.state.permission.roles.length > 0
    if (!hasRoles) {
      setTimeout(async () => {
        const roles = menu.filter((item) => item.token === store.state.User.token)[0].roles
        // 将该角色权限数组存储到vuex中
        store.commit("permission/setRoles", roles)
        // 根据返回的角色信息去过滤异步路由中该角色可访问的页面
        const accessRoutes = await store.dispatch("permission/generateRoutes", roles)
        // dynamically add accessible routes
        router.addRoutes(accessRoutes)
        // hack方法 router.addRoutes之后的next()可能会失效,因为可能next()的时候路由并没有完全add完成 next(to)解决
        next({ ...to, replace: true })
      }, 500)
    } else {
      next() //当有用户权限的时候,说明所有可访问路由已生成 如访问没权限的全面会自动进入404页面
    }
  } else {
    next({ path: "/login" })
  }
})

router.afterEach(() => {
  // finish progress bar
  NProgress.done()
})

vuex中做的事为: 将定义好的动态路由 通过 角色权限数组(后台返回的)进行过滤,过滤出用户有的路由,然后将该过滤后的路由添加到静态路由后面去
store/permission.js

import { asyncRoutes, constantRoutes } from '@/router'
/**
 * Filter asynchronous routing tables by recursion
 * @param routes asyncRoutes
 * @param roles
 */
export function filterAsyncRoutes(routes, roles) {
  const res = []
  routes.forEach(route => {
    const tmp = { ...route }
    if (hasPermission(roles, tmp)) {
      if (tmp.children) {
        tmp.children = filterAsyncRoutes(tmp.children, roles)
      }
      res.push(tmp)
    }
  })
  return res
}

function hasPermission(roles, route) {
  console.log(roles)
  console.log(route)
  if (route.meta && route.meta.roles) {
    console.log(roles.some(role => route.meta.roles.includes(role)))
    return roles.some(role => route.meta.roles.includes(role))
  } else {
    return true
  }
}

const state = {
  roles: [],
  routes: [],
  addRoutes: [],
}
const mutations = {
  setRoles(state, val) {
    state.roles = val
  },
  SET_ROUTES: (state, routes) => {
    state.addRoutes = routes
    state.routes = constantRoutes.concat(routes)
  },
}
const actions = {
  generateRoutes({ commit }, roles) {
    return new Promise(resolve => {
      let accessedRoutes
      if (roles.includes('admin')) { // admin直接添加所有权限
        accessedRoutes = asyncRoutes || []
      } else {
        accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
      }
      commit('SET_ROUTES', accessedRoutes)
      resolve(accessedRoutes)
    })
  },
}
export default {
  namespaced: true,
  state,
  mutations,
  actions,
}

步骤四:退出时清空用户信息,清空角色,清空路由

methods: {
    // 退出登录
    handleLogout() {
      window.localStorage.removeItem("token")
      // 清除用户信息
      this.$store.commit("User/removeUserInfo")
      // 清除角色权限列表
      this.$store.commit("permission/setRoles", [])
      // 清除角色权限数组
      this.$store.commit("permission/SET_ROUTES", [])
      Message({
        type: "success",
        message: "退出登录",
        showClose: true,
        duration: 3000,
      })
      this.$router.push({ path: "/login" })
    },
  }

希望能帮到你

文章参考:
花裤衩大佬:花裤衩大佬
本文代码:github 求 star

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

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

相关文章

pdf怎么转换成word?

随着数字化时代的到来,PDF(Portable Document Format)已成为最受欢迎的文档格式之一,因其在各种设备上的可视性和稳定性而备受推崇。然而在某些情况下,将PDF转换为Word文档可能是必要的,这使得编辑、修改和重新格式化文本变得更加…

在window上安装hadoop3.3.4

暑假不知道啥原因电脑死机啦。环境需要重新配一下 首先需要配置Hadoop集群,但是为了代码调试方便需要先在Windows上配置Hadoop环境。 1.前期准备 首先在搭建Hadoop环境之前需要先安装JDK,并且配置好Java环境变量。 这里有个bug就是Java环境变量中不允许…

2023秋招得物面经 8.31总结

1.数据结构中有哪些树 在数据结构中,常见的树包括: 二叉树(Binary Tree):每个节点最多有两个子节点,用于表示有层次关系的数据结构,如二叉搜索树、堆等。 二叉搜索树(Binary Searc…

jmeter调试错误大全

一、前言 在使用jmeter做接口测试的过程中大家是不是经常会遇到很多问题,但是无从下手,不知道从哪里开始找起,对于初学者而言这是一个非常头痛的事情。这里结合笔者的经验,总结出以下方法。 二、通过查看运行日志调试问题 写好…

虚拟机Linux20.04磁盘扩展

扩展之前必须要确保!没有快照! ps:先把快照删掉,如果担心弄坏的话可以先克隆一个 如果不删的话就会跟下面一样无法点击扩展: 删除了快照之后就可以点击这个【扩展】,输入你要的磁盘大小即可。 (我这里原…

智能感测棒形静电消除器所具备的特点

智能感测棒形静电消除器是一种具有联网监控功能的设备。它可以通过内置的传感器实时感知周围的静电情况,并采取相应的措施进行消除。 该设备通过联网功能,可以将感测到的静电信息传输到指定的监控平台或手机应用程序中进行实时监控与管理。用户可以随时…

Win11搭建 Elasticsearch 7 集群(一)

一: ES与JDK版本匹配一览表 elasticsearch从7.0开始默认安装了java运行环境,以便在没有安装java运行环境的机器上运行。如果配置了环境变量JAVA_HOME,则elasticsearh启动时会使用JAVA_HOME作为java路径,否则使用elasticsearch根目…

防溺水广播警示系统 python

防溺水广播警示系统通过pythonyolo系列网络框架模型算法,防溺水广播警示系统以识别和判断危险水域中是否有人员溺水的情况。一旦出现溺水现象,算法将立即发出警报信号,并自动启动广播系统进行警示。Python是一种由Guido van Rossum开发的通用…

[递归] 子集 全排列和组合问题

1.1 子集I 思路可以简单概括为 二叉树&#xff0c;每一次分叉要么选择一个元素&#xff0c;要么选择空&#xff0c;总共有n次&#xff0c;因此到n1进行保存结果&#xff0c;返回。像这样&#xff1a; #include <cstdio> #include <vector> #include <algorithm&…

[ros][ubuntu]ros在ubuntu18.04上工作空间创建和发布一个话题

构建catkin工作空间 mkdir -p ~/catkin_ws/src cd ~/catkin_ws/src catkin_init_workspace cd ~/catkin_ws/ catkin_make 配置环境变量 echo "source ~/catkin_ws/devel/setup.bash" >> ~/.bashrc source ~/.bashrc 检查环境变量 echo $ROS_PACKAGE_PATH…

学习笔记-ThreadLocal

ThreadLocal 什么是ThreadLocal&#xff1f; ThreadLocal 是线程本地变量类&#xff0c;在多线程并行执行过程中&#xff0c;将变量存储在ThreadLocal中&#xff0c;每个线程中都有独立的变量&#xff0c;因此不会出现线程安全问题。 应用举例 解决线程安全问题&#xff1a;例…

pytest---添加自定义命令行参数(pytest_addoption )

前言 在目前互联网公司中&#xff0c;都会存在多个测试环境&#xff0c;那么当我们编写的自动化想要在多套测试环境下进行运行时&#xff0c;如何使用&#xff1f;大多数人想到的可能是通过将我们自动化代码中的地址修改成不同环境&#xff0c;但是这时候就会增加一些工作量&am…

Java设计模式:四、行为型模式-07:状态模式

文章目录 一、定义&#xff1a;状态模式二、模拟场景&#xff1a;状态模式2.1 状态模式2.2 引入依赖2.3 工程结构2.4 模拟审核状态流转2.4.1 活动状态枚举2.4.2 活动信息类2.4.3 活动服务接口2.4.4 返回结果类 三、违背方案&#xff1a;状态模式3.0 引入依赖3.1 工程结构3.2 活…

JVM的故事——垃圾收集器

垃圾收集器 文章目录 垃圾收集器一、serial收集器二、parnew收集器三、parallel scavenge收集器四、serial old收集器五、parallel old收集器六、CMS收集器七、Garbage First收集器八、收集器的权衡 一、serial收集器 新生代收集器&#xff0c;最基础的收集器&#xff0c;单线…

C#备份数据库文件

c#备份数据库文件完整代码 sqlServer 存储过程&#xff1a; USE [PSIDBase] GO /****** Object: StoredProcedure [dbo].[sp_BackupDB] Script Date: 2023/8/31 16:49:02 ******/ SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GOALTER procedure [dbo].[sp_BackupDB]…

【Unity每日一记】WheelColider组件汽车游戏的关键

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;uni…

2023年腾讯云优惠券(代金券)领取方法整理汇总

腾讯云优惠券是腾讯云为了吸引用户而推出的一种优惠凭证&#xff0c;领券之后新购、续费、升级腾讯云的相关产品可以享受优惠&#xff0c;从而节省一点的费用&#xff0c;下面给大家分享腾讯云优惠券领取的几种方法。 一、腾讯云官网领券页面领取 腾讯云官网经常推出各种优惠活…

C#,数值计算——Midinf的计算方法与源程序

1 文本格式 using System; namespace Legalsoft.Truffer { public class Midinf : Midpnt { public new double func(double x) { return funk.funk(1.0 / x) / (x * x); } public Midinf(UniVarRealValueFun funcc, double aa,…

内存四区(个人学习笔记黑马学习)

1、内存分区模型 C程序在执行时&#xff0c;将内存大方向划分为4个区域&#xff1a; 代码区:存放函数体的二进制代码&#xff0c;由操作系统进行管理的全局区:存放全局变量和静态变量以及常量栈区:编译器自动分配释放,存放函数的参数值,局部变量等 堆区:由程序员分配和释放,若程…

SpringBoot整合Freemaker结合Vue实现页面填写一键自动生成Redis的配置文件

&#x1f9d1;‍&#x1f4bb;作者名称&#xff1a;DaenCode &#x1f3a4;作者简介&#xff1a;啥技术都喜欢捣鼓捣鼓&#xff0c;喜欢分享技术、经验、生活。 &#x1f60e;人生感悟&#xff1a;尝尽人生百味&#xff0c;方知世间冷暖。 &#x1f4d6;所属专栏&#xff1a;Sp…