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:
@@ -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-23):parent/grades/page.tsx 为每个子女并行查询 `getClassAverageTrend`,渲染 GradeTrendCard
|
||||
- ✅ v3-P3-4 改进(2026-06-23):GradeTrendCard 新增日期范围选择器(全部/近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/normalize(20 行)
|
||||
- ✅ 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 + 客户端分页 UI,PAGE_SIZE=20;✅ V2-P1-2:客户端过滤仅在初始数据时执行) |
|
||||
| `components/message-detail.tsx` | 消息详情(含回复) |
|
||||
| `components/message-compose.tsx` | 撰写新消息(✅ V2-P1-4:fieldErrors + aria-invalid 字段级错误展示) |
|
||||
| `components/unread-message-badge.tsx` | 未读消息计数徽章(侧边栏,每 60 秒轮询 `getUnreadMessageCountAction`;✅ V2-P2-1:POLL_INTERVAL_MS 常量) |
|
||||
| `components/unread-message-badge.tsx` | 未读消息计数徽章(侧边栏,每 30 秒轮询 `getUnreadMessageCountAction`;✅ V2-P2-1:POLL_INTERVAL_MS 常量;✅ V2-P3:间隔从 60s 缩短为 30s 与通知一致) |
|
||||
|
||||
**客户端行为**:
|
||||
- `message-list.tsx`:客户端调用 `getMessagesAction` 搜索消息(useMessageSearch hook,400ms 防抖,请求竞态取消);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 实时推送 Hook(EventSource + 轮询降级) |
|
||||
|
||||
**组件清单**:
|
||||
| 组件 | 职责 |
|
||||
|------|------|
|
||||
| `components/notification-list.tsx` | 通知列表(消息页底部,展示所有通知,支持标记已读;✅ V2-P0-1:useTranslations 命名空间从 "messages" 切换到 "notifications") |
|
||||
| `components/notification-dropdown.tsx` | 通知下拉菜单(站点头部,每 30 秒轮询 `getNotificationsAction` + `getUnreadNotificationCountAction`;✅ V2-P0-1:useTranslations 命名空间切换;✅ V2-P2-1:POLL_INTERVAL_MS 常量) |
|
||||
| `components/notification-dropdown.tsx` | 通知下拉菜单(站点头部,✅ V2-P3:改用 SSE 实时推送 `/api/notifications/stream` + 轮询降级;✅ V2-P0-1:useTranslations 命名空间切换;✅ V2-P2-1:POLL_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()` 包装实现请求级 memoization;P3-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/getActiveStudentIdsByClassId;v2-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/getActiveStudentIdsByClassId;v2-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` prop(string | null),null 时隐藏练习按钮;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` | 通知实时推送(SSE,15s 心跳 + 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`
|
||||
|
||||
@@ -7031,19 +7031,121 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "toggleTwoFactorAction",
|
||||
"name": "setupTwoFactorAction",
|
||||
"file": "actions-security.ts",
|
||||
"permission": "USER_PROFILE_UPDATE",
|
||||
"signature": "(enabled: boolean) => Promise<ActionState<TwoFactorStatus>>",
|
||||
"purpose": "启用/禁用 2FA(P2-9 新增:占位实现;v2 已禁用开关,显示'即将推出'提示,避免虚假安全感)",
|
||||
"signature": "() => Promise<ActionState<TwoFactorSetupData>>",
|
||||
"purpose": "2FA 启用流程第一步:生成 TOTP 密钥 + QR 码 Data URL(v3 新增:完整 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 哈希存储)+ 启用 2FA(v3 新增)",
|
||||
"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/<60;v4-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 秒轮询 getUnreadMessageCountAction;V2-P2-1 优化:轮询间隔提取为 POLL_INTERVAL_MS 常量)"
|
||||
"purpose": "未读消息计数徽章(侧边栏 Messages 导航项旁显示,每 30 秒轮询 getUnreadMessageCountAction;V2-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=draft,studentId 存生成者 ID)",
|
||||
"purpose": "生成班级诊断报告(聚合班级掌握度,识别薄弱知识点,status=draft,studentId 存生成者 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 prop,null 时隐藏练习按钮)",
|
||||
"purpose": "学生诊断视图(概览卡片、雷达图、强项/弱项列表、生成报告表单[DIAGNOSTIC_MANAGE]、最新报告与建议展示;v3-P2 新增:practiceHrefBase prop,null 时隐藏练习按钮;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,渲染 GradeTrendCard;v3-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"
|
||||
|
||||
Reference in New Issue
Block a user