如何在一个 Vue 项目中,实现一个通用的权限管理系统,支持路由、按钮和数据权限?

各位靓仔靓女,晚上好!我是今天的主讲人,咱们今天来聊聊 Vue 项目中权限管理那些事儿,保证让你的项目安全得像个保险箱,而且代码写起来还倍儿爽。

一、权限管理:为什么要搞事情?

先问大家一个问题,你家的门锁是摆设吗?当然不是!权限管理就像你家门锁,防止不该进的人进来,不该看的东西被看到,不该搞的事情被搞了。

在一个系统中,不同用户角色应该有不同的权限。比如,管理员可以查看所有数据,编辑所有内容,而普通用户可能只能查看自己的数据,修改自己的密码。

二、权限管理的分类:你想要哪种姿势?

权限管理不是只有一种玩法,常见的有以下几种:

  • 路由权限: 决定用户能访问哪些页面。
  • 按钮权限: 决定用户能点击哪些按钮,能执行哪些操作。
  • 数据权限: 决定用户能看到哪些数据,比如只能看到自己部门的数据。

三、技术选型:磨刀不误砍柴工

  • Vue Router: 路由管理,必须滴。
  • Vuex/Pinia: 状态管理,用来存放用户角色、权限等信息。
  • axios/fetch: 数据请求,跟后端大哥沟通的桥梁。

四、权限管理的核心思路:三步走战略

  1. 登录认证: 验证用户身份,获取用户角色和权限信息。
  2. 权限判断: 根据用户角色和权限信息,判断用户是否有权访问某个路由、按钮或数据。
  3. 权限控制: 根据判断结果,控制页面元素的显示和隐藏,阻止用户操作。

五、路由权限:守好进门的第一道关

  1. 定义路由元信息: 在路由配置中,添加 meta 字段,用于标识该路由需要的权限。

    // router/index.js
    import Vue from 'vue'
    import VueRouter from 'vue-router'
    import Home from '../views/Home.vue'
    import Admin from '../views/Admin.vue'
    
    Vue.use(VueRouter)
    
    const routes = [
      {
        path: '/',
        name: 'Home',
        component: Home
      },
      {
        path: '/admin',
        name: 'Admin',
        component: Admin,
        meta: {
          requiresAuth: true,  // 需要登录
          roles: ['admin']       // 需要管理员权限
        }
      },
      {
        path: '/login',
        name: 'Login',
        component: () => import('../views/Login.vue')
      },
      {
        path: '/about',
        name: 'About',
        // route level code-splitting
        // this generates a separate chunk (about.[hash].js) for this route
        // which is lazy-loaded when the route is visited.
        component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
      }
    ]
    
    const router = new VueRouter({
      mode: 'history',
      base: process.env.BASE_URL,
      routes
    })
    
    export default router

    这里 requiresAuth: true 表示需要登录才能访问,roles: ['admin'] 表示需要管理员角色才能访问。

  2. 全局路由守卫: 使用 router.beforeEach 监听路由变化,进行权限判断。

    // router/index.js
    router.beforeEach((to, from, next) => {
      const isAuthenticated = localStorage.getItem('token'); // 模拟token存在表示已登录
      const userRole = localStorage.getItem('role');  // 模拟用户角色
    
      if (to.meta.requiresAuth) {
        if (!isAuthenticated) {
          // 未登录,跳转到登录页面
          next({ name: 'Login' });
        } else {
          if (to.meta.roles && !to.meta.roles.includes(userRole)) {
            // 角色不匹配,跳转到 403 页面或者提示无权限
            alert('没有权限访问该页面');
            next({ name: 'Home' }); // 跳转到首页或者403页面
          } else {
            // 权限匹配,允许访问
            next();
          }
        }
      } else {
        // 不需要权限的页面,直接允许访问
        next();
      }
    });

    这段代码做了三件事:

    • 判断路由是否需要登录。
    • 如果需要登录,判断用户是否已登录。
    • 如果已登录,判断用户角色是否匹配。
  3. 登录逻辑: 在登录页面,验证用户身份,获取用户角色和权限信息,并存储到 Vuex/Pinia 或 localStorage 中。

    // Login.vue
    export default {
      data() {
        return {
          username: '',
          password: ''
        };
      },
      methods: {
        login() {
          // 模拟登录,实际需要调用后端接口
          if (this.username === 'admin' && this.password === '123') {
            localStorage.setItem('token', 'admin_token'); // 模拟token
            localStorage.setItem('role', 'admin');       // 模拟用户角色
            this.$router.push({ name: 'Admin' });
          } else if (this.username === 'user' && this.password === '123'){
            localStorage.setItem('token', 'user_token'); // 模拟token
            localStorage.setItem('role', 'user');       // 模拟用户角色
            this.$router.push({ name: 'Home' });
          }
          else {
            alert('用户名或密码错误');
          }
        }
      }
    };

    这里模拟了登录逻辑,实际项目中需要调用后端接口进行验证。

