# 个人中心管理设计

## Context

当前用户登录后无法编辑自己的个人信息和密码——这两件事现在只能由超管在"成员管理"里改。Sidebar 底部"Admin / 管理员"硬编码（与真实登录用户脱节）也是历史遗留 bug。

本期目标：新增 `/profile` 路由，让用户能改自己的姓名 / 手机 / 邮箱、修改密码、退出当前账号。顺手把 Sidebar 用户区改成绑定真实用户。

## 决策摘要

| 项 | 决策 |
|---|---|
| 入口 | Sidebar 底部用户区右侧加 `<Settings />` icon，仅 icon 跳转（整块不响应） |
| 可编辑字段 | 姓名 / 手机 / 邮箱（部门/角色只读展示） |
| 保存策略 | 复用 `useMemberStore.updateMember`；姓名/手机/邮箱保存后立刻 `useAuthStore.refreshUser` 同步 |
| 密码修改 | 原密 + 新密 + 确认 三档；新密 ≥ 8 字符且 ≠ 原密 |
| 密码生效时机 | 下次登录生效（不强制退出） |
| 退出登录 | 复用 `useAuthStore.logout` → 跳 `/login` |
| 路由权限 | 不加 RouteGuard（个人中心是当前用户自己的，无需权限点） |
| 默认 Tab | 「个人信息」 |
| 退出按钮位置 | Header 右上角，红色边框/红字 |
| 密码可见切换 | 末尾眼睛 icon（与 login 页风格一致） |
| Sidebar 副名 | 通过 `roleId` 反查 `roles` 拿角色名 |
| 部门展示 | 通过 `departmentId` 反查 `departments` 拿层级名（如"产品部 / 前端组"） |

## 1. 入口（Sidebar 改造）

**`apps/web/components/layout/Sidebar.tsx`**

底部用户区当前硬编码"A / Admin / 管理员"。改造：

```
┌──────────────────────────────────────┐
│ [头像首字母]  张三              [⚙]  │
│              产品经理                  │
└──────────────────────────────────────┘
```

- 头像首字母 = `user.name?.[0]`（fallback `'?'`）
- 主名 = `user.name`
- 副名 = `roles.find(r => r.id === user.roleId)?.name ?? '-'`
- 整块按钮**不响应点击**（移除 onClick）
- 右侧加 `<Settings className="h-4 w-4" />` 独立按钮 → `router.push('/profile')`
- 未登录：保留显示"未登录"占位，icon 不显示

## 2. 个人中心页

**新增 `apps/web/app/profile/page.tsx`**

```
┌─ Header (h-14) ──────────────────────────┐
│  个人中心                       [退出登录]  │
├──────────────────────────────────────────┤
│  ┌─ 个人信息 ─┐  ┌─ 修改密码 ─┐           │  ← Tab 切换
├──────────────────────────────────────────┤
│  [Tab 内容区]                             │
└──────────────────────────────────────────┘
```

**入口校验**：`useAuthStore.user` 不存在则 `router.push('/login')`，避免直访问空 store。

**Tab 样式**：沿用版本详情页 `border-b-2 + accent` 色风格。

**退出登录按钮**：
- Header 右上角，红色边框红字白底
- `onClick`: `confirm('确认退出当前账号？')` → `useAuthStore.logout()` → `router.push('/login')`

## 3. 个人信息表单

```
姓名 *        [_____________]
手机号 *      [_____________]   FieldError
邮箱 *        [_____________]   FieldError
部门          产品部 / 前端组   （只读，灰底）
角色          产品经理          （只读，灰底）

                       [取消] [保存]
```

**初始值**：从 `useMemberStore.members.find(m => m.id === user.id)` 取最新值（不直接信 `useAuthStore.user`）。

**校验**：
- 姓名：必填，trim 后非空，最长 20
- 手机号：必填，正则 `/^1[3-9]\d{9}$/`
- 邮箱：必填，正则 `/^[^\s@]+@[^\s@]+\.[^\s@]+$/`
- 失败：用 `<FieldError>` 在对应 input 下方显示文案

