# 登录接口返回菜单树和按钮权限 - 使用指南

## 概述

从本版本开始，登录接口（`POST /api/admin/login` 和 `POST /api/h5/login`）响应中新增了 `menus` 和 `buttons` 两个字段，用于直接返回结构化的菜单树和按钮权限列表，简化前端实现。

## 响应结构

### LoginResponse 字段说明

```json
{
  "code": 0,
  "msg": "success",
  "data": {
    "access_token": "xxx",
    "refresh_token": "xxx",
    "expires_in": 86400,
    "user": { ... },
    "permissions": ["user:menu", "user:create", "user:delete"],
    "menus": [
      {
        "id": 1,
        "perm_code": "user:menu",
        "name": "用户管理",
        "url": "/users",
        "sort": 1,
        "children": [
          {
            "id": 2,
            "perm_code": "user:list:menu",
            "name": "用户列表",
            "url": "/users/list",
            "sort": 10,
            "children": []
          }
        ]
      }
    ],
    "buttons": ["user:create", "user:delete", "user:update"]
  },
  "timestamp": 1638360000
}
```

| 字段 | 类型 | 说明 |
|------|------|------|
| `permissions` | `[]string` | 所有权限码（向后兼容，包含菜单和按钮） |
| `menus` | `[]MenuNode` | 菜单树（树形结构） |
| `buttons` | `[]string` | 按钮权限码列表（扁平数组） |

### MenuNode 结构说明

```typescript
interface MenuNode {
  id: number;           // 权限 ID
  perm_code: string;    // 权限码（如 "user:menu"）
  name: string;         // 菜单名称（如 "用户管理"）
  url: string;          // 路由路径（如 "/users"）
  sort: number;         // 排序值（升序）
  children: MenuNode[]; // 子菜单（递归结构）
}
```

## 前端使用示例

### 1. 登录并缓存菜单数据

```javascript
// 登录
const response = await api.post('/api/admin/login', {
  username: 'admin',
  password: 'password',
  device: 'web'
});

const { menus, buttons, permissions } = response.data;

// 缓存到 localStorage（推荐）
localStorage.setItem('menus', JSON.stringify(menus));
localStorage.setItem('buttons', JSON.stringify(buttons));
localStorage.setItem('permissions', JSON.stringify(permissions));
```

### 2. 渲染侧边栏菜单

```vue
<template>
  <aside class="sidebar">
    <menu-tree :items="menus" />
  </aside>
</template>

<script>
export default {
  data() {
    return {
      menus: []
    };
  },
  mounted() {
    // 从 localStorage 读取
    const cached = localStorage.getItem('menus');
    this.menus = cached ? JSON.parse(cached) : [];
  }
};
</script>
```

### 3. 控制按钮显示

```vue
<template>
  <div>
    <button v-if="hasPermission('user:create')">创建用户</button>
    <button v-if="hasPermission('user:delete')">删除用户</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      buttons: []
    };
  },
  mounted() {
    // 从 localStorage 读取
    const cached = localStorage.getItem('buttons');
    this.buttons = cached ? JSON.parse(cached) : [];
  },
  methods: {
    hasPermission(code) {
      return this.buttons.includes(code);
    }
  }
};
</script>
```

### 4. 页面刷新时恢复菜单

```javascript
// App.vue 或 main.js
const menus = localStorage.getItem('menus');
if (menus) {
  store.commit('setMenus', JSON.parse(menus));
} else {
  // 未登录，跳转到登录页
  router.push('/login');
}
```

## 核心特性

### 1. 平台过滤

登录时传递 `device` 参数（`web` 或 `h5`），系统会自动过滤对应平台的权限：

```javascript
// Web 后台登录
await api.post('/api/admin/login', {
  username: 'admin',
  password: 'password',
  device: 'web'  // 只返回 platform="web" 或 "all" 的菜单
});

// H5 端登录
await api.post('/api/h5/login', {
  username: 'user',
  password: 'password',
  device: 'h5'  // 只返回 platform="h5" 或 "all" 的菜单
});
```

### 2. 菜单自动排序

菜单树已按 `sort` 字段升序排序（包含所有层级），前端无需再次排序，直接渲染即可。

### 3. 超级管理员

超级管理员（`user_type = 1`）登录时，返回所有启用的菜单和按钮（仍然应用平台过滤）。

### 4. 孤儿节点处理

如果用户有子菜单权限但没有父菜单权限（如只有 "用户列表" 权限但没有 "用户管理" 权限），子菜单会被提升为根节点显示，避免菜单丢失。

## GetMe 接口行为

`GET /api/admin/me` 和 `GET /api/h5/me` 接口**不返回** `menus` 和 `buttons` 字段，只返回 `user` 和 `permissions`。

原因：
- GetMe 是高频接口（如每次路由切换都调用）
- 菜单树构建有计算成本
- 前端应将菜单数据缓存到 localStorage

```json
// GetMe 响应示例
{
  "code": 0,
  "data": {
    "user": { ... },
    "permissions": ["user:menu", "user:create"]
  }
}
```

## 向后兼容性

- 旧版前端仍可使用 `permissions` 字段正常工作
- 新版前端可以选择使用 `menus` 和 `buttons` 字段
- `permissions` 字段包含所有权限码（菜单 + 按钮）

## 最佳实践

1. **登录后立即缓存**：将 `menus` 和 `buttons` 存储到 localStorage，避免重复构建
2. **页面刷新时恢复**：从 localStorage 读取菜单数据，无需重新登录
3. **权限变更后刷新**：管理员修改权限后，提示用户重新登录或提供"刷新权限"按钮
4. **使用 buttons 控制按钮**：不要使用 `permissions` 字段判断按钮显示，使用 `buttons` 更清晰
5. **GetMe 不依赖菜单**：GetMe 接口用于验证 Token 有效性和获取用户信息，不要期望它返回菜单

## 常见问题

### 1. 权限变更后菜单未更新？

**原因**：前端使用了缓存的菜单数据。

**解决方案**：
- 短期：提示用户重新登录
- 长期：提供"刷新权限"按钮，调用 `POST /api/admin/login` 重新获取菜单

### 2. 菜单层级不正确？

**原因**：权限配置不当（子菜单的 `parent_id` 指向不存在的父菜单）。

**解决方案**：检查权限配置，确保父子关系正确。孤儿节点会被提升为根节点，同时后端会记录警告日志。

### 3. 性能影响？

**影响**：登录响应时间增加 < 50ms（权限数量 < 100 的场景）

**缓解**：
- 前端缓存菜单数据到 localStorage
- GetMe 接口未修改，性能无影响

### 4. 响应体过大？

**影响**：响应体增加约 5-10KB（取决于权限数量）

**缓解**：
- 使用 Gzip 压缩（压缩率约 60-70%）
- 前端缓存，登录后只传输一次
