VUE项目中实现权限控制,菜单权限,按钮权限,接口权限,路由权限,操作权限,数据权限实现

VUE项目中实现权限控制,菜单权限,按钮权限,接口权限,路由权限,操作权限,数据权限实现

    • 权限系统分类(RBAC)
      • 引言
      • 菜单权限
      • 按钮权限
      • 接口权限
      • 路由权限
    • 菜单权限方案
      • 方案一:菜单与路由分离
      • 方案二:菜单和路由都由后端返回
    • 路由权限控制(如果后端设计菜单后返回的菜单都挂载到路由上)
      • 方案一:初始化即挂载全部路由
      • 方案二:按需挂载路由
    • 按钮权限方案
      • 方案一:使用 `v-if` 判断
      • 方案二:通过自定义指令进行按钮权限判断

权限系统分类(RBAC)

引言

在实际项目开发中,权限管理是一个关键功能,用于控制不同用户对系统资源的访问。权限是对特定资源的访问许可,权限控制的目的是确保用户只能访问到被分配的资源。例如,网站管理员可以对网站数据进行增删改查,而普通用户只能浏览

菜单权限

菜单管理涉及定义和管理应用中的导航菜单。不同的用户角色可能会看到不同的菜单项,从而访问不同的功能模块。

按钮权限

不同的用户角色可能享有不同的操作权限,例如管理员可以增删改查,而普通用户只能查看。

接口权限

接口权限通常采用 JWT 形式进行验证。如果请求未通过验证,服务器会返回 401 状态码,客户端则跳转到登录页面重新登录。登录成功后,客户端会拿到 token 并将其存储起来,通过 axios 请求拦截器在每次请求时携带 token。

路由权限

路由权限控制用户可以访问的页面和路径。

菜单权限方案

方案一:菜单与路由分离

前端组件:

{
  name: "login",
  path: "/login",
  component: () => import("@/pages/Login.vue")
}

全局路由卫生

function hasPermission(router, accessMenu) {
  if (whiteList.includes(router.path)) {
    return true;
  }
  const menu = Util.getMenuByName(router.name, accessMenu);
  return !!menu.name;
}

Router.beforeEach(async (to, from, next) => {
  if (getToken()) {
    const userInfo = store.state.user.userInfo;
    if (!userInfo.name) {
      try {
        await store.dispatch("GetUserInfo"); // 获取用户信息
        await store.dispatch('updateAccessMenu'); // 更新访问菜单
        if (to.path === '/login') {
          next({ name: 'home_index' }); // 如果当前路径是登录页,跳转到首页
        } else {
          next({ ...to, replace: true }); // 替换当前路径
        }
      } catch (e) {
        if (whiteList.includes(to.path)) {
          next(); // 如果路径在白名单中,直接通过
        } else {
          next('/login'); // 否则跳转到登录页
        }
      }
    } else {
      if (to.path === '/login') {
        next({ name: 'home_index' }); // 如果当前路径是登录页,跳转到首页
      } else {
        if (hasPermission(to, store.getters.accessMenu)) {
          Util.toDefaultPage(store.getters.accessMenu, to, routes, next); // 跳转到默认页面
        } else {
          next({ path: '/403', replace: true }); // 没有权限,跳转到403页面
        }
      }
    }
  } else {
    if (whiteList.includes(to.path)) {
      next(); // 如果路径在白名单中,直接通过
    } else {
      next('/login'); // 否则跳转到登录页
    }
  }
  const menu = Util.getMenuByName(to.name, store.getters.accessMenu);
  Util.title(menu.title); // 设置页面标题
});

Router.afterEach((to) => {
  window.scrollTo(0, 0); // 滚动条回到顶部
});

优点:
前后端职责分明,前端负责路由定义,后端负责菜单管理。

缺点:
需要维护菜单与路由的对应关系,增加了复杂性。
每次路由跳转都需要进行权限判断

不推荐!!!!!

方案二:菜单和路由都由后端返回

思路: 由后端设计菜单表进行菜单管理,返回给前端后,由前端进行拼接成路由,然后进行挂载到全局路由上,router.addRouter()方法上。进行加载,
在这里插入图片描述
后端返回菜单json格式

[
  {
    name: "home",
    path: "/",
    component: "home"
  },
  {
    name: "userinfo",
    path: "/userinfo",
    component: "userInfo"
  }
]

前端处理逻辑

