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

优缺点
优点:
- 不用后端帮助,路由表维护在前端
- 逻辑相对比较简单,比较容易上手
- 权限少的系统用前端鉴权更加方便
缺点:
- 线上版本每次修改权限页面,都需要重新打包项目
- 大型项目不适用
- 如果需要在页面中增加角色并且控制可以访问的页面,则不能用前端鉴权
具体思路
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 localStoragereducer(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 > 0if (!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 routesrouter.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 barNProgress.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 = routesstate.routes = constantRoutes.concat(routes)},
}
const actions = {generateRoutes({ commit }, roles) {return new Promise(resolve => {let accessedRoutesif (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
相关文章:
前端实现动态路由(前端控制全部路由,后端返回用户角色)
优缺点 优点: 不用后端帮助,路由表维护在前端逻辑相对比较简单,比较容易上手权限少的系统用前端鉴权更加方便 缺点: 线上版本每次修改权限页面,都需要重新打包项目大型项目不适用如果需要在页面中增加角色并且控制可以访问的页…...
Spring5学习笔记—Spring事务处理
✅作者简介:大家好,我是Leo,热爱Java后端开发者,一个想要与大家共同进步的男人😉😉 🍎个人主页:Leo的博客 💞当前专栏: Spring专栏 ✨特色专栏: M…...
如何增长LLM推理token,从直觉到数学
背景: 最近大模型输入上文长度增长技术点的研究很火。为何要增长token长度,为何大家如此热衷于增长输入token的长度呢?其实你如果是大模型比价频繁的使用者,这个问题应该不难回答。增长了输入token的长度,那需要多次出入才能得到…...
《穷爸爸与富爸爸》时间是最宝贵的资产,只有它对所有人都是公平的
《穷爸爸与富爸爸》时间是最宝贵的资产,只有它对所有人都是公平的 罗伯特清崎,日裔美国人,投资家、教育家、企业家。 萧明 译 文章目录 《穷爸爸与富爸爸》时间是最宝贵的资产,只有它对所有人都是公平的[toc]摘录各阶层现金流图支…...
Git结合Gitee的企业开发模拟
本系列有两篇文章: 一是另外一篇《快速使用Git完整开发》,主要说明了关于Git工具的基础使用,包含三板斧(git add、git commit、git push)、Git基本配置、版本回退、分支管理、公钥与私钥、远端仓库和远端分支、忽略文…...
WEBGL(2):绘制单个点
代码如下: <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"widthdevi…...
C# task多线程创建,暂停,继续,结束使用
1、多线程任务创建 private void button1_Click(object sender, EventArgs e) //创建线程{CancellationToken cancellationToken tokensource.Token;Task.Run(() > //模拟耗时任务{for (int i 0; i < 100; i){if (cancellationToken.IsCancellationRequested){return;…...
界面控件DevExpress WinForms(v23.2)下半年发展路线图
本文主要概述了官方在下半年(v23.2)中一些与DevExpress WinForms相关的开发计划,重点关注的领域将是可访问性支持和支持.NET 8。 DevExpress WinForms有180组件和UI库,能为Windows Forms平台创建具有影响力的业务解决方案。同时能…...
vue实现按需加载的多种方式
1.import动态导入 const Home () > import( /* webpackChunkName: "Home" */ /views/Home.vue); 2.使用vue异步组件resolve 这种方式没有成功 //const 组件名 resolve > require([‘组件路径’],resolve) //(这种情况下一个组件生成一个js文件…...
el-switch组件在分页情况下的使用
1.需求: 系统使用者在点击发布状态的开关后,可以对应的发布或者取消发布试卷 2.前端代码: html代码(这里不贴其他表单项的代码了,直接贴el-Switch组件的代码): <!-- qwy: 使用Switch组件,设置发布状态,业务逻辑:在页面初始渲染的时候应该查询发布状态,以根据状…...
【100天精通python】Day49:python web编程_web框架,Flask的使用
目录 1 Web 框架 2 python 中常用的web框架 3 Flask 框架的使用 3.1 Flask框架安装 3.2 第一个Flask程序 3.3 路由 3.3.1 基本路由 3.3.2 动态路由 3.3.3 HTTP 方法 3.3.4 多个路由绑定到一个视图函数 3.3.5 访问URL 参数的路由 3.3.6 带默认值的动态路由 3.3.7 带…...
sql 查重以及删除重复
查重 select count(1),content from t_mall_longping group by content having count(1)>1 稳重删除重复(技术来源于 百度文心一言,好屌呀) CREATE TABLE tmp_duplicates ( hxid INT PRIMARY KEY );INSERT INTO tmp_duplicates SEL…...
Flux语言 -- InfluxDB笔记二
1. 基础概念理解 1.1 语序和MySQL不一样,像净水一样通过管道一层层过滤 1.2 不同版本FluxDB的语法也不太一样 2. 基本表达式 import "array" s 10 * 3 // 浮点型只能与浮点型进行运算 s1 9.0 / 3.0 s2 10.0 % 3.0 // 等于 1 s3 10.0 ^ 3.0 // 等于…...
18.Oauth2-微服务认证
1.Oauth2 OAuth 2.0授权框架支持第三方支持访问有限的HTTP服务,通过在资源所有者和HTTP服务之间进行一个批准交互来代表资源者去访问这些资源,或者通过允许第三方应用程序以自己的名义获取访问权限。 为了方便理解,可以想象OAuth2.0就是在用…...
vue和node使用websocket实现数据推送,实时聊天
需求:node做后端根据websocket,连接数据库,数据库的字段改变后,前端不用刷新页面也能更新到数据,前端也可以发送消息给后端,后端接受后把前端消息做处理再推送给前端展示 1.初始化node,生成pac…...
汽车电子笔记之:基于AUTOSAR的多核监控机制
目录 1、概述 2、系统监控的目标 2.1、任务的状态机 2.2、任务服务函数 2.3、任务周期性事件 2.4、时间监控的指标 2.5、时间监控的原理 2.6、CPU负载率监控原理 2.6.1、设计思路 2.6.2、监控方法的评价 3、基于WDGM模块热舞时序监控方法 3.1、活跃监督 3.2、截至时…...
GDB 源码分析 -- 断点源码解析
文章目录 一、断点简介1.1 硬件断点1.2 软件断点 二、断点源码分析2.1 断点相关结构体2.1.1 struct breakpoint2.1.2 struct bp_location 2.2 断点源码简介2.3 break设置断点2.4 enable break2.5 disable breakpoint2.6 delete breakpoint2.7 info break 命令源码解析 三、Linu…...
SpringMVC概述与简单使用
1.SpringMVC简介 SpringMVC也叫做Spring web mvc,是 Spring 框架的一部分,是在 Spring3.0 后发布的。 2.SpringMVC优点 1.基于 MVC 架构 基于 MVC 架构,功能分工明确。解耦合, 2.容易理解,上手快;使用简单。 就可以…...
传输层—UDP原理详解
目录 前言 1.netstat 2.pidof 3.UDP协议格式 4.UDP的特点 5.面向数据报 6.UDP的缓冲区 7.UDP使用注意事项 8.基于UDP的应用层协议 总结 前言 在之前的文章中为大家介绍了关于网络协议栈第一层就是应用层,包含套接字的使用,在应用层编码实现服务…...
CK-GW06-E03与汇川PLC的EtherNet/IP通信
准备阶段: CK-GWO6-E03网关POE交换机网线汇川PLC编程软件汇川AC801-0221-U0R0型号PLC 1.打开汇川PLC编程软件lnoProShop(V1.6.2)SP2 新建工程,选择对应的PLC型号,编程语言选择为“结构化文本(ST)语言”,然…...
使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式
一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明:假设每台服务器已…...
TDengine 快速体验(Docker 镜像方式)
简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能,本节首先介绍如何通过 Docker 快速体验 TDengine,然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker,请使用 安装包的方式快…...
【JVM】- 内存结构
引言 JVM:Java Virtual Machine 定义:Java虚拟机,Java二进制字节码的运行环境好处: 一次编写,到处运行自动内存管理,垃圾回收的功能数组下标越界检查(会抛异常,不会覆盖到其他代码…...
VTK如何让部分单位不可见
最近遇到一个需求,需要让一个vtkDataSet中的部分单元不可见,查阅了一些资料大概有以下几种方式 1.通过颜色映射表来进行,是最正规的做法 vtkNew<vtkLookupTable> lut; //值为0不显示,主要是最后一个参数,透明度…...
OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别
OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别 直接训练提示词嵌入向量的核心区别 您提到的代码: prompt_embedding = initial_embedding.clone().requires_grad_(True) optimizer = torch.optim.Adam([prompt_embedding...
用机器学习破解新能源领域的“弃风”难题
音乐发烧友深有体会,玩音乐的本质就是玩电网。火电声音偏暖,水电偏冷,风电偏空旷。至于太阳能发的电,则略显朦胧和单薄。 不知你是否有感觉,近两年家里的音响声音越来越冷,听起来越来越单薄? —…...
安宝特方案丨船舶智造的“AR+AI+作业标准化管理解决方案”(装配)
船舶制造装配管理现状:装配工作依赖人工经验,装配工人凭借长期实践积累的操作技巧完成零部件组装。企业通常制定了装配作业指导书,但在实际执行中,工人对指导书的理解和遵循程度参差不齐。 船舶装配过程中的挑战与需求 挑战 (1…...
虚拟电厂发展三大趋势:市场化、技术主导、车网互联
市场化:从政策驱动到多元盈利 政策全面赋能 2025年4月,国家发改委、能源局发布《关于加快推进虚拟电厂发展的指导意见》,首次明确虚拟电厂为“独立市场主体”,提出硬性目标:2027年全国调节能力≥2000万千瓦࿰…...
HTML前端开发:JavaScript 获取元素方法详解
作为前端开发者,高效获取 DOM 元素是必备技能。以下是 JS 中核心的获取元素方法,分为两大系列: 一、getElementBy... 系列 传统方法,直接通过 DOM 接口访问,返回动态集合(元素变化会实时更新)。…...
Python训练营-Day26-函数专题1:函数定义与参数
题目1:计算圆的面积 任务: 编写一个名为 calculate_circle_area 的函数,该函数接收圆的半径 radius 作为参数,并返回圆的面积。圆的面积 π * radius (可以使用 math.pi 作为 π 的值)要求:函数接收一个位置参数 radi…...
