# 菜单权限控制说明

## 📋 概述

系统通过 **权限编码（Permission Code）** 来控制用户对菜单的访问。菜单权限控制分为两个层面：

1. **菜单显示控制**：根据用户权限过滤菜单，只显示用户有权限访问的菜单
2. **路由访问控制**：通过路由守卫检查用户是否有权限访问某个页面

## 🔄 权限控制流程

```
用户登录
  ↓
获取用户角色和权限
  ↓
调用 /api/menus/user-menus 获取用户菜单
  ↓
后端根据用户权限过滤菜单
  ↓
前端动态生成路由和菜单
  ↓
路由守卫检查页面访问权限
```

## 🎯 如何配置菜单权限

### 1. 创建权限

首先需要在权限管理中创建权限，例如：

- `menu:read` - 查看菜单权限
- `user:read` - 查看用户权限
- `role:read` - 查看角色权限

### 2. 将权限分配给角色

在角色管理中，为角色分配相应的权限。例如：

- **管理员角色**：拥有所有权限
- **普通用户角色**：只拥有 `user:read` 权限

### 3. 为用户分配角色

在用户管理中，为用户分配角色。用户会继承角色的所有权限。

### 4. 为菜单设置权限编码

在菜单管理中，为菜单设置 `权限编码` 字段：

#### 示例配置

| 菜单名称 | 路径                | 权限编码    | 说明                                          |
| -------- | ------------------- | ----------- | --------------------------------------------- |
| 用户管理 | /system/users       | `user:read` | 只有拥有 `user:read` 权限的用户才能看到此菜单 |
| 角色管理 | /system/roles       | `role:read` | 只有拥有 `role:read` 权限的用户才能看到此菜单 |
| 权限管理 | /system/permissions | -           | 不设置权限编码，所有用户都可以看到            |
| 仪表盘   | /dashboard          | -           | 不设置权限编码，所有用户都可以看到            |

### 5. 权限编码规则

权限编码格式：`资源:操作`

常见示例：

- `user:read` - 查看用户
- `user:create` - 创建用户
- `user:update` - 更新用户
- `user:delete` - 删除用户
- `role:read` - 查看角色
- `menu:read` - 查看菜单

## 💻 技术实现

### 后端实现

#### 1. 菜单权限过滤（`MenusService.findUserMenus`）

```typescript
// 获取用户的所有权限
const userPermissions = await this.authService.getUserPermissions(userId);

// 过滤菜单：如果菜单有permission字段，检查用户是否有该权限
const filterMenus = (menus: any[]): any[] => {
  return menus
    .filter((menu) => {
      // 如果菜单没有设置权限要求，则显示
      if (!menu.permission) {
        return true;
      }
      // 如果设置了权限要求，检查用户是否有该权限
      return userPermissions.includes(menu.permission);
    })
    .map((menu) => {
      // 递归过滤子菜单
      if (menu.children && menu.children.length > 0) {
        menu.children = filterMenus(menu.children);
      }
      return menu;
    });
};
```

#### 2. 用户权限获取（`AuthService.getUserPermissions`）

```typescript
async getUserPermissions(userId: number): Promise<string[]> {
  const user = await this.usersService.findOne(userId);
  const permissions = new Set<string>();

  // 遍历用户的所有角色
  user.roles?.forEach((ur: any) => {
    // 遍历角色的所有权限
    ur.role.permissions?.forEach((rp: any) => {
      permissions.add(rp.permission.code);
    });
  });

  return Array.from(permissions);
}
```

### 前端实现

#### 1. 菜单转换为路由（`convertMenusToRoutes`）

```typescript
const route: RouteRecordRaw = {
  path: routePath,
  name: routeName,
  meta: {
    title: menu.name,
    requiresAuth: true,
    // 如果菜单有权限要求，添加到路由meta中
    ...(menu.permission && { permissions: [menu.permission] }),
  },
  component: componentLoader,
};
```

#### 2. 路由守卫检查（`router.beforeEach`）

```typescript
// 检查权限
const requiredPermissions = to.meta.permissions;
if (requiredPermissions && requiredPermissions.length > 0) {
  if (!authStore.hasAnyPermission(requiredPermissions)) {
    // 没有所需权限，跳转到 403 页面
    next({ name: "Forbidden" });
    return;
  }
}
```

#### 3. 权限检查方法（`authStore`）

```typescript
// 检查是否有指定权限
const hasPermission = (permission: string): boolean => {
  return user.value?.permissions?.includes(permission) ?? false;
};

// 检查是否有任一权限
const hasAnyPermission = (permissions: string[]): boolean => {
  if (!permissions || permissions.length === 0) return true;
  return permissions.some((perm) => hasPermission(perm));
};
```

## 📝 使用示例

### 示例 1：创建需要权限的菜单

1. 登录系统，进入 **菜单管理**
2. 点击 **新增菜单**
3. 填写菜单信息：
   - 菜单名称：`用户管理`
   - 路由路径：`/system/users`
   - 组件路径：`system/users/Index`
   - **权限编码**：`user:read` ⭐
   - 父菜单：选择 `系统管理`
4. 保存菜单

### 示例 2：创建公开菜单（所有用户可见）

1. 在菜单管理中新增菜单
2. **权限编码字段留空**
3. 这样所有用户都可以看到此菜单

### 示例 3：为用户分配权限

1. 进入 **权限管理**，创建权限：
   - 权限名称：`查看用户`
   - 权限编码：`user:read`
   - 资源：`user`
   - 操作：`read`

2. 进入 **角色管理**，编辑角色：
   - 为角色分配 `user:read` 权限

3. 进入 **用户管理**，编辑用户：
   - 为用户分配该角色

## ⚠️ 注意事项

1. **权限编码必须唯一**：每个权限编码在系统中是唯一的
2. **菜单权限为空则公开**：如果菜单的 `权限编码` 字段为空，所有用户都可以看到
3. **子菜单继承父菜单权限**：子菜单会独立检查权限，不会自动继承父菜单权限
4. **路由和菜单双重控制**：
   - 菜单显示控制：控制菜单是否在侧边栏显示
   - 路由访问控制：控制用户是否可以直接访问页面（通过 URL）
5. **权限变更后需重新登录**：权限变更后，用户需要重新登录才能看到新的菜单

## 🔍 调试技巧

### 1. 查看用户权限

在浏览器控制台执行：

```javascript
// 查看当前用户权限
console.log(useAuthStore().user?.permissions);

// 检查是否有特定权限
console.log(useAuthStore().hasPermission("user:read"));
```

### 2. 查看用户菜单

```javascript
// 查看当前用户的菜单
console.log(useAuthStore().menus);
```

### 3. 后端调试

在后端日志中查看：

- 用户权限列表
- 菜单过滤结果

## 📚 相关文档

- [RBAC 权限控制详解](./RBAC_GUIDE.md)
- [菜单管理使用说明](./MENU_MANAGEMENT.md)
- [权限管理使用说明](./PERMISSION_MANAGEMENT.md)