**部门展示**：递归查 `departmentId` 父子链，拼为"产品部 / 前端组"格式。

**角色展示**：`roles.find(r => r.id === user.roleId)?.name`。

**提交**：
1. 校验通过 → `useMemberStore.updateMember(user.id, { name, phone, email })`
2. 调 `useAuthStore.refreshUser()` 同步 user state（让 Sidebar 立即显示新名字）
3. 内联绿色提示"已保存"（2 秒自动消失）

**取消**：恢复初始值。

## 4. 密码修改表单 + 退出

```
当前密码 *    [_____________ 👁]   FieldError
新密码 *      [_____________ 👁]   FieldError
确认密码 *    [_____________ 👁]   FieldError

                       [取消] [保存]
```

**校验**：
- 当前密码：必填，等于 `member.password`，否则 `FieldError: '当前密码错误'`
- 新密码：必填，长度 ≥ 8（与 `passwordRule.length` 默认值一致），≠ 当前密码
- 确认密码：必填，必须等于新密码

**提交**：
1. 三项校验通过 → `useMemberStore.updateMember(user.id, { password: newPassword })`
2. 清空三个输入框
3. 内联绿色提示"密码已修改，下次登录生效"
4. 不强制退出

**眼睛 icon**：每个 input 末尾切换 `type="password" / "text"`，复用 login 页 Eye/EyeOff 模式。

**取消**：清空三个输入框。

## 数据流

```
useAuthStore.user (id, name, roleId, departmentId, phone, email)
   │
   ├─ Sidebar 用户区 ─→ 头像/姓名 + 角色名 + 设置 icon
   │
   └─ /profile 页面入口校验
         │
         ├─ 个人信息表单 ─→ updateMember() ─→ refreshUser() ─→ Sidebar 立即更新
         ├─ 密码表单 ─→ updateMember() ─→ 提示下次登录生效
         └─ 退出按钮 ─→ logout() ─→ router.push('/login')
```

## useAuthStore 改造

新增 `refreshUser()` 方法：

```ts
refreshUser: () => {
  const user = get().user;
  if (!user) return;
  // 从 localStorage 重新读 member（与 login 同源）
  const membersRaw = localStorage.getItem('ftb_members_v1');
  if (!membersRaw) return;
  const parsed = JSON.parse(membersRaw);
  const m = parsed.members?.find((x: any) => x.id === user.id);
  if (!m) return;
  const next = { id: m.id, name: m.name, roleId: m.roleId, departmentId: m.departmentId, phone: m.phone, email: m.email };
  set({ user: next });
  sessionStorage.setItem('ftb_auth_session', JSON.stringify(next));
  if (localStorage.getItem('ftb_auth_persist')) {
    localStorage.setItem('ftb_auth_persist', JSON.stringify(next));
  }
}
```

## 实施清单

新增：
- `apps/web/app/profile/page.tsx` — 个人中心页面（含两个 Tab + 退出按钮）

修改：
- `apps/web/components/layout/Sidebar.tsx` — 用户区绑定真实 user + 加 Settings icon
- `apps/web/stores/useAuthStore.ts` — 加 `refreshUser` 方法

## 验证

1. `pnpm type-check` 0 错误
2. `pnpm build` 14+1 个页面成功（新增 `/profile`）
3. 登录张三（产品经理）：
   - Sidebar 底部显示"张  张三 / 产品经理 / ⚙"
   - 点 ⚙ 跳 `/profile`
   - 改姓名提交后 Sidebar 立即同步新名字
   - 改密码后提示下次登录生效，重新登录用新密码可登入旧密码失败
   - 点退出 → 弹确认 → 跳 /login + session 清空
4. 三个表单的校验：手机/邮箱格式错误、新密短于 8、新密=旧密、确认不一致——都能在对应字段下显示红色文案
