Files
ftb-project-management/docs/superpowers/specs/2026-06-16-profile-center-design.md
Script Generator 7c49868c4f feat: 个人中心管理(编辑信息 + 修改密码 + 退出登录)
- 新增 /profile 路由:两个 Tab(个人信息 / 修改密码)+ 退出按钮
- Sidebar 底部用户区改造:绑定真实登录用户(头像/姓名/角色)+ 加 Settings icon 跳 /profile
- useAuthStore 加 refreshUser:保存信息后立即同步 Sidebar 显示
- 个人信息表单:姓名/手机/邮箱可改,部门/角色只读,复用 updateMember
- 密码修改:原密 + 新密(≥8位且≠原密) + 确认 三档校验,眼睛 icon 切换可见,下次登录生效
- 退出登录:confirm → useAuthStore.logout → /login

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-16 19:51:22 +08:00

181 lines
7.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.

# 个人中心管理设计
## 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、新密=旧密、确认不一致——都能在对应字段下显示红色文案