RuoYi-Vue3按钮权限控制:基于角色的操作权限动态展示

RuoYi-Vue3按钮权限控制:基于角色的操作权限动态展示

【免费下载链接】RuoYi-Vue3 :tada: (RuoYi)官方仓库 基于SpringBoot,Spring Security,JWT,Vue3 & Vite、Element Plus 的前后端分离权限管理系统 【免费下载链接】RuoYi-Vue3 项目地址: https://gitcode.com/GitHub_Trending/ruo/RuoYi-Vue3

一、权限控制痛点与解决方案

你是否还在为后台系统中按钮权限控制繁琐而头疼?当管理员、普通用户、访客等多角色共存时,如何确保不同用户只能看到自己有权操作的按钮?本文将系统讲解RuoYi-Vue3框架中基于角色的按钮权限控制实现方案,通过指令封装、权限判断、动态渲染三步法,彻底解决多角色权限管理难题。

读完本文你将掌握:

  • 权限控制核心指令v-hasPermiv-hasRole的工作原理
  • 从权限获取到按钮渲染的完整流程设计
  • 10种实战场景下的权限控制代码实现
  • 性能优化与常见问题解决方案

二、权限控制核心原理

2.1 权限控制整体架构

RuoYi-Vue3采用"指令封装+状态管理+DOM操作"的三层架构实现权限控制:

mermaid

2.2 权限数据流转流程

权限信息在系统中的流转路径如下:

mermaid

三、核心权限指令实现分析

3.1 操作权限指令:v-hasPermi

3.1.1 源码解析
/**
 * v-hasPermi 操作权限处理
 * Copyright (c) 2019 ruoyi
 */
import useUserStore from '@/store/modules/user'

export default {
  mounted(el, binding, vnode) {
    const { value } = binding
    const all_permission = "*:*:*"
    const permissions = useUserStore().permissions

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

      const hasPermissions = permissions.some(permission => {
        return all_permission === permission || permissionFlag.includes(permission)
      })

      if (!hasPermissions) {
        el.parentNode && el.parentNode.removeChild(el)
      }
    } else {
      throw new Error(`请设置操作权限标签值`)
    }
  }
}
3.1.2 关键逻辑说明
  1. 权限判断核心

    • all_permission = "*:*:*"定义超级权限标识
    • permissions.some()遍历用户拥有的权限集合
    • 支持两种匹配模式:超级权限直接通过或包含所需权限
  2. DOM操作处理

    • 无权限时通过el.parentNode.removeChild(el)移除元素
    • 采用父节点移除方式确保DOM结构完整性

3.2 角色权限指令:v-hasRole

3.2.1 源码解析
/**
 * v-hasRole 角色权限处理
 * Copyright (c) 2019 ruoyi
 */
import useUserStore from '@/store/modules/user'

export default {
  mounted(el, binding, vnode) {
    const { value } = binding
    const super_admin = "admin"
    const roles = useUserStore().roles

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

      const hasRole = roles.some(role => {
        return super_admin === role || roleFlag.includes(role)
      })

      if (!hasRole) {
        el.parentNode && el.parentNode.removeChild(el)
      }
    } else {
      throw new Error(`请设置角色权限标签值`)
    }
  }
}
3.2.2 与v-hasPermi的异同点
特性v-hasPermiv-hasRole
数据来源Pinia.permissionsPinia.roles
超级标识::*admin
匹配方式权限字符串匹配角色名称匹配
适用场景细粒度操作权限粗粒度角色权限
判断逻辑包含所需权限包含所需角色

四、权限指令注册与使用

4.1 指令注册流程

src/directive/index.js中统一注册权限指令:

import hasPermi from './permission/hasPermi'
import hasRole from './permission/hasRole'

export default function directive(app) {
  app.directive('hasPermi', hasPermi)
  app.directive('hasRole', hasRole)
}

然后在main.js中引入并使用:

import { createApp } from 'vue'
import App from './App.vue'
import directive from './directive'

const app = createApp(App)
directive(app) // 注册自定义指令
app.mount('#app')

4.2 模板中使用方式

4.2.1 v-hasPermi使用示例
<template>
  <!-- 单个权限判断 -->
  <el-button 
    v-hasPermi="['system:user:add']" 
    type="primary" 
    @click="handleAdd"
  >
    添加用户
  </el-button>

  <!-- 多个权限满足一个即可 -->
  <el-button 
    v-hasPermi="['system:user:edit', 'system:user:update']" 
    type="success" 
    @click="handleEdit"
  >
    编辑用户
  </el-button>
