权限控制应通过computed联动用户角色、路由配置与页面上下文,预先将权限字符串数组结构化为Set或映射对象,一次性初始化后供所有computed响应式读取;按钮级权限封装为语义化计算属性,如canDeleteUser;页面级权限结合路由meta声明并校验;严禁computed中发请求、遍历数组校验或忽略响应式包裹。

直接用 computed 做权限按钮渲染,核心是把「用户角色 + 路由/功能配置 + 当前页面上下文」三者联动起来,而不是硬编码 v-if 或反复调用 checkAuth() 方法。
权限数据要提前结构化,别等用时再拼
后端返回的权限字段(比如字符串数组 ['user:read', 'order:edit'])不能直接丢给按钮判断。先在 store 或 setup 中统一处理成可查结构:
- 转成 Set:const authSet = new Set(res.permissions)
- 或生成映射对象:{ 'user': { read: true, edit: false }, 'order': { edit: true } }
- 关键点:这个结构只初始化一次,后续所有 computed 都基于它响应式读取
按钮级权限用 computed 封装成布尔值
每个操作按钮对应一个计算属性,名字直白,逻辑内聚:
- const canDeleteUser = computed(() => authSet.has('user:delete'))
- const canExportReport = computed(() => route.name === 'report-list' && authSet.has('report:export'))
- 好处:模板里直接写
v-if="canDeleteUser",语义清晰;权限变更时自动更新,不用手动 $forceUpdate
结合路由元信息做页面级细粒度控制
在路由定义里加 meta 字段,把权限要求声明化:
- path: '/users', name: 'user-list', meta: { requiredAuth: ['user:read'] }
- 然后在页面组件中:const hasPageAuth = computed(() => route.meta.requiredAuth?.every(key => authSet.has(key)))
- 配合
<router-view v-if="hasPageAuth"></router-view>或骨架屏提示,避免无权限时空白页
避免常见坑:不要在 computed 里调接口、不重复校验、不忽略缓存
computed 必须是同步、无副作用的。以下写法危险:
- ❌ 在 computed 里发请求校验权限(破坏响应式链路,且无法缓存)
- ❌ 每次都用 includes() 遍历长数组(换成 Set.has(),O(1) 查找)
- ❌ 把整个权限列表当响应式对象传入,但没用 reactive 包裹导致更新不触发(确保权限数据是 ref 或 reactive)

















