refactor(grades,diagnostic): 完成成绩和学情诊断模块审计 P1+P2 改进项

P1-1: 抽取 stats-service.ts,将 8 个统计计算纯函数从 data-access 层分离
P1-5: 创建 WidgetBoundary 组件 + 补齐 teacher 路由 loading.tsx/error.tsx (14 文件)
P1-6: 同步架构图文档 004/005,新增 stats-service 与 widget-boundary 节点
P2-1: 补充 a11y ARIA 属性(5 图表 role=img + aria-label,2 表格 caption,3 列表 role=list,3 按钮 aria-label)
P2-3: 修复班级报告 studentId 字段语义错误(schema 改为可空 + 迁移 + 代码适配)
P2-4: 修复 grade_managed scope 返回空数据(改为子查询 classes 表按 gradeId 过滤)
P2-5: 新增 /parent/diagnostic/ 页面(多子女学情诊断聚合 + loading + error)
P2-6: 统一 SearchParams 工具(student/grades 和 management/grade/insights 改用 @/shared/lib/search-params)
This commit is contained in:
SpecialX
2026-06-22 17:07:32 +08:00
parent e997abaf5e
commit 5f3a1a4662
41 changed files with 9043 additions and 381 deletions

View File

@@ -694,7 +694,10 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
- ✅ P1-3 已修复:~~12 个查询/分析 Action 缺少 Zod 校验~~ 新增 12 个查询 schemaDeleteGradeRecordSchema/GetGradeRecordByIdSchema/GradeQuerySchema/ClassGradeStatsQuerySchema/StudentGradeSummaryQuerySchema/ClassRankingQuerySchema/ExportGradesSchema/GradeTrendQuerySchema/ClassComparisonQuerySchema/SubjectComparisonQuerySchema/GradeDistributionQuerySchema/RankingTrendQuerySchema所有 Action 使用 safeParse 校验
- ✅ P1-4 已修复:~~`batch-grade-entry.tsx`/`grade-record-form.tsx`/`grade-distribution-chart.tsx` 中存在 `as` 断言~~ 改用类型守卫函数isGradeType/isSemester/isDistributionTooltipPayload
- ✅ P1-5 已修复:~~teacher/grades 与 teacher/diagnostic 路由缺少 loading.tsx/error.tsx~~ 已为 7 个路由补齐 loading.tsx + error.tsx并新增 `WidgetBoundary` 通用组件
- ✅ P2-1 已修复:~~图表/表格/列表缺少 a11y ARIA 属性~~ 为 4 个成绩图表添加 `role="img"` + `aria-label`2 个表格添加 `<caption>`3 个图标按钮添加 `aria-label`
- ✅ P2-2 已修复:~~diagnostic 组件中存在 Tailwind 任意值~~ 改用标准 Tailwind 类
- ✅ P2-4 已修复:~~`buildScopeClassFilter``grade_managed` scope 返回 `sql\`1=0\``(空数据)~~ 改为子查询 `classId IN (SELECT id FROM classes WHERE grade_id IN (...))` 过滤所管年级的班级
- ✅ P2-6 已修复:~~student/grades 和 management/grade/insights 各自重复定义 `SearchParams` 类型与 `getParam` 函数~~ 改为统一从 `@/shared/lib/search-params` 导入
- ✅ actions 层无直接 DB 访问(标杆)
- ✅ data-access 按职责拆分为 3 个文件(标杆)
- ✅ P2 已修复:`export.ts``scoreMap.get(r.studentId)!` 非空断言清理为安全守卫(`if (!subjMap) continue`
@@ -1312,6 +1315,7 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
|------|------|------|
| `/parent/dashboard` | `dashboard/page.tsx` + `loading.tsx` | 家长仪表盘 |
| `/parent/grades` | `grades/page.tsx` + `loading.tsx` | 多子女成绩聚合 |
| `/parent/diagnostic` | `diagnostic/page.tsx` + `loading.tsx` + `error.tsx` | P2-5 新增:多子女学情诊断聚合 |
| `/parent/attendance` | `attendance/page.tsx` + `loading.tsx` | 多子女考勤聚合v4 新增预警横幅) |
| `/parent/leave` | `leave/page.tsx` + `loading.tsx` | v4 新增:请假申请(占位) |
| `/parent/children/[studentId]` | `children/[studentId]/page.tsx` + `loading.tsx` | 子女详情页v4 重构Tab 布局 + 多子女切换) |
@@ -1420,7 +1424,8 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
- ✅ P2 已修复:~~`getDiagnosticReports``conditions` 隐式 `any[]`~~ 改为显式 `SQL[]` 类型标注
- ✅ P0-2 已修复:~~`data-access-reports.ts` 直查 `users` 表获取姓名~~ 改为通过 `users/data-access.getUserNamesByIds` 跨模块接口
- ✅ P2-2 已修复:~~`class-diagnostic-view.tsx`/`student-diagnostic-view.tsx`/`mastery-radar-chart.tsx` 中存在 Tailwind 任意值~~ 改用标准 Tailwind 类w-44/max-w-32/text-xs/h-96/max-w-lg
- ⚠️ P2:班级报告将生成者 ID 存入 `studentId` 字段schema 设计缺陷 workaround
- P2-1 已修复:~~图表/表格/列表缺少 a11y ARIA 属性~~ 为 5 个图表添加 `role="img"` + `aria-label`2 个表格添加 `<caption>`3 个列表添加 `role="list"`3 个图标按钮添加 `aria-label`
- ✅ P2-3 已修复:~~班级报告将生成者 ID 存入 `studentId` 字段schema 设计缺陷 workaround~~ schema `learningDiagnosticReports.studentId` 改为可空,班级报告 `studentId` 置空,读取逻辑适配 null
- ✅ 与 grades 模块无职责重叠grades 管分数diagnostic 管知识点掌握度)
**文件清单**