</template>
4.2.2 v-hasRole使用示例
<template>
  <!-- 单个角色判断 -->
  <el-button 
    v-hasRole="['admin']" 
    type="danger" 
    @click="handleDelete"
  >
    删除用户
  </el-button>

  <!-- 多个角色满足一个即可 -->
  <el-button 
    v-hasRole="['admin', 'editor']" 
    type="warning" 
    @click="handleExport"
  >
    导出数据
  </el-button>
</template>

五、10种实战场景权限控制实现

5.1 基础表格操作按钮

<el-table-column label="操作" width="280">
  <template #default="scope">
    <el-button 
      size="small" 
      v-hasPermi="['system:user:query']" 
      @click="handleView(scope.row)"
    >
      查看
    </el-button>
    <el-button 
      size="small" 
      type="primary" 
      v-hasPermi="['system:user:edit']" 
      @click="handleEdit(scope.row)"
    >
      编辑
    </el-button>
    <el-popconfirm title="确定删除吗?" @confirm="handleDelete(scope.row)">
      <template #reference>
        <el-button 
          size="small" 
          type="danger" 
          v-hasPermi="['system:user:remove']"
        >
          删除
        </el-button>
      </template>
    </el-popconfirm>
  </template>
</el-table-column>

5.2 批量操作按钮组

<div class="table-operator">
  <el-button 
    type="primary" 
    icon="Plus" 
    v-hasPermi="['system:user:add']" 
    @click="handleAdd"
  >
    新增
  </el-button>
  <el-button 
    type="success" 
    icon="Download" 
    v-hasPermi="['system:user:export']" 
    @click="handleExport"
  >
    导出
  </el-button>
  <el-button 
    type="danger" 
    icon="Delete" 
    v-hasPermi="['system:user:remove']" 
    @click="handleBatchDelete"
    :disabled="multipleSelection.length === 0"
  >
    批量删除
  </el-button>
</div>

5.3 表单操作按钮

<el-form-item>
  <el-button type="primary" @click="submitForm">保存</el-button>
  <el-button @click="resetForm">重置</el-button>
  <el-button 
    type="danger" 
    v-hasRole="['admin']" 
    @click="handleFormDelete"
  >
    表单删除
  </el-button>
</el-form-item>

5.4 标签页权限控制

<el-tabs v-model="activeName" type="card">
  <el-tab-pane 
    label="基本信息" 
    name="basic"
  >
    基本信息内容
  </el-tab-pane>
  
  <el-tab-pane 
    label="权限设置" 
    name="permission"
    v-hasPermi="['system:user:permission']"
  >
    权限设置内容
  </el-tab-pane>
  
  <el-tab-pane 
    label="操作日志" 
    name="log"
    v-hasRole="['admin', 'audit']"
  >
    操作日志内容
  </el-tab-pane>
</el-tabs>

5.5 下拉菜单权限控制

<el-dropdown>
  <el-button type="primary">
    更多操作 <el-icon class="el-icon--right"><ArrowDown /></el-icon>
  </el-button>
  <template #dropdown>
    <el-dropdown-menu>
      <el-dropdown-item 
        v-hasPermi="['system:user:import']" 
        @click="handleImport"
      >
        导入数据
      </el-dropdown-item>
      <el-dropdown-item 
        v-hasPermi="['system:user:exportTemplate']" 
        @click="handleExportTemplate"
      >
        导出模板
      </el-dropdown-item>
      <el-dropdown-item 
        v-hasRole="['admin']" 
        @click="handleClear"
      >
        清空数据
      </el-dropdown-item>
    </el-dropdown-menu>
  </template>
</el-dropdown>

5.6 卡片内容权限控制

<el-card 
  v-hasPermi="['system:dashboard:view']" 
  class="box-card"
>
  <template #header>
    <div class="card-header">
      <span>系统仪表盘</span>
    </div>
  </template>
  <div class="dashboard-content">
    <!-- 仪表盘内容 -->
  </div>
</el-card>

5.7 图标按钮权限控制

<el-button 
  v-hasPermi="['system:user:refresh']" 
  icon="Refresh" 
  circle 
  @click="handleRefresh"
/>

<el-button 
  v-hasRole="['admin']" 
  icon="Setting" 
  circle 
  @click="handleSetting"
