# 登录接口返回菜单树和按钮权限 - 使用指南 ## 概述 从本版本开始,登录接口(`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 ``` ### 3. 控制按钮显示 ```vue ``` ### 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%) - 前端缓存,登录后只传输一次