签入版本

This commit is contained in:
小蚂蚁云
2026-10-25 17:24:27 +08:00
parent 48911cc08f
commit fdbeae092a
1010 changed files with 30682 additions and 95274 deletions
+34 -199
View File
@@ -1,211 +1,46 @@
# 6.15 首页与个人中心
# 6.23 首页与个人中心
:::tip 概述
首页与个人中心模块负责当前登录用户的信息获取、菜单加载、密码修改和个人信息更新。该模块不继承 BaseLogic,而是专用业务逻辑。
index 模块面向登录后的当前用户:用户信息、菜单下发、个人资料与密码维护。它是前端登录后的第一站(仪表盘与个人中心共用其接口)。
:::
## 模块特点
## 功能清单
| 特性 | 说明 |
|------|------|
| 基类 | 无(直接操作 Model) |
| 用途 | 当前登录用户的自身操作 |
| 权限 | 无需配置 #[Permission],已登录即可访问 |
| 超级管理员 | uid=1 拥有全部权限 `*:*:*` |
| 接口 | 说明 |
|---|---|
| GetUser | 当前登录用户信息(身份、角色、部门等) |
| GetMenus | 当前用户菜单树(按角色过滤,前端动态路由数据源) |
| GetMenusById | 按 ID 取菜单(管理场景) |
| UpdatePassword | 修改个人密码(校验旧密码 → crypto 哈希落库) |
| UpdateProfile | 更新个人资料(昵称、头像等) |
## 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;
}
```text
登录成功 → GetMenus(菜单树)→ asyncRoute store 生成路由 → addRoute
→ GetUser(用户信息)→ user store → 布局渲染(头像/名称/面包屑)
```
:::info 返回结构说明
- `roles`:完整角色对象数组(含 id、name、code 等),非仅 ID
- `permissions`:权限码数组,超级管理员为 `['*:*:*']`
- `city`:4 层城市编码数组 `[省, 市, 区, 街道]`
- `avatar`:自动补全域名前缀
- 字段名由 `Result::success()` 自动转为 camelCase 返回前端
菜单接口的返回结构决定侧边栏形态与前端路由表——它是动态菜单机制的后端一半(前端一半见 [9.5 路由与动态菜单](/zh/frontend/router.html))。
## 密码修改安全点
- 旧密码必验(防会话被盗用改密)
- 新密码经外部 salt + bcrypt 哈希
- 修改成功后其他会话可经在线用户页强制下线
## 界面入口
| 界面 | 内容 |
|---|---|
| dashboard/console | 首页仪表盘(CountTo 统计卡片 + echarts 图表) |
| setting/profile | 个人中心(资料编辑 + 修改密码 + 头像裁剪) |
:::warning 仪表盘数据现状
`ui/src/api/dashboard/console.ts` 的 `getConsoleInfo()` 目前返回**前端内置的模拟数据**(源码注释"模拟控制台数据"),没有对应后端统计接口——访问量/销售额等卡片为演示效果。接入真实统计时需新增后端 dashboard 接口并替换该函数;运行状态类真实数据请用监控中心接口(serverMonitor/onlineUser 等)。
:::
### 获取菜单
## 相关
```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' });
```
- [9.8 登录流程](/zh/frontend/login.html)
- [5.2 认证与授权流程](/zh/core/auth-flow.html)