/>

5.8 对话框按钮权限控制

<el-dialog title="用户详情" v-model="open">
  <div class="detail-content">
    <!-- 详情内容 -->
  </div>
  <template #footer>
    <el-button @click="open = false">关闭</el-button>
    <el-button 
      type="primary" 
      v-hasPermi="['system:user:edit']" 
      @click="handleUpdate"
    >
      更新信息
    </el-button>
  </template>
</el-dialog>

5.9 表格行内操作权限控制

<el-table :data="userList" stripe>
  <el-table-column label="姓名" prop="userName" />
  <el-table-column label="角色" prop="roleName" />
  <el-table-column label="操作" width="200">
    <template #default="scope">
      <el-button 
        size="small" 
        v-hasPermi="['system:user:edit']"
        @click="handleEdit(scope.row)"
      >
        编辑
      </el-button>
      <el-button 
        size="small" 
        type="danger" 
        v-hasPermi="['system:user:remove']"
        @click="handleDelete(scope.row)"
      >
        删除
      </el-button>
    </template>
  </el-table-column>
</el-table>

5.10 条件渲染与权限结合

<template>
  <div v-if="showButton">
    <el-button 
      v-hasPermi="['system:user:special']" 
      type="primary" 
      @click="handleSpecial"
    >
      特殊操作
    </el-button>
  </div>
</template>

<script setup>
import { ref, computed } from 'vue'
import useUserStore from '@/store/modules/user'

const userStore = useUserStore()
const showButton = computed(() => {
  // 结合业务逻辑和权限判断
  return userStore.userInfo.status === 1 && 
         userStore.permissions.includes('system:user:special')
})
</script>

六、权限状态管理实现

6.1 Pinia权限状态定义

src/store/modules/user.js中定义权限相关状态:

import { defineStore } from 'pinia'
import { login, logout, getInfo } from '@/api/login'
import { getToken, setToken, removeToken } from '@/utils/auth'

export const useUserStore = defineStore('user', {
  state: () => ({
    token: getToken(),
    name: '',
    avatar: '',
    roles: [],
    permissions: []
  }),
  
  actions: {
    // 登录获取用户信息
    async login(userInfo) {
      const { username, password } = userInfo
      const response = await login({ 
        username: username.trim(), 
        password: password 
      })
      setToken(response.token)
      this.token = response.token
    },
    
    // 获取用户信息(包含角色和权限)
    async getInfo() {
      const response = await getInfo()
      const { roles, permissions, username, avatar } = response
      
      // 角色必须是非空数组
      if (!roles || roles.length <= 0) {
        throw new Error('getInfo: roles must be a non-null array!')
      }
      
      this.roles = roles
      this.permissions = permissions
      this.name = username
      this.avatar = avatar
    },
    
    // 退出登录
    async logout() {
      await logout()
      removeToken()
      this.token = ''
      this.roles = []
      this.permissions = []
    }
  }
})

6.2 权限状态更新流程

当用户权限发生变化时,需要更新权限状态并重新渲染界面:

// 在用户编辑角色/权限后调用
const updatePermissions = async () => {
  const userStore = useUserStore()
  await userStore.getInfo() // 重新获取权限信息
  
  // 如果需要强制刷新页面
  // window.location.reload()
  
  // 或者使用key重置组件
  componentKey.value += 1
}

七、性能优化策略

7.1 权限判断缓存

对于频繁渲染的列表项,可以缓存权限判断结果:

// 在组件中创建权限缓存对象
const permissionCache = {}

// 权限判断函数
const hasPermission = (permission) => {
  // 如果缓存中有结果,直接返回
  if (permissionCache[permission] !== undefined) {
    return permissionCache[permission]
  }
  
  const userStore = useUserStore()
  const hasPermi = userStore.permissions.some(p => 
    p === '*:*:*' || permission.includes(p)
  )
  
  // 缓存结果
  permissionCache[permission] = hasPermi
  return hasPermi
}

7.2 批量权限处理

对于大量按钮的页面,使用计算属性统一处理权限判断:

<template>
  <el-button v-if="permissions.add" @click="handleAdd">添加</el-button>
  <el-button v-if="permissions.edit" @click="handleEdit">编辑</el-button>
  <el-button v-if="permissions.delete" @click="handleDelete">删除</el-button>
</template>

<script setup>
import { computed } from 'vue'
import useUserStore from '@/store/modules/user'

