vue3+ts+vuex+自定义指令实现权限控制

一个基于vue3+vite+ts的完整项目 VUE VBEN ADMIN2.0 介绍 vue-vben-admin-2.0 是一个全新的开源系统,基于ant-design-vue2.x,typescript4,vue3,vite实现的 vue3 风格的后台管理系统。 项目基于ant-design-vue,typescript,vue3.0,vite,tailwindcss,tsx实现的 vue3 风格的后台管理系统, gitHub 地址 vue-vben-admin2.0 - main 分支 预览地址 2.0 在线预览 文档 2.0 文档还没开始写. 阅读详情

1.原理

通过后端返回的权限数组,对前端路由进行筛选,筛选出可访问路由,重写路由导航守卫,在路由进入前进行判断,看是否为可访问路由,满足就跳转,不满足就跳转错误页面;在页面通过自定义指令结合返回的权限数组来控制按钮的显示和隐藏。

2.实现

1.获取用户的权限信息
// 获取用户信息,
		async GetInfo({ commit }) {
			return getInfo().then((response) => {
				const userInfo: IUserInfo = response.data;
				if (userInfo) {
					// 生成自定义权限map
					const operationMap = {};
					for (const op of userInfo.operations) {
						operationMap[op.id] = true;
					}
					userInfo.c_operation_map = operationMap;
					const permissionMap = {};
					for (const op of userInfo.moduleIds) {
						permissionMap[op] = true;
					}
					userInfo.c_permission_map = permissionMap;
					commit('SET_INFO', userInfo);
					if (userInfo.moduleIds.length === 0) {
						throw new Error('getInfo: moduleIds must be a non-null array !');
					}
				} else {
					throw new Error('getInfo: no user info !');
				}
				return response.data;
			});
		},
2.路由筛选
// store中筛选并存储
export interface IPermissionState {
	routers: RouteRecordRaw[];
	addRouters: RouteRecordRaw[];
}

function hasPermission(permissions: string[], route: RouteRecordRaw): boolean {
	// 特殊地,GENERAL不需要控制权限
	if (route.meta?.permission === 'GENERAL') {
		return true;
	}
	let flag = false;
	for (let i = 0, len = permissions.length; i < len; i++) {
		flag = route.meta?.permission === permissions[i];
		if (flag) {
			return true;
		}
	}
	return false;
}

// 路由筛选
function filterAsyncRouter(routes: RouteRecordRaw[], moduleIds: string[]) {
	const res: RouteRecordRaw[] = [];
	for (const route of routes) {
		if (hasPermission(moduleIds, route)) {
			const routeCopy = cloneDeep(route);
			if (!isUndefined(routeCopy.children)) {
				routeCopy.children = filterAsyncRouter(route.children!, moduleIds);
				if (routeCopy.children!.length > 0) {
					routeCopy.redirect = routeCopy.redirect || routeCopy.children[0].path;
				}
			}

			res.push(routeCopy);
		}
	}
	return res;
}

