# 6.15 首页与个人中心 :::tip 概述 首页与个人中心模块负责当前登录用户的信息获取、菜单加载、密码修改和个人信息更新。该模块不继承 BaseLogic,而是专用业务逻辑。 ::: ## 模块特点 | 特性 | 说明 | |------|------| | 基类 | 无(直接操作 Model) | | 用途 | 当前登录用户的自身操作 | | 权限 | 无需配置 #[Permission],已登录即可访问 | | 超级管理员 | uid=1 拥有全部权限 `*:*:*` | ## API 接口 | 方法 | 路径 | 说明 | |------|------|------| | GET | `/api/index/getUser` | 获取当前登录用户信息(含角色和权限) | | GET | `/api/index/getMenus` | 获取当前用户菜单(树形) | | PUT | `/api/index/updatePassword` | 修改密码 | | PUT | `/api/index/updateProfile` | 更新个人信息 | ## 核心代码 ### 获取用户信息 ```php // app/logic/IndexLogic.php public function getUser(int $userId): array { $user = User::with(['dept'])->find($userId); if (!$user) { throw new \Exception('用户不存在'); } $data = $user->toArray(); // 头像补全域名前缀 if (!empty($data['avatar'])) { $data['avatar'] = get_file_url($data['avatar']); } // 组装城市编码数组(province_code, city_code, district_code, street_code) $data['city'] = [ $data['province_code'] ?? '', $data['city_code'] ?? '', $data['district_code'] ?? '', $data['street_code'] ?? '', ]; // 获取角色列表(完整角色对象) $roleIds = UserRole::where('user_id', $userId)->column('role_id'); if (!empty($roleIds)) { $data['roles'] = Role::whereIn('id', $roleIds)->select()->toArray(); } else { $data['roles'] = []; } // 获取权限列表 if ($userId === 1) { $data['permissions'] = ['*:*:*']; } else { if (!empty($roleIds)) { $data['permissions'] = RoleMenu::alias('rm') ->join('think_menu m', 'rm.menu_id = m.id') ->whereIn('rm.role_id', $roleIds) ->where('m.is_delete', 0) ->where('m.type', 1) ->column('m.permission'); } else { $data['permissions'] = []; } } // authorities 字段(预留) $data['authorities'] = null; // 隐藏敏感字段 unset($data['password'], $data['salt']); return $data; } ``` :::info 返回结构说明 - `roles`:完整角色对象数组(含 id、name、code 等),非仅 ID - `permissions`:权限码数组,超级管理员为 `['*:*:*']` - `city`:4 层城市编码数组 `[省, 市, 区, 街道]` - `avatar`:自动补全域名前缀 - 字段名由 `Result::success()` 自动转为 camelCase 返回前端 ::: ### 获取菜单 ```php public function getMenus(int $userId): array { // 超级管理员:全部菜单 // type=0 仅返回可导航菜单(目录+页面),type=1 为权限按钮不返回 if ($userId === 1) { $menus = Menu::where('status', 0) ->where('hide', 0) ->where('type', 0) ->where('is_delete', 0) ->order('sort', 'asc') ->select()->toArray(); return $this->buildTree($menus); } // 普通用户:通过角色关联获取菜单 $roleIds = UserRole::where('user_id', $userId)->column('role_id'); if (empty($roleIds)) { return []; } $menuIds = RoleMenu::whereIn('role_id', $roleIds)->column('menu_id'); $menuIds = array_unique($menuIds); if (empty($menuIds)) { return []; } $menus = Menu::whereIn('id', $menuIds) ->where('status', 0) ->where('hide', 0) ->where('type', 0) ->where('is_delete', 0) ->order('sort', 'asc') ->select()->toArray(); return $this->buildTree($menus); } ``` :::info 菜单查询条件 获取菜单(非节点)数据时保证四个条件:`type=0`(可导航菜单)、`status=0`(启用)、`hide=0`(未隐藏)、`is_delete=0`(未删除)。 ::: :::info 菜单类型说明 代码中 `where('type', 0)` 用于筛选可导航的菜单项(目录和页面)。在当前实现中,`type=0` 表示可导航的菜单项(包括目录和页面),`type=1` 表示仅用于权限控制的按钮节点,不会出现在菜单树中。 ::: ### 修改密码 ```php public function updatePassword(int $userId, string $oldPassword, string $newPassword): bool { $user = User::find($userId); // 验证旧密码 if (!PasswordService::verify($oldPassword, $user->password, $user->salt)) { throw new \Exception('旧密码错误'); } // 加密新密码 $encrypted = PasswordService::encrypt($newPassword); $user->password = $encrypted['password']; $user->salt = $encrypted['salt']; return $user->save() !== false; } ``` ### 更新个人信息 ```php public function updateProfile(int $userId, array $data): bool { $user = User::find($userId); // 处理城市字段 if (isset($data['city']) && is_array($data['city'])) { $data['province_code'] = $data['city'][0] ?? ''; $data['city_code'] = $data['city'][1] ?? ''; $data['district_code'] = $data['city'][2] ?? ''; $data['street_code'] = $data['city'][3] ?? ''; $data['city_info'] = City::resolveCityNames($data['city']); unset($data['city']); } // 排除敏感字段 unset($data['password'], $data['salt'], $data['role_id'], $data['tenant_id'], $data['id']); return $user->save($data) !== false; } ``` :::warning 安全设计 updateProfile 会排除 `password`、`salt`、`role_id`、`tenant_id` 等敏感字段,防止用户越权修改自身权限。 ::: ## 前端调用 ```typescript // 获取当前用户信息 const userInfo = await getUserInfo(); // userInfo.permissions = ['sys:user:page', 'sys:article:add', ...] // userInfo.roles = [1] // 获取菜单(用于动态路由生成) const menus = await getMenuList(); // menus = [{id:1, name:'系统管理', path:'/system', children:[...]}] // 修改密码 await updatePassword({ oldPassword: '123456', newPassword: '654321' }); // 更新个人信息 await updateProfile({ realname: '张三', mobile: '13800138000' }); ```