const userStore = useUserStore()

// 统一计算权限
const permissions = computed(() => ({
  add: userStore.permissions.includes('system:user:add'),
  edit: userStore.permissions.includes('system:user:edit'),
  delete: userStore.permissions.includes('system:user:delete'),
  export: userStore.permissions.includes('system:user:export')
}))
</script>

7.3 路由级别权限控制

在路由守卫中进行页面级别的权限控制,减少不必要的组件渲染:

// src/permission.js
import router from './router'
import useUserStore from './store/modules/user'

router.beforeEach(async (to, from, next) => {
  const userStore = useUserStore()
  
  // 判断是否需要权限验证
  if (to.meta.permissions && to.meta.permissions.length > 0) {
    // 检查是否有权限访问该路由
    const hasPermission = to.meta.permissions.some(permission => 
      userStore.permissions.includes(permission) || 
      userStore.permissions.includes('*:*:*')
    )
    
    if (hasPermission) {
      next()
    } else {
      // 无权限跳转到403页面
      next({ path: '/403', replace: true })
    }
  } else {
    next()
  }
})

八、常见问题解决方案

8.1 按钮闪烁问题

问题:页面加载时按钮短暂显示后消失。
解决方案:使用v-cloak指令结合CSS隐藏未编译的模板:

[v-cloak] {
  display: none;
}
<el-button 
  v-cloak 
  v-hasPermi="['system:user:add']" 
  type="primary"
>
  添加用户
</el-button>

8.2 动态权限更新

问题:权限变更后按钮状态未实时更新。
解决方案:监听权限变化并重新执行权限判断:

// 在指令中添加权限变化监听
export default {
  mounted(el, binding, vnode) {
    // 原有的权限判断逻辑...
    
    // 监听权限变化
    const userStore = useUserStore()
    this.unsubscribe = userStore.$subscribe((mutation, state) => {
      // 权限发生变化时重新判断
      if (mutation.storeId === 'user' && 
          (mutation.type === 'roles' || mutation.type === 'permissions')) {
        this.mounted(el, binding, vnode)
      }
    })
  },
  
  unmounted() {
    // 取消订阅
    this.unsubscribe && this.unsubscribe()
  }
}

8.3 权限判断性能问题

问题:大量按钮同时进行权限判断导致页面卡顿。
解决方案:使用节流和批量处理优化:

// 批量处理权限判断
export function batchCheckPermissions(permissions, permissionList) {
  const all_permission = "*:*:*"
  return permissionList.reduce((result, item) => {
    result[item] = permissions.some(permission => 
      all_permission === permission || item.includes(permission)
    )
    return result
  }, {})
}

// 在组件中使用
const permissionList = ['system:user:add', 'system:user:edit', 'system:user:delete']
const permissionResults = batchCheckPermissions(userStore.permissions, permissionList)

九、总结与最佳实践

9.1 权限控制最佳实践总结

  1. 权限粒度选择

    • 粗粒度控制使用v-hasRole(如管理员功能)
    • 细粒度控制使用v-hasPermi(如具体操作按钮)
    • 页面级权限使用路由守卫+指令结合
  2. 代码组织建议

    • 权限指令统一注册管理
    • 复杂权限逻辑封装为工具函数
    • 权限常量集中管理
  3. 性能优化要点

    • 缓存权限判断结果
    • 批量处理权限检查
    • 路由级别预过滤

9.2 权限设计原则

mermaid

9.3 未来展望

RuoYi-Vue3权限系统未来可考虑的优化方向:

  • 基于组件的权限控制方案
  • 权限动态配置界面
  • 权限使用统计分析
  • 精细化数据权限控制

通过本文介绍的权限控制方案,你可以在RuoYi-Vue3项目中实现灵活、高效、安全的按钮权限管理。合理运用v-hasPermiv-hasRole指令,结合Pinia状态管理,能够轻松应对各种复杂的权限场景。

如果你觉得本文对你有帮助,请点赞、收藏、关注三连支持,下期将为大家带来RuoYi-Vue3数据权限控制的深度解析!

【免费下载链接】RuoYi-Vue3 :tada: (RuoYi)官方仓库 基于SpringBoot,Spring Security,JWT,Vue3 & Vite、Element Plus 的前后端分离权限管理系统 【免费下载链接】RuoYi-Vue3 项目地址: https://gitcode.com/GitHub_Trending/ruo/RuoYi-Vue3

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值