// 将后端菜单数据转换为路由格式
function generateAsyncRouter(menus) {
  const asyncRoutes = menus.map(menu => {
    const route = {
      path: menu.path,
      name: menu.menuName,
      meta: {
        title: menu.menuName,
        icon: menu.icon
      },
      hidden: menu.hidden
    }

    // 处理组件
    if (menu.menuType === 'content') {
      // 目录类型
      route.alwaysShow = true
      route.component = Layout
      if (menu.children && menu.children.length > 0) {
        route.children = generateAsyncRouter(menu.children)
      }
    } else if (menu.menuType === 'menu') {
      // 菜单类型,动态加载组件
      route.component = loadView(menu.path)
    }
    // button类型的不需要生成路由

    return route
  })

  // 添加404页面路由
  asyncRoutes.push({ 
    path: '*', 
    redirect: '/404', 
    hidden: true 
  })

优点:
前后端高度集成,灵活性高。

缺点:
前后端配合要求更高。
每次路由跳转都需要进行权限判断。
推荐这种用法

路由权限控制(如果后端设计菜单后返回的菜单都挂载到路由上)

方案一:初始化即挂载全部路由

const routerMap = [
  {
    path: '/permission',
    component: Layout,
    redirect: '/permission/index',
    alwaysShow: true,
    meta: {
      title: '权限管理',
      icon: 'lock',
      roles: ['admin', 'editor'] // 标记权限
    },
    children: [
      {
        path: 'page',
        component: () => import('@/views/permission/page'),
        name: 'pagePermission',
        meta: {
          title: '页面权限',
          roles: ['admin'] // 标记权限
        }
      },
      {
        path: 'directive',
        component: () => import('@/views/permission/directive'),
        name: 'directivePermission',
        meta: {
          title: '指令权限'
          // 如果没有设置权限标识,意味着这个页面不需要权限
        }
      }
    ]
  }
];

方案二:按需挂载路由

import router from './router';
import store from './store';
import { Message } from 'element-ui';
import NProgress from 'nprogress'; // 进度条
import 'nprogress/nprogress.css'; // 进度条样式
import { getToken } from '@/utils/auth'; // 从 cookie 获取 token

NProgress.configure({ showSpinner: false }); // 配置进度条

function hasPermission(roles, permissionRoles) {
  if (roles.includes('admin')) return true; // 管理员权限直接通过
  if (!permissionRoles) return true;
  return roles.some(role => permissionRoles.includes(role));
}

const whiteList = ['/login', '/authredirect']; // 不需要重定向的白名单

router.beforeEach((to, from, next) => {
  NProgress.start(); // 开始进度条
  if (getToken()) { // 确定是否有 token
    if (to.path === '/login') {
      next({ path: '/' });
      NProgress.done(); // 如果当前页面是仪表盘,不会触发 afterEach 钩子,所以手动处理
    } else {
      if (store.getters.roles.length === 0) { // 用户信息
        store.dispatch('GetUserInfo').then(res => { // 获取用户信息
          const roles = res.data.roles; // 注意:角色必须是一个数组!例如:['editor','develop']
          store.dispatch('GenerateRoutes', { roles }).then(() => { // 生成路由
            router.addRoutes(store.getters.addRouters); // 添加路由
            next({ ...to, replace: true }); // 替换当前路径,防止留下历史记录
          });
        }).catch((err) => {
          store.dispatch('FedLogOut').then(() => {
            Message.error(err || '验证失败,请重新登录');
            next({ path: '/' });
          });
        });
      } else {
        if (hasPermission(store.getters.roles, to.meta.roles)) {
          next(); // 有权限,继续跳转
        } else {
          next({ path: '/401', replace: true, query: { noGoBack: true }}); // 没有权限,跳转到401页面
        }
      }
    }
  } else {
    if (whiteList.includes(to.path)) {
      next(); // 如果路径在白名单中,直接通过
    } else {
      next('/login'); // 否则跳转到登录页
      NProgress.done(); // 如果当前页面是登录页,不会触发 afterEach 钩子,所以手动处理
    }
  }
});

router.afterEach(() => {
  NProgress.done(); // 结束进度条
});

这种是我实现的方式
全局路由上,获取到全部菜单,前端进行路由加载
在这里插入图片描述

按钮权限方案

方案一:使用 v-if 判断

优点:

简单直观,易于实现。
缺点:

如果页面较多,每个页面都需要获取用户权限并进行判断,增加复杂性。

方案二:通过自定义指令进行按钮权限判断

自定义指令:

import permissionV from './permission-v';

const install = function(Vue) {
  Vue.directive('permission-v', permissionV)
}

if (window.Vue) {
  window['permission-v'] = permissionV
  Vue.use(install); // eslint-disable-line
}

permissionV.install = install
export default permissionV

permission-v.js

import store from '@/store'

function checkBtnPermission(el, binding) {
  const { value } = binding
  const permissions = store.getters && store.getters.userInfo.permissions

  if (value && value instanceof Array) {
    if (value.length > 0) {
      const permission = value

      const hasPermission = permissions.some(permiss => {
        return permission.includes(permiss)
      })

      if (!hasPermission) {
        el.parentNode && el.parentNode.removeChild(el)
      }
    }
  } else {
    throw new Error(`need permissions! Like v-permission="['admin','editor']"`)
  }
}

export default {
  inserted(el, binding) {
    checkBtnPermission(el, binding)
  },
  update(el, binding) {
    checkBtnPermission(el, binding)
  }
}

注册到全局

import permission from '@/directive/permission-v'
// 注册全局指令
Vue.use(permission)
Vue.directive('permission-v', permission)

//使用方式 v-permission-v
<el-button v-permission-v="['system:role:add']" type="primary" @click="handleAdd" icon="el-icon-plus">新增角色</el-button>

感兴趣的同学可以关注下,日常更新技术文章和demo示例免费的。
在这里插入图片描述

https://bytecodestudio.site/

在这里插入图片描述
在这里插入图片描述

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

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

相关文章

ESXi Host Client创建ubuntu虚拟机教程及NVIDIA显卡驱动安装

参考文章 VMware虚拟机显卡直通记录 AIGC 实战&#xff08;环境篇&#xff09; - EXSI 8.0 Debian安装RTX3060显卡驱动 重点介绍 client版本是7.0.3 注意&#xff1a;下图中不要选择BIOS 按照两个链接中的方法进行操作&#xff0c;以及本章节的上面几个图片的配置之后&a…

DeepSeek帮助做【真】软件需求-而不是批量刷废话

尝试给DeepSeek一份系统用例规约&#xff0c;让它帮判断哪些地方还没有覆盖涉众利益。结果见以下 需求工作的重点可以放在建模精细的真实现状流程和精细的真实涉众利益上&#xff0c;AI帮助推演系统需求。

apache-poi导出excel数据

excel导出 自动设置宽度&#xff0c;设置标题框&#xff0c;设置数据边框。 excel导出 添加依赖 <dependency><groupId>org.apache.poi</groupId><artifactId>poi-ooxml</artifactId><version>5.2.2</version></dependency>…

10 FastAPI 的自动文档

FastAPI 是一个功能强大且易于使用的 Web 框架&#xff0c;它的最大亮点之一就是内置的 自动文档生成 功能。通过集成 Swagger UI 和 ReDoc&#xff0c;FastAPI 可以自动为我们的 API 生成交互式文档。这不仅使得开发者能够更快速地了解和测试 API&#xff0c;还能够为前端开发…

微软AI研究团队推出LLaVA-Rad:轻量级开源基础模型,助力先进临床放射学报告生成

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

mysql8.0使用MHA实现高可用

一、MHA 介绍 MHA&#xff08;Master HA&#xff09;是一款开源的 MySQL 的高可用程序&#xff0c;它为 MySQL 主从复制架构提供了 automating master failover 功能。MHA 在监控到 master 节点故障时&#xff0c;会提升其中拥有最新数据的 slave 节点成为新的master 节点&…

D3实现站点路线图demo分享

分享通过D3实现的站点路线分布图demo&#xff0c;后续会继续更新其他功能。 功能点 点位弹窗 效果图如下&#xff1a; 轨迹高亮 效果图如下&#xff1a; 添加路线箭头 箭头展示逻辑&#xff1a;根据高速路线最后两个点位&#xff0c;计算得出箭头的点位 效果图如下&#x…

【系统架构设计师】操作系统 ③ ( 存储管理 | 页式存储弊端 - 段式存储引入 | 段式存储 | 段表 | 段表结构 | 逻辑地址 的 合法段地址判断 )

文章目录 一、页式存储弊端 - 段式存储引入1、页式存储弊端 - 内存碎片2、页式存储弊端 - 逻辑结构不匹配3、段式存储引入 二、段式存储 简介1、段式存储2、段表3、段表 结构4、段内地址 / 段内偏移5、段式存储 优缺点6、段式存储 与 页式存储 对比 三、逻辑地址 的 合法段地址…

物联网软件开发与应用方向应该怎样学习,学习哪些内容,就业方向是怎样?(文末领取整套学习视频,课件)物联网硬件开发与嵌入式系统

随着物联网技术的飞速发展&#xff0c;物联网软件开发与应用方向成为了众多开发者关注的焦点。那么&#xff0c;如何在这个领域中脱颖而出呢&#xff1f;本文将为你提供一份详细的学习指南&#xff0c;帮助你从零开始&#xff0c;逐步掌握物联网软件开发与应用的核心技能。 一…

Linux——基础命令1

$&#xff1a;普通用户 #&#xff1a;超级用户 cd 切换目录 cd 目录 &#xff08;进入目录&#xff09; cd ../ &#xff08;返回上一级目录&#xff09; cd ~ &#xff08;切换到当前用户的家目录&#xff09; cd - &#xff08;返回上次目录&#xff09; pwd 输出当前目录…

OpenFeign远程调用返回的是List<T>类型的数据

在使用 OpenFeign 进行远程调用时&#xff0c;如果接口返回的是 List 类型的数据&#xff0c;可以通过以下方式处理&#xff1a; 直接定义返回类型为List Feign 默认支持 JSON 序列化/反序列化&#xff0c;如果服务端返回的是 List的JSON格式数据&#xff0c;可以直接在 Feig…

向量数据库简单对比

文章目录 一、Chroma二、Pinecone/腾讯云VectorDB/VikingDB三、redis四、Elasticsearch五、Milvus六、Qdrant七、Weaviate八、Faiss 一、Chroma 官方地址&#xff1a; https://www.trychroma.com/优点 ①简单&#xff0c;非常简单构建服务。 ②此外&#xff0c;Chroma还具有自…

字符指针、数组指针和函数指针

1. 字符指针变量 1.1 简单例子 字符指针 char* 在C语言中主要由两种用法&#xff1a; 1.用于存放一个字符变量的地址。 2.用字符指针接收一个字符串。 这里并不是将整个字符串的地址存入 pstr 指针&#xff0c;指针变量 pstr 中存放的是常量字符串的首字符 h 的地址。 以一个…

【Linux网络编程】之守护进程

【Linux网络编程】之守护进程 进程组进程组的概念组长进程 会话会话的概念会话ID 控制终端控制终端的概念控制终端的作用会话、终端、bash三者的关系 前台进程与后台进程概念特点查看当前终端的后台进程前台进程与后台进程的切换 作业控制相关概念作业状态&#xff08;一般指后…

JS宏进阶:XMLHttpRequest对象

一、概述 XMLHttpRequest简称XHR&#xff0c;它是一个可以在JavaScript中使用的对象&#xff0c;用于在后台与服务器交换数据&#xff0c;实现页面的局部更新&#xff0c;而无需重新加载整个页面&#xff0c;也是Ajax&#xff08;Asynchronous JavaScript and XML&#xff09;…

怎么查看电脑显存大小(查看电脑配置)

这里提供一个简单的方法查看 winr打开cmd 终端输入dxdiag进入DirectX 点击显示查看设备的显示内存&#xff08;VRAM&#xff09; 用这个方法查看电脑配置和显存是比较方便的 dxdiag功能 Dxdiag是Windows的DirectX诊断工具&#xff0c;其主要作用包括但不限于以下几点&#…

优惠券平台(一):基于责任链模式创建优惠券模板

前景概要 系统的主要实现是优惠券的相关业务&#xff0c;所以对于用户管理的实现我们简单用拦截器在触发接口前创建一个单一用户。 // 用户属于非核心功能&#xff0c;这里先通过模拟的形式代替。后续如果需要后管展示&#xff0c;会重构该代码 UserInfoDTO userInfoDTO new…

【机器学习】数据预处理之scikit-learn的Scaler与自定义Scaler类进行数据归一化

scikit-learn的Scaler数据归一化 一、摘要二、训练数据集和测试数据集的归一化处理原则三、scikit-learn中的Scalar类及示例四、自定义StandardScaler类进行数据归一化处理五、小结 一、摘要 本文主要介绍了scikit-learn中Scaler的使用方法&#xff0c;特别强调了数据归一化在…

机器学习中过拟合和欠拟合问题处理方法总结

目录 一、背景二、过拟合(Overfitting)2.1 基本概念2.2 过拟合4个最主要的特征2.3 防止过拟合的11个有效方法 三、欠拟合&#xff08;Underfitting&#xff09;3.1 基本概念3.2 欠拟合的4个特征3.3 防止欠拟合的11个有效方法 四、总结五、参考资料 一、背景 在机器学习模型训练…

ABP框架9——自定义拦截器的实现与使用

一、AOP编程 AOP定义:面向切片编程&#xff0c;着重强调功能&#xff0c;将功能从业务逻辑分离出来。AOP使用场景&#xff1a;处理通用的、与业务逻辑无关的功能&#xff08;如日志记录、性能监控、事务管理等&#xff09;拦截器:拦截方法调用并添加额外的行为&#xff0c;比如…