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

7.4 KiB
Raw Blame History

个人中心管理设计

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: '当前密码错误'
  • 新密码:必填,长度 ≥ 8passwordRule.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() 方法:

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、新密=旧密、确认不一致——都能在对应字段下显示红色文案