feat(attendance,elective): 实现所有 P2 长期改进项

P2 修复(来自审计报告):
- 2.4.4: Server Action 错误消息 i18n 化(attendance/elective 全部 Action)
- 2.5.3: 抽取 AttendancePageLayout 组件复用(admin/teacher 页面)
- 2.5.4: 抽取 ElectivePageLayout 组件复用(admin/teacher 列表页)
- 2.6.3: 考勤月历键盘导航(tabIndex + 方向键 + Home/End + role=grid)
- 2.8.2: getStudentAttendanceSummary 分页优化(SQL 聚合统计 + LIMIT 分页)
- 2.8.3: resolveCourseDisplayNames 缓存优化(React cache 去重)
- 2.1.4: elective data-access 跨模块依赖接口抽象(resolvers.ts 可注入)

P2 建议项:
- 选课时间冲突检测(parseSchedule + isScheduleConflict 纯函数 + checkScheduleConflict)
- 学分上限校验(MAX_CREDIT_PER_TERM + checkCreditLimit)
- 考勤/选课数据导出 Excel(export.ts + API 路由扩展)

新增文件:
- src/modules/attendance/components/attendance-page-layout.tsx
- src/modules/elective/components/elective-page-layout.tsx
- src/modules/elective/resolvers.ts
- src/modules/attendance/export.ts
- src/modules/elective/export.ts

校验:
- npm run lint 通过(exit 0)
- npx tsc --noEmit attendance/elective/parent 相关零错误
This commit is contained in:
SpecialX
2026-06-23 09:02:41 +08:00
parent c766951374
commit e2e0487a3b
50 changed files with 1514 additions and 411 deletions

View File