const store: Module<IPermissionState, IAppState> = {
	state: {
		routers: generalRoutes,
		addRouters: [],
	},
	mutations: {
		['SET_ROUTERS'](state, routers) {
			state.addRouters = routers;
			state.routers = generalRoutes.concat(routers);
		},
	},
	getters: {
		addRouters: (state) => {
			return state.addRouters;
		},
	},
	actions: {
		async GenerateRoutes({ commit }, moduleIds: string[]) {
			return new Promise<void>((resolve) => {
				getAsyncRoutesByJson().then((res) => {
					const asyncRoutes = res.asyncRoutes;
					const accessedRouters = filterAsyncRouter(asyncRoutes, moduleIds);
					for (const ar of accessedRouters) {
						if (!isUndefined(ar.children) && ar.children!.length > 0) {
							ar.redirect = ar.redirect || ar.children![0].path;
						}
					}
					commit('SET_ROUTERS', accessedRouters);
					resolve();
				});
			});
		},
	},
};
3.导航守卫重写
router.beforeEach(async (to, from, next: NavigationGuardNext) => {
	to.meta && isNotNil<string>(to.meta.title) && setDocumentTitle(to.meta.title);
	store.commit('SET_ROUTE_PATH', to.path);
	if (to.meta.permission === 'GENERAL') {
		next();
	} else {
		if (
			isNotNil<IUserInfo>((store.state as AllStateTypes).CompositeModule.user)
		) {
			const accessRoutes = router.getRoutes();
			// 如果跳转的路由不在可访问路由表,直接跳转403
			const target =
				accessRoutes &&
				accessRoutes.length &&
				accessRoutes.find((item) => {
					return item.name === to.name;
				});
			if (target) {
				next();
			} else {
				next({ path: '/403' });
			}
		} else {
			// 如果用户信息不存在需要重新获取
			await store.dispatch('GetUser');
			// 获取更新后的用户信息
			store
				.dispatch(
					'GenerateRoutes',
					(store.state as AllStateTypes).CompositeModule.user!.modules
				)
				.then(() => {
					// 根据roles权限生成可访问的路由表
					// 动态添加可访问路由表(vue-router3和vue-router4中有区别)
					store.getters.addRouters.forEach((route: RouteRecordRaw) => {
						router.addRoute(route);
					});
					setRouteRedirect(router.getRoutes());
					if (
						(
							store.state as AllStateTypes
						).CompositeModule.user!.modules.indexOf(
							to.meta.permission as string
						) === -1
					) {
						if (typeof to.redirectedFrom !== 'undefined') {
							const result = setRedirect(
								router.getRoutes(),
								to.redirectedFrom,
								(store.state as AllStateTypes).CompositeModule.user!.modules
							);
							if (result === '403') {
								next({
									path: '/403',
								});
							} else {
								if (result.redirect) {
									next({
										path: result.redirect as string,
									});
								} else {
									if (typeof to.redirectedFrom !== 'undefined') {
										next({
											path: to.redirectedFrom + '/' + result.path,
										});
									} else {
										next({ path: to.path });
									}
								}
							}
						} else {
							next({
								path: '/403',
							});
						}
					} else {
						if (to.path === '/assistant/base/apply') {
							// 有用户信息时,阻止进入申请页面
							next('/assistant/base/index');
							return;
						}
						next({ ...to, replace: true });
					}
				})
				.catch(() => {
					const key = 'notification';
					let countDown = 3;
					notification.error({
						key,
						message: '请求用户信息失败',
						description: `请重新登录!${countDown}秒后自动跳转登录页面`,
						duration: null,
					});
					const interval = setInterval(() => {
						notification.error({
							key,
							message: '请求用户信息失败',
							description: `请重新登录!${countDown - 1}秒后自动跳转登录页面`,
							duration: null,
						});
						countDown = countDown - 1;
					}, 1000);
					// 获取用户信息失败时,跳转到登录
					setTimeout(() => {
						clearInterval(interval);
						next({ path: '/lgn' });
					}, 3000);
				});
		}
	}
});

// 从别处跳转过来的路由判断
export function setRedirect(
	routes: RouteRecordRaw[], //筛选后的路由
	route: RouteLocation, //重定向来源路由
	modules: string[] //模块权限
) {
	let sourceRoute = {} as RouteRecordRaw;
	let targetRoute = {} as RouteRecordRaw;
	routes.forEach((item) => {
		if (item.name === route.name) {
			sourceRoute = item;
		}
	});
	if (sourceRoute?.children) {
		sourceRoute.children.forEach((item) => {
			if (modules.indexOf(String(item.meta?.permission)) !== -1) {
				targetRoute = item;
			}
		});
		if (typeof targetRoute !== 'undefined') {
			return targetRoute;
		} else {
			return '403';
		}
	} else {
		return '403';
	}
}

