# 9.7 权限控制 :::tip 概述 前端权限控制通过 `v-perm` / `v-perms` 指令实现按钮级权限控制。**前端权限只是体验层**(隐藏按钮),真正的安全防线是后端 `#[Permission]` 注解 + `auth.jwt` 中间件。 ::: ## 权限指令 ### v-perm(单权限) ```vue ``` ### v-perms(多权限,满足任一即可) ```vue ``` ## 指令实现 ```typescript // ui/src/directives/permission.ts import { usePermission } from '@/hooks/web/usePermission'; export const perm: ObjectDirective = { mounted(el: HTMLButtonElement, binding) { const { hasSomePermission } = usePermission(); if (!hasSomePermission(binding.value)) { el.parentNode.removeChild(el); // 无权限则移除元素(非隐藏) } }, }; ``` :::tip 移除而非隐藏 无权限时元素直接从 DOM **移除**(而非 `display:none`)——避免通过开发者工具看到"隐藏"的敏感操作按钮。 ::: ## usePermission Hook :::tip 超级管理员 用户权限列表包含 `*:*:*` 时,表示超级管理员,拥有所有权限。 ::: ```typescript // ui/src/hooks/web/usePermission.ts export function usePermission() { const userStore = useUserStore(); /** * 检查是否拥有指定权限 * 超级管理员(permissions 包含 '*:*:*')拥有所有权限 */ function hasSomePermission(permission: string | string[]): boolean { const perms = userStore.permissions; if (perms.includes('*:*:*')) return true; if (typeof permission === 'string') { return perms.includes(permission); } return permission.some((p) => perms.includes(p)); } return { hasSomePermission, hasEveryPermission }; } ``` ## 权限数据来源 用户登录后,`GET /api/index/getUser` 接口返回权限列表(`IndexService::getUser` → `JwtService::getUserPermissions`,与后端鉴权同一缓存): ```json { "permissions": [ "sys:user:page", "sys:user:add", "sys:user:update", "sys:user:delete", "sys:role:page" ] } ``` 超级管理员返回 `["*:*:*"]`,拥有所有权限。 ## 权限码对应关系 | 端 | 机制 | 示例 | |----|------|------| | 后端 | `#[Permission('sys:user:add', '添加用户')]` 注解 + 中间件反射鉴权 | 403 拦截 | | 前端 | `v-perm="['sys:user:add']"` 指令 | 按钮移除 | | 菜单 | `laravel_menu.permission` 字段(type=1 权限节点) | 角色树勾选 | 三处权限码必须**完全一致**(`sys:{模块}:{操作}`)。 ## 常见问题 | 现象 | 原因 | 处理 | |------|------|------| | 按钮不显示但接口可调 | 角色有权限码但 `getUser` 未返回 | 重新登录刷新权限缓存 | | 按钮显示但接口 403 | 菜单 permission 与注解不一致 | 逐字符比对权限码 | | 超管按钮仍隐藏 | permissions 未含 `*:*:*` | 检查 `getUser` 返回(uid=1 应为通配) | | 修改角色权限后前端无变化 | 前端权限缓存在 Store | 重新登录或刷新页面 | 详见 [5.4 权限管理(RBAC)](/zh/core/rbac.html)。