From 978d9a8309395ef3fbc376a4bcee5a16e42a0c59 Mon Sep 17 00:00:00 2001 From: SpecialX <47072643+wangxiner55@users.noreply.github.com> Date: Mon, 22 Jun 2026 01:06:16 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E5=A4=87=E8=AF=BE?= =?UTF-8?q?=E6=A8=A1=E5=9D=97=E5=B9=B6=E4=BF=AE=E5=A4=8D=E5=85=A8=E6=A8=A1?= =?UTF-8?q?=E5=9D=97=20P0/P1/P2=20=E7=BC=BA=E9=99=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 主要变更: - 新增 lesson-preparation 模块: 备课编辑器、节点编辑、AI 建议、知识点选择、版本历史、作业发布 - 新增 shared 通用组件: charts/question-bank-filters/schedule-list/ui (chip-nav/filter-bar/page-header/stat-card/stat-item) - 新增 student/admin 端 loading.tsx 与 error.tsx, 优化加载与错误态体验 - 新增 teacher/lesson-plans 页面 (列表/新建/编辑) - 新增 drizzle 迁移 0002_tiny_lionheart 及 snapshot - 新增 textbooks/schema.ts 与 exams/utils/normalize-structure.ts - 修复 Tiptap v3 SSR hydration 崩溃 (rich-text-block immediatelyRender: false) - 重构多模块 data-access/actions/组件, 修复权限校验与类型规范 - 同步架构文档 004/005 反映新增模块、导出、依赖关系 - 归档 bugs/* 测试报告与 e2e 测试脚本 (admin/parent/student/teacher web_test) --- bugs/admin_bug_v2.md | 532 ++ bugs/admin_bug_v3.md | 252 + bugs/admin_web_test.json | 308 + bugs/admin_web_test.md | 154 + bugs/back_bug_v2.md | 806 ++ bugs/back_bug_v3.md | 550 ++ bugs/lesson_preparation_bug_v2.md | 342 + bugs/others_bug_v2.md | 848 ++ bugs/others_bug_v3.md | 181 + bugs/parent_bug.md | 779 +- bugs/parent_web_test.json | 493 ++ bugs/parent_web_test.md | 278 + bugs/shared_bug_v2.md | 332 + bugs/shared_bug_v3.md | 307 + bugs/student_bug.md | 1022 +-- bugs/student_web_test.json | 265 + bugs/student_web_test.md | 160 + bugs/teacher_bug_v2.md | 883 ++ bugs/teacher_bug_v3.md | 307 + bugs/teacher_web_test.json | 636 ++ bugs/teacher_web_test.md | 211 + bugs/test_edit_page.py | 116 + bugs/test_edit_page2.py | 93 + bugs/test_node_editor.py | 103 + bugs/v2_after_create.png | Bin 0 -> 47471 bytes bugs/v2_after_login.png | Bin 0 -> 29459 bytes bugs/v2_edit.png | Bin 0 -> 47471 bytes bugs/v2_list.png | Bin 0 -> 46134 bytes bugs/v2_login.png | Bin 0 -> 31657 bytes bugs/v2_new.png | Bin 0 -> 47752 bytes bugs/v3_after_add.png | Bin 0 -> 90734 bytes bugs/v3_node_editor.png | Bin 0 -> 94879 bytes bugs/v3_node_selected.png | Bin 0 -> 91198 bytes check_lines.ps1 | 46 + count_lines.ps1 | 20 + debug.log | 1 + .../exams-grading-loading.tsx | 0 .../004_architecture_impact_map.md | 169 +- docs/architecture/005_architecture_data.json | 568 +- docs/feature/001_first_login_onboarding.md | 331 + docs/feature/f_bk.md | 412 + docs/feature/f_bk_design.md | 608 ++ .../plans/2026-06-18-lesson-preparation.md | 2937 +++++++ drizzle/0002_tiny_lionheart.sql | 58 + drizzle/meta/0002_snapshot.json | 7250 +++++++++++++++++ drizzle/meta/_journal.json | 7 + package-lock.json | 177 + package.json | 1 + scripts/check-parent.mjs | 51 + scripts/create-test-plan.ts | 38 + .../admin/announcements/[id]/page.tsx | 13 +- .../(dashboard)/admin/announcements/page.tsx | 20 +- src/app/(dashboard)/admin/attendance/page.tsx | 46 +- .../admin/audit-logs/data-changes/page.tsx | 34 +- .../admin/audit-logs/login-logs/page.tsx | 36 +- src/app/(dashboard)/admin/audit-logs/page.tsx | 36 +- .../admin/course-plans/[id]/edit/page.tsx | 16 +- .../admin/course-plans/[id]/page.tsx | 9 +- .../admin/course-plans/create/page.tsx | 14 +- .../(dashboard)/admin/course-plans/page.tsx | 26 +- src/app/(dashboard)/admin/dashboard/page.tsx | 10 +- .../admin/elective/[id]/edit/page.tsx | 17 +- .../admin/elective/create/page.tsx | 17 +- src/app/(dashboard)/admin/elective/page.tsx | 26 +- src/app/(dashboard)/admin/error.tsx | 22 + src/app/(dashboard)/admin/files/page.tsx | 10 +- src/app/(dashboard)/admin/loading.tsx | 38 + .../admin/scheduling/auto/page.tsx | 26 +- .../admin/scheduling/changes/page.tsx | 43 +- .../admin/scheduling/rules/page.tsx | 23 +- .../admin/school/academic-year/page.tsx | 15 +- .../(dashboard)/admin/school/classes/page.tsx | 15 +- .../admin/school/departments/page.tsx | 14 +- .../admin/school/grades/insights/page.tsx | 195 +- .../(dashboard)/admin/school/grades/page.tsx | 15 +- src/app/(dashboard)/admin/school/page.tsx | 4 +- .../(dashboard)/admin/school/schools/page.tsx | 15 +- .../(dashboard)/admin/users/import/page.tsx | 89 +- src/app/(dashboard)/announcements/page.tsx | 4 + .../management/grade/insights/page.tsx | 87 +- src/app/(dashboard)/messages/[id]/page.tsx | 9 +- src/app/(dashboard)/messages/compose/page.tsx | 4 + src/app/(dashboard)/messages/page.tsx | 7 +- src/app/(dashboard)/not-found.tsx | 12 +- .../(dashboard)/parent/attendance/page.tsx | 73 +- .../parent/children/[studentId]/page.tsx | 39 +- src/app/(dashboard)/parent/dashboard/page.tsx | 21 + src/app/(dashboard)/parent/grades/page.tsx | 73 +- src/app/(dashboard)/profile/page.tsx | 45 +- src/app/(dashboard)/settings/page.tsx | 13 +- .../(dashboard)/settings/security/page.tsx | 15 +- .../student/attendance/loading.tsx | 25 + .../(dashboard)/student/attendance/page.tsx | 4 +- .../(dashboard)/student/dashboard/page.tsx | 79 +- .../student/diagnostic/loading.tsx | 35 + .../(dashboard)/student/elective/loading.tsx | 26 + src/app/(dashboard)/student/elective/page.tsx | 2 +- src/app/(dashboard)/student/error.tsx | 21 + .../(dashboard)/student/grades/loading.tsx | 23 + src/app/(dashboard)/student/grades/page.tsx | 4 +- .../assignments/[assignmentId]/loading.tsx | 13 + .../assignments/[assignmentId]/page.tsx | 4 +- .../student/learning/assignments/loading.tsx | 33 + .../student/learning/assignments/page.tsx | 240 +- .../student/learning/courses/page.tsx | 6 +- .../learning/textbooks/[id]/loading.tsx | 17 + .../student/learning/textbooks/[id]/page.tsx | 18 +- .../student/learning/textbooks/loading.tsx | 18 + .../student/learning/textbooks/page.tsx | 17 +- src/app/(dashboard)/student/schedule/page.tsx | 15 +- .../(dashboard)/teacher/attendance/page.tsx | 43 +- .../teacher/attendance/sheet/page.tsx | 25 +- .../teacher/attendance/stats/page.tsx | 51 +- .../teacher/classes/my/[id]/page.tsx | 24 +- .../(dashboard)/teacher/classes/my/page.tsx | 7 +- .../teacher/classes/schedule/page.tsx | 13 +- .../teacher/classes/students/page.tsx | 13 +- .../teacher/course-plans/[id]/page.tsx | 3 +- .../(dashboard)/teacher/course-plans/page.tsx | 31 +- .../(dashboard)/teacher/dashboard/page.tsx | 24 +- .../diagnostic/class/[classId]/page.tsx | 9 +- .../(dashboard)/teacher/diagnostic/page.tsx | 31 +- .../diagnostic/student/[studentId]/page.tsx | 14 +- src/app/(dashboard)/teacher/elective/page.tsx | 31 +- .../teacher/exams/[id]/build/page.tsx | 74 +- .../teacher/exams/[id]/proctoring/page.tsx | 7 +- .../(dashboard)/teacher/exams/all/page.tsx | 15 +- .../(dashboard)/teacher/exams/create/page.tsx | 14 +- .../teacher/grades/analytics/page.tsx | 146 +- .../(dashboard)/teacher/grades/entry/page.tsx | 33 +- src/app/(dashboard)/teacher/grades/page.tsx | 54 +- .../(dashboard)/teacher/grades/stats/page.tsx | 81 +- .../homework/assignments/[id]/page.tsx | 29 +- .../assignments/[id]/submissions/page.tsx | 34 +- .../teacher/homework/assignments/page.tsx | 52 +- .../submissions/[submissionId]/page.tsx | 13 +- .../teacher/homework/submissions/page.tsx | 20 +- .../lesson-plans/[planId]/edit/page.tsx | 38 + .../teacher/lesson-plans/new/page.tsx | 23 + .../(dashboard)/teacher/lesson-plans/page.tsx | 39 + .../(dashboard)/teacher/questions/page.tsx | 32 +- .../teacher/schedule-changes/page.tsx | 7 +- .../teacher/textbooks/[id]/loading.tsx | 60 +- .../teacher/textbooks/[id]/page.tsx | 57 +- .../(dashboard)/teacher/textbooks/loading.tsx | 12 +- .../(dashboard)/teacher/textbooks/page.tsx | 22 +- src/app/api/export/route.ts | 9 +- src/auth.ts | 7 +- src/modules/announcements/actions.ts | 44 +- .../components/announcement-card.tsx | 56 +- .../components/announcement-detail.tsx | 9 +- .../components/announcement-list.tsx | 5 +- src/modules/announcements/data-access.ts | 116 +- .../components/attendance-stats-card.tsx | 21 +- .../attendance-stats-class-selector.tsx | 27 + src/modules/attendance/data-access.ts | 4 +- src/modules/audit/actions.ts | 204 +- .../components/audit-log-export-button.tsx | 10 +- .../audit/components/audit-log-filters.tsx | 38 +- .../audit/components/login-log-filters.tsx | 36 +- src/modules/audit/data-access.ts | 4 +- src/modules/classes/actions.ts | 18 +- .../class-detail/class-overview-stats.tsx | 45 +- .../classes/components/grade-classes-view.tsx | 18 +- .../classes/components/students-table.tsx | 11 +- src/modules/classes/data-access.ts | 63 +- src/modules/course-plans/actions.ts | 1 + .../components/course-plan-list.tsx | 6 +- src/modules/course-plans/data-access.ts | 14 +- .../admin-dashboard/admin-dashboard.tsx | 64 +- .../student-dashboard/student-grades-card.tsx | 204 +- .../student-dashboard/student-stats-grid.tsx | 100 +- .../student-today-schedule-card.tsx | 35 +- .../teacher-grade-trends.tsx | 167 +- .../teacher-dashboard/teacher-stats.tsx | 118 +- .../components/mastery-radar-chart.tsx | 149 +- src/modules/diagnostic/data-access-reports.ts | 9 +- src/modules/diagnostic/data-access.ts | 64 +- .../components/elective-course-list.tsx | 8 +- .../elective/data-access-operations.ts | 154 +- .../elective/data-access-selections.ts | 90 +- src/modules/elective/data-access.ts | 82 +- src/modules/exams/actions.ts | 16 +- src/modules/exams/ai-pipeline.ts | 27 +- .../exams/components/exam-assembly.tsx | 46 +- src/modules/exams/components/exam-filters.tsx | 47 +- src/modules/exams/components/exam-form.tsx | 6 +- src/modules/exams/data-access.ts | 35 +- .../exams/utils/normalize-structure.ts | 54 + src/modules/files/data-access.ts | 4 +- src/modules/grades/actions.ts | 2 +- .../grades/components/analytics-filters.tsx | 90 + .../components/class-comparison-chart.tsx | 160 +- .../grades/components/export-button.tsx | 18 +- .../components/grade-distribution-chart.tsx | 171 +- .../grades/components/grade-stats-card.tsx | 21 +- .../grades/components/grade-trend-chart.tsx | 164 +- .../components/stats-class-selector.tsx | 41 + .../components/subject-comparison-chart.tsx | 148 +- src/modules/grades/data-access-analytics.ts | 5 +- src/modules/grades/data-access.ts | 84 +- src/modules/grades/export.ts | 16 +- src/modules/homework/data-access.ts | 4 - src/modules/layout/components/app-sidebar.tsx | 7 +- src/modules/layout/components/site-header.tsx | 2 +- src/modules/layout/config/navigation.ts | 17 +- src/modules/lesson-preparation/actions-ai.ts | 31 + src/modules/lesson-preparation/actions-kp.ts | 39 + .../lesson-preparation/actions-publish.ts | 51 + src/modules/lesson-preparation/actions.ts | 284 + src/modules/lesson-preparation/ai-suggest.ts | 65 + .../components/block-renderer.tsx | 181 + .../components/blocks/exercise-block.tsx | 155 + .../components/blocks/reflection-block.tsx | 14 + .../components/blocks/rich-text-block.tsx | 81 + .../components/blocks/text-study-block.tsx | 128 + .../components/inline-question-editor.tsx | 179 + .../components/knowledge-point-picker.tsx | 95 + .../components/lesson-plan-card.tsx | 58 + .../components/lesson-plan-editor.tsx | 234 + .../components/lesson-plan-filters.tsx | 61 + .../components/lesson-plan-list.tsx | 42 + .../components/node-edit-panel.tsx | 103 + .../components/node-editor.tsx | 155 + .../components/nodes/lesson-node.tsx | 86 + .../components/publish-homework-dialog.tsx | 121 + .../components/question-bank-picker.tsx | 143 + .../components/template-picker.tsx | 69 + .../components/version-history-drawer.tsx | 102 + src/modules/lesson-preparation/constants.ts | 107 + .../data-access-knowledge.ts | 44 + .../data-access-templates.ts | 92 + .../data-access-versions.ts | 143 + src/modules/lesson-preparation/data-access.ts | 318 + .../hooks/use-lesson-plan-editor.ts | 170 + .../lesson-preparation/publish-service.ts | 175 + src/modules/lesson-preparation/schema.ts | 34 + .../lesson-preparation/seed-templates.ts | 9 + src/modules/lesson-preparation/types.ts | 177 + src/modules/messaging/actions.ts | 16 +- .../messaging/components/message-compose.tsx | 5 +- .../messaging/components/message-detail.tsx | 4 +- .../messaging/components/message-list.tsx | 6 +- .../components/notification-dropdown.tsx | 6 +- .../components/notification-list.tsx | 8 +- src/modules/messaging/data-access.ts | 78 +- .../messaging/notification-preferences.ts | 11 - src/modules/messaging/types.ts | 20 +- src/modules/notifications/actions.ts | 67 +- .../notifications/channels/wechat-channel.ts | 2 + src/modules/parent/components/child-card.tsx | 41 +- .../parent/components/child-detail-header.tsx | 15 +- .../parent/components/child-detail-panel.tsx | 4 +- .../parent/components/child-grade-summary.tsx | 223 +- .../components/child-homework-summary.tsx | 64 +- .../parent/components/child-schedule-card.tsx | 40 +- .../components/parent-children-data-page.tsx | 86 + .../parent/components/parent-dashboard.tsx | 21 +- src/modules/parent/data-access.ts | 60 +- src/modules/parent/types.ts | 14 +- src/modules/proctoring/data-access.ts | 36 +- src/modules/questions/actions.ts | 141 +- .../components/create-question-dialog.tsx | 4 +- .../questions/components/question-filters.tsx | 54 +- src/modules/questions/data-access.ts | 40 +- src/modules/scheduling/data-access.ts | 10 +- src/modules/school/actions.ts | 96 +- src/modules/school/data-access.ts | 48 +- src/modules/settings/actions.ts | 12 +- .../components/admin-settings-view.tsx | 151 +- .../components/ai-provider-settings-card.tsx | 26 +- .../notification-preferences-form.tsx | 10 +- .../components/password-change-form.tsx | 18 +- .../components/profile-settings-form.tsx | 13 +- .../settings/components/settings-view.tsx | 117 + .../components/student-settings-view.tsx | 145 +- .../components/teacher-settings-view.tsx | 169 +- .../components/student-courses-view.tsx | 155 +- .../components/student-schedule-view.tsx | 42 +- src/modules/textbooks/actions.ts | 118 +- .../textbooks/components/textbook-filters.tsx | 46 +- src/modules/textbooks/data-access.ts | 79 +- src/modules/textbooks/schema.ts | 64 + src/modules/textbooks/types.ts | 48 +- src/modules/users/actions.ts | 4 +- .../users/components/user-import-dialog.tsx | 18 +- src/modules/users/import-export.ts | 5 +- src/next-auth.d.ts | 6 +- src/proxy.ts | 7 +- .../components/charts/chart-card-shell.tsx | 90 + .../charts/comparison-radar-chart.tsx | 143 + .../components/charts/simple-bar-chart.tsx | 162 + .../components/charts/trend-line-chart.tsx | 153 + .../question/question-bank-filters.tsx | 137 + .../components/schedule/schedule-list.tsx | 112 + src/shared/components/ui/chip-nav.tsx | 78 + src/shared/components/ui/filter-bar.tsx | 124 + src/shared/components/ui/page-header.tsx | 44 + src/shared/components/ui/stat-card.tsx | 95 + src/shared/components/ui/stat-item.tsx | 38 + src/shared/hooks/use-permission.ts | 65 +- src/shared/lib/auth-guard.ts | 22 +- src/shared/lib/download.ts | 47 + src/shared/lib/permissions.ts | 10 +- src/shared/lib/search-params.ts | 9 + src/shared/lib/utils.ts | 51 + src/shared/types/action-state.test.ts | 42 +- src/shared/types/permissions.ts | 40 +- tests/e2e/teacher-web-test.spec.ts | 416 + tests/webapp/admin_full_test.py | 462 ++ tests/webapp/debug_drizzle.js | 30 + tests/webapp/parent_full_test.py | 989 +++ tests/webapp/parent_test_output.log | 1 + .../screenshots/teacher_course-plans.png | Bin 0 -> 48306 bytes .../webapp/screenshots/teacher_dashboard.png | Bin 0 -> 48357 bytes .../webapp/screenshots/teacher_diagnostic.png | Bin 0 -> 48368 bytes tests/webapp/screenshots/teacher_elective.png | Bin 0 -> 48356 bytes tests/webapp/screenshots/teacher_exams.png | Bin 0 -> 50275 bytes .../webapp/screenshots/teacher_exams_all.png | Bin 0 -> 50275 bytes tests/webapp/screenshots/teacher_homework.png | Bin 0 -> 51008 bytes .../teacher_homework_assignments.png | Bin 0 -> 51008 bytes .../teacher_homework_assignments_create.png | Bin 0 -> 51333 bytes .../teacher_homework_submissions.png | Bin 0 -> 50816 bytes .../screenshots/teacher_lesson-plans.png | Bin 0 -> 48156 bytes tests/webapp/student_full_test.py | 507 ++ tests/webapp/teacher_full_test.py | 766 ++ tsconfig.json | 6 +- 327 files changed, 34070 insertions(+), 5642 deletions(-) create mode 100644 bugs/admin_bug_v2.md create mode 100644 bugs/admin_bug_v3.md create mode 100644 bugs/admin_web_test.json create mode 100644 bugs/admin_web_test.md create mode 100644 bugs/back_bug_v2.md create mode 100644 bugs/back_bug_v3.md create mode 100644 bugs/lesson_preparation_bug_v2.md create mode 100644 bugs/others_bug_v2.md create mode 100644 bugs/others_bug_v3.md create mode 100644 bugs/parent_web_test.json create mode 100644 bugs/parent_web_test.md create mode 100644 bugs/shared_bug_v2.md create mode 100644 bugs/shared_bug_v3.md create mode 100644 bugs/student_web_test.json create mode 100644 bugs/student_web_test.md create mode 100644 bugs/teacher_bug_v2.md create mode 100644 bugs/teacher_bug_v3.md create mode 100644 bugs/teacher_web_test.json create mode 100644 bugs/teacher_web_test.md create mode 100644 bugs/test_edit_page.py create mode 100644 bugs/test_edit_page2.py create mode 100644 bugs/test_node_editor.py create mode 100644 bugs/v2_after_create.png create mode 100644 bugs/v2_after_login.png create mode 100644 bugs/v2_edit.png create mode 100644 bugs/v2_list.png create mode 100644 bugs/v2_login.png create mode 100644 bugs/v2_new.png create mode 100644 bugs/v3_after_add.png create mode 100644 bugs/v3_node_editor.png create mode 100644 bugs/v3_node_selected.png create mode 100644 check_lines.ps1 create mode 100644 count_lines.ps1 create mode 100644 debug.log rename src/app/(dashboard)/teacher/exams/grading/loading.tsx => deletes/exams-grading-loading.tsx (100%) create mode 100644 docs/feature/001_first_login_onboarding.md create mode 100644 docs/feature/f_bk.md create mode 100644 docs/feature/f_bk_design.md create mode 100644 docs/superpowers/plans/2026-06-18-lesson-preparation.md create mode 100644 drizzle/0002_tiny_lionheart.sql create mode 100644 drizzle/meta/0002_snapshot.json create mode 100644 scripts/check-parent.mjs create mode 100644 scripts/create-test-plan.ts create mode 100644 src/app/(dashboard)/admin/error.tsx create mode 100644 src/app/(dashboard)/admin/loading.tsx create mode 100644 src/app/(dashboard)/student/attendance/loading.tsx create mode 100644 src/app/(dashboard)/student/diagnostic/loading.tsx create mode 100644 src/app/(dashboard)/student/elective/loading.tsx create mode 100644 src/app/(dashboard)/student/error.tsx create mode 100644 src/app/(dashboard)/student/grades/loading.tsx create mode 100644 src/app/(dashboard)/student/learning/assignments/[assignmentId]/loading.tsx create mode 100644 src/app/(dashboard)/student/learning/assignments/loading.tsx create mode 100644 src/app/(dashboard)/student/learning/textbooks/[id]/loading.tsx create mode 100644 src/app/(dashboard)/student/learning/textbooks/loading.tsx create mode 100644 src/app/(dashboard)/teacher/lesson-plans/[planId]/edit/page.tsx create mode 100644 src/app/(dashboard)/teacher/lesson-plans/new/page.tsx create mode 100644 src/app/(dashboard)/teacher/lesson-plans/page.tsx create mode 100644 src/modules/attendance/components/attendance-stats-class-selector.tsx create mode 100644 src/modules/exams/utils/normalize-structure.ts create mode 100644 src/modules/grades/components/analytics-filters.tsx create mode 100644 src/modules/grades/components/stats-class-selector.tsx create mode 100644 src/modules/lesson-preparation/actions-ai.ts create mode 100644 src/modules/lesson-preparation/actions-kp.ts create mode 100644 src/modules/lesson-preparation/actions-publish.ts create mode 100644 src/modules/lesson-preparation/actions.ts create mode 100644 src/modules/lesson-preparation/ai-suggest.ts create mode 100644 src/modules/lesson-preparation/components/block-renderer.tsx create mode 100644 src/modules/lesson-preparation/components/blocks/exercise-block.tsx create mode 100644 src/modules/lesson-preparation/components/blocks/reflection-block.tsx create mode 100644 src/modules/lesson-preparation/components/blocks/rich-text-block.tsx create mode 100644 src/modules/lesson-preparation/components/blocks/text-study-block.tsx create mode 100644 src/modules/lesson-preparation/components/inline-question-editor.tsx create mode 100644 src/modules/lesson-preparation/components/knowledge-point-picker.tsx create mode 100644 src/modules/lesson-preparation/components/lesson-plan-card.tsx create mode 100644 src/modules/lesson-preparation/components/lesson-plan-editor.tsx create mode 100644 src/modules/lesson-preparation/components/lesson-plan-filters.tsx create mode 100644 src/modules/lesson-preparation/components/lesson-plan-list.tsx create mode 100644 src/modules/lesson-preparation/components/node-edit-panel.tsx create mode 100644 src/modules/lesson-preparation/components/node-editor.tsx create mode 100644 src/modules/lesson-preparation/components/nodes/lesson-node.tsx create mode 100644 src/modules/lesson-preparation/components/publish-homework-dialog.tsx create mode 100644 src/modules/lesson-preparation/components/question-bank-picker.tsx create mode 100644 src/modules/lesson-preparation/components/template-picker.tsx create mode 100644 src/modules/lesson-preparation/components/version-history-drawer.tsx create mode 100644 src/modules/lesson-preparation/constants.ts create mode 100644 src/modules/lesson-preparation/data-access-knowledge.ts create mode 100644 src/modules/lesson-preparation/data-access-templates.ts create mode 100644 src/modules/lesson-preparation/data-access-versions.ts create mode 100644 src/modules/lesson-preparation/data-access.ts create mode 100644 src/modules/lesson-preparation/hooks/use-lesson-plan-editor.ts create mode 100644 src/modules/lesson-preparation/publish-service.ts create mode 100644 src/modules/lesson-preparation/schema.ts create mode 100644 src/modules/lesson-preparation/seed-templates.ts create mode 100644 src/modules/lesson-preparation/types.ts delete mode 100644 src/modules/messaging/notification-preferences.ts create mode 100644 src/modules/parent/components/parent-children-data-page.tsx create mode 100644 src/modules/settings/components/settings-view.tsx create mode 100644 src/modules/textbooks/schema.ts create mode 100644 src/shared/components/charts/chart-card-shell.tsx create mode 100644 src/shared/components/charts/comparison-radar-chart.tsx create mode 100644 src/shared/components/charts/simple-bar-chart.tsx create mode 100644 src/shared/components/charts/trend-line-chart.tsx create mode 100644 src/shared/components/question/question-bank-filters.tsx create mode 100644 src/shared/components/schedule/schedule-list.tsx create mode 100644 src/shared/components/ui/chip-nav.tsx create mode 100644 src/shared/components/ui/filter-bar.tsx create mode 100644 src/shared/components/ui/page-header.tsx create mode 100644 src/shared/components/ui/stat-card.tsx create mode 100644 src/shared/components/ui/stat-item.tsx create mode 100644 src/shared/lib/download.ts create mode 100644 src/shared/lib/search-params.ts create mode 100644 tests/e2e/teacher-web-test.spec.ts create mode 100644 tests/webapp/admin_full_test.py create mode 100644 tests/webapp/debug_drizzle.js create mode 100644 tests/webapp/parent_full_test.py create mode 100644 tests/webapp/parent_test_output.log create mode 100644 tests/webapp/screenshots/teacher_course-plans.png create mode 100644 tests/webapp/screenshots/teacher_dashboard.png create mode 100644 tests/webapp/screenshots/teacher_diagnostic.png create mode 100644 tests/webapp/screenshots/teacher_elective.png create mode 100644 tests/webapp/screenshots/teacher_exams.png create mode 100644 tests/webapp/screenshots/teacher_exams_all.png create mode 100644 tests/webapp/screenshots/teacher_homework.png create mode 100644 tests/webapp/screenshots/teacher_homework_assignments.png create mode 100644 tests/webapp/screenshots/teacher_homework_assignments_create.png create mode 100644 tests/webapp/screenshots/teacher_homework_submissions.png create mode 100644 tests/webapp/screenshots/teacher_lesson-plans.png create mode 100644 tests/webapp/student_full_test.py create mode 100644 tests/webapp/teacher_full_test.py diff --git a/bugs/admin_bug_v2.md b/bugs/admin_bug_v2.md new file mode 100644 index 0000000..404368b --- /dev/null +++ b/bugs/admin_bug_v2.md @@ -0,0 +1,532 @@ +# Admin 前端文件规范核查报告 v2 + +> 版本:v2(基于 v1 报告的二次复查) +> 核查范围:`src/app/(dashboard)/admin/` 下全部 26 个 `page.tsx` 文件 +> 核查依据: +> - `.trae/rules/project_rules.md`(项目规则) +> - `docs/standards/coding-standards.md`(编码规范 v1.0) +> - `docs/architecture/004_architecture_impact_map.md`(架构影响地图) +> - React / Next.js 16 最佳实践 +> - Web 界面设计规范(WCAG 2.2 AA) +> 核查日期:2026-06-18(v2) +> 上次核查:2026-06-18(v1) + +--- + +## 〇、v1 → v2 修复状态追踪 + +**重要说明**:本次复查发现,自 v1 报告(`bugs/admin_bug.md`)输出后,`src/app/(dashboard)/admin/` 下全部 26 个 `page.tsx` 文件**内容均未发生任何修改**,`src/shared/lib/utils.ts` 也未新增共享工具函数。v1 报告提出的所有问题**全部未修复**。 + +### v1 问题修复状态对照表 + +| v1 编号 | 问题 | 严重级别 | v2 状态 | 备注 | +|---------|------|---------|---------|------| +| P0-1 | 全部 26 个页面缺少 `error.tsx` / `loading.tsx` | P0 | ❌ 未修复 | 仍无任何 error/loading 边界文件 | +| P0-2 | `attendance/page.tsx` 缺少权限校验 | P0 | ❌ 未修复 | 第 26 行仍为 `getAuthContext()`,未加 `requirePermission` | +| P1-1 | 全部 26 个页面组件缺少返回类型标注 | P1 | ❌ 未修复 | 全部页面函数仍无 `: Promise` | +| P1-2 | `getParam` 工具函数在 27 个文件中重复 | P1 | ❌ 未修复 | `shared/lib/utils.ts` 未新增 `getSearchParam` | +| P1-3 | 4 个文件使用 `as` 类型断言 | P1 | ❌ 未修复 | `audit-logs/*`、`attendance` 仍用 `as` | +| P1-4 | UI 文案中英文混用 | P1 | ❌ 未修复 | 仅 `users/import` 为中文,其余仍英文 | +| P2-1 | `school/grades/insights` 使用原生 `` | +| P2-2 | `users/import` 使用原生 `` | P2 | ❌ 未修复 | 第 93-128 行仍为原生 `
` | +| P2-3 | Tailwind 任意值违规 | P2 | ❌ 未修复 | `md:w-[360px]`、`h-[360px]` 仍存在 | +| P2-4 | `users/import` 硬编码颜色 `text-amber-500` | P2 | ❌ 未修复 | 第 67 行未变 | +| P2-5 | `school/grades/insights` 导入顺序违规 | P2 | ❌ 未修复 | `lucide-react` 仍在最后 | +| P2-6 | `course-plans/[id]/edit` 同模块重复导入 | P2 | ❌ 未修复 | 第 3-4 行仍分两行 | +| P2-7 | `scheduling/*` 从 `actions` 取数 | P2 | ❌ 未修复 | 仍从 `@/modules/scheduling/actions` 导入 | + +**结论**:v1 提出的 **2 个 P0 + 4 个 P1 + 7 个 P2 = 13 个问题,0 个已修复**。 + +--- + +## 一、v2 新增发现(v1 遗漏的问题) + +本次复查在 v1 基础上深度审查,新发现 **10 个问题**。 + +### P1 重要问题(v2 新增) + +#### P1-5(v2 新增)`attendance/page.tsx` 第 39 行违反 Prettier `printWidth: 100` + +**文件**:[src/app/(dashboard)/admin/attendance/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/admin/attendance/page.tsx#L39) + +**违反规范**: +- `.prettierrc` 配置 `"printWidth": 100` +- 编码规范 §十五:「Prettier 自动保证格式一致」 + +**现状**:第 39 行单行长度约 115 字符,超出 100 字符限制: +```tsx + status: status && status !== "all" ? (status as "present" | "absent" | "late" | "early_leave" | "excused") : undefined, +``` + +**说明**:项目 `.prettierrc` 已配置 `printWidth: 100`,但此行未触发格式化,可能是因为该文件未经过 `prettier --write` 处理,或 ESLint 未强制 Prettier 规则。 + +**修复建议**:抽取状态类型守卫后自然换行(同时解决 P1-3 的 `as` 断言问题): +```tsx +const isValidAttendanceStatus = (v?: string): v is AttendanceStatus => + v === "present" || v === "absent" || v === "late" || v === "early_leave" || v === "excused" + +// 在组件内 +status: status && status !== "all" && isValidAttendanceStatus(status) ? status : undefined, +``` + +--- + +#### P1-6(v2 新增)`school/grades/insights/page.tsx` 的 `getParam` 实现与其他文件不一致 + +**文件**:[src/app/(dashboard)/admin/school/grades/insights/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/admin/school/grades/insights/page.tsx#L17-L22) + +**现状**:该文件的 `getParam` 实现与其他 8 个 admin 页面**逻辑等价但写法不同**: + +```tsx +// school/grades/insights/page.tsx(第 17-22 行)—— 三分支写法 +const getParam = (params: SearchParams, key: string) => { + const v = params[key] + if (typeof v === "string") return v + if (Array.isArray(v)) return v[0] + return undefined +} + +// 其他 8 个 admin 页面 —— 三元写法 +const getParam = (params: SearchParams, key: string) => { + const v = params[key] + return Array.isArray(v) ? v[0] : v +} +``` + +**影响**:加剧 P1-2 的 DRY 问题,两种实现并存增加维护成本,且 `v[0]` 在 `noUncheckedIndexedAccess` 开启后返回 `string | undefined`,两种写法的类型推导行为可能不同。 + +**修复建议**:与 P1-2 一并解决,抽取到 `shared/lib/utils.ts` 统一实现。 + +--- + +#### P1-7(v2 新增)`attendance/page.tsx` 第 39 行使用内联字面量类型而非 `AttendanceStatus` 类型 + +**文件**:[src/app/(dashboard)/admin/attendance/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/admin/attendance/page.tsx#L39) + +**违反规范**: +- 编码规范 §4.2:「优先 `interface` 描述对象形状,`type` 用于联合、交叉、映射类型」 +- DRY 原则 + +**现状**:第 39 行内联了 5 个字面量类型,而非引用 `AttendanceStatus` 类型: +```tsx +status as "present" | "absent" | "late" | "early_leave" | "excused" +``` + +**说明**:`@/modules/attendance/types` 应已定义 `AttendanceStatus` 类型(其他模块如 `announcements`、`scheduling`、`course-plans`、`elective` 均有对应 status 类型导出)。内联字面量导致类型定义重复,若枚举值变更需多处修改。 + +**修复建议**: +```tsx +import type { AttendanceStatus } from "@/modules/attendance/types" + +const isValidAttendanceStatus = (v?: string): v is AttendanceStatus => + v === "present" || v === "absent" || v === "late" || v === "early_leave" || v === "excused" +``` + +--- + +### P2 一般问题(v2 新增) + +#### P2-8(v2 新增)`school/grades/insights/page.tsx` 第 24 行 `fmt` 工具函数内联定义 + +**文件**:[src/app/(dashboard)/admin/school/grades/insights/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/admin/school/grades/insights/page.tsx#L24) + +**违反规范**: +- 编码规范 §一:「单一职责」 +- 编码规范 §5.3:「工具函数 ≤ 40 行」(此函数 1 行,但属于通用工具应抽取) + +**现状**:第 24 行内联定义数字格式化函数: +```tsx +const fmt = (v: number | null, digits = 1) => (typeof v === "number" && Number.isFinite(v) ? v.toFixed(digits) : "-") +``` + +**影响**:该函数为通用数字格式化工具,可能在其他统计页面(如 `teacher/grades/stats`、`management/grade/insights`)重复出现。 + +**修复建议**:抽取到 `shared/lib/utils.ts`: +```tsx +export function formatNumber(v: number | null | undefined, digits = 1): string { + if (typeof v !== "number" || !Number.isFinite(v)) return "-" + return v.toFixed(digits) +} +``` + +--- + +#### P2-9(v2 新增)`school/grades/insights/page.tsx` 第 137 行可用可选链简化 + +**文件**:[src/app/(dashboard)/admin/school/grades/insights/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/admin/school/grades/insights/page.tsx#L137) + +**现状**:第 137 行使用三元表达式而非可选链: +```tsx +
{insights.latest ? insights.latest.title : "-"}
+``` + +**修复建议**:使用可选链 + 空值合并: +```tsx +
{insights.latest?.title ?? "-"}
+``` + +**说明**:同文件第 136 行已使用 `insights.latest?.scoreStats.avg ?? null`,写法不一致。 + +--- + +#### P2-10(v2 新增)`school/page.tsx` 缺少 `export const dynamic` 声明 + +**文件**:[src/app/(dashboard)/admin/school/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/admin/school/page.tsx) + +**现状**:该文件仅 5 行,使用 `redirect()` 跳转,但**未声明** `export const dynamic = "force-dynamic"`: +```tsx +import { redirect } from "next/navigation" + +export default function AdminSchoolPage() { + redirect("/admin/school/classes") +} +``` + +**对比**:admin 目录下其他 25 个页面均声明了 `export const dynamic = "force-dynamic"`,仅此文件缺失。 + +**影响**:Next.js 可能在构建时尝试静态生成此页面,`redirect()` 在静态生成阶段的行为与运行时不同,可能导致构建警告或行为不一致。 + +**修复建议**:补充声明: +```tsx +import { redirect } from "next/navigation" + +export const dynamic = "force-dynamic" + +export default function AdminSchoolPage(): never { + redirect("/admin/school/classes") +} +``` + +**注**:`redirect()` 抛出异常永不返回,返回类型应标注为 `never`。 + +--- + +#### P2-11(v2 新增)`users/import/page.tsx` 是同步函数但无 `dynamic` 导出,与其他页面不一致 + +**文件**:[src/app/(dashboard)/admin/users/import/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/admin/users/import/page.tsx#L14) + +**现状**:第 14 行为同步函数组件,且无 `export const dynamic` 声明: +```tsx +export default function UserImportPage() { + return ( /* ... */ ) +} +``` + +**对比**:admin 目录下其他 24 个数据获取页面均声明 `export const dynamic = "force-dynamic"`,仅此文件与 `school/page.tsx` 缺失。 + +**说明**:该页面为纯静态内容(无数据获取),理论上可静态生成,但与 admin 路由组整体策略不一致。需明确决策: +- 若 admin 路由组统一 `force-dynamic`(因权限校验需运行时),则此页面应补充声明 +- 若允许静态页面,则应在架构文档中说明例外 + +**修复建议**:为保持一致性,补充 `export const dynamic = "force-dynamic"`,或显式注释说明为何例外。 + +--- + +#### P2-12(v2 新增)多个编辑页缺少返回上一页的导航 + +**违反规范**: +- Web 界面设计规范:「焦点管理必须合理」 +- 用户体验最佳实践:「始终提供返回路径」 + +**现状**:以下编辑/创建页面**未提供返回按钮**,用户只能通过浏览器后退或侧边栏导航: + +| 文件 | 是否有返回按钮 | +|------|--------------| +| `announcements/[id]/page.tsx` | ❌ 无 | +| `course-plans/create/page.tsx` | ❌ 无(仅 `CoursePlanForm` 的 `backHref` prop) | +| `course-plans/[id]/page.tsx` | ❌ 无(仅 `CoursePlanDetail` 的 `backHref` prop) | +| `course-plans/[id]/edit/page.tsx` | ❌ 无(仅 `CoursePlanForm` 的 `backHref` prop) | +| `elective/create/page.tsx` | ❌ 无(仅 `ElectiveCourseForm` 的 `backHref` prop) | +| `elective/[id]/edit/page.tsx` | ❌ 无(仅 `ElectiveCourseForm` 的 `backHref` prop) | +| `users/import/page.tsx` | ✅ 有(第 20-25 行 `ArrowLeft` 返回按钮) | + +**说明**:`users/import/page.tsx` 在页面顶部提供了显式的返回按钮(``),是正确的做法。其他编辑页虽通过子组件的 `backHref` prop 传递了返回路径,但返回入口依赖子组件内部实现,页面层未统一控制。 + +**修复建议**:在所有编辑/创建页面顶部统一添加返回按钮,与 `users/import/page.tsx` 保持一致;或将返回按钮抽取为共享组件 `PageBackButton`。 + +--- + +#### P2-13(v2 新增)大部分页面缺少 `metadata` 导出 + +**违反规范**: +- Next.js 16 最佳实践:「页面应导出 `metadata` 用于 SEO 与标签页标题」 +- 编码规范 §十四:「文档与交付物」 + +**现状**: + +| 文件 | 是否导出 `metadata` | +|------|-------------------| +| `users/import/page.tsx` | ✅ 有(第 9-12 行) | +| 其余 25 个页面 | ❌ 无 | + +**影响**:浏览器标签页标题默认显示全局标题,无法区分当前所在 admin 子页面,影响用户体验(多个标签页难以区分)。 + +**修复建议**:为每个页面补充 `metadata` 导出: +```tsx +import type { Metadata } from "next" + +export const metadata: Metadata = { + title: "审计日志 - Next_Edu", + description: "查看系统所有用户操作记录", +} +``` + +--- + +#### P2-14(v2 新增)`school/grades/insights/page.tsx` 使用原生 `` 导致整页刷新 + +**文件**:[src/app/(dashboard)/admin/school/grades/insights/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/admin/school/grades/insights/page.tsx#L55-L72) + +**现状**:第 55-72 行使用原生 HTML `` 提交筛选器,会导致**整页刷新**,丢失当前滚动位置与页面状态。 + +**违反规范**: +- 编码规范 §7.3:「URL 状态:使用 `nuqs`(已集成)」 +- React 最佳实践:「避免不必要的整页刷新」 + +**影响**: +- 用户体验差:每次筛选都触发整页白屏加载(叠加 P0-1 缺少 `loading.tsx` 问题更严重) +- 与项目已集成的 `nuqs` URL 状态管理方案不一致 +- 其他筛选页(`audit-logs/*`、`attendance`)使用子组件内的客户端筛选,此页面是唯一使用原生 form 提交的 + +**修复建议**: +1. **方案 A(推荐)**:将筛选器提取为客户端组件,使用 `nuqs` 的 `useQueryState` 管理 `gradeId` 参数,实现无刷新筛选 +2. **方案 B(最小改动)**:保持服务端筛选,但补充 `loading.tsx` 缓解白屏问题 + +--- + +## 二、v2 核查概览(含 v1 + v2 全部问题) + +| 维度 | 文件数 | 通过 | 待改进 | v2 新增 | +|------|--------|------|--------|---------| +| 架构分层 | 26 | 24 | 2 | 0 | +| TypeScript 规范 | 26 | 4 | 22 | +3 | +| 安全与权限 | 26 | 3 | 23 | 0 | +| UI 一致性与设计令牌 | 26 | 18 | 8 | +1 | +| 错误与加载边界 | 26 | 0 | 26 | 0 | +| 代码复用(DRY) | 26 | 0 | 26 | +2 | +| 格式化(Prettier) | 26 | 25 | 1 | +1 | +| 导航与 UX | 26 | 1 | 25 | +2 | +| SEO(metadata) | 26 | 1 | 25 | +1 | + +**累计问题数**:v1 的 13 个 + v2 新增 10 个 = **23 个问题**,全部未修复。 + +--- + +## 三、v2 问题清单汇总(按严重程度排序) + +### P0 严重(必须立即修复) + +| 编号 | 问题 | v1/v2 | 文件 | +|------|------|-------|------| +| P0-1 | 全部 26 个页面缺少 `error.tsx` / `loading.tsx` | v1 | 全部 | +| P0-2 | `attendance/page.tsx` 缺少 `requirePermission` 权限校验 | v1 | `attendance/page.tsx` | + +### P1 重要(应尽快修复) + +| 编号 | 问题 | v1/v2 | 文件 | +|------|------|-------|------| +| P1-1 | 全部 26 个页面缺少返回类型 `Promise` | v1 | 全部 | +| P1-2 | `getParam` 在 27 个文件重复定义 | v1 | 9 个 admin 文件 | +| P1-3 | 4 个文件使用 `as` 类型断言 | v1 | `audit-logs/*`、`attendance` | +| P1-4 | UI 文案中英文混用 | v1 | ~20 个文件 | +| P1-5 | `attendance` 第 39 行超 `printWidth: 100` | **v2** | `attendance/page.tsx` | +| P1-6 | `school/grades/insights` 的 `getParam` 实现不一致 | **v2** | `school/grades/insights/page.tsx` | +| P1-7 | `attendance` 使用内联字面量而非 `AttendanceStatus` 类型 | **v2** | `attendance/page.tsx` | + +### P2 一般(建议修复) + +| 编号 | 问题 | v1/v2 | 文件 | +|------|------|-------|------| +| P2-1 | `school/grades/insights` 使用原生 `
` | v1 | `users/import/page.tsx` | +| P2-3 | Tailwind 任意值 `w-[360px]`、`h-[360px]` | v1 | `school/grades/insights/page.tsx` | +| P2-4 | `users/import` 硬编码颜色 `text-amber-500` | v1 | `users/import/page.tsx` | +| P2-5 | `school/grades/insights` 导入顺序违规 | v1 | `school/grades/insights/page.tsx` | +| P2-6 | `course-plans/[id]/edit` 同模块重复导入 | v1 | `course-plans/[id]/edit/page.tsx` | +| P2-7 | `scheduling/*` 从 `actions` 取数 | v1 | `scheduling/*` | +| P2-8 | `fmt` 工具函数内联定义 | **v2** | `school/grades/insights/page.tsx` | +| P2-9 | 第 137 行可用可选链简化 | **v2** | `school/grades/insights/page.tsx` | +| P2-10 | `school/page.tsx` 缺少 `export const dynamic` | **v2** | `school/page.tsx` | +| P2-11 | `users/import` 缺少 `dynamic` 声明(不一致) | **v2** | `users/import/page.tsx` | +| P2-12 | 多个编辑页缺少返回按钮 | **v2** | 6 个编辑/创建页 | +| P2-13 | 25 个页面缺少 `metadata` 导出 | **v2** | 25 个文件 | +| P2-14 | 原生 `` 整页刷新 | **v2** | `school/grades/insights/page.tsx` | + +--- + +## 四、React 性能优化建议(v2 更新) + +### R1 利用 Suspense 流式渲染(v1 提出,未实施) + +**现状**:所有页面使用 `export const dynamic = "force-dynamic"` 整页动态渲染。 + +**建议**:对数据量大的页面(`audit-logs/*`、`school/grades/insights`、`attendance`)拆分 Suspense 边界。详见 v1 报告 R1。 + +### R2 `school/grades/insights/page.tsx` 串行查询可并行(v1 提出,未实施) + +**现状**:第 30-33 行 `getGrades()` 与 `getGradeHomeworkInsights()` 串行执行,但两者无数据依赖。 + +**建议**:改为 `Promise.all` 并行。详见 v1 报告 R2。 + +### R3 列表页 `classOptions` 映射可下沉至 data-access(v1 提出,未实施) + +详见 v1 报告 R3。 + +### R4(v2 新增)`school/grades/insights/page.tsx` 表格未虚拟化,大数据量下性能风险 + +**文件**:[src/app/(dashboard)/admin/school/grades/insights/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/admin/school/grades/insights/page.tsx#L164-L180) + +**现状**:第 164-180 行与第 208-220 行使用 `insights.assignments.map()` 与 `insights.classes.map()` 直接渲染整张表格,无分页或虚拟化。 + +**说明**:`getGradeHomeworkInsights({ limit: 50 })` 限制为 50 条,但 `insights.classes` 无限制,大型学校(如 50+ 班级的年级)可能渲染数百行 DOM 节点。 + +**修复建议**: +- 短期:在 data-access 层对 `classes` 也加 `limit` +- 长期:引入 `@tanstack/react-virtual` 虚拟化长列表 + +--- + +## 五、Web 界面设计规范建议(v2 更新) + +### W1-W5(v1 提出,未实施) + +详见 v1 报告第四部分:`
`、标题层级、`aria-live`、`EmptyState` 图标语义。 + +### W6(v2 新增)`school/grades/insights/page.tsx` 原生 `` 缺少 `aria-label` 或 `aria-labelledby`,且 `