Files
junhong_cmp_fiber/docs/login-menu-button-response/使用指南.md
2026-01-30 17:22:38 +08:00

253 lines
6.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 登录接口返回菜单树和按钮权限 - 使用指南
## 概述
从本版本开始,登录接口(`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%
- 前端缓存,登录后只传输一次