Files
NextEdu/docs/architecture/audit/settings-profile-audit-report.md
SpecialX 5d42495480 feat(settings): 设置与个人信息模块审计重构 — i18n + 服务注入解耦 + Error Boundary + 流式渲染
- 新增 SettingsService 接口 + Context 注入,组件层不再直接 import users/messaging actions

- 新增 resolveRoleSettingsConfig 配置驱动角色路由,删除 parent/student/teacher-settings-view 冗余文件

- 新增 SettingsSectionErrorBoundary,每个 TabsContent + profile 角色概览区块均包裹

- 新增 ProfileStudentOverview/ProfileTeacherOverview 异步 Server Component + 骨架屏,支持流式渲染

- 抽取 buildStudentOverviewData 等纯函数到 lib/student-overview-data.ts,便于单元测试

- 新增 settings.json 翻译文件(zh-CN + en),所有组件改用 useTranslations/getTranslations

- 重构 profile/page.tsx:i18n 适配 + Suspense 分区加载 + 业务逻辑抽离

- 同步更新架构图 004/005
2026-06-22 16:15:36 +08:00

429 lines
27 KiB
Markdown
Raw Permalink 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.
# 设置和个人信息模块审计报告
> 审查日期2026-06-22
> 审查范围:`src/modules/settings/**`、`src/app/(dashboard)/settings/**`、`src/app/(dashboard)/admin/settings/**`、`src/app/(dashboard)/profile/**`
> 架构图参考:`docs/architecture/004_architecture_impact_map.md` §2.23、`docs/architecture/005_architecture_data.json`
---
## 一、现有实现概要
### 1.1 文件分布
| 层 | 路径 | 文件数 | 说明 |
|----|------|--------|------|
| 路由层 - 通用设置 | `src/app/(dashboard)/settings/` | 1 个 `page.tsx` + `error.tsx` + `loading.tsx` | 角色分发到 4 个 SettingsView |
| 路由层 - 管理员系统设置 | `src/app/(dashboard)/admin/settings/` | 1 个 `page.tsx` | 仅 admin 可访问,渲染 `AdminSettingsView` |
| 路由层 - 安全设置 | `src/app/(dashboard)/settings/security/` | 1 个 `page.tsx` + `error.tsx` + `loading.tsx` | 独立密码修改页 |
| 路由层 - 个人资料 | `src/app/(dashboard)/profile/` | 1 个 `page.tsx` + `error.tsx` + `loading.tsx` | 个人资料展示页317 行) |
| 模块层 - actions | `src/modules/settings/actions.ts`160 行) | AI Provider CRUD + test | ✅ 使用 `requirePermission(AI_CONFIGURE)` |
| 模块层 - actions-password | `src/modules/settings/actions-password.ts`87 行) | 修改密码 | ✅ 使用 `requirePermission(USER_PROFILE_UPDATE)` + Zod + 限流 |
| 模块层 - data-access | `src/modules/settings/data-access.ts`158 行) | AI Provider + 密码 DB 操作 | ✅ `server-only` |
| 模块层 - types | `src/modules/settings/types.ts`16 行) | AI Provider 类型 | |
| 模块层 - 组件 | `src/modules/settings/components/` | 10 个组件 | 见下表 |
| i18n | **缺失** | 0 | 无 `settings.json` / `profile.json` 翻译文件 |
**组件清单**
| 组件 | 行数 | 职责 |
|------|------|------|
| [settings-view.tsx](file:///e:/Desktop/CICD/src/modules/settings/components/settings-view.tsx) | 179 | 统一设置页布局5 标签页 + 角色差异 props 注入 + Tab URL 持久化) |
| [admin-settings-view.tsx](file:///e:/Desktop/CICD/src/modules/settings/components/admin-settings-view.tsx) | 185 | **mock 实现**4 个 Card学校信息/安全策略/文件上传/通知配置),`setTimeout` 模拟保存 |
| [ai-provider-settings-card.tsx](file:///e:/Desktop/CICD/src/modules/settings/components/ai-provider-settings-card.tsx) | 357 | AI Provider 管理(选择/新建/测试/保存) |
| [notification-preferences-form.tsx](file:///e:/Desktop/CICD/src/modules/settings/components/notification-preferences-form.tsx) | 326 | 通知偏好(渠道/类别/免打扰时段) |
| [password-change-form.tsx](file:///e:/Desktop/CICD/src/modules/settings/components/password-change-form.tsx) | 169 | 修改密码(强度指示器 + 显示切换) |
| [profile-settings-form.tsx](file:///e:/Desktop/CICD/src/modules/settings/components/profile-settings-form.tsx) | 146 | 个人资料编辑表单 |
| [theme-preferences-card.tsx](file:///e:/Desktop/CICD/src/modules/settings/components/theme-preferences-card.tsx) | 55 | 主题切换system/light/dark |
| [parent-settings-view.tsx](file:///e:/Desktop/CICD/src/modules/settings/components/parent-settings-view.tsx) | 60 | 家长设置视图(复用 SettingsView + 快捷链接) |
| [teacher-settings-view.tsx](file:///e:/Desktop/CICD/src/modules/settings/components/teacher-settings-view.tsx) | 66 | 教师设置视图(同上) |
| [student-settings-view.tsx](file:///e:/Desktop/CICD/src/modules/settings/components/student-settings-view.tsx) | 54 | 学生设置视图(同上) |
### 1.2 数据流
```
[Route] /settings/page.tsx
├─▶ users/data-access.getUserProfile (跨模块 data-access类型导入)
├─▶ notifications/preferences.getNotificationPreferences (跨模块 data-access)
└─▶ 按 roles.includes("admin"|"student"|"parent") 分发
├─ admin → SettingsView无 generalExtra
├─ student → StudentSettingsView → SettingsView
├─ parent → ParentSettingsView → SettingsView
└─ teacher → TeacherSettingsView → SettingsView
[Route] /admin/settings/page.tsx
└─▶ AdminSettingsViewmock无数据流
[Route] /settings/security/page.tsx
└─▶ PasswordChangeForm → settings/actions-password.changePasswordAction
[Route] /profile/page.tsx
├─▶ users/data-access.getUserProfile
├─▶ classes/data-access.getStudentClasses / getStudentSchedule (学生分支)
├─▶ homework/data-access.getStudentHomeworkAssignments / getStudentDashboardGrades (学生分支)
├─▶ classes/data-access.getTeacherClasses / getTeacherTeachingSubjects (教师分支)
└─▶ 页面层内联 80+ 行业务计算weekday 转换、作业状态统计、排序切片)
[Component] ProfileSettingsForm
└─▶ users/actions.updateUserProfile ❌ 跨模块 action 直调
[Component] NotificationPreferencesForm
└─▶ messaging/actions.updateNotificationPreferencesAction ❌ 跨模块 action 直调
[Component] AiProviderSettingsCard
└─▶ settings/actions.getAiProviderSummaries / upsertAiProviderAction / testAiProviderAction ✅ 模块内
```
### 1.3 架构图记录情况
`004_architecture_impact_map.md` §2.23 记录了 settings 模块的基本结构,但存在以下遗漏和不一致:
- **未记录 `profile/page.tsx` 的数据流**profile 页面编排了 users/classes/homework 三个模块的 data-access但架构图未记录
- **未记录跨模块 action 直调问题**`profile-settings-form.tsx` 直调 `users/actions.updateUserProfile``notification-preferences-form.tsx` 直调 `messaging/actions.updateNotificationPreferencesAction`,架构图标注为"已修复"但实际仍存在
- **未记录 `AdminSettingsView` 是 mock 实现**:架构图描述其有 4 个 Card 但未说明无真实数据持久化
- **未记录 i18n 缺失**:架构图未标注 settings 模块所有文本均为硬编码
- **通知偏好归属不一致**:架构图 §2.17 称通知偏好已迁移至 notifications 模块,但 `notification-preferences-form.tsx` 仍从 `messaging/actions` 导入 action
---
## 二、现存问题与原因分析
### 2.1 国际化完全缺失P0
| 位置 | 问题 | 违反规则 |
|------|------|----------|
| [settings-view.tsx](file:///e:/Desktop/CICD/src/modules/settings/components/settings-view.tsx) L96-104 | "Settings"、"Back to dashboard" 等硬编码英文 | "所有用户可见文本必须适配 i18n使用 next-intl提取翻译键" |
| [admin-settings-view.tsx](file:///e:/Desktop/CICD/src/modules/settings/components/admin-settings-view.tsx) 全文 | "系统设置"、"学校信息"、"安全策略" 等硬编码中文 | 同上 |
| [profile-settings-form.tsx](file:///e:/Desktop/CICD/src/modules/settings/components/profile-settings-form.tsx) L80-82 | "Profile Information"、"Update your personal information." 硬编码 | 同上 |
| [notification-preferences-form.tsx](file:///e:/Desktop/CICD/src/modules/settings/components/notification-preferences-form.tsx) L47-99 | CHANNELS/CATEGORIES 数组中 label/description 全部硬编码 | 同上 |
| [password-change-form.tsx](file:///e:/Desktop/CICD/src/modules/settings/components/password-change-form.tsx) L72-76 | "Change Password"、"Choose a strong password..." 硬编码 | 同上 |
| [theme-preferences-card.tsx](file:///e:/Desktop/CICD/src/modules/settings/components/theme-preferences-card.tsx) L24-28 | "Theme"、"Choose how the admin console looks..." 硬编码(且写死 "admin console" | 同上 |
| [ai-provider-settings-card.tsx](file:///e:/Desktop/CICD/src/modules/settings/components/ai-provider-settings-card.tsx) L251-257 | "AI Providers"、"Manage AI vendors..." 硬编码 | 同上 |
| [profile/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/profile/page.tsx) 全文 | "Profile"、"Personal Information"、"Account Information" 等硬编码 | 同上 |
| [settings/loading.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/settings/loading.tsx) 等错误页 | "页面加载失败" 中文硬编码,与英文页面不统一 | 同上 |
| `src/shared/i18n/messages/{zh-CN,en}/` | **无 settings.json / profile.json** | i18n 命名空间缺失 |
| `src/i18n/request.ts` | 未加载 settings/profile 命名空间 | 同上 |
**原因**settings 模块在历次重构中未纳入 i18n 改造范围,`i18n/request.ts` 只加载 6 个命名空间common/auth/onboarding/classes/errors/dashboard
**后果**无法支持中英文切换admin 端中文、其他端英文,体验割裂;新增语言需逐文件修改。
### 2.2 跨模块 Action 直调违反解耦原则P0
| 位置 | 问题 | 违反规则 |
|------|------|----------|
| [profile-settings-form.tsx](file:///e:/Desktop/CICD/src/modules/settings/components/profile-settings-form.tsx) L16 | `import { updateUserProfile } from "@/modules/users/actions"` | "模块内部组件绝不直接 import 其他业务模块的 actions 或 data-access只能通过注入的接口调用" |
| [notification-preferences-form.tsx](file:///e:/Desktop/CICD/src/modules/settings/components/notification-preferences-form.tsx) L16 | `import { updateNotificationPreferencesAction } from "@/modules/messaging/actions"` | 同上 |
| [settings-view.tsx](file:///e:/Desktop/CICD/src/modules/settings/components/settings-view.tsx) L28 | `import { UserProfile } from "@/modules/users/data-access"` | 类型导入,语法允许但耦合类型定义 |
| [settings-view.tsx](file:///e:/Desktop/CICD/src/modules/settings/components/settings-view.tsx) L29 | `import type { NotificationPreferences } from "@/modules/notifications/types"` | 类型导入,可接受 |
**原因**settings 组件直接消费 users/messaging 模块的 Server Action未通过接口抽象 + Context 注入。
**后果**settings 模块无法独立测试mock users/messaging action 困难users/messaging action 签名变更会直接破坏 settings 组件;无法在不修改 settings 组件的前提下替换数据源。
### 2.3 AdminSettingsView 是 mock 实现P0
| 位置 | 问题 | 违反规则 |
|------|------|----------|
| [admin-settings-view.tsx](file:///e:/Desktop/CICD/src/modules/settings/components/admin-settings-view.tsx) L20-25 | `await new Promise((r) => setTimeout(r, 800))` 模拟保存,无 Server Action 调用 | "app/ 只能调用 modules/ 的 Server Actions 和 data-access不直接访问数据库" — 这里连 action 都没调 |
| 同文件 L23 | `toast.success("设置已保存")` 撒谎,实际未保存 | 用户体验问题 |
| 同文件全文 | 4 个 Card学校信息/安全策略/文件上传/通知配置)的输入框无 `name` 属性、无表单提交逻辑 | 表单不可用 |
| [admin/settings/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/admin/settings/page.tsx) | 与 `/settings` 页面割裂admin 用户有两个设置入口 | 信息架构混乱 |
**原因**:初版占位实现,后续未接入真实数据层。
**后果**admin 调整的安全策略/文件上传限制/通知配置均不生效;与 `/settings` 页面功能重叠但行为不一致。
### 2.4 角色路由硬编码非配置驱动P1
| 位置 | 问题 | 违反规则 |
|------|------|----------|
| [settings/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/settings/page.tsx) L28-44 | `if (roles.includes("admin")) ... if (roles.includes("student")) ...` 4 分支硬编码 | "采用配置驱动设计,例如通过角色配置决定该模块渲染哪些 Widget/子模块" |
| [profile/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/profile/page.tsx) L48-49 | `const isStudent = roles.includes("student")` | 同上 |
**原因**:角色分发逻辑写在页面层,未抽取为配置。
**后果**:新增角色(如 grade_head需修改页面代码角色与设置视图的映射关系不可配置。
### 2.5 缺少分区 Error Boundary 和 SuspenseP1
| 位置 | 问题 | 违反规则 |
|------|------|----------|
| [settings-view.tsx](file:///e:/Desktop/CICD/src/modules/settings/components/settings-view.tsx) L132-184 | 5 个 TabsContent 内部组件ProfileSettingsForm / NotificationPreferencesForm / ThemePreferencesCard / PasswordChangeForm / AiProviderSettingsCard无独立 Error Boundary | "每个独立的数据区块必须用 React Error Boundary 包裹" |
| 同上 | AiProviderSettingsCard 在 useEffect 中异步加载 providers无 Suspense 包裹 | "异步数据使用 React Suspense + 骨架屏" |
| [profile/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/profile/page.tsx) L229-314 | 学生概览 / 教师概览区块无独立 Error Boundary | 同上 |
**原因**:仅依赖页面级 `error.tsx` / `loading.tsx`,未做分区隔离。
**后果**AI Provider 加载失败会导致整个 Security 标签页崩溃ProfileSettingsForm 提交失败不会优雅降级。
### 2.6 Profile 页面职责臃肿P1
| 位置 | 问题 | 违反规则 |
|------|------|----------|
| [profile/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/profile/page.tsx) L37-317 | 单文件 317 行,混合:用户基本信息展示 + 学生作业统计 + 课表筛选 + 教师班级展示 | "页面组件" 建议 ≤ 500 行(虽未超限,但职责过多) |
| 同文件 L51-110 | 学生分支内联 60 行业务计算dueSoonCount / overdueCount / gradedCount / upcomingAssignments 排序) | "数据获取、计算、格式化等纯逻辑全部放入纯函数或 hooks与 UI 分离" |
| 同文件 L27-35 | `WEEKDAY_MAP` / `toWeekday` 日期工具函数定义在页面文件内 | 同上 |
**原因**profile 页面直接编排了 dashboard 模块的学生概览组件,未通过 service 层。
**后果**:业务逻辑不可测试、不可复用;学生/教师概览与 dashboard 模块重复。
### 2.7 类型安全与表单规范问题P2
| 位置 | 问题 | 违反规则 |
|------|------|----------|
| [profile-settings-form.tsx](file:///e:/Desktop/CICD/src/modules/settings/components/profile-settings-form.tsx) L44 | `zodResolver(profileFormSchema) as Resolver<ProfileFormValues>` 使用 `as` 断言 | "禁止 `as` 断言(除非从 `unknown` 转换或测试中)" |
| [notification-preferences-form.tsx](file:///e:/Desktop/CICD/src/modules/settings/components/notification-preferences-form.tsx) L121 | `useActionState(updateNotificationPreferencesAction, null)` 第二参数 `null` 类型不安全 | 应为 `ActionState<null>` 初值 |
| [admin-settings-view.tsx](file:///e:/Desktop/CICD/src/modules/settings/components/admin-settings-view.tsx) L22 | `await new Promise((r) => setTimeout(r, 800))` 参数 `r` 隐式 any | "禁止 any" |
### 2.8 可访问性缺失P2
| 位置 | 问题 | 违反规则 |
|------|------|----------|
| [settings-view.tsx](file:///e:/Desktop/CICD/src/modules/settings/components/settings-view.tsx) L106-130 | Tabs 组件虽有 Radix 内置 a11y但 TabsTrigger 仅有图标+文字,无 `aria-label` | "可访问性a11y语义化标签、ARIA 属性、键盘导航" |
| [notification-preferences-form.tsx](file:///e:/Desktop/CICD/src/modules/settings/components/notification-preferences-form.tsx) L198-205 | 隐藏 checkbox + Switch 双控件模式,屏幕阅读器可能重复朗读 | 同上 |
| [password-change-form.tsx](file:///e:/Desktop/CICD/src/modules/settings/components/password-change-form.tsx) L90-99 | 密码显示切换按钮 `tabIndex={-1}`,键盘用户无法触达 | "键盘导航" |
### 2.9 监控埋点缺失P2
| 位置 | 问题 | 违反规则 |
|------|------|----------|
| 全模块 | 无任何埋点接口预留密码修改成功率、AI Provider 测试通过率、通知偏好变更频率等) | "监控:方案中预留关键操作埋点接口" |
### 2.10 行业差距安全功能单薄P2
| 缺失功能 | 影响 |
|----------|------|
| 头像上传 | 用户无法个性化头像profile 页只能显示文字 fallback |
| 两步验证2FA/MFA | K12 系统涉及学生隐私,仅密码保护不够 |
| 活跃会话管理 | 用户无法查看/远程登出其他设备会话 |
| 登录历史查看 | 非管理员用户无法查看自己的登录记录 |
| 账号数据导出/注销 | 不符合 GDPR-like 合规要求 |
| 通知预览 | 通知偏好表单无"发送测试通知"功能 |
| 设置搜索 | 设置项较多时无快速定位 |
---
## 三、行业差距对比
### 3.1 与优秀 K12 产品的差距
| 维度 | 优秀实践Google Classroom / PowerSchool / Veracross | 当前状态 | 差距影响 |
|------|--------------------------------------------------------|----------|----------|
| **设置信息架构** | 统一入口,按角色动态显示分组,支持搜索 | admin 有两个入口(`/admin/settings` mock + `/settings`),其他角色统一 | admin 体验割裂,功能不可用 |
| **个人资料** | 头像上传 + 字段级权限可见性(学生看不到自己手机号,家长可见) | 无头像上传,所有字段对本人可见 | 个性化缺失,字段级权限未实现 |
| **安全中心** | 2FA、会话列表、登录历史、密码泄露检测 | 仅密码修改 | K12 数据安全合规风险 |
| **通知偏好** | 按事件类型细分(作业/成绩/考勤/公告/消息),支持渠道矩阵 + 免打扰 | 已有基础,但无"测试通知"按钮 | 功能完整度尚可,交互反馈缺失 |
| **主题/语言** | 主题切换 + 语言切换同页 | 主题有,语言切换在 shared 但未集成到设置页 | 用户需到别处找语言切换 |
| **AI 配置** | 多 Provider + 测试 + 用量统计 | 多 Provider + 测试,无用量统计 | 教育机构无法监控 AI 成本 |
| **空状态/骨架屏** | 每个数据区块独立骨架屏 + 空状态 | 仅页面级 loading.tsx | 局部加载失败时整页白屏 |
### 3.2 多角色使用习惯差距
| 角色 | 优秀实践 | 当前状态 |
|------|----------|----------|
| **admin** | 系统设置(学校信息/策略)与个人设置在同一入口的不同分组 | 两套页面割裂,系统设置是 mock |
| **teacher** | 设置页可快速跳转常用教学功能 | ✅ 有 Quick linksTeacherSettingsView |
| **parent** | 设置页可切换查看不同孩子的通知偏好 | 仅一套偏好,无法按孩子细分 |
| **student** | 设置页简洁,无系统配置 | ✅ 简洁 |
---
## 四、改进优先级建议
### P0紧急影响安全/合规/核心功能)
1. **创建 settings i18n 命名空间**:新增 `zh-CN/settings.json` + `en/settings.json`,覆盖所有设置/个人资料文本;更新 `i18n/request.ts` 加载新命名空间。
2. **消除跨模块 action 直调**:定义 `SettingsService` 接口(含 `updateProfile` / `updateNotificationPreferences` 方法),通过 React Context 注入;`ProfileSettingsForm` / `NotificationPreferencesForm` 改为消费 Context。
3. **AdminSettingsView 接入真实数据层**:将 4 个 Card学校信息/安全策略/文件上传/通知配置)接入 `school/data-access` 或新增 `system-settings` data-access移除 mock `setTimeout`
### P1重要影响可维护性/体验)
4. **配置驱动角色路由**:新增 `settings-config.ts`,定义 `Role → SettingsViewConfig` 映射description / backHref / generalExtra`/settings/page.tsx` 改为查表分发。
5. **分区 Error Boundary + Suspense**:为每个 TabsContent 内部组件包裹 `<ErrorBoundary>` + `<Suspense fallback={<Skeleton/>}>`
6. **Profile 页面拆分**:将学生概览/教师概览业务逻辑抽为 `useStudentProfileOverview` / `useTeacherProfileOverview` hooks`WEEKDAY_MAP`/`toWeekday` 移至 `shared/lib/utils`
7. **移除 `as` 断言**`profile-settings-form.tsx``zodResolver(...) as Resolver<...>` 改为类型兼容写法。
### P2优化提升完整度
8. **头像上传**profile 页新增头像上传组件(复用 `files/data-access`)。
9. **2FA / 会话管理**security 标签页新增 2FA 开关 + 活跃会话列表。
10. **通知测试按钮**:通知偏好表单新增"发送测试通知"按钮。
11. **语言切换集成**:在 Appearance 标签页集成 `LocaleSwitcher`
12. **埋点接口**:在 `SettingsService` 接口预留 `trackEvent` 方法。
13. **a11y 修复**:密码显示切换按钮移除 `tabIndex={-1}`;通知偏好表单移除冗余隐藏 checkbox。
---
## 五、架构图同步说明
本次审计发现架构图需补充/修改以下节点:
### 5.1 `004_architecture_impact_map.md` §2.23 settings 模块
- **修改"已知问题"**:新增"跨模块 action 直调未修复"`profile-settings-form``users/actions``notification-preferences-form``messaging/actions`
- **修改"已知问题"**:新增"AdminSettingsView 为 mock 实现,无数据持久化"
- **修改"已知问题"**:新增"i18n 完全缺失,所有文本硬编码"
- **修改"依赖关系"**:明确标注 `profile-settings-form.tsx` 依赖 `users/actions`action 级,非 data-access
- **新增"文件清单"**:补充 `profile/page.tsx`317 行)的归属说明(虽在 app 层,但编排 settings 相关数据)
### 5.2 `005_architecture_data.json` settings 节点
- **`modules.settings.knownIssues`**:新增 3 条(跨模块 action 直调 / AdminSettingsView mock / i18n 缺失)
- **`modules.settings.exports`**:补充 `SettingsService` 接口(重构后新增)
- **`dependencyMatrix`**settings → users 的依赖类型从 `data-access` 改为 `action`(标注为待修复)
### 5.3 `004` §2.17 notifications 模块
- **修正不一致**`notification-preferences-form.tsx` 仍从 `messaging/actions` 导入 action但架构图称"通知偏好已迁移至 notifications 模块" — 需标注"表单层 action 调用未同步迁移"
---
## 六、重构方案设计
### 6.1 完全解耦SettingsService 接口 + Context 注入
```typescript
// src/modules/settings/types.ts (新增)
export interface ProfileService {
getProfile: () => Promise<UserProfile | null>
updateProfile: (input: UpdateProfileInput) => Promise<ActionState<UserProfile>>
}
export interface NotificationService {
getPreferences: () => Promise<NotificationPreferences>
updatePreferences: (input: UpdateNotificationPreferencesInput) => Promise<ActionState<null>>
}
export interface SettingsService {
profile: ProfileService
notifications: NotificationService
trackEvent?: (event: string, payload?: Record<string, unknown>) => void
}
```
```tsx
// src/modules/settings/components/settings-service-context.tsx (新增)
const SettingsServiceContext = createContext<SettingsService | null>(null)
export function SettingsServiceProvider({ service, children }: { service: SettingsService; children: ReactNode }) {
return <SettingsServiceContext.Provider value={service}>{children}</SettingsServiceContext.Provider>
}
export function useSettingsService(): SettingsService {
const ctx = useContext(SettingsServiceContext)
if (!ctx) throw new Error("useSettingsService must be used within SettingsServiceProvider")
return ctx
}
```
页面层注入实现:
```tsx
// /settings/page.tsx
const serverService: SettingsService = {
profile: {
getProfile: async () => getUserProfile(userId),
updateProfile: async (input) => updateUserProfile(input),
},
notifications: {
getPreferences: async () => getNotificationPreferences(userId),
updatePreferences: async (input) => updateNotificationPreferencesAction(null, input),
},
}
return <SettingsServiceProvider service={serverService}><SettingsView {...} /></SettingsServiceProvider>
```
### 6.2 组合优先:角色配置驱动
```typescript
// src/modules/settings/config/role-settings-config.ts (新增)
export interface RoleSettingsConfig {
description: string
backHref: string
generalExtra?: ReactNode
}
export const ROLE_SETTINGS_CONFIG: Partial<Record<Role, RoleSettingsConfig>> = {
admin: { description: "settings.admin.description", backHref: "/admin/dashboard" },
teacher: { description: "settings.teacher.description", backHref: "/teacher/dashboard", generalExtra: <TeacherQuickLinks /> },
student: { description: "settings.student.description", backHref: "/student/dashboard", generalExtra: <StudentQuickLinks /> },
parent: { description: "settings.parent.description", backHref: "/parent/dashboard", generalExtra: <ParentQuickLinks /> },
}
```
### 6.3 国际化就绪:翻译文件结构
```json
// src/shared/i18n/messages/zh-CN/settings.json
{
"title": "设置",
"backToDashboard": "返回仪表盘",
"tabs": {
"general": "通用",
"notifications": "通知",
"appearance": "外观",
"security": "安全",
"ai": "AI"
},
"profile": {
"title": "个人信息",
"description": "更新您的个人资料",
"fields": {
"name": "姓名",
"email": "邮箱",
"phone": "电话",
"address": "地址",
"gender": "性别",
"age": "年龄",
"role": "角色"
}
},
"notifications": {
"title": "通知偏好",
"channels": { "push": "推送通知", "email": "邮件", "sms": "短信" },
"categories": { "messages": "消息", "announcements": "公告", "homework": "作业", "grades": "成绩", "attendance": "考勤" },
"quietHours": { "title": "免打扰时段", "enable": "启用", "start": "开始时间", "end": "结束时间" }
},
"security": {
"changePassword": { "title": "修改密码", "current": "当前密码", "new": "新密码", "confirm": "确认密码" },
"session": { "title": "会话", "signOut": "退出登录" }
},
"appearance": { "theme": { "title": "主题", "system": "跟随系统", "light": "浅色", "dark": "深色" } },
"ai": { "providers": { "title": "AI 服务商", "test": "测试", "save": "保存" } }
}
```
### 6.4 错误与边界处理
每个 TabsContent 内部组件用 `<ErrorBoundary>` + `<Suspense>` 包裹:
```tsx
<TabsContent value="ai">
<ErrorBoundary fallback={<SettingsSectionError />}>
<Suspense fallback={<AiProviderSkeleton />}>
<AiProviderSettingsCard />
</Suspense>
</ErrorBoundary>
</TabsContent>
```
### 6.5 可测试性
- `SettingsService` 接口可 mock组件单测无需真实 DB
- `WEEKDAY_MAP` / `toWeekday` 移至 `shared/lib/utils` 后可独立测试
- 学生概览计算逻辑抽为 `useStudentProfileOverview` hook可独立测试
### 6.6 可扩展性
- 新增角色只需在 `ROLE_SETTINGS_CONFIG` 添加条目
- 新增设置标签页只需在 `settings-view.tsx` 的 tabs 配置添加条目
- 新增系统设置 Card 只需在 `AdminSettingsView` 组合新 Card
### 6.7 企业级补充
- **a11y**:密码显示切换按钮移除 `tabIndex={-1}`;通知偏好表单移除冗余隐藏 checkbox仅用 Switch + `name` 属性
- **性能**SettingsView 保持客户端组件(需 URL searchParams但各标签页内容组件按需加载
- **安全**`SettingsService` 实现在 Server Action 层调用 `requirePermission`,组件层不绕过
- **监控**`SettingsService.trackEvent` 预留埋点接口