权限封装不是写个指令那么简单:一次真实项目的反思

开场白

在中后台系统中,「权限控制」几乎是必不可少的一环。最常见、也看起来最优雅的方案,就是封装一个 v-permission 指令,在模板中一行搞定:

vue 复制代码
<el-button v-permission="'permission_xxx'">操作</el-button>

但在一次真实项目中,这种"看似优雅"的方案,却让我踩了一个非常隐蔽、却极其致命的坑 。 本文结合真实案例,聊聊:为什么权限封装,远不只是写一个指令那么简单。

问题背景:权限明明有,按钮却时隐时现

项目背景:

  • Vue 2 + Element UI
  • 表格使用el-table
  • 权限通过Vuex管理
  • 使用自定义v-permission指令控制按钮显示

问题现象非常诡异:

用户明确拥有权限,但动态渲染的表格中的按钮时隐时现不稳定

刷新页面、切换路由,偶现/必现不一致

而同一权限,在非表格页面是正常显示的

原始实现:v-permission指令(简化版)

js 复制代码
export default {
  inserted(el, binding) {
    const { value } = binding
    const roles = store.getters.roles
    if (value && Array.isArray(value)) {
      const hasPermission = roles.some(role =>
        value.includes(role)
      )
      if (!hasPermission) {
        el.parentNode && el.parentNode.removeChild(el)
      }
    }
  }
}

设计初衷很简单:

有权限 → 保留 DOM

没权限 → 直接移除 DOM
看起来没毛病,对吧?

真正的坑:指令 + 动态表格 = 隐形雷区

问题的根源,藏在三个「底层细节」里

inserted 是"一次性生命周期"

inserted 只会在 DOM 插入时执行一次:

  • 权限数据 异步加载
  • 表格行 动态渲染
  • 指令不会等待数据"稳定"

结果就是:指令执行时,权限数据可能还没准备好,哪怕10ms后权限就加载完成,指令也不会重新执行。

el-table 是"高度敏感组件"

el-table 的内部逻辑非常复杂:

  • 行高度计算
  • 虚拟渲染
  • DOM复用
  • slot动态插入

而v-permission做了什么?直接、粗暴地修改真实 DOM。

js 复制代码
el.parentNode.removeChild(el)

这会导致:VueVirtual DOM真实 DOM状态不一致,表格行高度计算被破坏,某些行"渲染过一次就不再回来"。

命令式 DOM 操作 vs 声明式渲染

这是最本质的冲突:

概念 / 维度 Vue v-permission
渲染方式 声明式渲染 命令式 DOM 操作
数据驱动 UI = f(state),由数据决定渲染结果 指令直接删节点
更新机制 批量更新,异步处理 DOM 变更 同步修改 DOM,立即生效
响应性 自动追踪依赖并更新 不响应权限数据变化,执行一次性操作
安全性 Virtual DOM真实 DOM一致 破坏Virtual DOM真实 DOM对应关系
可维护性 状态改变自动刷新 UI UI 状态与数据脱节,难调试

在普通页面里,这种冲突不明显,但在动态表格中,会被无限放大

那权限不是要"封装"吗?

1.封装成全局 mixin(Vue2)

js 复制代码
// src/mixins/permissionMixin.js
export default {
  computed: {
    $hasPermission() {
      return (code) => {
        const roles = this.$store.getters.roles || []
        return roles.includes(code)
      }
    }
  }
}

// main.js
import Vue from 'vue'
import permissionMixin from '@/mixins/permissionMixin'
Vue.mixin(permissionMixin)

// 具体使用页面
<template>
  <el-button v-if="$hasPermission('permission_xxx')">操作</el-button>
</template>

2.封装成组合函数(Vue3 / Composition API)

js 复制代码
// src/composables/usePermission.js
import { computed } from 'vue'
import store from '@/store'

export function usePermission() {
  const roles = computed(() => store.getters.roles || [])
  const hasPermission = (code) => roles.value.includes(code)
  return { hasPermission }
}

// 具体使用页面
<script setup>
import { usePermission } from '@/composables/usePermission'
const { hasPermission } = usePermission()
</script>

<template>
    <el-button v-if="hasPermission('permission_xxx')">操作</el-button>
</template>

3.全局权限函数 / 工具方法

