Vue3 - 实现动态获取菜单路由和按钮权限控制指令

GitHub Demo 地址

在线预览

前言

关于动态获取路由已在这里给出方案 Vue - vue-admin-template模板项目改造:动态获取菜单路由
这里是在此基础上升级成vue3ts,数据和网络请求是通过mock实现的
具体代码请看demo!!!

本地权限控制,具体是通过查询用户信息获取用户角色,在路由守卫中通过角色过滤本地配置的路由,把符合角色权限的路由生成一个路由数组

动态获取菜单路由其实思路是一样的,只不过路由数组变成从服务器获取,通过查询某个角色的菜单列表,然后在路由守卫中把获取到的菜单数组转成路由数组

动态路由实现是参考vue-element-admin的issues写的,相关issues:
vue-element-admin/issues/167
vue-element-admin/issues/293
vue-element-admin/issues/3326#issuecomment-832852647

关键点

主要在接口菜单列表中把父componentLayout 改为字符串 ‘Layout’,
children的component: () => import(‘@/views/system/user/index.vue’), 改成 字符串’system/user/index’,然后在获取到数据后再转回来
!!!!!!!!!!!! 接口格式可以根据项目需要自定义,不一定非得按照这里的来

vue3 中component使用和vue略有差异,需要加上完整路径,并且从字符串换成组件的方式也有不同
!!!!!!!!!注意文件路径

import { defineAsyncComponent } from 'vue'
const modules = import.meta.glob('../../views/**/**.vue')// 加载路由
const loadView = (view: string) => {// 路由懒加载// return defineAsyncComponent(() => import(`/src/views/${view}.vue`))return modules[`../../views/${view}.vue`]
}

调用

loadView(route.component)

本地路由格式:

import { AppRouteType } from '@/router/types'const Layout = () => import('@/layout/index.vue')const systemRouter: AppRouteType = {path: '/system',name: 'system',component: Layout,meta: { title: 'SystemSetting', icon: 'ep:setting', roles: ['admin'] },children: [{path: 'user',name: 'user',component: () => import('@/views/system/user/index.vue'),meta: {title: 'SystemUser',icon: 'user',buttons: ['user-add', 'user-edit', 'user-look', 'user-export', 'user-delete', 'user-assign', 'user-resetPwd']}},{path: 'role',name: 'role',component: () => import('@/views/system/role/index.vue'),meta: {title: 'SystemRole',icon: 'role',buttons: ['role-add', 'role-edit', 'role-look', 'role-delete', 'role-setting']}},{path: 'menu',name: 'menu',component: () => import('@/views/system/menu/index.vue'),meta: {title: 'SystemMenu',icon: 'menu',buttons: ['menu-add', 'menu-edit', 'menu-look', 'menu-delete']}},{path: 'dict',name: 'dict',component: () => import('@/views/system/dict/index.vue'),meta: {title: 'SystemDict',icon: 'dict',buttons: ['dict-type-add', 'dict-type-edit', 'dict-type-delete', 'dict-item-add', 'dict-item-edit', 'dict-item-delete']}}]
}
export default systemRouter

ts路由类型定义

import type { RouteRecordRaw, RouteMeta, RouteRecordRedirectOption } from 'vue-router'export type Component<T = any> = ReturnType<typeof defineComponent> | (() => Promise<typeof import('*.vue')>) | (() => Promise<T>)// element-plus图标
// https://icon-sets.iconify.design/ep/
// 其他的
// https://icon-sets.iconify.design/
// 动态图标
// https://icon-sets.iconify.design/line-md/
// https://icon-sets.iconify.design/svg-spinners/export interface AppRouteMetaType extends RouteMeta {title?: stringicon?: string // 设置svg图标和通过iconify使用的element-plus图标,根据 : 判断是否是iconify图标hidden?: booleanaffix?: booleankeepAlive?: booleanroles?: string[]buttons?: string[]
}export interface AppRouteType extends Omit<RouteRecordRaw, 'props'> {path: stringname?: stringcomponent?: Component | stringcomponents?: Componentchildren?: AppRouteType[]fullPath?: stringmeta?: AppRouteMetaTyperedirect?: stringalias?: string | string[]
}// 动态路由类型
export interface AppDynamicRouteType extends AppRouteType {id: stringcode: stringtitle: stringparentId: stringparentTitle: stringmenuType: stringcomponent: string | Componenticon: stringsort: numberhidden: booleanlevel: numberchildren?: AppDynamicRouteType[]buttons?: string[]
}