/**
 * 修改全部路由:当路由存在重定向时检查重定向路由是否有权限,没有权限重定向改为第一个子路由,无子路由重定向到403
 * @param routes
 * @param route
 */
export function setRouteRedirect(routes: RouteRecordRaw[]) {
	routes.forEach((pItem) => {
		if (pItem.redirect) {
			// 查找重定向的路由地址是否有权限
			const target = routes.find((item) => {
				return item.path === pItem.redirect;
			});
			if (!target) {
				if (pItem.children && pItem.children.length) {
					pItem.redirect =
						pItem.path +
						(/\/$/.test(pItem.path) ? '' : '/') +
						pItem.children[0].path;
				} else {
					pItem.redirect = '/403';
				}
			}
		}
	});
}

// 模块权限判断:用于无法使用自定义指令的组件
export function getPermission(name: string) {
	if ((store.state as AllStateTypes).CompositeModule.user) {
		return (
			(store.state as AllStateTypes).CompositeModule.user!.modules.indexOf(
				name
			) > -1
		);
	} else {
		return false;
	}
}
// 操作权限判断:用于无法使用自定义指令的组件
export function getOperation(key: string) {
	if ((store.state as AllStateTypes).CompositeModule.user) {
		return (
			(store.state as AllStateTypes).CompositeModule.user!.operations.indexOf(
				key
			) > -1
		);
	} else {
		return false;
	}
}
4.自定义指令实现

注:vue2和vue3自定义指令钩子函数有所区别

/**
 * Action 权限指令
 * 指令用法:
 *  - 在需要控制 operation 级别权限的组件上使用 v-operation:[method] , 如下:
 *    <i-button v-operation:add >添加用户</a-button>
 *    <a-button v-operation:delete>删除用户</a-button>
 *    <a v-operation:edit @click="edit(record)">修改</a>
 *
 *  - 当前用户没有权限时,组件上使用了该指令则会被隐藏
 *
 */
export const operation: Directive = {
	// eslint-disable-next-line @typescript-eslint/no-explicit-any
	mounted(el: HTMLElement, binding: DirectiveBinding<any>) {
		const operationName = binding.arg!;
		const userInfo: IUserInfo | null = (store.state as AllStateTypes)
			.CompositeModule.user;
		if (userInfo && userInfo.c_operation_map[operationName]) {
			return;
		}
		(el.parentNode && el.parentNode.removeChild(el)) ||
			(el.style.display = 'none');
	},
};

