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

6.4 KiB
Raw Blame History

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

概述

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

响应结构

LoginResponse 字段说明

{
  "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 结构说明

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

前端使用示例

1. 登录并缓存菜单数据

// 登录
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. 渲染侧边栏菜单

<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. 控制按钮显示

<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. 页面刷新时恢复菜单

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

核心特性

1. 平台过滤

登录时传递 device 参数(webh5),系统会自动过滤对应平台的权限:

// 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/meGET /api/h5/me 接口不返回 menusbuttons 字段,只返回 userpermissions

原因:

  • GetMe 是高频接口(如每次路由切换都调用)
  • 菜单树构建有计算成本
  • 前端应将菜单数据缓存到 localStorage
// GetMe 响应示例
{
  "code": 0,
  "data": {
    "user": { ... },
    "permissions": ["user:menu", "user:create"]
  }
}

向后兼容性

  • 旧版前端仍可使用 permissions 字段正常工作
  • 新版前端可以选择使用 menusbuttons 字段
  • permissions 字段包含所有权限码(菜单 + 按钮)

最佳实践

  1. 登录后立即缓存:将 menusbuttons 存储到 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%
  • 前端缓存,登录后只传输一次