接口路由格式:

{id: '22',code: '/system',title: '系统设置',parentId: '',parentTitle: '',menuType: 'catalog', // catalog | menu | buttoncomponent: 'Layout', // "Layout" | "system/menu" (文件路径: src/views/) | ""// component: Layout,icon: 'ep:setting',sort: 1,hidden: false,level: 1,children: [{id: '22-1',code: 'user',title: '用户管理',parentId: '22',parentTitle: '系统设置',menuType: 'menu',component: 'system/user/index',// component: () => import('@/views/system/user'),icon: 'user',sort: 2,hidden: false,level: 2,children: [],buttons: ['user-add', 'user-edit', 'user-look', 'user-export', 'user-delete', 'user-assign', 'user-resetPwd']},{id: '22-2',code: 'role',title: '角色管理',parentId: '22',parentTitle: '系统设置',menuType: 'menu',component: 'system/role/index',icon: 'role',sort: 3,hidden: false,level: 2,children: [],buttons: ['role-add', 'role-edit', 'role-look', 'role-delete', 'role-setting']},{id: '22-3',code: 'menu',title: '菜单管理',parentId: '22',parentTitle: '系统设置',menuType: 'menu',component: 'system/menu/index',icon: 'menu',sort: 4,hidden: false,level: 2,children: [],buttons: ['menu-add', 'menu-edit', 'menu-look', 'menu-delete']},{id: '22-4',code: 'dict',title: '字典管理',parentId: '22',parentTitle: '系统设置',menuType: 'menu',component: 'system/dict/index',icon: 'dict',sort: 5,hidden: false,level: 2,children: [],buttons: ['dict-type-add', 'dict-type-edit', 'dict-type-delete', 'dict-item-add', 'dict-item-edit', 'dict-item-delete']}]}

我这里在mock中加了个角色editor2,当editor2登录使用的从服务器获取动态路由,其他角色从本地获取路由

在这里插入图片描述

permission.ts 实现,其中filterAsyncRoutes2方法就是格式化菜单路由的方法

import { defineAsyncComponent } from 'vue'
import { cloneDeep } from 'lodash-es'
import { defineStore } from 'pinia'
import { store } from '@/store'
import { asyncRoutes, constantRoutes } from '@/router'import { AppRouteType, AppDynamicRouteType } from '@/router/types'const modules = import.meta.glob('../../views/**/**.vue')
const Layout = () => import('@/layout/index.vue')/*** Use meta.role to determine if the current user has permission* @param roles* @param route*/
const hasPermission = (roles: string[], route: AppRouteType) => {if (route.meta && route.meta.roles) {return roles.some((role) => {if (route.meta?.roles !== undefined) {return (route.meta.roles as string[]).includes(role)}})}return true
}/*** Filter asynchronous routing tables by recursion* @param routes asyncRoutes* @param roles*/
const filterAsyncRoutes = (routes: AppRouteType[], roles: string[]) => {const res: AppRouteType[] = []routes.forEach((route) => {const tmp = cloneDeep(route)// const tmp = { ...route }if (hasPermission(roles, tmp)) {if (tmp.children) {tmp.children = filterAsyncRoutes(tmp.children, roles)}res.push(tmp)}})return res
}// 加载路由
const loadView = (view: string) => {// 路由懒加载// return defineAsyncComponent(() => import(`/src/views/${view}.vue`))return modules[`../../views/${view}.vue`]
}/*** 通过递归格式化菜单路由 (配置项规则:https://panjiachen.github.io/vue-element-admin-site/zh/guide/essentials/router-and-nav.html#配置项)* @param routes*/
export function filterAsyncRoutes2(routes: AppDynamicRouteType[]) {const res: AppDynamicRouteType[] = []routes.forEach((route) => {const tmp = cloneDeep(route)// const tmp = { ...route }tmp.id = route.idtmp.path = route.codetmp.name = route.codetmp.meta = { title: route.title, icon: route.icon, buttons: route.buttons }if (route.component === 'Layout') {tmp.component = Layout} else if (route.component) {tmp.component = loadView(route.component)}if (route.children && route.children.length > 0) {tmp.children = filterAsyncRoutes2(route.children)}res.push(tmp)})return res
}// setup
export const usePermissionStore = defineStore('permission', () => {// stateconst routes = ref<AppRouteType[]>([])// actionsfunction setRoutes(newRoutes: AppRouteType[]) {routes.value = constantRoutes.concat(newRoutes)}function generateRoutes(roles: string[]) {return new Promise<AppRouteType[]>((resolve, reject) => {let accessedRoutes: AppRouteType[] = []if (roles.includes('admin')) {accessedRoutes = asyncRoutes || []} else {accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)}setRoutes(accessedRoutes)resolve(accessedRoutes)})}function generateDynamicRoutes(menus: AppDynamicRouteType[]) {return new Promise<AppRouteType[]>((resolve, reject) => {const accessedRoutes = filterAsyncRoutes2(menus)setRoutes(accessedRoutes) // Todo: 内部拼接constantRoutes,所以查出来的菜单不用包含constantRoutesresolve(accessedRoutes)})}return { routes, setRoutes, generateRoutes, generateDynamicRoutes }
})// 非setup
export function usePermissionStoreHook() {return usePermissionStore(store)
}

按钮权限控制

directive文件夹,创建permission.ts指令设置路由内的按钮权限

import { useUserStoreHook } from '@/store/modules/user'
import { Directive, DirectiveBinding } from 'vue'
import router from '@/router/index'/*** 按钮权限 eg: v-hasPerm="['user-add','user-edit']"*/
export const hasPerm: Directive = {mounted(el: HTMLElement, binding: DirectiveBinding) {// 「超级管理员」拥有所有的按钮权限const { roles, perms } = useUserStoreHook()if (roles.includes('admin')) {return true}// 「其他角色」按钮权限校验const buttons = router.currentRoute.value.meta.buttons as string[]const { value } = bindingif (value) {const requiredPerms = value // DOM绑定需要的按钮权限标识const hasPerm = buttons?.some((perm) => {return requiredPerms.includes(perm)})if (!hasPerm) {el.parentNode && el.parentNode.removeChild(el)}} else {throw new Error("need perms! Like v-has-perm=\"['user-add','user-edit']\"")}}
}

创建index.ts文件,全局注册 directive

import type { App } from 'vue'import { hasPerm } from './permission'// 全局注册 directive
export function setupDirective(app: App<Element>) {// 使 v-hasPerm 在所有组件中都可用app.directive('hasPerm', hasPerm)
}

在main.ts注册自定义指令

import { setupDirective } from '@/directive'const app = createApp(App)
// 全局注册 自定义指令(directive)
setupDirective(app)

使用

<el-button v-hasPerm="['user-item-add']"> 新增 </el-button>

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

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

相关文章

(总目录)springboot - 实现zip文件上传并对zip文件解压, 包含上传oss

全文目录,一步到位 1.本文概述1.1 本文简介 2. 功能实现2.1 统一文件校验2.2 普通(多)文件上传[服务器]2.2.1 controller层2.2.2 service层2.2.3 业务impl实现类2.2.4 FileIOUtils工具包代码 2.3 zip文件的解压2.4 图片文件的压缩2.5 oss文件后端上传2.6 oss文件前端上传2.7 后…

matlab读写json文件

Background 通常&#xff0c;在matlab中使用mat文件进行数据存储。MAT文件是MATLAB中用来存储数据的二进制文件格式。MAT文件可以包含各种数据类型&#xff0c;包括数字、矩阵、向量、结构体、字符和函数等。但是&#xff0c;当和其他语言有交互时&#xff0c;mat文件会不太方便…

golang实现远程控制主机

文章目录 ssh原理使用golang远程下发命令使用golang远程传输文件 ssh原理 说到ssh原理个人觉得解释最全的一张图是这张华为画的 Connection establishment 这一步就是建立tcp连接 version negotiation 这一步是ssh客户端(连接者)和被ssh服务端(连接者)进行协议的交换&#xf…

Jetpack Compose 的简单 MVI 框架

Jetpack Compose 的简单 MVI 框架 在 Jetpack Compose 应用程序中管理状态的一种简单方法 选择正确的架构是至关重要的&#xff0c;因为架构变更后期代价高昂。MVP已被MVVM和MVI取代&#xff0c;而MVI更受欢迎。MVI通过强制实施结构化的状态管理方法&#xff0c;只在reducer中…

控制台日志打印console的封装,加入美化、行显示与打印开关,支持node.js环境

控制台日志打印console的封装&#xff0c;加入美化、行显示与打印开关&#xff0c;支持node.js环境 为什么要写这个&#xff1f; 封装这个控制台日志打印工具&#xff0c;主要是在项目中自己做的SDK需要提供给其他开发人员使用&#xff0c;加入了日志美化和打印打开&#xff…

【数据结构】顺序表与ArrayList

作者主页&#xff1a;paper jie 的博客 本文作者&#xff1a;大家好&#xff0c;我是paper jie&#xff0c;感谢你阅读本文&#xff0c;欢迎一建三连哦。 本文录入于《JAVA数据结构》专栏&#xff0c;本专栏是针对于大学生&#xff0c;编程小白精心打造的。笔者用重金(时间和精…

【云计算】虚拟私有云 VPC

虚拟私有云 VPC 1.前言1.1 基本介绍1.2 VPC 的作用1.3 VPC 的适用人群 2.VPC 基本概念2.1 VPC 相关基本概念2.2 其他相关基本概念 3.VPC 通信场景3.1 VPC 内部互通3.2 VPC 间互通3.2.1 对等连接3.2.2 Transit Gateway 或者云联网 3.3 访问 Internet3.3.1 Internet 网关3.3.2 NA…

【HCIE】04.网络安全技术

端口隔离 在同一VLAN中可以隔离二层与三层通信&#xff0c;让同VLAN内的设备可以通信或者不可以通信。 定义一个端口隔离组&#xff0c;在一个组内无法互访&#xff0c;不在一个组里面可以进行互访 port-isolate enable group1 //使能端口隔离功能 port-isolate mdoe all //全…

B : DS顺序表--连续操作

Description 建立顺序表的类&#xff0c;属性包括&#xff1a;数组、实际长度、最大长度&#xff08;设定为1000&#xff09; 该类具有以下成员函数&#xff1a; 构造函数&#xff1a;实现顺序表的初始化。 插入多个数据的multiinsert(int i, int n, int item[])函数&#…

Unity 开发人员转CGE(castle Game engine)城堡游戏引擎指导手册

Unity 开发人员的城堡游戏引擎概述 一、简介2. Unity相当于什么GameObject&#xff1f;3. 如何设计一个由多种资产、生物等组成的关卡&#xff1f;4. 在哪里放置特定角色的代码&#xff08;例如生物、物品&#xff09;&#xff1f;Unity 中“向 GameObject 添加 MonoBehaviour”…

Vue3大屏项目实现数字跳动的效果

一、vue-count-to组件&#xff1a; 1、安装&#xff1a; npm install vue3-count-to --save 2、使用&#xff1a; <template><BaseCountTo:startVal"startVal":endVal"endVal":duration"duration":decimals"decimals":pr…

基于复旦微的FMQL45T900全国产化ARM核心模块(100%国产化)

TES745D是一款基于上海复旦微电子FMQL45T900的全国产化ARM核心板。该核心板将复旦微的FMQL45T900&#xff08;与XILINX的XC7Z045-2FFG900I兼容&#xff09;的最小系统集成在了一个87*117mm的核心板上&#xff0c;可以作为一个核心模块&#xff0c;进行功能性扩展&#xff0c;能…

Redis 五大类型源码及底层实现

面试题&#xff1a; 谈谈Redis数据类型的底层数据结构&#xff1a; SDS动态字符串双向链表玉缩列表ziplist哈希表hashtable跳表kiplist整数集合intset快速列表quicklist紧凑列表listpack Redis源代码的核心部分 官网&#xff1a;GitHub - redis/redis: Redis is an in-memory…

在已知的二维坐标里找到最接近的点

一、业务场景 最近在研发的项目&#xff0c;在做可视化层&#xff0c;在全球地图上&#xff0c;对我们的国家的陆地地图经纬度按照步长为1的间隔做了二维处理。在得到一组整数的点位信息后&#xff0c;需要将我们已有的数据库数据(业务项目)按照地址的经纬度&#xff0c;映射到…

大数据Flink(八十三):SQL语法的DML:With、SELECT WHERE、SELECT DISTINCT 子句

文章目录 SQL语法的DML:With、SELECT & WHERE、SELECT DISTINCT 子句 一、DML:With 子句

本地Docker Registry远程连接,为你带来高效便捷的镜像管理体验!

Linux 本地 Docker Registry本地镜像仓库远程连接 文章目录 Linux 本地 Docker Registry本地镜像仓库远程连接1. 部署Docker Registry2. 本地测试推送镜像3. Linux 安装cpolar4. 配置Docker Registry公网访问地址5. 公网远程推送Docker Registry6. 固定Docker Registry公网地址…

jmeterbeanshell调用jsonpath获取对应值

1.jmeter 新建线程组、Java Request、BeanShell Assertion、View Results Tree 2、在BeanShell Assertion中贴入代码&#xff1a; import org.apache.jmeter.extractor.json.jsonpath.JSONManager; import java.util.List; JSONManager js new JSONManager(); String jsonStr…

电商项目高级篇-01 elasticsearch

电商项目高级篇-01 elasticsearch 1、linux下安装elasticsearch和可视化工具2、docker设置虚拟机开机启动和容器开机启动3、elasticsearch的curd3.1、新增、更新3.2、查询 1、linux下安装elasticsearch和可视化工具 将安装好jdk1.8和tomcat的centos7下安装elasticsearch dock…

贝叶斯神经网络 BBB 学习中遇到的一些问题

这里写目录标题 贝叶斯公式模型概率的公式1/n 形式的贝叶斯公式 全概率公式全概率公式的积分形式 后验推理后验预测分布 posterior predictive distributionKL 散度 平均场 VIBayes by Backprop 代码重新参数化 贝叶斯公式 模型概率的公式 一开始看了这个 https://zhuanlan.z…

Unity 2021.x及以下全版本Crack

前言 最近Unity那档子事不出来了吗&#xff0c;搞得所有人都挺烦的&#xff0c;顺便在公司内网需要我完成一个游戏的项目&#xff0c;就研究了一下如何将Unity给Crack掉。 注意所有操作应有连接外网的权限 以我选择的版本为例&#xff0c;我使用的是Unity 2021.3.5f1与Unity…