feat(school,classes): 实现 P2 长期问题全量改进项
P2-2: 新增 OrgTreeNav 组件(学校→年级→班级三级树形导航,支持搜索过滤/选中高亮/展开折叠) P2-3: 新增 promoteGradesAction 年级升级功能(中文数字/阿拉伯数字识别,按 order 降序避免冲突) P2-4: 新增 bulkEnrollStudentsAction(CSV 批量导入学生)+ bulkAssignSubjectTeachersAction(CSV 批量分配教师) P2-5: 为 department/academicYear/grade 的 9 个 CRUD Action 补充 logAudit 审计日志 同步更新架构图文档 004/005
This commit is contained in:
@@ -4625,11 +4625,29 @@
|
||||
"textbook-content-panel.tsx"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "useKpDialogState",
|
||||
"file": "hooks/use-kp-dialog-state.ts",
|
||||
"signature": "() => { editingKp, setEditingKp, editKpDialogOpen, setEditKpDialogOpen, isUpdatingKp, setIsUpdatingKp, questionDialogOpen, setQuestionDialogOpen, targetKpForQuestion, setTargetKpForQuestion, deleteConfirmOpen, setDeleteConfirmOpen, pendingDeleteKpId, setPendingDeleteKpId }",
|
||||
"purpose": "知识点对话框状态管理 Hook(编辑/题目/删除确认),从 use-knowledge-point-actions 拆分",
|
||||
"usedBy": [
|
||||
"hooks/use-knowledge-point-actions.ts"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "useKpCrud",
|
||||
"file": "hooks/use-kp-crud.ts",
|
||||
"signature": "(args: UseKpCrudArgs) => { handleCreateKnowledgePoint, requestDeleteKnowledgePoint, confirmDeleteKnowledgePoint, handleUpdateKnowledgePoint }",
|
||||
"purpose": "知识点 CRUD 操作 Hook,依赖 useKpDialogState 提供的状态,从 use-knowledge-point-actions 拆分",
|
||||
"usedBy": [
|
||||
"hooks/use-knowledge-point-actions.ts"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "useKnowledgePointActions",
|
||||
"file": "hooks/use-knowledge-point-actions.ts",
|
||||
"signature": "(textbookId, selectedChapterId, selectedChapterTextbookId, highlightedKpId, setHighlightedKpId, onKpCreated?) => { editingKp, setEditingKp, editKpDialogOpen, setEditKpDialogOpen, isUpdatingKp, questionDialogOpen, setQuestionDialogOpen, targetKpForQuestion, setTargetKpForQuestion, deleteConfirmOpen, setDeleteConfirmOpen, handleCreateKnowledgePoint, requestDeleteKnowledgePoint, confirmDeleteKnowledgePoint, handleUpdateKnowledgePoint }",
|
||||
"purpose": "知识点操作Hook(6参数)",
|
||||
"purpose": "知识点操作门面 Hook(组合 useKpDialogState + useKpCrud,对外保持原有 API 不变)",
|
||||
"usedBy": [
|
||||
"textbook-reader.tsx"
|
||||
]
|
||||
@@ -4637,8 +4655,8 @@
|
||||
{
|
||||
"name": "useGraphData",
|
||||
"file": "hooks/use-graph-data.ts",
|
||||
"signature": "(textbookId: string, viewMode: GraphViewMode) => { data: KnowledgeGraphData | null, isLoading: boolean, error: string | null, reload: () => void }",
|
||||
"purpose": "Task 11 新增:知识图谱数据加载 Hook,按 textbookId + viewMode 加载,使用派生值模式避免 effect 中 setState",
|
||||
"signature": "(textbookId: string, viewMode: GraphViewMode) => { data: KnowledgeGraphData | null, isLoading: boolean, isRefreshing: boolean, error: string | null, reload: () => void }",
|
||||
"purpose": "Task 11 新增:知识图谱数据加载 Hook,按 textbookId + viewMode 加载,使用派生值模式避免 effect 中 setState。区分 isLoading(首次加载)和 isRefreshing(切换模式刷新,保留旧数据避免 UI 闪烁)",
|
||||
"usedBy": [
|
||||
"components/knowledge-graph.tsx"
|
||||
]
|
||||
@@ -4975,42 +4993,51 @@
|
||||
"name": "TextbookReader",
|
||||
"purpose": "教材阅读器"
|
||||
},
|
||||
{
|
||||
"name": "TeacherTextbookReader",
|
||||
"purpose": "教师端 TextbookReader 客户端包装(v1 测试修复:解决 Server→Client 函数 prop 序列化问题)"
|
||||
},
|
||||
{
|
||||
"name": "TextbookSettingsDialog",
|
||||
"purpose": "教材设置对话框"
|
||||
}
|
||||
],
|
||||
"uiDeps": [],
|
||||
"uiDepsNote": "已通过 render prop 解耦,不再直接 import questions 模块组件",
|
||||
"uiDepsNote": "已通过 render prop 解耦,TeacherTextbookReader 已移至 app 层(src/app/(dashboard)/teacher/textbooks/[id]/_components/),textbooks 模块不再直接 import questions 模块组件",
|
||||
"knownIssues": [
|
||||
"i18n 覆盖率约 95%(chapter-sidebar-list 已接入,actions.ts 已接入,section-error-boundary 默认值已改英文)",
|
||||
"i18n 覆盖率约 98%(chapter-sidebar-list/actions.ts/section-error-boundary/8 处硬编码英文 toast 已全部接入,Zod refine 消息改为英文,错误分支不再复用成功消息)",
|
||||
"类型断言残留 3 处 as string",
|
||||
"P2 图谱方向键导航未实现",
|
||||
"v1 测试已修复:textbook-reader.tsx SheetTrigger 越界、Server→Client 函数 prop 序列化、seed 数据 i18n key 不匹配"
|
||||
"v1 测试已修复:textbook-reader.tsx SheetTrigger 越界、Server→Client 函数 prop 序列化、seed 数据 i18n key 不匹配",
|
||||
"v2 核查修复:class-mastery 视图模式已实现、跨教材前置依赖双向 IN 过滤、ChapterSidebarList canEdit 默认 false、isTeacher 冗余变量移除、graph-kp-node 节点宽度常量化、GraphNodeDetailPanel textbookId 未使用 prop 移除、use-knowledge-point-actions 拆分为门面+状态+CRUD 三 Hook、use-graph-data 区分 isLoading/isRefreshing 避免 UI 闪烁、TeacherTextbookReader 移至 app 层解耦跨模块依赖"
|
||||
],
|
||||
"files": {
|
||||
"actions.ts": 502,
|
||||
"data-access.ts": 586,
|
||||
"data-access-graph.ts": 184,
|
||||
"types.ts": 94,
|
||||
"schema.ts": 62,
|
||||
"constants.ts": 99,
|
||||
"utils.ts": 203,
|
||||
"graph-layout.ts": 105,
|
||||
"actions.ts": 515,
|
||||
"data-access.ts": 662,
|
||||
"data-access-graph.ts": 207,
|
||||
"types.ts": 106,
|
||||
"schema.ts": 81,
|
||||
"constants.ts": 96,
|
||||
"utils.ts": 225,
|
||||
"graph-layout.ts": 121,
|
||||
"analytics.tsx": 43,
|
||||
"hooks/use-knowledge-point-actions.ts": 121,
|
||||
"hooks/use-knowledge-point-actions.ts": 49,
|
||||
"hooks/use-kp-dialog-state.ts": 38,
|
||||
"hooks/use-kp-crud.ts": 122,
|
||||
"hooks/use-text-selection.ts": 57,
|
||||
"hooks/use-graph-data.ts": 58,
|
||||
"components/teacher-textbook-reader.tsx": 41,
|
||||
"components/knowledge-graph.tsx": 249,
|
||||
"components/graph-kp-node.tsx": 80,
|
||||
"components/graph-prerequisite-edge.tsx": 40,
|
||||
"components/graph-toolbar.tsx": 77,
|
||||
"components/graph-node-detail-panel.tsx": 171
|
||||
"hooks/use-graph-data.ts": 76,
|
||||
"components/chapter-sidebar-list.tsx": 342,
|
||||
"components/create-chapter-dialog.tsx": 111,
|
||||
"components/graph-kp-node.tsx": 92,
|
||||
"components/graph-node-detail-panel.tsx": 181,
|
||||
"components/graph-prerequisite-edge.tsx": 48,
|
||||
"components/graph-toolbar.tsx": 93,
|
||||
"components/knowledge-graph.tsx": 376,
|
||||
"components/knowledge-point-dialogs.tsx": 175,
|
||||
"components/knowledge-point-list.tsx": 122,
|
||||
"components/section-error-boundary.tsx": 71,
|
||||
"components/textbook-card.tsx": 181,
|
||||
"components/textbook-content-panel.tsx": 189,
|
||||
"components/textbook-filters.tsx": 71,
|
||||
"components/textbook-form-dialog.tsx": 139,
|
||||
"components/textbook-reader.tsx": 446,
|
||||
"components/textbook-settings-dialog.tsx": 203
|
||||
},
|
||||
"auditReport": "audit/textbooks-audit-report.md"
|
||||
}
|
||||
@@ -5963,8 +5990,8 @@
|
||||
},
|
||||
{
|
||||
"path": "actions-invitations.ts",
|
||||
"lines": 280,
|
||||
"description": "邀请码与注册(8 个 Action,P0-3 修复)"
|
||||
"lines": 502,
|
||||
"description": "邀请码与注册 + 批量操作(10 个 Action,P0-3 修复;P2-4 新增批量导入学生/批量分配教师)"
|
||||
},
|
||||
{
|
||||
"path": "actions-schedule.ts",
|
||||
@@ -6022,6 +6049,13 @@
|
||||
"signature": "(gradeId) => Promise<ActionState<string>>",
|
||||
"purpose": "删除年级"
|
||||
},
|
||||
{
|
||||
"name": "promoteGradesAction",
|
||||
"permission": "GRADE_MANAGE",
|
||||
"signature": "(prevState, formData) => Promise<ActionState<string>>",
|
||||
"purpose": "年级升级(order +1 + 名称升级)",
|
||||
"auditLog": "grade.promote"
|
||||
},
|
||||
{
|
||||
"name": "createDepartmentAction",
|
||||
"permission": "SCHOOL_MANAGE",
|
||||
@@ -6401,6 +6435,15 @@
|
||||
"usedBy": [
|
||||
"updateAcademicYear"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "OrgTreeNode",
|
||||
"type": "type",
|
||||
"definition": "组织架构树节点(学校/年级/班级三级,P2-2 修复)",
|
||||
"usedBy": [
|
||||
"getOrgTree",
|
||||
"school/components/org-tree-nav.tsx"
|
||||
]
|
||||
}
|
||||
],
|
||||
"components": [
|
||||
@@ -6455,6 +6498,12 @@
|
||||
"file": "components/school-skeleton.tsx",
|
||||
"purpose": "卡片加载骨架屏(animate-pulse)",
|
||||
"props": ""
|
||||
},
|
||||
{
|
||||
"name": "OrgTreeNav",
|
||||
"file": "components/org-tree-nav.tsx",
|
||||
"purpose": "学校→年级→班级三级树形导航(P2-2 修复):搜索过滤 + 选中高亮 + 展开/折叠 + 不同节点类型图标(School/GraduationCap/Users)+ 默认展开第一级",
|
||||
"props": "nodes: OrgTreeNode[], onSelect?: (node: OrgTreeNode) => void, selectedId?: string"
|
||||
}
|
||||
],
|
||||
"hooks": [
|
||||
@@ -8622,7 +8671,7 @@
|
||||
"deps": [
|
||||
"shared.db",
|
||||
"shared.db.schema.gradeRecords",
|
||||
"grades/lib/grade-utils.buildScopeClassFilter"
|
||||
"grades/lib/scope-filter.buildScopeClassFilter"
|
||||
],
|
||||
"usedBy": [
|
||||
"grades/data-access.getClassGradeStatsWithMeta"
|
||||
@@ -8664,7 +8713,7 @@
|
||||
"shared.db",
|
||||
"shared.db.schema.gradeRecords",
|
||||
"shared.db.schema.users",
|
||||
"grades/lib/grade-utils.buildScopeClassFilter",
|
||||
"grades/lib/scope-filter.buildScopeClassFilter",
|
||||
"users/data-access.getUserNamesByIds"
|
||||
],
|
||||
"usedBy": [
|
||||
@@ -9368,11 +9417,12 @@
|
||||
"name": "exportGradeRecordsToExcel",
|
||||
"signature": "(params: { classId: string; subjectId?: string; examId?: string; scope: DataScope; currentUserId?: string }) => Promise<Buffer>",
|
||||
"file": "export.ts",
|
||||
"purpose": "导出成绩单(Sheet1 成绩明细,Sheet2 统计汇总:均分/中位数/最高分/最低分/标准差/及格率/优秀率/参考人数)(P3 更新:传递 scope/currentUserId 到 data-access)",
|
||||
"purpose": "导出成绩单(Sheet1 成绩明细,Sheet2 统计汇总:均分/中位数/最高分/最低分/标准差/及格率/优秀率/参考人数)(P3 更新:传递 scope/currentUserId 到 data-access;P3-7:硬编码中文改用 next-intl getTranslations)",
|
||||
"deps": [
|
||||
"shared.lib.excel.exportToExcel",
|
||||
"data-access.getGradeRecords",
|
||||
"data-access.getClassGradeStats"
|
||||
"data-access.getClassGradeStats",
|
||||
"next-intl/server.getTranslations"
|
||||
],
|
||||
"usedBy": [
|
||||
"actions.exportGradesAction",
|
||||
@@ -9383,7 +9433,7 @@
|
||||
"name": "exportClassGradeReportToExcel",
|
||||
"signature": "(params: { classId: string; scope: DataScope; currentUserId?: string }) => Promise<Buffer>",
|
||||
"file": "export.ts",
|
||||
"purpose": "导出班级成绩总表(多科目横向对比,含总分/平均分/排名列)(P3 更新:适配 PaginatedGradeRecords 结构 + 传递 scope/currentUserId)",
|
||||
"purpose": "导出班级成绩总表(多科目横向对比,含总分/平均分/排名列)(P3 更新:适配 PaginatedGradeRecords 结构 + 传递 scope/currentUserId;P3-6:复用 stats-service.computeAverageScore 替代局部 avg;P3-7:硬编码中文改用 next-intl getTranslations)",
|
||||
"deps": [
|
||||
"shared.db",
|
||||
"shared.db.schema.classes",
|
||||
@@ -9391,7 +9441,9 @@
|
||||
"shared.db.schema.gradeRecords",
|
||||
"shared.db.schema.users",
|
||||
"shared.lib.excel.exportToExcel",
|
||||
"data-access.getGradeRecords"
|
||||
"data-access.getGradeRecords",
|
||||
"stats-service.computeAverageScore",
|
||||
"next-intl/server.getTranslations"
|
||||
],
|
||||
"usedBy": [
|
||||
"actions.exportGradesAction"
|
||||
@@ -9440,8 +9492,8 @@
|
||||
{
|
||||
"name": "buildScopeClassFilter",
|
||||
"signature": "(scope: DataScope, currentUserId?: string) => SQL | null",
|
||||
"file": "lib/grade-utils.ts",
|
||||
"purpose": "根据 DataScope 构建 gradeRecords 表的行级权限过滤条件(P1-2 新增:从 data-access/data-access-analytics 抽取;P3 更新:class_members scope 内置 studentId 过滤,需传入 currentUserId 参数)",
|
||||
"file": "lib/scope-filter.ts",
|
||||
"purpose": "根据 DataScope 构建 gradeRecords 表的行级权限过滤条件(P1-2 新增:从 data-access/data-access-analytics 抽取;P3 更新:class_members scope 内置 studentId 过滤,需传入 currentUserId 参数;P3-26:从 grade-utils.ts 迁移至 scope-filter.ts)",
|
||||
"usedBy": [
|
||||
"data-access.getGradeRecords",
|
||||
"data-access.getClassGradeStats",
|
||||
@@ -9469,16 +9521,17 @@
|
||||
"name": "computeAverageScore",
|
||||
"signature": "(scores: number[]) => number",
|
||||
"file": "stats-service.ts",
|
||||
"purpose": "计算分数平均值(空数组返回 0)(P1-1 新增:从 data-access.getStudentGradeSummary 抽取为纯函数)",
|
||||
"purpose": "计算分数平均值(空数组返回 0)(P1-1 新增:从 data-access.getStudentGradeSummary 抽取为纯函数;P3-6:export.ts 复用此函数替代局部 avg)",
|
||||
"usedBy": [
|
||||
"data-access.getStudentGradeSummary"
|
||||
"data-access.getStudentGradeSummary",
|
||||
"export.exportClassGradeReportToExcel"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "buildGradeTrendPoints",
|
||||
"signature": "(rows: RawScoreRow[]) => GradeTrendPoint[]",
|
||||
"file": "stats-service.ts",
|
||||
"purpose": "构建成绩趋势数据点(按考试标题分组,归一化分数 0-100)(P1-1 新增:从 data-access-analytics.getGradeTrend 抽取为纯函数)",
|
||||
"purpose": "构建成绩趋势数据点(按考试标题分组,归一化分数 0-100)(P1-1 新增:从 data-access-analytics.getGradeTrend 抽取为纯函数;P3-24:使用 isGradeTrendType 类型守卫替代 as 断言)",
|
||||
"usedBy": [
|
||||
"data-access-analytics.getGradeTrend"
|
||||
]
|
||||
@@ -11040,19 +11093,23 @@
|
||||
},
|
||||
{
|
||||
"name": "logNotificationSend",
|
||||
"signature": "(result: ChannelSendResult) => void",
|
||||
"signature": "(result: ChannelSendResult, payload?: { userId: string; title: string }) => Promise<void>",
|
||||
"file": "data-access.ts",
|
||||
"purpose": "记录单条发送日志(当前使用 console.info;未来可扩展 notification_logs 表)",
|
||||
"deps": [],
|
||||
"purpose": "记录单条发送日志到 notification_logs 表(DB 写入失败时降级 console,不阻塞通知流程)",
|
||||
"deps": [
|
||||
"shared.db",
|
||||
"shared.db.schema.notificationLogs",
|
||||
"@paralleldrive/cuid2"
|
||||
],
|
||||
"usedBy": [
|
||||
"logNotificationSendBatch"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "logNotificationSendBatch",
|
||||
"signature": "(results: ChannelSendResult[]) => void",
|
||||
"signature": "(results: ChannelSendResult[], payload?: { userId: string; title: string }) => Promise<void>",
|
||||
"file": "data-access.ts",
|
||||
"purpose": "批量记录发送日志",
|
||||
"purpose": "批量记录发送日志(并行调用 logNotificationSend)",
|
||||
"deps": [
|
||||
"logNotificationSend"
|
||||
],
|
||||
@@ -11221,6 +11278,13 @@
|
||||
"messaging (via re-export)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "NotificationLog",
|
||||
"type": "interface",
|
||||
"file": "types.ts",
|
||||
"definition": "{ id, userId, title, channel: NotificationChannel, status: 'success' | 'failure', messageId: string | null, error: string | null, sentAt }",
|
||||
"usedBy": []
|
||||
},
|
||||
{
|
||||
"name": "PaginatedResult",
|
||||
"type": "interface",
|
||||
@@ -14339,7 +14403,7 @@
|
||||
},
|
||||
"dbTables": {
|
||||
"_meta": {
|
||||
"total": 58,
|
||||
"total": 59,
|
||||
"orm": "Drizzle ORM 0.45",
|
||||
"database": "MySQL",
|
||||
"idStrategy": "CUID2 (varchar length 128)",
|
||||
@@ -14598,6 +14662,10 @@
|
||||
"owner": "notifications",
|
||||
"description": "消息通知"
|
||||
},
|
||||
"notificationLogs": {
|
||||
"owner": "notifications",
|
||||
"description": "通知发送日志(channel/status/messageId/error/sentAt)"
|
||||
},
|
||||
"notificationPreferences": {
|
||||
"owner": "notifications",
|
||||
"description": "通知偏好(email/sms/push + 分类开关)"
|
||||
@@ -14980,7 +15048,9 @@
|
||||
"textbooks": {
|
||||
"dependsOn": [
|
||||
"shared",
|
||||
"auth"
|
||||
"auth",
|
||||
"users",
|
||||
"classes"
|
||||
],
|
||||
"uses": {
|
||||
"shared": [
|
||||
@@ -14990,6 +15060,12 @@
|
||||
],
|
||||
"auth": [
|
||||
"auth"
|
||||
],
|
||||
"users": [
|
||||
"data-access.getCurrentStudentUser"
|
||||
],
|
||||
"classes": [
|
||||
"data-access-students.getClassStudents"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user