@@ -451,7 +451,7 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
| **表单字段** | `TextareaField` | `components/form-fields/textarea-field.tsx` | 通用多行文本字段FormField + Textarea 包装) | 1 个P1-2: create-question-dialog |
| **图表组件** | `ChartCardShell` | `components/charts/chart-card-shell.tsx` | 图表卡片外壳Card+Header+EmptyState+Content 统一结构) | 8 个P3-c |
| **图表组件** | `TrendLineChart` | `components/charts/trend-line-chart.tsx` | 趋势折线图LineChart 统一配置,支持单/多系列) | 8 个P3-c: grade-trend-chart 等) |
| **图表组件** | `SimpleBarChart` | `components/charts/simple-bar-chart.tsx` | 柱状图BarChart 统一配置,支持单/多 Bar + Cell 分桶着色) | 8 个P3-c: grade-distribution-chart 等) |
| **图表组件** | `SimpleBarChart` | `components/charts/simple-bar-chart.tsx` | 柱状图BarChart 统一配置,支持单/多 Bar + Cell 分桶着色 + defs 自定义 SVG 图案 | 8 个P3-c: grade-distribution-chart 等) |
| **图表组件** | `ComparisonRadarChart` | `components/charts/comparison-radar-chart.tsx` | 对比雷达图RadarChart 统一配置,支持双 Radar 对比) | 8 个P3-c: subject-comparison-chart, mastery-radar-chart 等) |
| **课表组件** | `ScheduleList` / `ScheduleListItem` | `components/schedule/schedule-list.tsx` | 课表列表+列表项(课程+时间+地点+班级徽章separator/card 两种变体) | 3 个P3-a: student-today-schedule-card, child-schedule-card, student-schedule-view |
| **题库组件** | `QuestionBankFilters` | `components/question/question-bank-filters.tsx` | 题库筛选栏(搜索+题型+难度default/compact 两种布局) | 2 个P3-d: exam-assembly, question-bank-picker |
@@ -766,6 +766,7 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
- ✅ v3-P2 改进2026-06-23`getGradeTrend`/`getGradeDistribution`/`getSubjectComparison`/`getClassComparison` 均新增 `semester``examId` 可选参数,支持按学期和考试筛选分析
- ✅ v3-P2 改进2026-06-23新增 `getSchoolWideGradeSummary` data-access 函数 + `SchoolWideSummaryCard` 组件 + `SchoolWideGradeSummary`/`SchoolWideGradeSummaryItem` 类型,管理员全校成绩汇总视图(按年级聚合平均分/及格率/优秀率/学生数/班级数加权平均计算全校汇总admin/school/grades/insights/page.tsx 顶部新增 SchoolWideSummaryCard
- ✅ v3-P2 改进2026-06-23parent/grades/page.tsx 为每个子女并行查询 `getClassAverageTrend`,渲染 GradeTrendCard
- ✅ v3-P3-4 改进2026-06-23GradeTrendCard 新增日期范围选择器(全部/近7天/近30天/近90天通过 nuqs `trendRange` URL 参数持久化useEffect 中计算截止时间戳避免渲染阶段调用 Date.now()
- ✅ P3 修复2026-06-23~~`lib/grade-utils.ts` 72 行超 40 行工具函数上限~~ P3-26 将 `buildScopeClassFilter` 迁移至 `lib/scope-filter.ts`grade-utils.ts 仅保留 toNumber/normalize20 行)
- ✅ P3 修复2026-06-23~~`stats-service.ts` createDefaultBuckets 不必要导出~~ P3-10 移除 export 关键字,改为内部函数
- ✅ P3 修复2026-06-23~~`stats-service.ts` buildGradeTrendPoints 使用 as 断言~~ P3-24 新增 isGradeTrendType 类型守卫函数替代 as 断言
@@ -1111,7 +1112,7 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
- ✅ V2-P1-2 已修复:~~MessageList 客户端过滤冗余~~ 客户端过滤仅在初始数据type=all时执行搜索结果已由服务端按 tab 过滤
- ✅ V2-P1-3 已修复:~~消息详情页分散编排~~ 新增 `getMessageDetailPageData` 编排函数,替代 page.tsx 中 `after()` + `getMessageById` + `markMessageAsRead` 的分散编排
- ✅ V2-P1-4 已修复:~~表单无服务端校验错误展示~~ `message-compose.tsx` 新增 `fieldErrors` 状态 + `aria-invalid` 字段级错误展示receiverId/subject/content
- ✅ V2-P2-1 已修复:~~轮询间隔魔法数字~~ `unread-message-badge.tsx` 轮询间隔提取为 `POLL_INTERVAL_MS` 常量60_000ms
- ✅ V2-P2-1 已修复:~~轮询间隔魔法数字~~ `unread-message-badge.tsx` 轮询间隔提取为 `POLL_INTERVAL_MS` 常量(~~60_000ms~~ → 30_000ms✅ V2-P3 与通知组件保持一致
**文件清单**
| 文件 | 行数 | 职责 |
@@ -1128,7 +1129,7 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
| `components/message-list.tsx` | 消息列表(✅ P1-7使用 `useMessageSearch` hook + 客户端分页 UIPAGE_SIZE=20✅ V2-P1-2客户端过滤仅在初始数据时执行 |
| `components/message-detail.tsx` | 消息详情(含回复) |
| `components/message-compose.tsx` | 撰写新消息(✅ V2-P1-4fieldErrors + aria-invalid 字段级错误展示) |
| `components/unread-message-badge.tsx` | 未读消息计数徽章(侧边栏,每 60 秒轮询 `getUnreadMessageCountAction`;✅ V2-P2-1POLL_INTERVAL_MS 常量) |
| `components/unread-message-badge.tsx` | 未读消息计数徽章(侧边栏,每 30 秒轮询 `getUnreadMessageCountAction`;✅ V2-P2-1POLL_INTERVAL_MS 常量;✅ V2-P3间隔从 60s 缩短为 30s 与通知一致 |
**客户端行为**
- `message-list.tsx`:客户端调用 `getMessagesAction` 搜索消息useMessageSearch hook400ms 防抖请求竞态取消V2-P1-2 优化客户端过滤仅在初始数据type=all时执行
@@ -1147,6 +1148,7 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
- Preferences`getNotificationPreferences` / `upsertNotificationPreferences`(✅ P0-4 / P1-5 修复后从 messaging 迁移)
- Channels`InAppChannelSender` / `SmsChannelSender` / `EmailChannelSender` / `WeChatChannelSender`
- Components`NotificationList` / `NotificationDropdown`(✅ P1-4 新增:从 messaging/components 迁移)
- Hooks`useNotificationStream`(✅ V2-P3 新增SSE 实时推送 + 轮询降级 Hook
**依赖关系**
- 依赖:`shared/*``@/auth``classes`(✅ P1-1 已修复:通过 classes data-access.getClassExists/getStudentIdsByClassId
@@ -1160,6 +1162,7 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
- ✅ P2-11 已修复:~~通知标记已读无埋点~~ `markNotificationAsReadAction` / `markAllNotificationsAsReadAction` 新增 `trackEvent` 埋点notification.marked_read / notification.marked_all_read
- ✅ V2-P0-1 已修复:~~通知 i18n 键混在 messages.json 中~~ 新增独立的 `notifications.json` 命名空间zh-CN/en通知组件 `useTranslations``"messages"` 切换到 `"notifications"``src/i18n/request.ts` 新增 notifications 命名空间加载
- ✅ V2-P2-1 已修复:~~轮询间隔魔法数字~~ `notification-dropdown.tsx` 轮询间隔提取为 `POLL_INTERVAL_MS` 常量30_000ms
- ✅ V2-P3 已优化:~~30 秒轮询~~ `notification-dropdown.tsx` 改为 SSE 实时推送(`/api/notifications/stream`SSE 不可用时自动降级为轮询(调用 Server Actions间隔 30 秒)
- ⚠️ P1发送日志仅 console`notification_logs`
- ✅ 渠道抽象优秀(接口 + 工厂 + Mock 实现)
@@ -1174,16 +1177,18 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
| `index.ts` | ~75 | 对外导出入口(✅ P1-4新增组件和 CRUD Action 导出) |
| `channels/*` | 5 文件 | 4 个渠道实现 |
| `components/notification-list.tsx` | ~140 | ✅ P1-4 新增(从 messaging 迁移):通知列表组件 |
| `components/notification-dropdown.tsx` | ~180 | ✅ P1-4 新增(从 messaging 迁移):通知下拉菜单组件 |
| `components/notification-dropdown.tsx` | ~150 | ✅ P1-4 新增(从 messaging 迁移):通知下拉菜单组件;✅ V2-P3改用 SSE 实时推送 + 轮询降级 |
| `hooks/use-notification-stream.ts` | ~195 | ✅ V2-P3 新增SSE 实时推送 HookEventSource + 轮询降级) |
**组件清单**
| 组件 | 职责 |
|------|------|
| `components/notification-list.tsx` | 通知列表(消息页底部,展示所有通知,支持标记已读;✅ V2-P0-1useTranslations 命名空间从 "messages" 切换到 "notifications" |
| `components/notification-dropdown.tsx` | 通知下拉菜单(站点头部,每 30 秒轮询 `getNotificationsAction` + `getUnreadNotificationCountAction`;✅ V2-P0-1useTranslations 命名空间切换;✅ V2-P2-1POLL_INTERVAL_MS 常量) |
| `components/notification-dropdown.tsx` | 通知下拉菜单(站点头部,✅ V2-P3改用 SSE 实时推送 `/api/notifications/stream` + 轮询降级;✅ V2-P0-1useTranslations 命名空间切换;✅ V2-P2-1POLL_INTERVAL_MS 常量) |
**客户端行为**
- `notification-dropdown.tsx``POLL_INTERVAL_MS`30_000ms轮询 `getNotificationsAction`pageSize=10 `getUnreadNotificationCountAction` 刷新通知和未读计数
- `notification-dropdown.tsx`✅ V2-P3 改用 `useNotificationStream` Hook 消费 SSE 实时推送(`/api/notifications/stream`SSE 不可用时自动降级为轮询(调用 `getNotificationsAction` + `getUnreadNotificationCountAction`,间隔 30 秒)
- `hooks/use-notification-stream.ts`:✅ V2-P3 新增,管理 SSE 连接生命周期EventSource onopen/onmessage/onerror降级时通过 `pollFnRef` 调用 Server Actions 轮询
---
@@ -1509,13 +1514,13 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
**导出函数**
- Actions`generateStudentReportAction` / `generateClassReportAction` / `publishReportAction` / `deleteReportAction`v2-P2-3 修复:删除死代码 `getDiagnosticReportsAction` / `getDiagnosticReportByIdAction`,页面直接调用 data-access 并自行权限校验)
- Data-access`updateMasteryFromSubmission`v2-P1-8 修复累积模式v2-P2-5 修复db.transaction 包裹)/ `getStudentMastery` / `getStudentMasterySummary` / `getClassMasterySummary`v2-P2-4 修复totalStudents 语义 + 班级平均掌握度按学生平均)/ `getKnowledgePointStats`v2-P1-7 修复:页面先查班级再传参)
- Data-access-reports`generateDiagnosticReport` / `generateClassDiagnosticReport`v2-P2-6 修复:校验掌握度数据/ `getDiagnosticReports` / `getDiagnosticReportById` / `publishDiagnosticReport` / `deleteDiagnosticReport`(✅ P2 已修复:使用 `React.cache()` 包装实现请求级 memoization
- Stats-service✅ v2-P1-6 新增):`serializeMasteryWithKp` / `computeAverageMastery` / `classifyStrengthsWeaknesses` / `buildStudentMasterySummary` / `aggregateClassMastery` / `computeKpStats` / `computeClassAverageMastery` / `buildStudentsNeedingAttention` / `buildClassMasterySummary` / `buildStudentReportContent` / `buildClassReportContent` / `computeMasteryLevel` / `serializeMastery`(从 data-access / data-access-reports 抽取的纯统计函数)
- Data-access`updateMasteryFromSubmission`v2-P1-8 修复累积模式v2-P2-5 修复db.transaction 包裹)/ `getStudentMastery`P3-19 修复:移除 export改为模块内部函数/ `getStudentMasterySummary`P3-18 修复getUserNamesByIds 与 getStudentMastery 并行查询)/ `getClassMasterySummary`v2-P2-4 修复totalStudents 语义 + 班级平均掌握度按学生平均)/ `getKnowledgePointStats`v2-P1-7 修复:页面先查班级再传参)
- Data-access-reports`generateDiagnosticReport` / `generateClassDiagnosticReport`v2-P2-6 修复:校验掌握度数据P3-27 修复:使用 DiagnosticReportError 结构化错误码)/ `getDiagnosticReports`P3-15 修复:支持分页 limit/offset返回 { reports, total } 结构)/ `getDiagnosticReportById` / `publishDiagnosticReport` / `deleteDiagnosticReport`(✅ P2 已修复:使用 `React.cache()` 包装实现请求级 memoizationP3-1 修复toNumber 从 grades 模块导入)/ `DiagnosticReportError`P3-27 新增:结构化错误码类
- Stats-service✅ v2-P1-6 新增):`serializeMasteryWithKp` / `computeAverageMastery` / `classifyStrengthsWeaknesses`P3-16 修复:弱项阈值从 <60 改为 <80消除 60-79 盲区)/ `buildStudentMasterySummary` / `aggregateClassMastery` / `computeKpStats` / `computeClassAverageMastery` / `buildStudentsNeedingAttention` / `buildClassMasterySummary` / `buildStudentReportContent` / `buildClassReportContent` / `computeMasteryLevel` / `serializeMastery`(从 data-access / data-access-reports 抽取的纯统计函数)
- Schema`GenerateStudentReportSchema` / `GenerateClassReportSchema` / `PublishReportSchema` / `DeleteReportSchema`v2-P2-3 修复:删除死代码 `GetDiagnosticReportsSchema` / `GetDiagnosticReportByIdSchema`
**依赖关系**
- 依赖:`shared/*``@/auth``exams`(✅ P1-1 已修复:通过 exams data-access.getExamSubmissionWithAnswers`questions`(✅ P1-1 已修复:通过 questions data-access.getKnowledgePointsForQuestions`classes`(✅ P1-1 已修复:通过 classes data-access.getClassExists/getClassNameById/getActiveStudentIdsByClassIdv2-P1-7 新增 getStudentActiveClassId`users`(✅ P1-1 已修复:通过 users data-access.getUserNamesByIds/getUserIdsByGradeId
- 依赖:`shared/*``@/auth``exams`(✅ P1-1 已修复:通过 exams data-access.getExamSubmissionWithAnswers`questions`(✅ P1-1 已修复:通过 questions data-access.getKnowledgePointsForQuestions`classes`(✅ P1-1 已修复:通过 classes data-access.getClassExists/getClassNameById/getActiveStudentIdsByClassIdv2-P1-7 新增 getStudentActiveClassId`users`(✅ P1-1 已修复:通过 users data-access.getUserNamesByIds/getUserIdsByGradeId`grades`P3-1 修复:通过 grades/lib/grade-utils.toNumber 复用工具函数)
- 被依赖:无
**已知问题**
@@ -1539,6 +1544,13 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
- ✅ v2-P2-7 已修复:~~`report-list.tsx` 过滤器 Label 缺少 `htmlFor`~~ 添加 `htmlFor``id`
- ✅ 与 grades 模块无职责重叠
- ✅ v3-P2 改进2026-06-23`StudentDiagnosticView` 新增 `practiceHrefBase` propstring | nullnull 时隐藏练习按钮teacher/diagnostic/student/[studentId] 传入 `practiceHrefBase="/teacher/questions"`parent/diagnostic 传入 `practiceHrefBase={null}` 隐藏练习按钮
- ✅ P3-1 已修复2026-06-23~~`data-access-reports.ts` 本地定义 `toNumber``grades/lib/grade-utils.ts` 重复~~ 改为从 grades 模块导入
- ✅ P3-15 已修复2026-06-23~~`getDiagnosticReports` 无分页,可能返回大量数据~~ 添加 limit/offset 分页支持,返回 `{ reports, total }` 结构Promise.all 并行查询总数和数据
- ✅ P3-16 已修复2026-06-23~~强弱项分类存在 60-79 盲区~~ 弱项阈值从 <60 改为 <80确保所有知识点都被分类
- ✅ P3-17 已修复:~~班级报告 strengths 无数量上限~~ `buildClassReportContent` 中 strengths 已限制为前 5 个(按掌握度降序)
- ✅ P3-18 已修复2026-06-23~~`getStudentMasterySummary` 串行查询用户名和掌握度~~ 改为 Promise.all 并行查询
- ✅ P3-19 已修复2026-06-23~~`getStudentMastery` 使用 export 但仅内部使用~~ 移除 export改为模块内部函数
- ✅ P3-27 已修复2026-06-23~~`generateDiagnosticReport` / `generateClassDiagnosticReport``throw new Error("...")` 直接暴露给用户~~ 改为使用 `DiagnosticReportError` 结构化错误码(继承 BusinessError
**文件清单**
| 文件 | 行数 | 职责 |
@@ -1952,6 +1964,7 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
| **Server Actions** | `recommendStudyPathAction` | `modules/ai/actions.ts` | 学习路径推荐权限AI_CHAT— V2 新增 |
| **Server Actions** | `getAiUsageStatsAction` | `modules/ai/actions.ts` | AI 使用统计权限AI_CONFIGURE— V2 新增 |
| **SSE Route** | `POST /api/ai/chat/stream` | `app/api/ai/chat/stream/route.ts` | 流式 AI 对话SSE— V2 新增 |
| **SSE Route** | `GET /api/notifications/stream` | `app/api/notifications/stream/route.ts` | 通知实时推送SSE15s 心跳 + 5min 超时)— V2-P3 新增 |
| **Service** | `AiService` | `modules/ai/types.ts` | 服务端 AI 服务接口(含 8 个方法) |
| **Service** | `AiClientService` | `modules/ai/types.ts` | 客户端 AI 服务接口Server Action 引用集合) |
| **Provider** | `AiClientProvider` | `modules/ai/context/ai-client-provider.tsx` | React Context Provider注入 AiClientService |
@@ -2036,6 +2049,7 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
| `modules/ai/hooks/use-ai-chat.ts` | ~57 | 非流式 AI 对话 Hook |
| `modules/ai/hooks/use-ai-suggestion.ts` | ~72 | AI 建议 Hook |
| `app/api/ai/chat/stream/route.ts` | ~160 | SSE 流式端点 — V2 新增 |
| `app/api/notifications/stream/route.ts` | ~120 | 通知实时推送 SSE 端点 — V2-P3 新增 |
**i18n**
- 翻译文件:`shared/i18n/messages/{locale}/ai.json`

View File

@@ -7031,19 +7031,121 @@
]
},
{
"name": "toggleTwoFactorAction",
"name": "setupTwoFactorAction",
"file": "actions-security.ts",
"permission": "USER_PROFILE_UPDATE",
"signature": "(enabled: boolean) => Promise<ActionState<TwoFactorStatus>>",
"purpose": "启用/禁用 2FAP2-9 新增占位实现v2 已禁用开关,显示'即将推出'提示,避免虚假安全感",
"signature": "() => Promise<ActionState<TwoFactorSetupData>>",
"purpose": "2FA 启用流程第一步:生成 TOTP 密钥 + QR 码 Data URLv3 新增:完整 TOTP 实现,替代 v2 的占位开关",
"deps": [
"requirePermission",
"data-access-system-settings.upsertSystemSetting"
"users/data-access.getUserProfile",
"data-access-two-factor.getTwoFactorEnabled",
"data-access-two-factor.setTotpSecret",
"lib/totp.generateTotpSecret",
"lib/totp.buildOtpAuthUrl",
"lib/totp.generateQrCodeDataUrl"
],
"usedBy": [
"components/security-center-card.tsx"
]
},
{
"name": "verifyTwoFactorAction",
"file": "actions-security.ts",
"permission": "USER_PROFILE_UPDATE",
"signature": "(token: string) => Promise<ActionState<{ backupCodes: string[]; status: TwoFactorStatus }>>",
"purpose": "2FA 启用流程第二步:校验一次性码 + 生成 10 个备份码bcrypt 哈希存储)+ 启用 2FAv3 新增)",
"deps": [
"requirePermission",
"data-access-two-factor.getTotpSecret",
"data-access-two-factor.setBackupCodesHashed",
"data-access-two-factor.setTwoFactorEnabled",
"data-access-two-factor.setTwoFactorEnabledAt",
"lib/totp.verifyTotpCode",
"lib/totp.generateBackupCodes",
"lib/totp.hashBackupCodes"
],
"usedBy": [
"components/security-center-card.tsx"
]
},
{
"name": "disableTwoFactorAction",
"file": "actions-security.ts",
"permission": "USER_PROFILE_UPDATE",
"signature": "(token: string) => Promise<ActionState<TwoFactorStatus>>",
"purpose": "关闭 2FA需提供有效 TOTP 码或备份码确认身份清除密钥和备份码v3 新增)",
"deps": [
"requirePermission",
"data-access-two-factor.getTwoFactorEnabled",
"data-access-two-factor.getTotpSecret",
"data-access-two-factor.getBackupCodesHashed",
"data-access-two-factor.setTwoFactorEnabled",
"data-access-two-factor.setTwoFactorEnabledAt",
"data-access-two-factor.deleteTotpSecret",
"data-access-two-factor.deleteBackupCodes",
"data-access-two-factor.setBackupCodesHashed",
"lib/totp.verifyTotpCode",
"lib/totp.verifyBackupCode",
"lib/totp.consumeBackupCode"
],
"usedBy": [
"components/security-center-card.tsx"
]
},
{
"name": "regenerateBackupCodesAction",
"file": "actions-security.ts",
"permission": "USER_PROFILE_UPDATE",
"signature": "(token: string) => Promise<ActionState<{ backupCodes: string[]; status: TwoFactorStatus }>>",
"purpose": "重新生成备份码:需 TOTP 码确认身份使旧备份码失效v3 新增)",
"deps": [
"requirePermission",
"data-access-two-factor.getTwoFactorEnabled",
"data-access-two-factor.getTotpSecret",
"data-access-two-factor.setBackupCodesHashed",
"lib/totp.verifyTotpCode",
"lib/totp.generateBackupCodes",
"lib/totp.hashBackupCodes"
],
"usedBy": [
"components/security-center-card.tsx"
]
},
{
"name": "preflightTwoFactorAction",
"file": "actions-security.ts",
"permission": "(public, login 前预检)",
"signature": "(email: string) => Promise<{ required: boolean }>",
"purpose": "登录预检:根据邮箱查询用户是否启用 2FA登录表单据此展示 2FA 输入框v3 新增;不验证密码,防邮箱枚举)",
"deps": [
"shared.db",
"shared.db.schema.users",
"data-access-two-factor.getTwoFactorEnabled"
],
"usedBy": [
"modules/auth/components/login-form.tsx"
]
},
{
"name": "verifyTwoFactorForLogin",
"file": "actions-security.ts",
"permission": "(internal, 供 auth.ts 调用)",
"signature": "(params: { userId: string; token?: string }) => Promise<{ required: boolean; valid: boolean }>",
"purpose": "登录时 2FA 校验:检查用户是否启用 2FA 并校验 TOTP 码或备份码(消耗备份码);由 auth.ts authorize 回调调用v3 新增)",
"deps": [
"data-access-two-factor.getTwoFactorEnabled",
"data-access-two-factor.getTotpSecret",
"data-access-two-factor.getBackupCodesHashed",
"data-access-two-factor.setBackupCodesHashed",
"lib/totp.verifyTotpCode",
"lib/totp.verifyBackupCode",
"lib/totp.consumeBackupCode"
],
"usedBy": [
"auth.ts"
]
},
{
"name": "revokeAllOtherSessionsAction",
"file": "actions-security.ts",
@@ -9657,7 +9759,7 @@
{
"name": "GradeDistributionChart",
"file": "components/grade-distribution-chart.tsx",
"purpose": "分数分布柱状图recharts BarChart彩色区间 90-100/80-89/70-79/60-69/<60",
"purpose": "分数分布柱状图recharts BarChart彩色区间 90-100/80-89/70-79/60-69/<60v4-P3-4 改进:每个分数段使用不同 SVG pattern + 颜色双重编码,色盲友好",
"deps": [
"recharts",
"shared/components/ui/chart"
@@ -10864,7 +10966,7 @@
{
"name": "UnreadMessageBadge",
"file": "components/unread-message-badge.tsx",
"purpose": "未读消息计数徽章(侧边栏 Messages 导航项旁显示,每 60 秒轮询 getUnreadMessageCountActionV2-P2-1 优化:轮询间隔提取为 POLL_INTERVAL_MS 常量)"
"purpose": "未读消息计数徽章(侧边栏 Messages 导航项旁显示,每 30 秒轮询 getUnreadMessageCountActionV2-P2-1 优化:轮询间隔提取为 POLL_INTERVAL_MS 常量V2-P3 优化:间隔从 60s 缩短为 30s 与通知组件保持一致"
}
],
"hooks": [
@@ -10922,7 +11024,7 @@
"data-access.getNotifications"
],
"usedBy": [
"notification-dropdown.tsx",
"hooks/use-notification-stream.ts",
"notification-list.tsx"
]
},
@@ -10936,7 +11038,7 @@
"data-access.getUnreadNotificationCount"
],
"usedBy": [
"notification-dropdown.tsx"
"hooks/use-notification-stream.ts"
]
},
{
@@ -12629,22 +12731,21 @@
"name": "getStudentMastery",
"signature": "(studentId: string) => Promise<MasteryWithKnowledgePoint[]>",
"file": "data-access.ts",
"purpose": "获取学生在所有知识点的掌握度(含知识点名称,按掌握度降序)",
"purpose": "获取学生在所有知识点的掌握度(含知识点名称,按掌握度降序)。P3-19 修复:移除 export改为模块内部函数",
"deps": [
"shared.db",
"shared.db.schema.knowledgePointMastery",
"shared.db.schema.knowledgePoints"
],
"usedBy": [
"data-access.getStudentMasterySummary",
"teacher/diagnostic/student/[studentId]/page.tsx"
"data-access.getStudentMasterySummary"
]
},
{
"name": "getStudentMasterySummary",
"signature": "(studentId: string) => Promise<StudentMasterySummary | null>",
"file": "data-access.ts",
"purpose": "获取学生掌握度摘要平均掌握度、强项≥80%、弱项<60%",
"purpose": "获取学生掌握度摘要平均掌握度、强项≥80%、弱项<80%[P3-16修复]。P3-18 修复getUserNamesByIds 与 getStudentMastery 并行查询",
"deps": [
"shared.db",
"shared.db.schema.users",
@@ -12710,11 +12811,12 @@
"name": "generateDiagnosticReport",
"signature": "(studentId: string, period: string, generatedBy: string) => Promise<string>",
"file": "data-access-reports.ts",
"purpose": "生成个人诊断报告(计算 overallScore、强项/弱项列表、复习建议status=draft",
"purpose": "生成个人诊断报告(计算 overallScore、强项/弱项列表、复习建议status=draft。P3-27 修复:使用 DiagnosticReportError 结构化错误码P3-1 修复toNumber 从 grades 模块导入",
"deps": [
"shared.db",
"shared.db.schema.learningDiagnosticReports",
"data-access.getStudentMasterySummary",
"grades.lib.grade-utils.toNumber",
"@paralleldrive/cuid2"
],
"usedBy": [
@@ -12725,7 +12827,7 @@
"name": "generateClassDiagnosticReport",
"signature": "(classId: string, period: string, generatedBy: string) => Promise<string>",
"file": "data-access-reports.ts",
"purpose": "生成班级诊断报告聚合班级掌握度识别薄弱知识点status=draftstudentId 存生成者 ID",
"purpose": "生成班级诊断报告聚合班级掌握度识别薄弱知识点status=draftstudentId 存生成者 ID。P3-27 修复:使用 DiagnosticReportError 结构化错误码",
"deps": [
"shared.db",
"shared.db.schema.learningDiagnosticReports",
@@ -12738,19 +12840,20 @@
},
{
"name": "getDiagnosticReports",
"signature": "(filters: DiagnosticReportQueryParams) => Promise<DiagnosticReportWithDetails[]>",
"signature": "(filters: DiagnosticReportQueryParams, scope?: DataScope) => Promise<DiagnosticReportListResult>",
"file": "data-access-reports.ts",
"purpose": "查询诊断报告列表(可按 studentId/reportType/status/period 过滤,含学生名和生成者名v3 修复conditions 显式标注 SQL[] 类型,移除 round2 死代码",
"purpose": "查询诊断报告列表(可按 studentId/reportType/status/period 过滤,含学生名和生成者名。P3-15 修复:支持分页 limit/offset返回 { reports, total } 结构Promise.all 并行查询总数和数据",
"deps": [
"shared.db",
"shared.db.schema.learningDiagnosticReports",
"shared.db.schema.users"
"shared.db.schema.users",
"grades.lib.grade-utils.toNumber"
],
"usedBy": [
"actions.getDiagnosticReportsAction",
"teacher/diagnostic/page.tsx",
"teacher/diagnostic/student/[studentId]/page.tsx",
"student/diagnostic/page.tsx"
"student/diagnostic/page.tsx",
"parent/diagnostic/page.tsx"
]
},
{
@@ -12987,7 +13090,7 @@
"name": "StudentMasterySummary",
"type": "interface",
"file": "types.ts",
"definition": "{ studentId, studentName, averageMastery, totalKnowledgePoints, strengths(≥80), weaknesses(<60), allMastery }",
"definition": "{ studentId, studentName, averageMastery, totalKnowledgePoints, strengths(≥80), weaknesses(<80)[P3-16修复消除60-79盲区], allMastery }",
"usedBy": [
"data-access.getStudentMasterySummary",
"data-access-reports.generateDiagnosticReport",
@@ -13042,12 +13145,21 @@
"name": "DiagnosticReportQueryParams",
"type": "interface",
"file": "types.ts",
"definition": "{ studentId?, reportType?, status?, period? }",
"definition": "{ studentId?, reportType?, status?, period?, limit?(P3-15), offset?(P3-15) }",
"usedBy": [
"data-access-reports.getDiagnosticReports",
"actions.getDiagnosticReportsAction"
]
},
{
"name": "DiagnosticReportListResult",
"type": "interface",
"file": "types.ts",
"definition": "{ reports: DiagnosticReportWithDetails[], total: number }P3-15 修复:分页查询结果)",
"usedBy": [
"data-access-reports.getDiagnosticReports"
]
},
{
"name": "MasteryRadarPoint",
"type": "interface",
@@ -13075,7 +13187,7 @@
{
"name": "StudentDiagnosticView",
"file": "components/student-diagnostic-view.tsx",
"purpose": "学生诊断视图(概览卡片、雷达图、强项/弱项列表、生成报告表单[DIAGNOSTIC_MANAGE]、最新报告与建议展示v3-P2 新增practiceHrefBase propnull 时隐藏练习按钮)",
"purpose": "学生诊断视图(概览卡片、雷达图、强项/弱项列表、生成报告表单[DIAGNOSTIC_MANAGE]、最新报告与建议展示v3-P2 新增practiceHrefBase propnull 时隐藏练习按钮P3-22 改进:练习按钮添加 aria-label 含知识点名",
"props": "{ studentId, summary, classAverage?, reports?, practiceHrefBase?: string | null }",
"deps": [
"usePermission",
@@ -16131,6 +16243,12 @@
"type": "data-access",
"description": "关联考试提交(examSubmissions/submissionAnswers)"
},
{
"from": "diagnostic",
"to": "grades",
"type": "lib-import",
"description": "复用 toNumber 工具函数(P3-1 修复:从 grades/lib/grade-utils 导入)"
},
{
"from": "elective",
"to": "school",
@@ -17467,7 +17585,7 @@
"grades/data-access-analytics.getClassAverageTrend"
],
"permission": "grade_record:read",
"description": "家长成绩视图(按 DataScope.children 过滤v4 新增 ParentExportButton 占位v3-P2 更新:为每个子女并行查询 getClassAverageTrend渲染 GradeTrendCard"
"description": "家长成绩视图(按 DataScope.children 过滤v4 新增 ParentExportButton 占位v3-P2 更新:为每个子女并行查询 getClassAverageTrend渲染 GradeTrendCardv3-P3-4 更新GradeTrendCard 新增日期范围选择器,通过 nuqs trendRange URL 参数持久化"
},
"/parent/diagnostic": {
"component": "子女学情诊断",
@@ -17642,6 +17760,30 @@
],
"studentMode": "强制苏格拉底式引导系统提示"
},
"/api/notifications/stream": {
"methods": [
"GET"
],
"handler": "通知实时推送 SSE 端点ReadableStream + setInterval 定时推送)",
"auth": "MESSAGE_READ",
"validation": "requirePermission 权限校验",
"protocol": "Server-Sent Events",
"events": [
"update — 未读数 + 最新通知列表(连接建立时立即推送,之后每 15 秒推送)",
"error — 权限拒绝或内部错误",
"[DONE] — 连接超时5 分钟)自动关闭"
],
"pushStrategy": "连接建立立即推送 + 15 秒间隔定时推送 + 5 分钟超时自动关闭",
"module": "notifications",
"deps": [
"requirePermission",
"data-access.getUnreadNotificationCount",
"data-access.getNotifications"
],
"usedBy": [
"hooks/use-notification-stream.ts"
]
},
"/api/onboarding/complete": {
"methods": [
"POST"