View File

@@ -6184,6 +6184,104 @@
"usedBy": [
"components/password-change-form.tsx"
]
},
{
"name": "updateUserAvatarAction",
"file": "actions-avatar.ts",
"permission": "USER_PROFILE_UPDATE",
"signature": "(imageUrl: string) => Promise<ActionState<{ image: string }>>",
"purpose": "更新用户头像 URLP2-8 新增:文件上传通过 /api/upload 路由完成,此 action 仅更新 users.image 字段)",
"deps": [
"requirePermission",
"users/data-access.updateUserAvatar"
],
"usedBy": [
"components/avatar-upload.tsx"
]
},
{
"name": "removeUserAvatarAction",
"file": "actions-avatar.ts",
"permission": "USER_PROFILE_UPDATE",
"signature": "() => Promise<ActionState<null>>",
"purpose": "移除用户头像P2-8 新增)",
"deps": [
"requirePermission",
"users/data-access.updateUserAvatar"
],
"usedBy": [
"components/avatar-upload.tsx"
]
},
{
"name": "sendTestNotificationAction",
"file": "actions-notifications.ts",
"permission": "USER_PROFILE_UPDATE",
"signature": "(input: { channel: 'push' | 'email' | 'sms' }) => Promise<ActionState<null>>",
"purpose": "发送测试通知P2-10 新增:占位实现,待接入真实通知发送服务)",
"deps": [
"requirePermission"
],
"usedBy": [
"components/notification-preferences-form.tsx"
]
},
{
"name": "getAdminSystemSettingsAction",
"file": "actions-system-settings.ts",
"permission": "SETTINGS_ADMIN",
"signature": "() => Promise<ActionState<AdminSettingsFormValues>>",
"purpose": "获取管理员系统设置P0-3 新增:从 system_settings 表加载 4 个分类配置)",
"deps": [
"requirePermission",
"data-access-system-settings.getAllSystemSettings"
],
"usedBy": [
"components/admin-settings-view.tsx"
]
},
{
"name": "saveAdminSystemSettingsAction",
"file": "actions-system-settings.ts",
"permission": "SETTINGS_ADMIN",
"signature": "(values: AdminSettingsFormValues) => Promise<ActionState<null>>",
"purpose": "保存管理员系统设置P0-3 新增4 分类 Zod 校验 + 批量 upsert",
"deps": [
"requirePermission",
"data-access-system-settings.upsertSystemSettings"
],
"usedBy": [
"components/admin-settings-view.tsx"
]
},
{
"name": "getSecurityCenterAction",
"file": "actions-security.ts",
"permission": "USER_PROFILE_UPDATE",
"signature": "() => Promise<ActionState<SecurityCenterData>>",
"purpose": "获取安全中心数据P2-9 新增2FA 状态 + 最近 10 条登录历史)",
"deps": [
"requirePermission",
"data-access-system-settings.getSystemSetting",
"shared.db.schema.loginLogs"
],
"usedBy": [
"components/security-center-card.tsx"
]
},
{
"name": "toggleTwoFactorAction",
"file": "actions-security.ts",
"permission": "USER_PROFILE_UPDATE",
"signature": "(enabled: boolean) => Promise<ActionState<TwoFactorStatus>>",
"purpose": "启用/禁用 2FAP2-9 新增:占位实现,仅记录用户偏好,未接入真实 TOTP 校验)",
"deps": [
"requirePermission",
"data-access-system-settings.upsertSystemSetting"
],
"usedBy": [
"components/security-center-card.tsx"
]
}
],
"dataAccess": [
@@ -6276,6 +6374,56 @@
"shared.db",
"shared.db.schema.passwordSecurity"
]
},
{
"name": "getSystemSettingsByCategory",
"signature": "(category: SystemSettingCategory) => Promise<SystemSettingRecord[]>",
"file": "data-access-system-settings.ts",
"purpose": "获取指定分类下所有设置项P0-3 新增)",
"deps": [
"shared.db",
"shared.db.schema.systemSettings"
]
},
{
"name": "getAllSystemSettings",
"signature": "() => Promise<SystemSettingRecord[]>",
"file": "data-access-system-settings.ts",
"purpose": "获取所有系统设置项P0-3 新增)",
"deps": [
"shared.db",
"shared.db.schema.systemSettings"
]
},
{
"name": "getSystemSetting",
"signature": "(category: SystemSettingCategory, key: string) => Promise<SystemSettingRecord | null>",
"file": "data-access-system-settings.ts",
"purpose": "获取单个设置项P0-3 新增)",
"deps": [
"shared.db",
"shared.db.schema.systemSettings"
]
},
{
"name": "upsertSystemSetting",
"signature": "(params: { category, key, value, valueType, updatedBy? }) => Promise<void>",
"file": "data-access-system-settings.ts",
"purpose": "插入或更新设置项P0-3 新增)",
"deps": [
"shared.db",
"shared.db.schema.systemSettings"
]
},
{
"name": "upsertSystemSettings",
"signature": "(items: Array<{ category, key, value, valueType }>, updatedBy?: string) => Promise<void>",
"file": "data-access-system-settings.ts",
"purpose": "批量 upsert 设置项P0-3 新增)",
"deps": [
"shared.db",
"shared.db.schema.systemSettings"
]
}
],
"types": [
@@ -6347,11 +6495,39 @@
},
{
"name": "AdminSettingsView",
"purpose": "系统设置视图(学校信息/安全策略/文件上传/通知配置 4 个 Cardi18n消费方/admin/settings 页面",
"purpose": "系统设置视图(P0-3 已修复:从 mock 改为真实数据层,通过 Server Actions 加载/保存到 system_settings 表4 个 Card学校信息/安全策略/文件上传/通知配置i18nsettings.admin",
"deps": [
"getAdminSystemSettingsAction",
"saveAdminSystemSettingsAction"
],
"usedBy": [
"app/(dashboard)/admin/settings/page.tsx"
]
},
{
"name": "AvatarUpload",
"file": "components/avatar-upload.tsx",
"purpose": "头像上传/预览/删除客户端组件P2-8 新增:文件通过 /api/upload 上传,调用 updateUserAvatarAction 更新 users.image验证 JPEG/PNG/WebP/GIF + 2MB 上限i18nsettings.profile.avatar",
"deps": [
"updateUserAvatarAction",
"removeUserAvatarAction"
],
"usedBy": [
"app/(dashboard)/profile/page.tsx"
]
},
{
"name": "SecurityCenterCard",
"file": "components/security-center-card.tsx",
"purpose": "安全中心卡片P2-9 新增2FA 开关 + 最近 10 条登录历史2FA 状态存储在 system_settings 表;登录历史来自 login_logs 表i18nsettings.security.center",
"deps": [
"getSecurityCenterAction",
"toggleTwoFactorAction"
],
"usedBy": [
"components/settings-view.tsx"
]
},
{
"name": "ProfileSettingsForm",
"purpose": "个人资料设置表单(通过 useSettingsService().profile.updateProfile 调用i18nsettings.profile",
@@ -6363,7 +6539,10 @@
},
{
"name": "ThemePreferencesCard",
"purpose": "主题偏好卡片i18nsettings.appearance"
"purpose": "主题偏好卡片i18nsettings.appearanceP2-11 已增强:集成 LocaleSwitcher 语言切换到 Appearance 标签页)",
"deps": [
"shared/components/locale-switcher"
]
},
{
"name": "SettingsView",
@@ -6436,7 +6615,7 @@
{
"name": "NotificationPreferencesForm",
"file": "components/notification-preferences-form.tsx",
"purpose": "通知偏好设置表单Switch 切换 email/sms/push 通道 + 5 个分类开关;通过 useSettingsService().notifications.updatePreferences 调用i18nsettings.notifications",
"purpose": "通知偏好设置表单Switch 切换 email/sms/push 通道 + 5 个分类开关;通过 useSettingsService().notifications.updatePreferences 调用i18nsettings.notificationsP2-10 已增强:每个已启用渠道旁显示测试按钮,调用 sendTestNotificationAction",
"deps": [
"useSettingsService",
"shared/components/ui/switch",
@@ -15313,6 +15492,17 @@
"permission": "grade_record:read",
"description": "家长成绩视图(按 DataScope.children 过滤v4 新增 ParentExportButton 占位)"
},
"/parent/diagnostic": {
"component": "子女学情诊断",
"type": "server",
"module": "diagnostic",
"dataAccess": [
"diagnostic/data-access.getStudentMasterySummary",
"diagnostic/data-access-reports.getDiagnosticReports"
],
"permission": "diagnostic:read",
"description": "P2-5 新增:家长查看多子女学情诊断(按 DataScope.children 遍历,复用 StudentDiagnosticView 组件;含 loading.tsx + error.tsx"
},
"/parent/attendance": {
"component": "StudentAttendanceView (per child) + ParentAttendanceWarning",
"type": "server",

View File

@@ -326,25 +326,25 @@
### P1较严重 — 架构与质量)
| # | 问题 | 改进方向 |
|---|------|----------|
| P1-1 | 统计业务逻辑混入 data-access | 抽取 `grades/stats-service.ts`,将 `getClassGradeStats`/`getClassComparison`/`getSubjectComparison`/`getGradeDistribution`/`getRankingTrend` 的统计计算迁移至纯函数(参考 homework/stats-service.ts 范例) |
| P1-2 | 重复工具函数 | 抽取 `grades/lib/scope-filter.ts``buildScopeClassFilter`)和 `grades/lib/stats-utils.ts``toNumber`/`normalize` |
| P1-3 | 12 个 Action 缺失 Zod 校验 | 为 `deleteGradeRecordAction`/`getGradeRecordsAction`/`getClassGradeStatsAction`/`getStudentGradeSummaryAction`/`getClassRankingAction`/`getGradeRecordByIdAction`/`exportGradesAction` + 5 个 analytics Action 创建对应 Zod schema |
| P1-4 | `as` 断言违规4 处) | 使用类型守卫或 Zod 运行时校验替代 |
| P1-5 | Error Boundary 和 Suspense 缺失 | 创建 `grades/components/widget-boundary.tsx`Error Boundary + Suspense + Skeleton 组合每个数据区块独立包裹teacher/grades 和 teacher/diagnostic 路由补齐 loading.tsx/error.tsx |
| P1-6 | 架构图同步 | 更新 `004` 和 `005` 文档grades 行数、diagnostic deps、新增 stats-service.ts、新增 lib/、补齐 actions-analytics exports |
| # | 问题 | 改进方向 | 状态 |
|---|------|----------|------|
| P1-1 | 统计业务逻辑混入 data-access | 抽取 `grades/stats-service.ts`,将 `getClassGradeStats`/`getClassComparison`/`getSubjectComparison`/`getGradeDistribution`/`getRankingTrend` 的统计计算迁移至纯函数(参考 homework/stats-service.ts 范例) | ✅ 已完成 |
| P1-2 | 重复工具函数 | 抽取 `grades/lib/scope-filter.ts``buildScopeClassFilter`)和 `grades/lib/stats-utils.ts``toNumber`/`normalize` | ✅ 已完成 |
| P1-3 | 12 个 Action 缺失 Zod 校验 | 为 `deleteGradeRecordAction`/`getGradeRecordsAction`/`getClassGradeStatsAction`/`getStudentGradeSummaryAction`/`getClassRankingAction`/`getGradeRecordByIdAction`/`exportGradesAction` + 5 个 analytics Action 创建对应 Zod schema | ✅ 已完成 |
| P1-4 | `as` 断言违规4 处) | 使用类型守卫或 Zod 运行时校验替代 | ✅ 已完成 |
| P1-5 | Error Boundary 和 Suspense 缺失 | 创建 `grades/components/widget-boundary.tsx`Error Boundary + Suspense + Skeleton 组合每个数据区块独立包裹teacher/grades 和 teacher/diagnostic 路由补齐 loading.tsx/error.tsx | ✅ 已完成 |
| P1-6 | 架构图同步 | 更新 `004` 和 `005` 文档grades 行数、diagnostic deps、新增 stats-service.ts、新增 lib/、补齐 actions-analytics exports | ✅ 已完成 |
### P2优化 — 体验与扩展)
| # | 问题 | 改进方向 |
|---|------|----------|
| P2-1 | a11y 无障碍缺失 | 补充 ARIA 属性:图表 `role="img"` + `aria-label`;按钮 `aria-label`;表格 `caption`;列表 `role="list"` |
| P2-2 | Tailwind 任意值 | 移除 `w-[180px]`/`h-[360px]`/`max-w-[520px]`,改用设计令牌或注释说明 |
| P2-3 | 班级报告 studentId 字段语义错误 | 修改 `learningDiagnosticReports` schema将 `studentId` 改为可空,或增加 `classId`/`generatedBy` 字段 |
| P2-4 | grade_managed scope 返回空数据 | 修复 `buildScopeClassFilter`grade_managed scope 应返回所管年级的班级过滤条件 |
| P2-5 | admin/parent 无 diagnostic UI | 新增 `/parent/diagnostic/` 页面家长查看子女诊断报告admin 可复用 teacher 视图 |
| P2-6 | SearchParams 工具未统一 | student/grades 和 management/grade/insights 改用 `@/shared/lib/search-params` |
| # | 问题 | 改进方向 | 状态 |
|---|------|----------|------|
| P2-1 | a11y 无障碍缺失 | 补充 ARIA 属性:图表 `role="img"` + `aria-label`;按钮 `aria-label`;表格 `caption`;列表 `role="list"` | ✅ 已完成 |
| P2-2 | Tailwind 任意值 | 移除 `w-[180px]`/`h-[360px]`/`max-w-[520px]`,改用设计令牌或注释说明 | ✅ 已完成 |
| P2-3 | 班级报告 studentId 字段语义错误 | 修改 `learningDiagnosticReports` schema将 `studentId` 改为可空,或增加 `classId`/`generatedBy` 字段 | ✅ 已完成 |
| P2-4 | grade_managed scope 返回空数据 | 修复 `buildScopeClassFilter`grade_managed scope 应返回所管年级的班级过滤条件 | ✅ 已完成 |
| P2-5 | admin/parent 无 diagnostic UI | 新增 `/parent/diagnostic/` 页面家长查看子女诊断报告admin 可复用 teacher 视图 | ✅ 已完成 |
| P2-6 | SearchParams 工具未统一 | student/grades 和 management/grade/insights 改用 `@/shared/lib/search-params` | ✅ 已完成 |
### P3长期 — 行业对标)