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>
This commit is contained in:
180
docs/superpowers/specs/2026-06-16-profile-center-design.md
Normal file
180
docs/superpowers/specs/2026-06-16-profile-center-design.md
Normal file
@@ -0,0 +1,180 @@
|
||||
# 个人中心管理设计
|
||||
|
||||
## 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、新密=旧密、确认不一致——都能在对应字段下显示红色文案
|
||||
Reference in New Issue
Block a user