自学内容网 自学内容网

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

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

权限管理的分类

根据功能的不同,权限控制可以分为以下几类:

菜单权限

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

路由权限

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

按钮权限

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

接口权限

接口权限通常采用 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); // 滚动条回到顶部
    });
    

    优点

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

    缺点

    • 需要维护菜单与路由的对应关系,增加了复杂性。
    • 每次路由跳转都需要进行权限判断。
方案二:菜单和路由都由后端返回
  • 前端统一定义路由组件

    const Home = () => import("../pages/Home.vue");
    const UserInfo = () => import("../pages/UserInfo.vue");
    export default {
      home: Home,
      userInfo: UserInfo
    };
    
  • 后端返回路由组件

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

    优点

    • 前后端高度集成,灵活性高。

    缺点

    • 前后端配合要求更高。
    • 每次路由跳转都需要进行权限判断。
路由权限控制
方案一:初始化即挂载全部路由
  • 路由定义

    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 判断
  • 优点

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

    • 如果页面较多,每个页面都需要获取用户权限并进行判断,增加复杂性。
方案二:通过自定义指令进行按钮权限判断
  • 配置路由

    {
      path: '/permission',
      component: Layout,
      name: ' ',
      meta: {
        btnPermissions: ['admin', 'supper', 'normal']
      },
      children: [
        {
          path: 'supper',
          component: _import('system/supper'),
          name: ' ',
          meta: {
            btnPermissions: ['admin', 'supper']
          }
        },
        {
          path: 'normal',
          component: _import('system/normal'),
          name: ' ',
          meta: {
            btnPermissions: ['admin']
          }
        }
      ]
    }
    
  • 自定义权限鉴定指令

    import Vue from 'vue';
    
    const has = Vue.directive('has', {
      bind: function (el, binding, vnode) {
        let btnPermissionsArr = binding.value ? [binding.value] : vnode.context.$route.meta.btnPermissions;
        if (!Vue.prototype.$_has(btnPermissionsArr)) {
          el.parentNode.removeChild(el); // 移除元素
        }
      }
    });
    
    Vue.prototype.$_has = function (value) {
      const btnPermissionsStr = sessionStorage.getItem("btnPermissions");
      if (!btnPermissionsStr) {
        return false;
      }
      return value.includes(btnPermissionsStr);
    };
    
    export { has };
    
  • 使用自定义指令

    <el-button @click='editClick' type="primary" v-has>编辑</el-button>
    

    优点

    • 代码简洁,易于维护。

    缺点

    • 需要维护按钮权限信息。
接口权限

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

axios.interceptors.request.use(config => {
  config.headers['Authorization'] = `Bearer ${cookie.get('token')}`; // 在请求头中携带 token
  return config;
});

总结

权限管理是项目开发中不可或缺的一部分,合理的权限控制可以提升系统的安全性。通过上述方案,可以根据项目的具体需求选择合适的权限管理方式。


原文地址:https://blog.csdn.net/misstianyun/article/details/143584187

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!