六、按钮权限:防止手贱党乱点

  1. 自定义指令: 定义一个自定义指令,用于判断按钮是否需要权限,并控制按钮的显示和隐藏。

    // directives/permission.js
    import Vue from 'vue';
    
    Vue.directive('permission', {
      inserted: function (el, binding) {
        const requiredPermission = binding.value;  // 获取指令绑定的权限标识
        const userPermissions = localStorage.getItem('permissions')?.split(',') || []; // 模拟用户权限列表
    
        if (!userPermissions.includes(requiredPermission)) {
          el.parentNode.removeChild(el); // 移除元素
          // 或者 el.style.display = 'none';  // 隐藏元素
        }
      }
    });

    这段代码做了两件事:

    • 获取按钮需要的权限标识。
    • 判断用户是否拥有该权限,如果没有,则移除或隐藏按钮。
  2. 使用指令: 在模板中使用自定义指令,绑定按钮需要的权限标识。

    <template>
      <div>
        <button v-permission="'create'">新建</button>
        <button v-permission="'edit'">编辑</button>
        <button v-permission="'delete'">删除</button>
      </div>
    </template>
    
    <script>
    export default {
      mounted() {
          // 模拟权限信息
          const role = localStorage.getItem('role');
          let permissions = [];
          if(role === 'admin'){
              permissions = ['create', 'edit', 'delete', 'view'];
          } else {
              permissions = ['view'];
          }
          localStorage.setItem('permissions', permissions.join(','));
      }
    };
    </script>

    这里,v-permission="'create'" 表示只有拥有 create 权限的用户才能看到 “新建” 按钮。

  3. 权限数据: 权限信息通常从后端获取,并存储到 Vuex/Pinia 或 localStorage 中。

七、数据权限:保护你的敏感数据

  1. 后端配合: 数据权限的实现,需要后端的配合。后端需要根据用户角色和权限信息,过滤返回的数据。
  2. 前端处理: 前端接收到数据后,可以根据用户角色和权限信息,进行二次过滤或隐藏。

    // 假设后端返回的数据如下:
    const data = [
      { id: 1, name: '张三', department: '研发部', salary: 10000 },
      { id: 2, name: '李四', department: '销售部', salary: 8000 },
      { id: 3, name: '王五', department: '财务部', salary: 12000 }
    ];
    
    // 前端根据用户角色进行过滤
    const userRole = localStorage.getItem('role');
    let filteredData = data;
    
    if (userRole === '普通用户') {
      // 普通用户只能看到自己的数据
      filteredData = data.filter(item => item.name === '李四'); // 假设李四是当前用户
    } else if (userRole === '部门经理') {
      // 部门经理只能看到自己部门的数据
      filteredData = data.filter(item => item.department === '销售部');
    }
    
    // 显示过滤后的数据
    console.log(filteredData);

    这里,我们根据用户角色,对数据进行了过滤。

八、权限管理表格:一目了然

为了更清晰地展示不同角色拥有的权限,我们可以使用表格来管理。

角色 路由权限 按钮权限 数据权限
管理员 所有 所有 所有数据
普通用户 首页 查看 只能看到自己的数据
部门经理 首页、部门管理 查看、编辑 只能看到自己部门的数据

九、代码示例:一个简单的权限管理 Demo

为了方便大家理解,我写了一个简单的权限管理 Demo,包含路由权限和按钮权限。

// App.vue
<template>
  <div id="app">
    <nav>
      <router-link to="/">Home</router-link> |
      <router-link to="/admin" v-if="$route.meta.requiresAuth">Admin</router-link> |
      <router-link to="/login">Login</router-link>
    </nav>
    <router-view/>
  </div>
</template>

<script>
export default {
  name: 'App'
};
</script>

// Home.vue
<template>
  <div>
    <h1>Home</h1>
    <p>Welcome to the Home page!</p>
    <button v-permission="'view'">查看</button>
  </div>
</template>

<script>
export default {
  mounted() {
      // 模拟权限信息
      const role = localStorage.getItem('role');
      let permissions = [];
      if(role === 'admin' || role === 'user'){
          permissions = ['view'];
      }
      localStorage.setItem('permissions', permissions.join(','));
  }
};
</script>

// Admin.vue
<template>
  <div>
    <h1>Admin</h1>
    <p>Welcome to the Admin page!</p>
    <button v-permission="'create'">新建</button>
    <button v-permission="'edit'">编辑</button>
    <button v-permission="'delete'">删除</button>
  </div>
</template>

<script>
export default {
  mounted() {
      // 模拟权限信息
      const role = localStorage.getItem('role');
      let permissions = [];
      if(role === 'admin'){
          permissions = ['create', 'edit', 'delete', 'view'];
      }
      localStorage.setItem('permissions', permissions.join(','));
  }
};
</script>

// Login.vue
<template>
  <div>
    <h1>Login</h1>
    <input type="text" v-model="username" placeholder="Username">
    <input type="password" v-model="password" placeholder="Password">
    <button @click="login">Login</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      username: '',
      password: ''
    };
  },
  methods: {
    login() {
      // 模拟登录,实际需要调用后端接口
      if (this.username === 'admin' && this.password === '123') {
        localStorage.setItem('token', 'admin_token'); // 模拟token
        localStorage.setItem('role', 'admin');       // 模拟用户角色
        this.$router.push({ name: 'Admin' });
      } else if (this.username === 'user' && this.password === '123'){
        localStorage.setItem('token', 'user_token'); // 模拟token
        localStorage.setItem('role', 'user');       // 模拟用户角色
        this.$router.push({ name: 'Home' });
      }
      else {
        alert('用户名或密码错误');
      }
    }
  }
};
</script>

十、注意事项:细节决定成败

  • 权限标识: 权限标识要统一规范,方便管理。
  • 权限缓存: 权限信息可以缓存到 localStorage 中,避免每次都从后端获取。
  • 后端安全: 后端也要进行权限验证,防止前端绕过权限控制。
  • 用户体验: 权限不足时,要给出友好的提示,避免用户困惑。

十一、总结:权限管理,安全第一

权限管理是每个系统都必须考虑的问题,它关系到系统的安全和数据的隐私。希望今天的分享能够帮助大家更好地理解和实现 Vue 项目中的权限管理。

记住,代码的世界里,安全永远是第一位的!

好了,今天的讲座就到这里,大家有什么问题可以提问。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注