js 复制代码
// src/utils/permission.js
export function hasPermission(code) {
  const roles = store.getters.roles || []
  return roles.includes(code)
}
<template> 
    <el-button v-if="hasPermission('permission_xxx')">操作</el-button> 
</template>

4.封装权限组件<Auth>/<Permission>

js 复制代码
// components/Auth.vue
// 用组件包裹需要权限控制的内容,组件内部判断权限。
<template>
  <slot v-if="allowed" />
</template>

<script>
import { hasPermission } from '@/utils/permission'

export default {
  props: { code: { type: String, required: true } },
  computed: {
    allowed() {
      return hasPermission(this.code)
    }
  }
}
</script>

<Auth code="permission_xxx">
  <el-button>操作</el-button>
</Auth>

权限封装方式详细对比表(包含原理与适用场景)

封装方式 原理 优点 适用场景 响应式 可复用性 维护成本
1️⃣ 全局 mixin(Vue 2) 利用 Vue mixin 将权限判断方法注入所有组件,通过计算属性或方法访问 Vuex 中的角色信息 页面无需重复写 computed,权限逻辑集中,易管理 Vue 2 中小到中型项目、按钮或表格权限 响应式 全局可用
2️⃣ 组合函数(Vue 3 / Composition API) 利用 Composition API 的响应式特性,将角色数据封装成 computed 或函数,返回权限判断方法,可在 setup 中直接使用 响应式,逻辑可复用,可扩展复杂组合(OR/AND),支持动态表格和路由权限 Vue 3 项目、大型项目、复杂权限场景 响应式 中低
3️⃣ 全局函数 / 工具方法 将权限判断逻辑封装成纯函数,读取 Vuex 或其他全局状态,返回 true/false 简单直观,页面调用简洁 小型项目或单独组件权限 ⚠️ 需配合 computed 才能响应式
4️⃣ 权限组件 <Auth> / <Permission> 封装为组件,内部根据权限判断是否渲染 slot 内容,利用 Vue 响应式系统自动更新 DOM 语义清晰,可嵌套组合,多权限场景可读性高,易维护 中大型项目,复杂权限场景,多层 slot 或动态表格 响应式

写在最后

权限控制看似是一个简单的功能点,但在实际项目中,它的实现方式直接影响系统的稳定性和可维护性。通过本次项目经验,我们可以总结出几个重要的教训:

  • 权限判断必须依赖响应式状态,保证动态变化时 UI 自动更新
  • 显示 / 隐藏由 Vue 控制 ,使用v-ifv-show或权限组件实现
  • 避免在指令中直接操作 DOM ,否则可能破坏 Vue 虚拟 DOM真实 DOM的一致性

权限控制不仅仅是"功能实现",更是前端工程能力的体现。理解 Vue 的响应式和声明式渲染机制,结合合适的封装方式,才能构建稳定、可扩展的权限体系。

相关推荐
程序员清洒4 小时前
Flutter for OpenHarmony:GridView — 网格布局实现
android·前端·学习·flutter·华为
VX:Fegn08954 小时前
计算机毕业设计|基于ssm + vue超市管理系统(源码+数据库+文档)
前端·数据库·vue.js·spring boot·后端·课程设计
0思必得05 小时前
[Web自动化] 反爬虫
前端·爬虫·python·selenium·自动化
LawrenceLan5 小时前
Flutter 零基础入门(二十六):StatefulWidget 与状态更新 setState
开发语言·前端·flutter·dart
秋秋小事5 小时前
TypeScript 模版字面量与类型操作
前端·typescript
2401_892000526 小时前
Flutter for OpenHarmony 猫咪管家App实战 - 添加提醒实现
前端·javascript·flutter
Yolanda946 小时前
【项目经验】vue h5移动端禁止缩放
前端·javascript·vue.js
VX:Fegn08957 小时前
计算机毕业设计|基于springboot + vue酒店管理系统(源码+数据库+文档)
vue.js·spring boot·课程设计
广州华水科技7 小时前
单北斗GNSS形变监测一体机在基础设施安全中的应用与技术优势
前端
EndingCoder7 小时前
案例研究:从 JavaScript 迁移到 TypeScript
开发语言·前端·javascript·性能优化·typescript