稀土掘金技术社区 07月08日 14:15
前端权限系统怎么做才不会写吐?我们项目踩过的 3 套失败方案总结
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

文章分享了项目在权限系统重构过程中的经验教训。上线前两个月,权限系统崩溃三次,问题包括页面展示与真实权限不一致、权限判断分散、权限数据和按钮逻辑耦合严重。文章详细介绍了三种失败的方案:按钮级权限写死在模板、使用 router.meta.permission 控制页面级权限、封装权限组件但使用反直觉。最终方案采用 hook + 指令 + 路由统一层级设计,分为接口统一管理权限 key、统一 Hook 调用、封装 v-permission 指令,并说明了页面级权限的实现方式。文章强调权限控制的核心在于权限 key 的一致性、复用性和分层能力。

🔹 第一套失败方案:将按钮级权限直接写死在模板中,导致权限代码重复,难以维护,且权限粒度变更时会导致全崩。

🔹 第二套失败方案:尝试使用 router.meta.permission 统一控制页面级权限,但过于抽象,组件级、按钮级、表单字段级权限失效,且无法处理同路由不同内容区域权限不同的场景。

🔹 第三套失败方案:封装权限组件,但使用反直觉,嵌套组件调试困难,TypeScript 报类型错误,权限失效时组件不渲染,开发环境难以排查原因。

🔹 最终方案:采用 hook + 指令 + 路由统一层级设计,分为接口统一管理权限 key、统一 Hook 调用、封装 v-permission 指令,并说明页面级权限的实现方式,实现了权限的统一管理和维护。

🔹 文章强调权限控制的核心在于权限 key 的一致性、复用性和分层能力,最终方案满足了页面、按钮、字段统一接入权限,新增权限点只需修改枚举,无需大改,新人接手也能快速理解逻辑和调试。

原创 ErpanOmer 2025-07-01 08:31 重庆

点击关注公众号,技术干货及时达!

上线前两个月,我们的权限系统崩了三次。

不是接口没权限,而是:

页面展示和真实权限不一致;

权限判断写得四分五裂;

权限数据和按钮逻辑耦合得死死的,测试一改就炸。

于是,我们老老实实把整个权限体系拆了重构,从接口到路由、到组件、到 v-permission 指令,走了一遍完整的流程。

结果:「代码可维护,调试容易,后端调整也能快速兜底。」

这篇文章不讲理论,「只还原我们项目真踩过的 3 套失败方案和最终落地方案。」

❌ 第一套:按钮级权限直接写死在模板里当时我们的写法是这样的:

<!-- 用户管理页 --><el-button v-if="authList.includes('user:add')">添加用户</el-button>

接口返回的是一个权限数组:

["user:add""user:delete""user:list"]

然后整个项目几十个地方都这么判断。

结果:

不能重用,每个组件都判断一次;

权限粒度变更就全崩,比如从 

user:add

 改成 

user:add_user

后端权限更新后,前端要全局搜索权限 key 改代码;

「典型的“写起来爽,维护时哭”方案。」

❌ 第二套:用 router.meta.permission 统一控制,结果太抽象重构后我们尝试统一控制页面级权限:

// router.ts{  path: '/user',  component: User,  meta: {    permission: 'user:list'  }}

再通过导航守卫:

router.beforeEach((to, from, next) => {  const p = to.meta.permission  if (p && !authList.includes(p)) {    return next('/403')  }  next()})

这个方案页面级权限是解决了,但「组件级 / 按钮级 / 表单字段级」全都失效了。

而且你会发现,大量页面是“同路由但不同内容区域权限不同”,导致这种 

meta.permission

 方案显得太粗暴。
❌ 第三套:封装权限组件,结果被吐槽“反人类”当时我们团队有人设计了一个组件:

<Permission code="user:add">  <el-button>添加用户</el-button></Permission>

这个组件内部逻辑是:

const slots = useSlots()if (!authList.includes(props.code)) return nullreturn slots.default()

结果:

逻辑上看似没问题,但使用非常反直觉;

特别是嵌套多个组件时,调试麻烦,断点打不进真实组件;

TypeScript 报类型错误,编辑器无法识别 slot 类型;

更麻烦的是,权限失效的时候,组件不会渲染,开发环境都看不到是为什么!

最终方案:hook + 指令 + 路由统一层级设计我们最后把权限体系重构为 3 层:

🔹1. 接口统一管理权限 key → 后端返回精简列表(扁平权限)

export type AuthCode =  | 'user:add'  | 'user:delete'  | 'user:edit'  | 'order:export'  | 'dashboard:view'

服务端返回用户权限集,保存在 

authStore

(Pinia / Vuex / Context)中。
🔹2. 统一 Hook 调用:

usePermission(code)

import { useAuthStore } from '@/store/auth'export function usePermission(code: string): boolean {  const store = useAuthStore()  return store.permissionList.includes(code)}

用法:

<el-button v-if="usePermission('user:add')">添加用户</el-button>

这才是真正组件内部逻辑干净、容易复用、TS 支持的方案。

🔹3. 封装一个 v-permission 指令(可选)

app.directive('permission', {  mounted(el, binding) {    const authList = getUserPermissions() // 从全局 store 获取    if (!authList.includes(binding.value)) {      el.remove()    }  }})

模板中使用:

<el-button v-permission="'order:export'">导出订单</el-button>

适合动态组件、render 生成的按钮,「不适合复杂嵌套逻辑」,但实际项目中效果拔群。

🧪 页面级权限怎么做?不再用 

router.meta

,而是把每个路由页封装为权限包裹组件:

<template>  <PermissionView code="dashboard:view">    <Dashboard />  </PermissionView></template>

权限组件内部处理:

没权限 → 自动跳转 403

有权限 → 渲染内容

这样即使权限接口变了,组件逻辑也统一保留,「避免页面空白或者闪跳」

权限这事,不是实现难,而是维护难。「最核心的不是你怎么控制显示,而是权限 key 的一致性、复用性、分层能力。」

最终我们稳定版本满足了:

页面、按钮、字段统一接入权限

新增权限点只需要改枚举,不需要大改

新人接手也能一眼看懂逻辑,能调试

AI编程资讯AI Coding专区指南:https://aicoding.juejin.cn/aicoding

""~

阅读原文

跳转微信打开

Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

权限系统 重构 前端开发 Vue.js
相关文章