管理不同权限用户的左侧菜单展示以及权限按钮的启用 / 禁用之其中一种解决方案

0 阅读3分钟

一、权限管理方案设计

1. 权限模型

推荐采用 RBAC(基于角色的访问控制)  模型,把用户分配到不同角色,角色再关联对应的权限。

  • 权限项:是最小的控制单元,像 view_useredit_product 这种。
  • 角色:由多个权限项组合而成,例如 admineditorviewer
  • 用户:和一个或多个角色相对应。

2. 权限数据存储

  • 前端存储:登录成功后,从后端获取权限数据,然后存到 Vuex/Pinia 或者 localStorage 中。

  • 权限数据结构示例

javascript

{
  user: { id: 1, name: "张三", role: "admin" },
  permissions: ["view_user", "edit_user", "view_product", "edit_product"]
}

二、左侧菜单动态展示

1. 菜单配置

创建一个菜单配置文件,把权限和菜单项关联起来。

javascript

// src/config/menu.js
export const menuList = [
  {
    path: "/dashboard",
    name: "Dashboard",
    icon: "dashboard",
    permission: "view_dashboard" // 访问该菜单所需权限
  },
  {
    path: "/user",
    name: "用户管理",
    icon: "user",
    permission: "view_user",
    children: [
      {
        path: "/user/list",
        name: "用户列表",
        permission: "view_user"
      },
      {
        path: "/user/add",
        name: "添加用户",
        permission: "add_user"
      }
    ]
  },
  // 其他菜单项...
];

2. 菜单组件实现

在组件里依据用户权限过滤菜单项。

vue

<!-- src/components/Sidebar.vue -->
<template>
  <div class="sidebar">
    <el-menu :default-active="activeMenu" mode="vertical">
      <template v-for="item in filteredMenu">
        <!-- 一级菜单 -->
        <el-menu-item 
          v-if="!item.children && hasPermission(item.permission)"
          :key="item.path"
          :index="item.path"
        >
          <i :class="item.icon"></i>
          <span slot="title">{{ item.name }}</span>
        </el-menu-item>
        
        <!-- 子菜单 -->
        <el-submenu 
          v-else-if="item.children && hasPermission(item.permission)"
          :key="item.path"
          :index="item.path"
        >
          <template slot="title">
            <i :class="item.icon"></i>
            <span>{{ item.name }}</span>
          </template>
          <el-menu-item
            v-for="child in item.children"
            :key="child.path"
            :index="child.path"
            v-if="hasPermission(child.permission)"
          >
            {{ child.name }}
          </el-menu-item>
        </el-submenu>
      </template>
    </el-menu>
  </div>
</template>

<script>
import { mapState } from "vuex";
import { menuList } from "@/config/menu";

export default {
  computed: {
    ...mapState(["permissions"]),
    // 过滤后的菜单项
    filteredMenu() {
      return menuList.filter(item => this.hasPermission(item.permission));
    }
  },
  methods: {
    // 权限检查方法
    hasPermission(permission) {
      // 如果没有设置权限,默认可见
      if (!permission) return true;
      // 检查用户是否拥有该权限
      return this.permissions.includes(permission);
    }
  }
};
</script>

三、权限按钮的展示与禁用

1. 自定义指令实现

借助自定义指令来控制按钮的显示和禁用状态。

javascript

// src/directives/permission.js
export const permission = {
  inserted(el, binding, vnode) {
    const { value } = binding;
    const permissions = vnode.context.$store.state.permissions;
    
    if (value) {
      // 检查是否有该权限
      const hasPermission = permissions.includes(value);
      
      if (!hasPermission) {
        // 没有权限:隐藏按钮
        el.parentNode && el.parentNode.removeChild(el);
        // 或者禁用按钮(根据需求选择)
        // el.disabled = true;
        // el.classList.add('is-disabled');
      }
    } else {
      console.error('需要指定权限标识!');
      el.parentNode && el.parentNode.removeChild(el);
    }
  }
};

2. 全局注册指令

在 main.js 里全局注册这个指令。

javascript

// src/main.js
import Vue from "vue";
import { permission } from "./directives/permission";

Vue.directive("permission", permission);

3. 在组件中使用

vue

<!-- 使用示例 -->
<template>
  <div>
    <!-- 有权限时显示 -->
    <el-button 
      v-permission="'add_user'"
      type="primary"
      @click="addUser"
    >
      添加用户
    </el-button>
    
    <!-- 无权限时禁用 -->
    <el-button 
      :disabled="!hasPermission('edit_user')"
      type="success"
      @click="editUser"
    >
      编辑用户
    </el-button>
  </div>
</template>

<script>
export default {
  methods: {
    hasPermission(permission) {
      return this.$store.state.permissions.includes(permission);
    }
  }
};
</script>

四、路由权限控制

对路由访问权限进行控制,防止用户手动输入 URL 访问受限页面。

javascript

// src/router/index.js
import router from "./router";
import store from "./store";

router.beforeEach((to, from, next) => {
  // 获取用户权限
  const permissions = store.state.permissions;
  
  // 检查路由是否需要权限
  if (to.meta.permission) {
    if (permissions.includes(to.meta.permission)) {
      next(); // 有权限,放行
    } else {
      next({ path: "/403" }); // 无权限,跳转到403页面
    }
  } else {
    next(); // 无需权限,直接放行
  }
});

五、权限管理流程

  1. 用户登录:用户输入账号密码登录系统。
  2. 权限验证:后端验证用户身份,返回用户角色和权限信息。
  3. 权限存储:前端把权限信息存到 Vuex/Pinia 或者 localStorage 中。
  4. 菜单渲染:根据用户权限动态渲染左侧菜单。
  5. 按钮控制:在组件里通过自定义指令或者方法控制按钮的显示和禁用。
  6. 路由拦截:对路由进行拦截,防止未授权访问。

六、优缺点分析

优点

  • 可扩展性强:能够轻松添加新的角色和权限,而不用修改大量代码。
  • 维护便捷:权限配置集中管理,降低了维护成本。
  • 安全性高:从菜单、按钮、路由三个层面进行权限控制,有效防止越权访问。

缺点

  • 初期配置复杂:需要设计合理的权限模型和数据结构。
  • 性能影响:在复杂应用中,频繁的权限检查可能会对性能产生一定影响。