export const permission = {
	// eslint-disable-next-line @typescript-eslint/no-explicit-any
	mounted(el: HTMLElement, binding: DirectiveBinding<any>) {
		const permissionName = binding.arg!;
		const userInfo: IUserInfo | null = (store.state as AllStateTypes)
			.CompositeModule.user;
		if (userInfo && userInfo.c_permission_map[permissionName]) {
			return;
		}
		(el.parentNode && el.parentNode.removeChild(el)) ||
			(el.style.display = 'none');
	},
};
5.路由表权限配置
 							{
							path: 'list',
 								name: 'ctrl.monitor.list',
								meta: {
 									title: '列表',
 									permission: 'LIST',  //用于筛选模块权限
 									module: '模块',
 									operations: [
 										{
 											key: 'ADD', // 用于自定义指令-按钮权限控制
 											name: '新增',
 										},
 										{
 											key: 'DELETE',
 											name: '删除',
 										},
 										]
 										}
6.按钮权限使用
<span
							class="operation-btn"
							v-operation:ADD
							>新增</span
						>
						<span
							class="operation-btn"
							v-operation:DELETE
							>删除</span
						>

注意:1.权限改变后页面需要手动刷新,非响应式;
2.不建议在组件上使用自定义指令,在某些UI组件上使用就会失效例如(a-tabs);
3.前端筛选路由过于复杂,既要判断当前路由还要判断重定向路由,这部分逻辑也可由后端处理,之间返回生成好的路由表;
在这里插入图片描述

基于 Vue3 + TS + Ant Design Vue 实现精细化菜单按钮权限授权组件 用户体验佳:直观的树形选择 + 实时按钮预览代码可维护:清晰的职责分离,TypeScript 类型保障扩展性强:易于添加新的权限维度(如数据权限)性能优秀:合理的缓存策略和状态管理通过本文的实现,你可以快速在自己的项目中集成类似的权限管理系统,提升应用的安全性和灵活性。技术栈适用场景:后台管理系统、SaaS 平台、企业级应用💬 欢迎在评论区交流你的权限管理实践经验! 阅读详情

相关推荐

【Vue 第二十五章】Vue3自定义指令、权限按钮实现思路、自定义拖拽指令

Vue3自定义指令、权限按钮实现思路、自定义拖拽指令

前端知识分享喵 3162

vue3+ts vuex的使用

vuex学习

_nives的博客 1694

QGIS中高效处理CAD数据的两种实用技巧

本文详细解析了在QGIS中高效处理CAD数据的两种核心方法。针对DWG/DXF文件在QGIS中直接打开失败的问题,介绍了使用QGIS内置数据导入管理器进行标准化转换的流程,并重点推荐了功能更强大的Another DXF Importer插件,以更好地保留文字、块属性等复杂信息。文章对比了两种方法的优劣与适用场景,并提供了坐标系设置、中文乱码等常见问题的避坑指南,帮助用户根据数据需求选择最佳导入策略。

weixin_28363123的博客 118

vue3+Ts使用vuex模块化和非模块化管理的2种方式(非pinia)

vue3+Ts使用vuex模块化和非模块化管理的2种方式(非pinia)

weixin_45441173的博客 4334

提升RAG效果:为何 JSON 格式远胜 Markdown?

在构建强大的 RAG (检索增强生成) 系统时,文档解析是至关重要的第一步。它直接影响着后续的检索效率和生成质量。在众多文档格式中,JSON (JavaScript Object Notation) 格式正逐渐展现出其相对于传统 Markdown 格式的巨大优势。本文将深入探讨 JSON 在 RAG 系统文档解析中的卓越之处,并结合具体案例,让你理解为何 JSON 才是构建下一代智能应用的更优选择。RAG 系统的核心目标是让模型能够利用外部知识库,生成更精准、更贴合实际的答案。

simoncool23的博客 2719

免杀思路总结

免杀思路总结 1.免杀技术简介 1.免杀的重要性 2.免杀的难度 3.杀毒软件的排行 国外 国内 1 、Bitdefender 1、360 2 、Norton 360 2、智量安全(没测过,据说很厉害) 3 、Kaspersky 3、火绒安全 4 、Webroot 4、金山独霸 5 、Trend Micro 5

LiBai'S BLOG 1万+

vue3+ts使用使用vuex

1、 安装 vuex npm install vuex -S 2、在目录下创建store文件 import { createStore } from 'vuex' export default createStore({ // 1、 存储所有全局数据 state: { num: '1' }, // 2、 需要通过计算获取state里的内容获取的数据 // 只能读取不可修改 getters: { }, // 3、定义对s

this__valve的博客 1421

【华为OD机试真题】282、求满足条件的最长子串的长度 | 机试真题+思路参考+代码分析 (C++、Java、Py、C语言、JS)

题目描述 >给定一个字符串,只包含字母和数字,按要求找出字符串中的最长(连续)子串的长度,字符串本身是其最长的子串,子串要求: >  1、只包含1个字母(a~ z,A~Z), 其余必须是数字; 2、字母可以在子 串中的任意位置; >  如果找不到满足要求的子串,如全是字母或全是数字,则返回-1

KJ.JK 1万+

vue3 ts使用vuex

在store文件夹下新建index.ts文件。 写下如下内容 import { createStore } from 'vuex' export default createStore({ state: { theme:true }, mutations: { changeTheme(state){ state.theme=!state.theme } }, actions: { }, modules: { } }) 在

LOG的博客 2298

在ABAQUS中使用VUMAT子程序实现Hashin失效准则的详细解析与实践:深入探讨复合材料模拟仿真的高级编程技巧

Hashin失效准则是由材料科学家Shlomo Hashin于1962年提出的一种基于能量方法的多向失效理论,主要用于预测复合材料的破坏行为。与传统的单向失效理论不同,Hashin准则考虑了材料内部的多种独立破坏机制,如基体断裂、界面脱粘、纤维断裂等,这些机制在不同应力状态下可能同时或分别发生。通过计算各个机制的能量释放率,Hashin失效准则能够更为准确地预测复合材料在复杂应力状态下的破坏行为。

qq_38334677的博客 1950

Android Telephony主要模块及功能

Telephony的代码主要由五个模块组成 1.Telephony Frameworks 代码区域: frameworks/opt/telephony/ 其中主要的文件在如下目录 /frameworks/opt/telephony/src/java/com/android/internal/telephony/ 其中比较常见的有以下几类文件 1.BaseCommands、RIL构成R

泊客 7727

vue3.0+Ts+全局自定义指令实现动态按钮【操作】

本文采用vue3.0+Ts+全局自定义指令实现动态按钮【操作】,应用场景为后端发送动态菜单+操作,操作信息存放在路由的meta中并将路由存在vuex中。模板文件使用全局自定义指令动态判断是否显示该按钮。

weixin_44871749的博客 2731

置顶必读(1) |《YOLOv11实战:从入门到深度优化》专栏导读,带你一文了解本专栏!

🏆 本文收录于 《YOLOv8实战:从入门到深度优化》 专栏,该专栏专注于分享我在YOLOv8目标检测模型中的实战经验,涵盖从入门基础到深度优化的全方位解决方案。无论你是计算机视觉的新手,还是具有多年深度学习经验的专家,本专栏都将为你提供高效、可复现的技术方案,助你掌握YOLOv8的核心技术,提升目标检测性能,突破瓶颈,优化模型,并加速项目开发🚀!

**My Coding Family** 12万+

vue3+vue-router+vuex+ts实现权限管理

一、说明 权限管理一般从路由权限、接口权限、页面权限、按钮权限四个角度去考虑。 我基于业务需求,实现了路由权限、页面权限、按钮权限。以下主要展示路由权限。 tips:(页面权限和按钮权限 需要在具体的页面or按钮中去实现;接口权限需要和后端去协商约定实现) 二、实现思路+源码: 1.router.ts import store from "@/store/index"; import { createRouter, createWebHashHistory } from "vue-router"; impo

qq_40420929的博客 2007

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

vue2/vue3自定义指令——鉴权

vue2: src/directives/index.ts import Vue from 'vue'; import store from '@/store'; export default function directive() { Vue.directive('auth', { inserted: (el, binding) => { if (!store.state.buttons.has(binding.value)) {

Eleuuuuy的博客 1376

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

Vue3-Composition-Admin权限管理系统详解:从页面权限到指令权限的完整实现

在现代化的Vue3管理后台开发中,权限管理系统是保障应用安全性的核心模块。**Vue3-Composition-Admin**作为基于Vue3的现代化管理端模板,提供了从页面权限到指令权限的完整解决方案。本文将深入解析这个项目的权限实现机制,帮助开发者快速构建安全可靠的后台管理系统。 ## 🚀 权限系统架构概览 Vue3-Composition-Admin的权限系统采用分层设计,主要包含以下

gitblog_00364的博客 346

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

vue3+ts之动态路由

vue3+ts之动态路由

皮皮的博客 5254

vue3+ts通用管理后台练习项目

一个基于 RBAC 设计的通用后台管理系统,支持通过配置文件批量完成特定功能,如搜索、表格数据显示,导入、导出。项目中封装了大量通用组件,如表单、表格、Echarts 图表、头像上传、面包屑、左侧菜单等。

weixin_46463984的博客 2517

Vue Vben Admin状态管理终极指南:Pinia vs Vuex 4深度对比

Vue Vben Admin作为一款基于Vue3和TypeScript的企业级中后台框架,其状态管理方案选择直接影响项目性能与开发效率。本文将深入对比Vue生态中两大主流状态管理库——Pinia与Vuex 4,帮助开发者快速掌握在Vue Vben Admin中实现高效状态管理的核心方法。 ## 为什么状态管理对Vue Vben Admin至关重要 在复杂的中后台系统中,组件间的数据共享和状态同

gitblog_01170的博客 1038

Vue自定义权限指令v-permission:从原理到生产级实现

在前端开发中,权限控制是保障系统安全与用户体验的核心机制,尤其在后台管理系统等复杂应用中。其基本原理是通过预定义的规则,判断用户角色对特定资源或功能的访问与操作权限。从技术实现角度看,声明式编程和组件化思想为此提供了优雅的解决方案。Vue.js的自定义指令技术,允许开发者扩展HTML元素的行为,将权限校验逻辑封装为可复用的指令,从而实现视图层与业务逻辑的解耦,提升代码的可维护性和工程化水平。这种技术方案的价值在于,它提供了一种标准化、声明式的方式来管理前端权限,避免了权限代码散落各处的问题。其典型的应用场景

weixin_30595035的博客 451

基于Vue3TS构建的商城后台管理系统:高效、稳定、易于维护的电商平台管理解决方案

基于Vue3TS构建的商城后台管理系统:高效、稳定、易于维护的电商平台管理解决方案 去发现同类优质开源项目:https://gitcode.com/ 项目介绍 在当今数字化转型时代,一个强大且易于使用的后台管理系统是电商平台成功的基石。基于Vue3+TS构建的商城后台管理系统,融合了最新的前端技术,为电商平台提供了一套高效、稳定、易于维护的管理界面,助力企业快速响应市场变化,提升运营效率。 项目技...

gitblog_06783的博客 742

基于Vue3+TS构建的商城后台管理系统

基于Vue3+TS构建的商城后台管理系统 去发现同类优质开源项目:https://gitcode.com/ 简介 本项目是一个基于Vue3和TypeScript构建的商城后台管理系统。系统采用了现代化的前端框架与技术,提供了高效、稳定、易于维护的管理界面,适用于各类电商平台的运营管理。 特点 基于Vue3:利用Vue3的新特性和优化,提升应用性能和开发效率。 TypeScript支持:增强代码的...

gitblog_06779的博客 679

创建vue3+vite+ts+elementplus+router+axios项目

当post请求的数据需要用query的方式传时,复杂类型的传参会出现问题。可以使用qs库的方法使其转换成form-data类型且容易识别的格式。根据父子组件渲染顺序,父组件请求的接口数据是异步到达的,所以子组件里需要再把红框的内容放入watch()方法里面。类似于all会等全部执行完,但是不管reject还是resolve都是从then返回。返回最快执行的那个,最快的是resolve就从then返回,最快的是reject就从catch返回。用数组的方法,比较笨,可以不用看,只是数组的用法比较妙。

Charming_flower的博客 956

详细的vue学习路线

以下是一个,适合你逐步掌握 Vue.js 的各项技能。路线包含知识点拆解、学习顺序、推荐时间分配、以及实战建议。

Zero_pl的博客 1934

Vue3+Ts+Vant项目:路由守卫、router-link、Vuex

Vue3+Ts+Vant项目:路由守卫、router-link、Vuex

weixin_52053631的博客 776
上一篇: Webpack构建流程
下一篇: monaco-editor+vue2+ts实现sql编辑器
大兵的猫
博客等级 码龄7年 364粉丝 49原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大兵的猫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值