# 个人中心 UI 改版设计 ## Context 当前 `/profile` 页面(commit `7c49868`)的视觉太单调——内容用 `max-w-xl` 限宽,单张白卡片孤立漂浮在空白页上,没有头部装饰,整体像一个被推平的弹窗。改为 GitHub/Notion 风格的"个人页":渐变 Banner + 大头像 + Tab 切换的卡片。 ## 决策摘要 | 项 | 决策 | |---|---| | 整体风格 | 顶部渐变 Banner + 头像 + 下方 Tab 卡片(方案 A) | | Banner 高度 | h-40(160px) | | Banner 背景 | `bg-gradient-to-br from-[var(--accent)] to-[var(--accent-hover)]` | | 头像 | 80px 圆形,主色背景 + 白字首字母,`ring-4 ring-white/20` | | 退出按钮位置 | Banner 右上角 absolute(白底红描边红字) | | Tab 风格 | 沿用版本详情页风格(active 主色下划线) | | 字段布局 | 单栏堆叠(用户决定,避免两栏挤迫) | | 卡片宽度 | `max-w-3xl mx-auto` 居中 | | 保存反馈 | 提交中 spinner + 已保存绿色文字 3 秒淡出 | | 移动端 | 不考虑(项目仅面向桌面端) | ## 1. Banner 视觉规格 **结构**: ``` ┌─────────────────────────────────────────────────────┐ │ [渐变背景 h-40] │ │ [退出登录] │ │ │ │ [80px 圆头像] 张三 │ │ "张" 产品经理 · 产品部 / 前端组 │ │ 📞 138... · ✉ zhang@... │ └─────────────────────────────────────────────────────┘ ``` **实现要点**: - 容器 `relative h-40 px-8 py-6 rounded-b-3xl bg-gradient-to-br from-[var(--accent)] to-[var(--accent-hover)] text-white` - 内部 flex 布局:左侧头像 + 文字组,右上 absolute 退出按钮 - 头像:`h-20 w-20 rounded-full bg-white/15 ring-4 ring-white/20 flex items-center justify-center text-3xl font-semibold`,内容是 `user.name?.[0]` - 主名 `text-2xl font-semibold` - 副名 `text-sm text-white/80`,格式:"角色名 · 部门层级" - 联系方式 `text-xs text-white/60`,Phone/Mail icon + 文本 - 退出按钮:`absolute top-5 right-5 h-8 px-3 rounded-lg bg-white/10 border border-white/20 hover:bg-white/20 text-white`(白色透明) ## 2. Tab 内容布局 **结构**: ``` [Banner] ┌─ 个人信息 ─┐ ┌─ 修改密码 ─┐ ════════════ ┌──────────────────────────────────────────┐ │ 基本信息 │ │ 修改后立即生效 │ │ ───────────────────────────────────── │ │ │ │ 姓名 * │ │ [_____________________________________] │ │ │ │ 手机号 * │ │ [_____________________________________] │ │ │ │ 邮箱 * │ │ [_____________________________________] │ │ │ │ 部门 │ │ [产品部 / 前端组] │ │ │ │ 角色 │ │ [产品经理] │ │ │ │ [取消] [保存] │ └──────────────────────────────────────────┘ ``` **实现要点**: - Banner 与 Tab 之间留 `mt-6` 间距 - Tab 容器 `max-w-3xl mx-auto px-5`,无外边框,仅 Tab 按钮带下划线 - 卡片 `max-w-3xl mx-auto rounded-2xl border border-[var(--line)] bg-[var(--bg-card)] p-6` - 卡片头:标题 `text-base font-semibold` + 副标题 `text-xs text-[var(--ink-muted)] mt-1` - 标题区下细分割线 `border-t border-[var(--line)] pt-5 mt-4` - 字段单栏堆叠 `space-y-4` - 必填星号 `text-red-500` - 部门、角色字段 `disabled bg-zinc-50` **密码 Tab**: - 卡片标题"修改密码"+ 副标题"密码修改后下次登录生效" - 三个字段单栏堆叠(与第 1 节澄清一致) - 眼睛 icon 切换可见性保留 ## 3. 保存反馈微调 **保存按钮三态**: ``` 普通: [取消] [保存] 提交中: [取消] [● 保存中...] ← spinner 禁用 成功: 已保存 ✓ [取消] [保存] ← 绿色文字 3 秒淡出 ``` - 提交瞬间设 `submitting=true`,按钮 disabled + 左侧 Loader2 icon spinning - 成功后 `submitting=false` + `savedAt=Date.now()`,3 秒后 `setTimeout` 清掉 savedAt - 已保存提示:`text-emerald-600` + Check icon **取消按钮**: - 个人信息 Tab:`handleReset` 重读 `useMemberStore.members.find(m => m.id === user.id)` 的字段 - 密码 Tab:清空三个输入 + 清空 errors **Banner 与表单同步**: - 字段改了未保存:Banner 仍显老值(不联动) - 保存成功 → `refreshUser()` → Banner 立即更新 ## 数据流 ``` useAuthStore.user │ └─ 触发渲染 │ ├─ Banner(user.name / role.name / dept name 链 / phone / email) └─ 表单字段(从 useMemberStore.members.find 取最新值) 提交流程: form submit → updateMember() → refreshUser() → Banner 自动重渲 ``` ## 实施清单 修改: - `apps/web/app/profile/page.tsx` — 整页重写:Banner + Tab + 卡片布局 + 保存 spinner + 已保存淡出 不变: - `apps/web/components/layout/Sidebar.tsx`(已正确) - `apps/web/stores/useAuthStore.ts`(已加 refreshUser) - `apps/web/components/FieldError.tsx`(复用) ## 验证 1. `pnpm type-check` 0 错误 2. `pnpm build` 14+1 个页面成功 3. 进入 `/profile`: - 顶部渐变 Banner,左下圆头像 + 姓名/角色/部门/联系方式 - 右上角白底红字退出按钮 - Banner 下 Tab 切换条 + 卡片 4. 改姓名后保存: - 按钮 spinner 一闪 - 显示绿色"已保存 ✓"3 秒后淡出 - Banner 上的姓名立即更新(refreshUser 生效) 5. 修改密码:spinner + "密码已修改,下次登录生效" 3 秒淡出 6. 取消按钮:恢复初始值