From 04b7a40bdcac4e4e3118fead663848c08b1c7fb6 Mon Sep 17 00:00:00 2001 From: SpecialX <47072643+wangxiner55@users.noreply.github.com> Date: Mon, 31 Aug 2026 11:25:21 +0800 Subject: [PATCH] =?UTF-8?q?feat(portal-shell):=20=E5=AD=A6=E7=94=9F?= =?UTF-8?q?=E5=9F=9F=E5=85=A8=E9=A1=B5=E9=9D=A2=E8=BF=81=E7=A7=BB=E4=B8=8E?= =?UTF-8?q?=E8=A7=84=E8=8C=83=E5=90=88=E8=A7=84=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 学生域 32 页全量迁移(含作答/自动保存/提交/诊断) - 补齐 4 个 MSW mock 缺口,修 diagnostic case 名 - 修 4 处 Tailwind 任意值;新增共享组件与路由 --- .../docs/needtodo/SUMMARY-NeedTodo.md | 229 ++++ .../docs/needtodo/admin-NeedTodo.md | 880 +++++++------- .../docs/needtodo/parent-NeedTodo.md | 79 +- .../docs/needtodo/student-NeedTodo.md | 1015 +++++++---------- .../docs/needtodo/teacher-NeedTodo.md | 892 +++++++-------- .../docs/needtodo/teacher-migration-gap.md | 678 +++++++++++ .../src/app/shell/admin/users/[id]/error.tsx | 38 + .../app/shell/admin/users/[id]/loading.tsx | 9 + .../src/app/shell/admin/users/[id]/page.tsx | 23 + .../src/app/shell/messages/[id]/page.tsx | 22 + .../src/app/shell/messages/compose/page.tsx | 24 + .../app/shell/messages/group-compose/page.tsx | 27 + .../src/app/shell/profile/page.tsx | 23 + .../src/app/shell/student/diagnostic/page.tsx | 24 + .../shell/teacher/classes/my/[id]/page.tsx | 25 + .../app/shell/teacher/classes/my/error.tsx | 38 + .../app/shell/teacher/classes/my/loading.tsx | 9 + .../src/app/shell/teacher/classes/my/page.tsx | 23 + .../shell/teacher/classes/students/page.tsx | 13 + .../src/app/shell/teacher/exams/all/error.tsx | 38 + .../app/shell/teacher/exams/all/loading.tsx | 9 + .../src/app/shell/teacher/exams/all/page.tsx | 23 + .../app/shell/teacher/exams/create/error.tsx | 38 + .../shell/teacher/exams/create/loading.tsx | 9 + .../app/shell/teacher/exams/create/page.tsx | 23 + .../exams/grading/[submissionId]/page.tsx | 20 + .../app/shell/teacher/exams/grading/error.tsx | 38 + .../shell/teacher/exams/grading/loading.tsx | 9 + .../app/shell/teacher/exams/grading/page.tsx | 13 + .../homework/assignments/create/page.tsx | 23 + .../attendance/admin-attendance-client.tsx | 8 +- .../attendance-grade-correlation-card.tsx | 8 +- .../attendance/attendance-records-list.tsx | 2 +- .../attendance/class-comparison-card.tsx | 6 +- .../features/admin/attendance/export-utils.ts | 97 ++ .../admin/audit-logs/login-logs-client.tsx | 2 +- .../course-plans/course-plan-item-editor.tsx | 2 +- .../error-book/error-book-detail-dialog.tsx | 101 +- .../admin/files/file-batch-operations.tsx | 69 +- .../lesson-plans/delete-confirm-dialog.tsx | 52 +- .../admin/lesson-plans/export-utils.ts | 116 ++ .../organization-detail-dialog.tsx | 110 ++ .../features/admin/plugins/plugins-client.tsx | 42 +- .../admin/questions/batch-operations.tsx | 59 +- .../questions/create-question-dialog.tsx | 45 +- .../questions/question-detail-dialog.tsx | 99 +- .../scheduling/schedule-changes-client.tsx | 57 +- .../admin/school/class-invitation-manager.tsx | 140 ++- .../admin/school/class-schedule-dialog.tsx | 149 +-- .../admin/school/departments-client.tsx | 40 +- .../admin/school/grade-insights-client.tsx | 2 +- .../admin/students/student-detail-dialog.tsx | 107 ++ .../admin/students/students-list-client.tsx | 168 ++- .../admin/teachers/teacher-detail-dialog.tsx | 112 ++ .../admin/users/user-detail-client.tsx | 400 +++++++ .../admin/viewports/viewports-client.tsx | 42 +- .../messages/message-compose-client.tsx | 159 +++ .../shared/messages/message-detail-client.tsx | 148 +++ .../messages/message-group-compose-client.tsx | 194 ++++ .../shared/profile/profile-client.tsx | 329 ++++++ .../student/dashboard/dashboard-client.tsx | 686 +++++++++++ .../student/diagnostic/diagnostic-client.tsx | 645 +++++++++++ .../error-book/error-book-detail-dialog.tsx | 323 ++++++ .../grades/components/growth-archive-card.tsx | 248 ++++ .../grades/components/ranking-trend-card.tsx | 202 ++++ .../components/score-distribution-card.tsx | 189 +++ .../student/grades/grades-list-client.tsx | 2 +- .../student/grades/report-card-print.css | 88 ++ .../student/leave/leave-request-form.tsx | 224 ++++ .../student/leave/leave-request-list.tsx | 269 +++++ .../components/ai-assistant-widget-inner.tsx | 349 ++++++ .../ai/components/ai-assistant-widget.tsx | 24 + .../ai/components/ai-chart-renderer.tsx | 374 ++++++ .../teacher/ai/components/ai-chat-input.tsx | 128 +++ .../ai/components/ai-chat-messages.tsx | 201 ++++ .../teacher/ai/components/ai-chat-panel.tsx | 263 +++++ .../ai/components/ai-error-book-analysis.tsx | 295 +++++ .../ai/components/ai-grading-assist.tsx | 201 ++++ .../ai-lesson-content-generator.tsx | 217 ++++ .../ai/components/ai-markdown-renderer.tsx | 164 +++ .../ai/components/ai-provider-selector.tsx | 69 ++ .../ai-question-variant-generator.tsx | 222 ++++ .../ai/components/ai-usage-dashboard.tsx | 269 +++++ .../teacher/ai/context/ai-client-provider.tsx | 62 + .../ai/context/create-ai-client-service.ts | 101 ++ .../features/teacher/ai/hooks/stream-utils.ts | 141 +++ .../teacher/ai/hooks/use-ai-chat-stream.ts | 177 +++ .../teacher/ai/hooks/use-ai-suggestion.ts | 74 ++ .../teacher/ai/hooks/use-drag-position.ts | 130 +++ .../teacher/ai/hooks/use-floating-ball.ts | 165 +++ .../ai/hooks/use-position-persistence.ts | 106 ++ .../src/features/teacher/ai/schema.ts | 280 +++++ .../teacher/ai/services/ai-service.ts | 559 +++++++++ .../teacher/ai/services/content-safety.ts | 299 +++++ .../teacher/ai/services/prompt-templates.ts | 308 +++++ .../teacher/ai/services/usage-tracker.ts | 132 +++ .../src/features/teacher/ai/types.ts | 332 ++++++ .../attendance-grade-correlation-card.tsx | 373 ++++++ .../components/attendance-page-layout.tsx | 40 + .../components/attendance-record-list.tsx | 173 +++ .../components/attendance-report-print.tsx | 332 ++++++ .../components/attendance-rules-form.tsx | 205 ++++ .../components/attendance-sheet.tsx | 577 ++++++++++ .../attendance-stats-class-selector.tsx | 29 + .../components/attendance-trend-chart.tsx | 130 +++ .../components/attendance-warnings-card.tsx | 136 +++ .../components/class-comparison-card.tsx | 212 ++++ .../teacher/attendance/components/index.ts | 23 + .../components/student-attendance-view.tsx | 174 +++ .../attendance/lib/correlation-compute.ts | 152 +++ .../teacher/attendance/lib/trend-compute.ts | 97 ++ .../teacher/attendance/lib/type-guards.ts | 17 + .../teacher/attendance/lib/warning-compute.ts | 158 +++ .../src/features/teacher/attendance/types.ts | 160 +++ .../components/class-delete-dialog.tsx | 43 + .../class-detail/class-assignments-widget.tsx | 161 +++ .../components/class-detail/class-header.tsx | 132 +++ .../class-detail/class-overview-stats.tsx | 58 + .../class-detail/class-quick-actions.tsx | 80 ++ .../class-detail/class-schedule-widget.tsx | 153 +++ .../class-detail/class-students-widget.tsx | 161 +++ .../class-detail/class-trends-widget.tsx | 541 +++++++++ .../components/class-invitation-manager.tsx | 327 ++++++ .../classes/components/class-list-table.tsx | 150 +++ .../classes/components/class-list-toolbar.tsx | 34 + .../classes/components/edit-class-dialog.tsx | 161 +++ .../classes/components/my-classes-grid.tsx | 104 ++ .../components/schedule-create-dialog.tsx | 259 +++++ .../components/schedule-delete-dialog.tsx | 67 ++ .../components/schedule-edit-dialog.tsx | 270 +++++ .../classes/components/schedule-utils.ts | 122 ++ .../classes/components/schedule-view.tsx | 199 ++++ .../classes/components/students-table.tsx | 96 ++ .../teacher/classes/hooks/use-class-data.ts | 56 + .../classes/hooks/use-class-filters.ts | 42 + .../components/course-plan-calendar.tsx | 228 ++++ .../components/course-plan-detail.tsx | 503 ++++++++ .../components/course-plan-form.tsx | 358 ++++++ .../components/course-plan-item-editor.tsx | 292 +++++ .../components/course-plan-list.tsx | 198 ++++ .../components/course-plan-progress.tsx | 70 ++ .../components/sortable-week-row.tsx | 152 +++ .../components/template-picker-dialog.tsx | 247 ++++ .../teacher/course-plans/components/types.ts | 75 ++ .../course-plans/lib/calendar-utils.ts | 177 +++ .../teacher/course-plans/lib/export-utils.ts | 143 +++ .../teacher/course-plans/lib/track-event.ts | 20 + .../dashboard/components/dashboard-utils.ts | 65 ++ .../components/recent-submissions.tsx | 180 +++ .../components/teacher-classes-card.tsx | 109 ++ .../components/teacher-grade-trends.tsx | 119 ++ .../components/teacher-homework-card.tsx | 164 +++ .../components/teacher-quick-actions.tsx | 47 + .../dashboard/components/teacher-schedule.tsx | 181 +++ .../components/teacher-todo-card.tsx | 153 +++ .../teacher/dashboard/components/types.ts | 70 ++ .../dashboard/dashboard-cards-client.tsx | 47 + .../dashboard/services/dashboard-service.tsx | 125 ++ .../components/class-diagnostic-view.tsx | 588 ++++++++++ .../diagnostic/components/confidence-utils.ts | 53 + .../components/mastery-radar-chart.tsx | 97 ++ .../diagnostic/components/report-list.tsx | 360 ++++++ .../components/student-diagnostic-view.tsx | 397 +++++++ .../teacher/diagnostic/components/types.ts | 113 ++ .../teacher/diagnostic/role-config.ts | 48 + .../services/default-diagnostic-service.ts | 82 ++ .../services/diagnostic-monitor-context.tsx | 74 ++ .../diagnostic/services/diagnostic-monitor.ts | 85 ++ .../services/diagnostic-service-context.tsx | 61 + .../diagnostic/services/diagnostic-service.ts | 77 ++ .../services/monitored-diagnostic-service.ts | 150 +++ .../components/elective-course-detail.tsx | 248 ++++ .../components/elective-course-form.tsx | 363 ++++++ .../components/elective-course-list.tsx | 258 +++++ .../components/elective-page-layout.tsx | 32 + .../teacher/elective/components/index.ts | 14 + .../components/parent-selection-view.tsx | 99 ++ .../components/student-selection-view.tsx | 364 ++++++ .../features/teacher/elective/constants.ts | 70 ++ .../features/teacher/elective/lib/lottery.ts | 67 ++ .../teacher/elective/lib/schedule-conflict.ts | 132 +++ .../src/features/teacher/elective/types.ts | 70 ++ .../components/add-error-book-dialog.tsx | 162 +++ .../components/chapter-weakness-chart.tsx | 246 ++++ .../components/class-error-bar-chart.tsx | 186 +++ .../error-book/components/class-filter.tsx | 103 ++ .../components/error-book-detail-dialog.tsx | 356 ++++++ .../components/error-book-item-card.tsx | 127 +++ .../error-book/components/error-book-list.tsx | 61 + .../grouped-student-error-table.tsx | 246 ++++ .../knowledge-point-weakness-chart.tsx | 218 ++++ .../error-book/components/review-buttons.tsx | 101 ++ .../components/subject-distribution-chart.tsx | 170 +++ .../error-book/components/subject-tabs.tsx | 111 ++ .../components/top-wrong-questions.tsx | 122 ++ .../error-book/components/ui/progress.tsx | 54 + .../error-book/components/ui/scroll-area.tsx | 58 + .../error-book/components/ui/status-badge.tsx | 85 ++ .../features/teacher/error-book/lib/format.ts | 37 + .../error-book/lib/question-content.ts | 151 +++ .../features/teacher/error-book/lib/schema.ts | 75 ++ .../teacher/error-book/lib/sm2-algorithm.ts | 202 ++++ .../features/teacher/error-book/lib/types.ts | 263 +++++ .../teacher/exams/ai-pipeline/index.ts | 60 + .../teacher/exams/ai-pipeline/parse.ts | 465 ++++++++ .../exams/components/anti-cheat-monitor.tsx | 263 +++++ .../assembly/question-bank-list.tsx | 125 ++ .../assembly/selected-question-list.tsx | 300 +++++ .../components/assembly/structure-editor.tsx | 101 ++ .../teacher/exams/components/exam-actions.tsx | 148 +++ .../exams/components/exam-ai-generator.tsx | 182 +++ .../components/exam-analytics-dashboard.tsx | 267 +++++ .../exams/components/exam-assembly-config.tsx | 75 ++ .../exam-assembly-question-pool.tsx | 102 ++ .../components/exam-assembly-selected.tsx | 141 +++ .../exams/components/exam-assembly.tsx | 337 ++++++ .../exams/components/exam-basic-info-form.tsx | 196 ++++ .../exams/components/exam-boundaries.tsx | 182 +++ .../teacher/exams/components/exam-card.tsx | 89 ++ .../exams/components/exam-data-table.tsx | 242 ++++ .../exams/components/exam-form-types.ts | 191 ++++ .../teacher/exams/components/exam-form.tsx | 120 ++ .../teacher/exams/components/exam-grid.tsx | 28 + .../exams/components/exam-mode-config.tsx | 213 ++++ .../exams/components/exam-mode-selector.tsx | 144 +++ .../exams/components/exam-paper-preview.tsx | 244 ++++ .../exams/components/exam-preview-dialog.tsx | 125 ++ .../exam-preview-question-editor.tsx | 116 ++ .../exams/components/exam-preview-utils.ts | 474 ++++++++ .../teacher/exams/components/exam-preview.tsx | 73 ++ .../exams/components/exam-rich-form.tsx | 340 ++++++ .../exams/components/exam-status-badge.tsx | 53 + .../teacher/exams/components/exam-viewer.tsx | 277 +++++ .../components/question-options-editor.tsx | 102 ++ .../question-sub-questions-editor.tsx | 122 ++ .../exams/editor/editor-to-structure.ts | 256 +++++ .../exams/editor/exam-nodes-to-editor-doc.ts | 114 ++ .../exams/editor/exam-rich-editor-inner.tsx | 365 ++++++ .../exams/editor/exam-rich-editor-types.ts | 114 ++ .../teacher/exams/editor/exam-rich-editor.tsx | 40 + .../exams/editor/extensions/blank-node.tsx | 68 ++ .../exams/editor/extensions/dotted-mark.ts | 42 + .../exams/editor/extensions/group-block.tsx | 109 ++ .../exams/editor/extensions/image-node.ts | 53 + .../teacher/exams/editor/extensions/index.ts | 10 + .../editor/extensions/question-block.tsx | 140 +++ .../exams/editor/extensions/section-block.tsx | 116 ++ .../features/teacher/exams/editor/index.ts | 22 + .../exams/editor/selection-toolbar.tsx | 411 +++++++ .../exams/editor/structure-to-editor.ts | 90 ++ .../features/teacher/exams/editor/types.ts | 25 + .../exams/editor/utils/count-questions.ts | 56 + .../exams/hooks/use-exam-preview-state.ts | 191 ++++ .../features/teacher/exams/lib/type-guards.ts | 82 ++ .../exams/services/exam-service-context.tsx | 80 ++ .../exams/services/exam-service-port.ts | 133 +++ .../features/teacher/exams/services/index.ts | 28 + .../grades/batch-grade-entry-dialog.tsx | 64 ++ .../grades/batch-grade-entry-selectors.tsx | 127 +++ .../grades/batch-grade-entry-stats.tsx | 78 ++ .../grades/batch-grade-entry-table.tsx | 140 +++ .../grades/batch-grade-entry-toolbar.tsx | 58 + .../teacher/grades/batch-grade-entry.tsx | 421 +++++++ .../teacher/grades/class-comparison-chart.tsx | 257 +++++ .../teacher/grades/class-grade-report.tsx | 194 ++++ .../teacher/grades/draft-lock-banner.tsx | 78 ++ .../teacher/grades/excel-import-dialog.tsx | 553 +++++++++ .../features/teacher/grades/export-button.tsx | 115 ++ .../grades/grade-distribution-chart.tsx | 238 ++++ .../teacher/grades/grade-record-detail.tsx | 325 ++++++ .../teacher/grades/grade-record-form.tsx | 288 +++++ .../teacher/grades/grade-record-list.tsx | 242 ++++ .../teacher/grades/grade-record-row.tsx | 224 ++++ .../teacher/grades/grade-trend-card.tsx | 156 +++ .../teacher/grades/grade-trend-chart.tsx | 85 ++ .../teacher/grades/grades-list-client.tsx | 20 +- .../teacher/grades/growth-archive-chart.tsx | 200 ++++ .../grades/knowledge-point-mastery-chart.tsx | 251 ++++ .../teacher/grades/lib/grade-utils.ts | 27 + .../teacher/grades/lib/scope-check.ts | 57 + .../teacher/grades/ranking-trend-card.tsx | 90 ++ .../grades/report-card-print-action.tsx | 48 + .../grades/report-card-print-button.tsx | 98 ++ .../teacher/grades/report-card-view.tsx | 393 +++++++ .../grades/school-wide-summary-card.tsx | 213 ++++ .../features/teacher/grades/score-cell.tsx | 43 + .../teacher/grades/stats-class-selector.tsx | 70 ++ .../teacher/grades/student-grade-summary.tsx | 182 +++ .../grades/subject-comparison-chart.tsx | 84 ++ .../features/teacher/grades/type-guards.ts | 50 + .../src/features/teacher/grades/types.ts | 375 ++++++ .../components/excellent-submissions.tsx | 164 +++ .../homework-assignment-exam-content-card.tsx | 55 + ...rk-assignment-exam-error-explorer-lazy.tsx | 104 ++ ...omework-assignment-exam-error-explorer.tsx | 65 ++ .../homework-assignment-exam-preview-pane.tsx | 58 + .../components/homework-assignment-form.tsx | 341 ++++++ ...assignment-question-error-detail-panel.tsx | 242 ++++ ...ssignment-question-error-overview-card.tsx | 149 +++ .../homework-batch-grading-view.tsx | 243 ++++ .../components/homework-grading-panel.tsx | 293 +++++ .../components/homework-grading-toolbar.tsx | 262 +++++ .../components/homework-grading-view.tsx | 208 ++++ .../components/homework-submission-result.tsx | 234 ++++ .../homework-take-confirm-dialog.tsx | 80 ++ .../components/homework-take-question.tsx | 148 +++ .../components/homework-take-sidebar.tsx | 258 +++++ .../components/homework-take-toolbar.tsx | 139 +++ .../components/homework-take-view.tsx | 457 ++++++++ .../homework/components/question-renderer.tsx | 427 +++++++ .../homework/components/scan-image-viewer.tsx | 244 ++++ .../homework/components/scan-uploader.tsx | 268 +++++ .../student-homework-review-view.tsx | 292 +++++ .../teacher/homework/components/types.ts | 86 ++ .../homework/lib/question-content-utils.ts | 400 +++++++ .../leave/components/leave-request-form.tsx | 214 ++++ .../leave/components/leave-review-dialog.tsx | 181 +++ .../leave/components/leave-review-list.tsx | 144 +++ .../ai-differentiation-dialog.tsx | 344 ++++++ .../lesson-plans/ai-feedback-dialog.tsx | 137 +++ .../lesson-plans/anchor-migration-banner.tsx | 54 + .../lesson-plans/attachment-picker.tsx | 266 +++++ .../lesson-plans/blocks/blackboard-block.tsx | 177 +++ .../lesson-plans/blocks/exercise-block.tsx | 118 ++ .../lesson-plans/blocks/homework-block.tsx | 90 ++ .../lesson-plans/blocks/import-block.tsx | 74 ++ .../lesson-plans/blocks/interaction-block.tsx | 110 ++ .../lesson-plans/blocks/key-point-block.tsx | 80 ++ .../blocks/new-teaching-block.tsx | 111 ++ .../lesson-plans/blocks/objective-block.tsx | 86 ++ .../lesson-plans/blocks/reflection-block.tsx | 84 ++ .../blocks/rich-text-block-inner.tsx | 3 + .../lesson-plans/blocks/rich-text-block.tsx | 117 ++ .../lesson-plans/blocks/summary-block.tsx | 72 ++ .../lesson-plans/blocks/text-study-block.tsx | 119 ++ .../lesson-plans/blocks/type-guards.ts | 168 +++ .../teacher/lesson-plans/blocks/types.ts | 339 ++++++ .../lesson-plans/consistency-check-dialog.tsx | 113 ++ .../lesson-plans/detail-panel/detail-head.tsx | 64 ++ .../detail-panel/detail-panel.tsx | 145 +++ .../detail-panel/detail-props.tsx | 88 ++ .../lesson-plans/detail-panel/qa-editor.tsx | 205 ++++ .../lesson-plans/hooks/editor-slice.ts | 243 ++++ .../lesson-plans/hooks/expanded-slice.ts | 52 + .../lesson-plans/hooks/history-slice.ts | 75 ++ .../lesson-plans/hooks/selection-slice.ts | 23 + .../hooks/use-lesson-plan-editor.ts | 22 + .../hooks/use-lesson-plan-persistence.ts | 155 +++ .../lesson-plans/hooks/use-node-ai-assist.ts | 134 +++ .../lesson-plans/hooks/version-slice.ts | 51 + .../lesson-plans/inline-question-editor.tsx | 89 ++ .../lesson-plans/knowledge-point-picker.tsx | 156 +++ .../teacher/lesson-plans/lesson-plan-card.tsx | 65 ++ .../lesson-plans/lesson-plan-dialogs.tsx | 140 +++ .../lesson-plans/lesson-plan-mobile-view.tsx | 76 ++ .../lesson-plan-publish-button.tsx | 53 + .../lesson-plan-readonly-view.tsx | 61 + .../lesson-plans/lesson-plan-toolbar.tsx | 197 ++++ .../lesson-plans/lib/ai-node-assist.ts | 40 + .../teacher/lesson-plans/lib/anchor-mark.ts | 100 ++ .../lesson-plans/lib/document-migration.ts | 374 ++++++ .../features/teacher/lesson-plans/lib/id.ts | 14 + .../teacher/lesson-plans/lib/type-guards.ts | 79 ++ .../lesson-plans/paper-editor/inline-node.tsx | 57 + .../paper-editor/inline-qa-dialog.tsx | 87 ++ .../paper-editor/paper-context-menu.tsx | 111 ++ .../paper-editor/paper-editor.tsx | 143 +++ .../paper-editor/paper-toolbar.tsx | 168 +++ .../paper-editor/textbook-tiptap-editor.tsx | 96 ++ .../providers/lesson-plan-provider.tsx | 378 ++++++ .../services/default-data-service.ts | 231 ++++ .../services/default-question-service.ts | 24 + .../services/external-questions-bridge.ts | 27 + .../structure-tree/structure-tree.tsx | 65 ++ .../structure-tree/tree-node-row.tsx | 78 ++ .../features/teacher/lesson-plans/types.ts | 162 +++ .../lesson-plans/version-diff-view.tsx | 80 ++ .../lesson-plans/version-diff-viewer.tsx | 59 + .../lesson-plans/version-history-drawer.tsx | 122 ++ .../practice/adaptive-practice-utils.ts | 65 ++ .../practice/components/answer-input.tsx | 203 ++++ .../practice/components/answer-result.tsx | 83 ++ .../class-knowledge-point-weakness-chart.tsx | 182 +++ .../class-practice-comparison-table.tsx | 150 +++ .../components/inactive-students-alert.tsx | 79 ++ .../practice/components/practice-history.tsx | 128 +++ .../components/practice-result-view.tsx | 135 +++ .../components/practice-session-view.tsx | 328 ++++++ .../components/practice-starter-with-nav.tsx | 45 + .../practice/components/practice-starter.tsx | 338 ++++++ .../practice-type-breakdown-chart.tsx | 161 +++ .../practice/components/question-card.tsx | 160 +++ .../practice/components/question-content.tsx | 76 ++ .../student-practice-ranking-table.tsx | 154 +++ .../teacher/practice/components/types.ts | 58 + .../questions/components/batch-operations.tsx | 108 ++ .../components/create-question-button.tsx | 37 + .../components/create-question-dialog.tsx | 288 +++++ .../components/import-export-buttons.tsx | 163 +++ .../teacher/questions/components/index.ts | 18 + .../components/knowledge-point-selector.tsx | 102 ++ .../questions/components/options-editor.tsx | 121 ++ .../questions/components/question-actions.tsx | 174 +++ .../question-bank-results-client.tsx | 31 + .../components/question-cascade-filter.tsx | 109 ++ .../questions/components/question-columns.tsx | 141 +++ .../components/question-content-renderer.tsx | 157 +++ .../components/question-data-table.tsx | 141 +++ .../teacher/questions/utils/parse-content.ts | 123 ++ .../teacher/questions/utils/track-event.ts | 94 ++ .../components/auto-schedule-panel.tsx | 168 +++ .../components/auto-schedule-result.tsx | 172 +++ .../components/schedule-change-form.tsx | 315 +++++ .../components/schedule-change-list.tsx | 324 ++++++ .../components/schedule-conflicts-view.tsx | 169 +++ .../components/schedule-grid-view.tsx | 182 +++ .../components/scheduling-rules-form.tsx | 277 +++++ .../components/chapter-sidebar-list.tsx | 148 +++ .../components/create-chapter-dialog.tsx | 68 ++ .../textbooks/components/force-graph.tsx | 594 ++++++++++ .../textbooks/components/graph-kp-node.tsx | 135 +++ .../components/graph-node-detail-panel.tsx | 223 ++++ .../components/graph-prerequisite-edge.tsx | 83 ++ .../textbooks/components/graph-toolbar.tsx | 66 ++ .../components/knowledge-graph-controls.tsx | 58 + .../components/knowledge-graph-inner.tsx | 311 +++++ .../components/knowledge-graph-node.tsx | 179 +++ .../textbooks/components/knowledge-graph.tsx | 33 + .../components/knowledge-point-dialogs.tsx | 121 ++ .../components/knowledge-point-list.tsx | 110 ++ .../textbooks/components/textbook-card.tsx | 173 +++ .../components/textbook-content-panel.tsx | 61 + .../components/textbook-form-dialog.tsx | 77 ++ .../components/textbook-form-fields.tsx | 103 ++ .../components/textbook-reader-content.tsx | 45 + .../components/textbook-reader-toc.tsx | 57 + .../textbooks/components/textbook-reader.tsx | 48 + .../components/textbook-settings-dialog.tsx | 71 ++ .../teacher/textbooks/hooks/use-graph-data.ts | 84 ++ .../hooks/use-knowledge-point-actions.ts | 49 + .../teacher/textbooks/hooks/use-kp-create.ts | 44 + .../teacher/textbooks/hooks/use-kp-crud.ts | 60 + .../teacher/textbooks/hooks/use-kp-delete.ts | 58 + .../textbooks/hooks/use-kp-dialog-state.ts | 41 + .../teacher/textbooks/hooks/use-kp-update.ts | 42 + .../textbooks/hooks/use-text-selection.ts | 70 ++ .../teacher/textbooks/lib/analytics.tsx | 47 + .../teacher/textbooks/lib/constants.ts | 100 ++ .../teacher/textbooks/lib/graph-layout.ts | 237 ++++ .../teacher/textbooks/lib/type-guards.ts | 115 ++ .../features/teacher/textbooks/lib/utils.ts | 224 ++++ .../src/features/teacher/textbooks/types.ts | 116 ++ .../error-book-mutations.graphql.ts | 54 + .../src/lib/api/operations/profile.graphql.ts | 49 + apps/portal-shell/src/lib/api/profile.ts | 86 ++ .../src/lib/api/questions-extended.ts | 0 apps/portal-shell/src/mocks/graphql-data.ts | 96 ++ .../components/charts/chart-card-shell.tsx | 82 ++ .../src/shared/components/charts/chart.tsx | 170 +++ .../charts/comparison-radar-chart.tsx | 77 ++ .../components/charts/grade-trend-chart.tsx | 280 +++++ .../src/shared/components/charts/index.ts | 16 + .../components/charts/simple-bar-chart.tsx | 148 +++ .../components/charts/trend-line-chart.tsx | 119 ++ .../components/dashboard/comparison-badge.tsx | 94 ++ .../dashboard/dashboard-error-fallback.tsx | 37 + .../dashboard-notification-widget.tsx | 102 ++ .../dashboard/dashboard-responsive-layout.tsx | 104 ++ .../dashboard/dashboard-time-range-filter.tsx | 82 ++ .../src/shared/components/ui/alert-dialog.tsx | 159 +++ .../src/shared/components/ui/alert.tsx | 56 + .../src/shared/components/ui/avatar.tsx | 53 + .../src/shared/components/ui/chart.tsx | 114 ++ .../src/shared/components/ui/checkbox.tsx | 73 ++ .../src/shared/components/ui/chip-nav.tsx | 70 ++ .../components/ui/confirm-delete-dialog.tsx | 73 ++ .../src/shared/components/ui/dialog.tsx | 149 +++ .../shared/components/ui/dropdown-menu.tsx | 165 +++ .../src/shared/components/ui/label.tsx | 22 + .../src/shared/components/ui/progress.tsx | 32 + .../src/shared/components/ui/scroll-area.tsx | 60 + .../src/shared/components/ui/select.tsx | 73 ++ .../src/shared/components/ui/sheet.tsx | 104 ++ .../src/shared/components/ui/switch.tsx | 63 + .../src/shared/components/ui/table.tsx | 141 +++ .../src/shared/components/ui/tabs.tsx | 66 ++ .../src/shared/components/ui/textarea.tsx | 28 + .../src/shared/hooks/use-permission.ts | 59 + apps/portal-shell/src/shared/lib/download.ts | 45 + .../src/shared/lib/route-permissions.ts | 12 + .../src/shared/lib/sanitize-html.ts | 54 + packages/ui-tokens/src/tailwind-theme.css | 6 + .../src/graphql/generated/schema.graphql | 61 + 493 files changed, 70985 insertions(+), 2112 deletions(-) create mode 100644 apps/portal-shell/docs/needtodo/SUMMARY-NeedTodo.md create mode 100644 apps/portal-shell/docs/needtodo/teacher-migration-gap.md create mode 100644 apps/portal-shell/src/app/shell/admin/users/[id]/error.tsx create mode 100644 apps/portal-shell/src/app/shell/admin/users/[id]/loading.tsx create mode 100644 apps/portal-shell/src/app/shell/admin/users/[id]/page.tsx create mode 100644 apps/portal-shell/src/app/shell/messages/[id]/page.tsx create mode 100644 apps/portal-shell/src/app/shell/messages/compose/page.tsx create mode 100644 apps/portal-shell/src/app/shell/messages/group-compose/page.tsx create mode 100644 apps/portal-shell/src/app/shell/profile/page.tsx create mode 100644 apps/portal-shell/src/app/shell/student/diagnostic/page.tsx create mode 100644 apps/portal-shell/src/app/shell/teacher/classes/my/[id]/page.tsx create mode 100644 apps/portal-shell/src/app/shell/teacher/classes/my/error.tsx create mode 100644 apps/portal-shell/src/app/shell/teacher/classes/my/loading.tsx create mode 100644 apps/portal-shell/src/app/shell/teacher/classes/my/page.tsx create mode 100644 apps/portal-shell/src/app/shell/teacher/classes/students/page.tsx create mode 100644 apps/portal-shell/src/app/shell/teacher/exams/all/error.tsx create mode 100644 apps/portal-shell/src/app/shell/teacher/exams/all/loading.tsx create mode 100644 apps/portal-shell/src/app/shell/teacher/exams/all/page.tsx create mode 100644 apps/portal-shell/src/app/shell/teacher/exams/create/error.tsx create mode 100644 apps/portal-shell/src/app/shell/teacher/exams/create/loading.tsx create mode 100644 apps/portal-shell/src/app/shell/teacher/exams/create/page.tsx create mode 100644 apps/portal-shell/src/app/shell/teacher/exams/grading/[submissionId]/page.tsx create mode 100644 apps/portal-shell/src/app/shell/teacher/exams/grading/error.tsx create mode 100644 apps/portal-shell/src/app/shell/teacher/exams/grading/loading.tsx create mode 100644 apps/portal-shell/src/app/shell/teacher/exams/grading/page.tsx create mode 100644 apps/portal-shell/src/app/shell/teacher/homework/assignments/create/page.tsx create mode 100644 apps/portal-shell/src/features/admin/attendance/export-utils.ts create mode 100644 apps/portal-shell/src/features/admin/lesson-plans/export-utils.ts create mode 100644 apps/portal-shell/src/features/admin/organization/organization-detail-dialog.tsx create mode 100644 apps/portal-shell/src/features/admin/students/student-detail-dialog.tsx create mode 100644 apps/portal-shell/src/features/admin/teachers/teacher-detail-dialog.tsx create mode 100644 apps/portal-shell/src/features/admin/users/user-detail-client.tsx create mode 100644 apps/portal-shell/src/features/shared/messages/message-compose-client.tsx create mode 100644 apps/portal-shell/src/features/shared/messages/message-detail-client.tsx create mode 100644 apps/portal-shell/src/features/shared/messages/message-group-compose-client.tsx create mode 100644 apps/portal-shell/src/features/shared/profile/profile-client.tsx create mode 100644 apps/portal-shell/src/features/student/dashboard/dashboard-client.tsx create mode 100644 apps/portal-shell/src/features/student/diagnostic/diagnostic-client.tsx create mode 100644 apps/portal-shell/src/features/student/error-book/error-book-detail-dialog.tsx create mode 100644 apps/portal-shell/src/features/student/grades/components/growth-archive-card.tsx create mode 100644 apps/portal-shell/src/features/student/grades/components/ranking-trend-card.tsx create mode 100644 apps/portal-shell/src/features/student/grades/components/score-distribution-card.tsx create mode 100644 apps/portal-shell/src/features/student/grades/report-card-print.css create mode 100644 apps/portal-shell/src/features/student/leave/leave-request-form.tsx create mode 100644 apps/portal-shell/src/features/student/leave/leave-request-list.tsx create mode 100644 apps/portal-shell/src/features/teacher/ai/components/ai-assistant-widget-inner.tsx create mode 100644 apps/portal-shell/src/features/teacher/ai/components/ai-assistant-widget.tsx create mode 100644 apps/portal-shell/src/features/teacher/ai/components/ai-chart-renderer.tsx create mode 100644 apps/portal-shell/src/features/teacher/ai/components/ai-chat-input.tsx create mode 100644 apps/portal-shell/src/features/teacher/ai/components/ai-chat-messages.tsx create mode 100644 apps/portal-shell/src/features/teacher/ai/components/ai-chat-panel.tsx create mode 100644 apps/portal-shell/src/features/teacher/ai/components/ai-error-book-analysis.tsx create mode 100644 apps/portal-shell/src/features/teacher/ai/components/ai-grading-assist.tsx create mode 100644 apps/portal-shell/src/features/teacher/ai/components/ai-lesson-content-generator.tsx create mode 100644 apps/portal-shell/src/features/teacher/ai/components/ai-markdown-renderer.tsx create mode 100644 apps/portal-shell/src/features/teacher/ai/components/ai-provider-selector.tsx create mode 100644 apps/portal-shell/src/features/teacher/ai/components/ai-question-variant-generator.tsx create mode 100644 apps/portal-shell/src/features/teacher/ai/components/ai-usage-dashboard.tsx create mode 100644 apps/portal-shell/src/features/teacher/ai/context/ai-client-provider.tsx create mode 100644 apps/portal-shell/src/features/teacher/ai/context/create-ai-client-service.ts create mode 100644 apps/portal-shell/src/features/teacher/ai/hooks/stream-utils.ts create mode 100644 apps/portal-shell/src/features/teacher/ai/hooks/use-ai-chat-stream.ts create mode 100644 apps/portal-shell/src/features/teacher/ai/hooks/use-ai-suggestion.ts create mode 100644 apps/portal-shell/src/features/teacher/ai/hooks/use-drag-position.ts create mode 100644 apps/portal-shell/src/features/teacher/ai/hooks/use-floating-ball.ts create mode 100644 apps/portal-shell/src/features/teacher/ai/hooks/use-position-persistence.ts create mode 100644 apps/portal-shell/src/features/teacher/ai/schema.ts create mode 100644 apps/portal-shell/src/features/teacher/ai/services/ai-service.ts create mode 100644 apps/portal-shell/src/features/teacher/ai/services/content-safety.ts create mode 100644 apps/portal-shell/src/features/teacher/ai/services/prompt-templates.ts create mode 100644 apps/portal-shell/src/features/teacher/ai/services/usage-tracker.ts create mode 100644 apps/portal-shell/src/features/teacher/ai/types.ts create mode 100644 apps/portal-shell/src/features/teacher/attendance/components/attendance-grade-correlation-card.tsx create mode 100644 apps/portal-shell/src/features/teacher/attendance/components/attendance-page-layout.tsx create mode 100644 apps/portal-shell/src/features/teacher/attendance/components/attendance-record-list.tsx create mode 100644 apps/portal-shell/src/features/teacher/attendance/components/attendance-report-print.tsx create mode 100644 apps/portal-shell/src/features/teacher/attendance/components/attendance-rules-form.tsx create mode 100644 apps/portal-shell/src/features/teacher/attendance/components/attendance-sheet.tsx create mode 100644 apps/portal-shell/src/features/teacher/attendance/components/attendance-stats-class-selector.tsx create mode 100644 apps/portal-shell/src/features/teacher/attendance/components/attendance-trend-chart.tsx create mode 100644 apps/portal-shell/src/features/teacher/attendance/components/attendance-warnings-card.tsx create mode 100644 apps/portal-shell/src/features/teacher/attendance/components/class-comparison-card.tsx create mode 100644 apps/portal-shell/src/features/teacher/attendance/components/index.ts create mode 100644 apps/portal-shell/src/features/teacher/attendance/components/student-attendance-view.tsx create mode 100644 apps/portal-shell/src/features/teacher/attendance/lib/correlation-compute.ts create mode 100644 apps/portal-shell/src/features/teacher/attendance/lib/trend-compute.ts create mode 100644 apps/portal-shell/src/features/teacher/attendance/lib/type-guards.ts create mode 100644 apps/portal-shell/src/features/teacher/attendance/lib/warning-compute.ts create mode 100644 apps/portal-shell/src/features/teacher/attendance/types.ts create mode 100644 apps/portal-shell/src/features/teacher/classes/components/class-delete-dialog.tsx create mode 100644 apps/portal-shell/src/features/teacher/classes/components/class-detail/class-assignments-widget.tsx create mode 100644 apps/portal-shell/src/features/teacher/classes/components/class-detail/class-header.tsx create mode 100644 apps/portal-shell/src/features/teacher/classes/components/class-detail/class-overview-stats.tsx create mode 100644 apps/portal-shell/src/features/teacher/classes/components/class-detail/class-quick-actions.tsx create mode 100644 apps/portal-shell/src/features/teacher/classes/components/class-detail/class-schedule-widget.tsx create mode 100644 apps/portal-shell/src/features/teacher/classes/components/class-detail/class-students-widget.tsx create mode 100644 apps/portal-shell/src/features/teacher/classes/components/class-detail/class-trends-widget.tsx create mode 100644 apps/portal-shell/src/features/teacher/classes/components/class-invitation-manager.tsx create mode 100644 apps/portal-shell/src/features/teacher/classes/components/class-list-table.tsx create mode 100644 apps/portal-shell/src/features/teacher/classes/components/class-list-toolbar.tsx create mode 100644 apps/portal-shell/src/features/teacher/classes/components/edit-class-dialog.tsx create mode 100644 apps/portal-shell/src/features/teacher/classes/components/my-classes-grid.tsx create mode 100644 apps/portal-shell/src/features/teacher/classes/components/schedule-create-dialog.tsx create mode 100644 apps/portal-shell/src/features/teacher/classes/components/schedule-delete-dialog.tsx create mode 100644 apps/portal-shell/src/features/teacher/classes/components/schedule-edit-dialog.tsx create mode 100644 apps/portal-shell/src/features/teacher/classes/components/schedule-utils.ts create mode 100644 apps/portal-shell/src/features/teacher/classes/components/schedule-view.tsx create mode 100644 apps/portal-shell/src/features/teacher/classes/components/students-table.tsx create mode 100644 apps/portal-shell/src/features/teacher/classes/hooks/use-class-data.ts create mode 100644 apps/portal-shell/src/features/teacher/classes/hooks/use-class-filters.ts create mode 100644 apps/portal-shell/src/features/teacher/course-plans/components/course-plan-calendar.tsx create mode 100644 apps/portal-shell/src/features/teacher/course-plans/components/course-plan-detail.tsx create mode 100644 apps/portal-shell/src/features/teacher/course-plans/components/course-plan-form.tsx create mode 100644 apps/portal-shell/src/features/teacher/course-plans/components/course-plan-item-editor.tsx create mode 100644 apps/portal-shell/src/features/teacher/course-plans/components/course-plan-list.tsx create mode 100644 apps/portal-shell/src/features/teacher/course-plans/components/course-plan-progress.tsx create mode 100644 apps/portal-shell/src/features/teacher/course-plans/components/sortable-week-row.tsx create mode 100644 apps/portal-shell/src/features/teacher/course-plans/components/template-picker-dialog.tsx create mode 100644 apps/portal-shell/src/features/teacher/course-plans/components/types.ts create mode 100644 apps/portal-shell/src/features/teacher/course-plans/lib/calendar-utils.ts create mode 100644 apps/portal-shell/src/features/teacher/course-plans/lib/export-utils.ts create mode 100644 apps/portal-shell/src/features/teacher/course-plans/lib/track-event.ts create mode 100644 apps/portal-shell/src/features/teacher/dashboard/components/dashboard-utils.ts create mode 100644 apps/portal-shell/src/features/teacher/dashboard/components/recent-submissions.tsx create mode 100644 apps/portal-shell/src/features/teacher/dashboard/components/teacher-classes-card.tsx create mode 100644 apps/portal-shell/src/features/teacher/dashboard/components/teacher-grade-trends.tsx create mode 100644 apps/portal-shell/src/features/teacher/dashboard/components/teacher-homework-card.tsx create mode 100644 apps/portal-shell/src/features/teacher/dashboard/components/teacher-quick-actions.tsx create mode 100644 apps/portal-shell/src/features/teacher/dashboard/components/teacher-schedule.tsx create mode 100644 apps/portal-shell/src/features/teacher/dashboard/components/teacher-todo-card.tsx create mode 100644 apps/portal-shell/src/features/teacher/dashboard/components/types.ts create mode 100644 apps/portal-shell/src/features/teacher/dashboard/dashboard-cards-client.tsx create mode 100644 apps/portal-shell/src/features/teacher/dashboard/services/dashboard-service.tsx create mode 100644 apps/portal-shell/src/features/teacher/diagnostic/components/class-diagnostic-view.tsx create mode 100644 apps/portal-shell/src/features/teacher/diagnostic/components/confidence-utils.ts create mode 100644 apps/portal-shell/src/features/teacher/diagnostic/components/mastery-radar-chart.tsx create mode 100644 apps/portal-shell/src/features/teacher/diagnostic/components/report-list.tsx create mode 100644 apps/portal-shell/src/features/teacher/diagnostic/components/student-diagnostic-view.tsx create mode 100644 apps/portal-shell/src/features/teacher/diagnostic/components/types.ts create mode 100644 apps/portal-shell/src/features/teacher/diagnostic/role-config.ts create mode 100644 apps/portal-shell/src/features/teacher/diagnostic/services/default-diagnostic-service.ts create mode 100644 apps/portal-shell/src/features/teacher/diagnostic/services/diagnostic-monitor-context.tsx create mode 100644 apps/portal-shell/src/features/teacher/diagnostic/services/diagnostic-monitor.ts create mode 100644 apps/portal-shell/src/features/teacher/diagnostic/services/diagnostic-service-context.tsx create mode 100644 apps/portal-shell/src/features/teacher/diagnostic/services/diagnostic-service.ts create mode 100644 apps/portal-shell/src/features/teacher/diagnostic/services/monitored-diagnostic-service.ts create mode 100644 apps/portal-shell/src/features/teacher/elective/components/elective-course-detail.tsx create mode 100644 apps/portal-shell/src/features/teacher/elective/components/elective-course-form.tsx create mode 100644 apps/portal-shell/src/features/teacher/elective/components/elective-course-list.tsx create mode 100644 apps/portal-shell/src/features/teacher/elective/components/elective-page-layout.tsx create mode 100644 apps/portal-shell/src/features/teacher/elective/components/index.ts create mode 100644 apps/portal-shell/src/features/teacher/elective/components/parent-selection-view.tsx create mode 100644 apps/portal-shell/src/features/teacher/elective/components/student-selection-view.tsx create mode 100644 apps/portal-shell/src/features/teacher/elective/constants.ts create mode 100644 apps/portal-shell/src/features/teacher/elective/lib/lottery.ts create mode 100644 apps/portal-shell/src/features/teacher/elective/lib/schedule-conflict.ts create mode 100644 apps/portal-shell/src/features/teacher/elective/types.ts create mode 100644 apps/portal-shell/src/features/teacher/error-book/components/add-error-book-dialog.tsx create mode 100644 apps/portal-shell/src/features/teacher/error-book/components/chapter-weakness-chart.tsx create mode 100644 apps/portal-shell/src/features/teacher/error-book/components/class-error-bar-chart.tsx create mode 100644 apps/portal-shell/src/features/teacher/error-book/components/class-filter.tsx create mode 100644 apps/portal-shell/src/features/teacher/error-book/components/error-book-detail-dialog.tsx create mode 100644 apps/portal-shell/src/features/teacher/error-book/components/error-book-item-card.tsx create mode 100644 apps/portal-shell/src/features/teacher/error-book/components/error-book-list.tsx create mode 100644 apps/portal-shell/src/features/teacher/error-book/components/grouped-student-error-table.tsx create mode 100644 apps/portal-shell/src/features/teacher/error-book/components/knowledge-point-weakness-chart.tsx create mode 100644 apps/portal-shell/src/features/teacher/error-book/components/review-buttons.tsx create mode 100644 apps/portal-shell/src/features/teacher/error-book/components/subject-distribution-chart.tsx create mode 100644 apps/portal-shell/src/features/teacher/error-book/components/subject-tabs.tsx create mode 100644 apps/portal-shell/src/features/teacher/error-book/components/top-wrong-questions.tsx create mode 100644 apps/portal-shell/src/features/teacher/error-book/components/ui/progress.tsx create mode 100644 apps/portal-shell/src/features/teacher/error-book/components/ui/scroll-area.tsx create mode 100644 apps/portal-shell/src/features/teacher/error-book/components/ui/status-badge.tsx create mode 100644 apps/portal-shell/src/features/teacher/error-book/lib/format.ts create mode 100644 apps/portal-shell/src/features/teacher/error-book/lib/question-content.ts create mode 100644 apps/portal-shell/src/features/teacher/error-book/lib/schema.ts create mode 100644 apps/portal-shell/src/features/teacher/error-book/lib/sm2-algorithm.ts create mode 100644 apps/portal-shell/src/features/teacher/error-book/lib/types.ts create mode 100644 apps/portal-shell/src/features/teacher/exams/ai-pipeline/index.ts create mode 100644 apps/portal-shell/src/features/teacher/exams/ai-pipeline/parse.ts create mode 100644 apps/portal-shell/src/features/teacher/exams/components/anti-cheat-monitor.tsx create mode 100644 apps/portal-shell/src/features/teacher/exams/components/assembly/question-bank-list.tsx create mode 100644 apps/portal-shell/src/features/teacher/exams/components/assembly/selected-question-list.tsx create mode 100644 apps/portal-shell/src/features/teacher/exams/components/assembly/structure-editor.tsx create mode 100644 apps/portal-shell/src/features/teacher/exams/components/exam-actions.tsx create mode 100644 apps/portal-shell/src/features/teacher/exams/components/exam-ai-generator.tsx create mode 100644 apps/portal-shell/src/features/teacher/exams/components/exam-analytics-dashboard.tsx create mode 100644 apps/portal-shell/src/features/teacher/exams/components/exam-assembly-config.tsx create mode 100644 apps/portal-shell/src/features/teacher/exams/components/exam-assembly-question-pool.tsx create mode 100644 apps/portal-shell/src/features/teacher/exams/components/exam-assembly-selected.tsx create mode 100644 apps/portal-shell/src/features/teacher/exams/components/exam-assembly.tsx create mode 100644 apps/portal-shell/src/features/teacher/exams/components/exam-basic-info-form.tsx create mode 100644 apps/portal-shell/src/features/teacher/exams/components/exam-boundaries.tsx create mode 100644 apps/portal-shell/src/features/teacher/exams/components/exam-card.tsx create mode 100644 apps/portal-shell/src/features/teacher/exams/components/exam-data-table.tsx create mode 100644 apps/portal-shell/src/features/teacher/exams/components/exam-form-types.ts create mode 100644 apps/portal-shell/src/features/teacher/exams/components/exam-form.tsx create mode 100644 apps/portal-shell/src/features/teacher/exams/components/exam-grid.tsx create mode 100644 apps/portal-shell/src/features/teacher/exams/components/exam-mode-config.tsx create mode 100644 apps/portal-shell/src/features/teacher/exams/components/exam-mode-selector.tsx create mode 100644 apps/portal-shell/src/features/teacher/exams/components/exam-paper-preview.tsx create mode 100644 apps/portal-shell/src/features/teacher/exams/components/exam-preview-dialog.tsx create mode 100644 apps/portal-shell/src/features/teacher/exams/components/exam-preview-question-editor.tsx create mode 100644 apps/portal-shell/src/features/teacher/exams/components/exam-preview-utils.ts create mode 100644 apps/portal-shell/src/features/teacher/exams/components/exam-preview.tsx create mode 100644 apps/portal-shell/src/features/teacher/exams/components/exam-rich-form.tsx create mode 100644 apps/portal-shell/src/features/teacher/exams/components/exam-status-badge.tsx create mode 100644 apps/portal-shell/src/features/teacher/exams/components/exam-viewer.tsx create mode 100644 apps/portal-shell/src/features/teacher/exams/components/question-options-editor.tsx create mode 100644 apps/portal-shell/src/features/teacher/exams/components/question-sub-questions-editor.tsx create mode 100644 apps/portal-shell/src/features/teacher/exams/editor/editor-to-structure.ts create mode 100644 apps/portal-shell/src/features/teacher/exams/editor/exam-nodes-to-editor-doc.ts create mode 100644 apps/portal-shell/src/features/teacher/exams/editor/exam-rich-editor-inner.tsx create mode 100644 apps/portal-shell/src/features/teacher/exams/editor/exam-rich-editor-types.ts create mode 100644 apps/portal-shell/src/features/teacher/exams/editor/exam-rich-editor.tsx create mode 100644 apps/portal-shell/src/features/teacher/exams/editor/extensions/blank-node.tsx create mode 100644 apps/portal-shell/src/features/teacher/exams/editor/extensions/dotted-mark.ts create mode 100644 apps/portal-shell/src/features/teacher/exams/editor/extensions/group-block.tsx create mode 100644 apps/portal-shell/src/features/teacher/exams/editor/extensions/image-node.ts create mode 100644 apps/portal-shell/src/features/teacher/exams/editor/extensions/index.ts create mode 100644 apps/portal-shell/src/features/teacher/exams/editor/extensions/question-block.tsx create mode 100644 apps/portal-shell/src/features/teacher/exams/editor/extensions/section-block.tsx create mode 100644 apps/portal-shell/src/features/teacher/exams/editor/index.ts create mode 100644 apps/portal-shell/src/features/teacher/exams/editor/selection-toolbar.tsx create mode 100644 apps/portal-shell/src/features/teacher/exams/editor/structure-to-editor.ts create mode 100644 apps/portal-shell/src/features/teacher/exams/editor/types.ts create mode 100644 apps/portal-shell/src/features/teacher/exams/editor/utils/count-questions.ts create mode 100644 apps/portal-shell/src/features/teacher/exams/hooks/use-exam-preview-state.ts create mode 100644 apps/portal-shell/src/features/teacher/exams/lib/type-guards.ts create mode 100644 apps/portal-shell/src/features/teacher/exams/services/exam-service-context.tsx create mode 100644 apps/portal-shell/src/features/teacher/exams/services/exam-service-port.ts create mode 100644 apps/portal-shell/src/features/teacher/exams/services/index.ts create mode 100644 apps/portal-shell/src/features/teacher/grades/batch-grade-entry-dialog.tsx create mode 100644 apps/portal-shell/src/features/teacher/grades/batch-grade-entry-selectors.tsx create mode 100644 apps/portal-shell/src/features/teacher/grades/batch-grade-entry-stats.tsx create mode 100644 apps/portal-shell/src/features/teacher/grades/batch-grade-entry-table.tsx create mode 100644 apps/portal-shell/src/features/teacher/grades/batch-grade-entry-toolbar.tsx create mode 100644 apps/portal-shell/src/features/teacher/grades/batch-grade-entry.tsx create mode 100644 apps/portal-shell/src/features/teacher/grades/class-comparison-chart.tsx create mode 100644 apps/portal-shell/src/features/teacher/grades/class-grade-report.tsx create mode 100644 apps/portal-shell/src/features/teacher/grades/draft-lock-banner.tsx create mode 100644 apps/portal-shell/src/features/teacher/grades/excel-import-dialog.tsx create mode 100644 apps/portal-shell/src/features/teacher/grades/export-button.tsx create mode 100644 apps/portal-shell/src/features/teacher/grades/grade-distribution-chart.tsx create mode 100644 apps/portal-shell/src/features/teacher/grades/grade-record-detail.tsx create mode 100644 apps/portal-shell/src/features/teacher/grades/grade-record-form.tsx create mode 100644 apps/portal-shell/src/features/teacher/grades/grade-record-list.tsx create mode 100644 apps/portal-shell/src/features/teacher/grades/grade-record-row.tsx create mode 100644 apps/portal-shell/src/features/teacher/grades/grade-trend-card.tsx create mode 100644 apps/portal-shell/src/features/teacher/grades/grade-trend-chart.tsx create mode 100644 apps/portal-shell/src/features/teacher/grades/growth-archive-chart.tsx create mode 100644 apps/portal-shell/src/features/teacher/grades/knowledge-point-mastery-chart.tsx create mode 100644 apps/portal-shell/src/features/teacher/grades/lib/grade-utils.ts create mode 100644 apps/portal-shell/src/features/teacher/grades/lib/scope-check.ts create mode 100644 apps/portal-shell/src/features/teacher/grades/ranking-trend-card.tsx create mode 100644 apps/portal-shell/src/features/teacher/grades/report-card-print-action.tsx create mode 100644 apps/portal-shell/src/features/teacher/grades/report-card-print-button.tsx create mode 100644 apps/portal-shell/src/features/teacher/grades/report-card-view.tsx create mode 100644 apps/portal-shell/src/features/teacher/grades/school-wide-summary-card.tsx create mode 100644 apps/portal-shell/src/features/teacher/grades/score-cell.tsx create mode 100644 apps/portal-shell/src/features/teacher/grades/stats-class-selector.tsx create mode 100644 apps/portal-shell/src/features/teacher/grades/student-grade-summary.tsx create mode 100644 apps/portal-shell/src/features/teacher/grades/subject-comparison-chart.tsx create mode 100644 apps/portal-shell/src/features/teacher/grades/type-guards.ts create mode 100644 apps/portal-shell/src/features/teacher/grades/types.ts create mode 100644 apps/portal-shell/src/features/teacher/homework/components/excellent-submissions.tsx create mode 100644 apps/portal-shell/src/features/teacher/homework/components/homework-assignment-exam-content-card.tsx create mode 100644 apps/portal-shell/src/features/teacher/homework/components/homework-assignment-exam-error-explorer-lazy.tsx create mode 100644 apps/portal-shell/src/features/teacher/homework/components/homework-assignment-exam-error-explorer.tsx create mode 100644 apps/portal-shell/src/features/teacher/homework/components/homework-assignment-exam-preview-pane.tsx create mode 100644 apps/portal-shell/src/features/teacher/homework/components/homework-assignment-form.tsx create mode 100644 apps/portal-shell/src/features/teacher/homework/components/homework-assignment-question-error-detail-panel.tsx create mode 100644 apps/portal-shell/src/features/teacher/homework/components/homework-assignment-question-error-overview-card.tsx create mode 100644 apps/portal-shell/src/features/teacher/homework/components/homework-batch-grading-view.tsx create mode 100644 apps/portal-shell/src/features/teacher/homework/components/homework-grading-panel.tsx create mode 100644 apps/portal-shell/src/features/teacher/homework/components/homework-grading-toolbar.tsx create mode 100644 apps/portal-shell/src/features/teacher/homework/components/homework-grading-view.tsx create mode 100644 apps/portal-shell/src/features/teacher/homework/components/homework-submission-result.tsx create mode 100644 apps/portal-shell/src/features/teacher/homework/components/homework-take-confirm-dialog.tsx create mode 100644 apps/portal-shell/src/features/teacher/homework/components/homework-take-question.tsx create mode 100644 apps/portal-shell/src/features/teacher/homework/components/homework-take-sidebar.tsx create mode 100644 apps/portal-shell/src/features/teacher/homework/components/homework-take-toolbar.tsx create mode 100644 apps/portal-shell/src/features/teacher/homework/components/homework-take-view.tsx create mode 100644 apps/portal-shell/src/features/teacher/homework/components/question-renderer.tsx create mode 100644 apps/portal-shell/src/features/teacher/homework/components/scan-image-viewer.tsx create mode 100644 apps/portal-shell/src/features/teacher/homework/components/scan-uploader.tsx create mode 100644 apps/portal-shell/src/features/teacher/homework/components/student-homework-review-view.tsx create mode 100644 apps/portal-shell/src/features/teacher/homework/components/types.ts create mode 100644 apps/portal-shell/src/features/teacher/homework/lib/question-content-utils.ts create mode 100644 apps/portal-shell/src/features/teacher/leave/components/leave-request-form.tsx create mode 100644 apps/portal-shell/src/features/teacher/leave/components/leave-review-dialog.tsx create mode 100644 apps/portal-shell/src/features/teacher/leave/components/leave-review-list.tsx create mode 100644 apps/portal-shell/src/features/teacher/lesson-plans/ai-differentiation-dialog.tsx create mode 100644 apps/portal-shell/src/features/teacher/lesson-plans/ai-feedback-dialog.tsx create mode 100644 apps/portal-shell/src/features/teacher/lesson-plans/anchor-migration-banner.tsx create mode 100644 apps/portal-shell/src/features/teacher/lesson-plans/attachment-picker.tsx create mode 100644 apps/portal-shell/src/features/teacher/lesson-plans/blocks/blackboard-block.tsx create mode 100644 apps/portal-shell/src/features/teacher/lesson-plans/blocks/exercise-block.tsx create mode 100644 apps/portal-shell/src/features/teacher/lesson-plans/blocks/homework-block.tsx create mode 100644 apps/portal-shell/src/features/teacher/lesson-plans/blocks/import-block.tsx create mode 100644 apps/portal-shell/src/features/teacher/lesson-plans/blocks/interaction-block.tsx create mode 100644 apps/portal-shell/src/features/teacher/lesson-plans/blocks/key-point-block.tsx create mode 100644 apps/portal-shell/src/features/teacher/lesson-plans/blocks/new-teaching-block.tsx create mode 100644 apps/portal-shell/src/features/teacher/lesson-plans/blocks/objective-block.tsx create mode 100644 apps/portal-shell/src/features/teacher/lesson-plans/blocks/reflection-block.tsx create mode 100644 apps/portal-shell/src/features/teacher/lesson-plans/blocks/rich-text-block-inner.tsx create mode 100644 apps/portal-shell/src/features/teacher/lesson-plans/blocks/rich-text-block.tsx create mode 100644 apps/portal-shell/src/features/teacher/lesson-plans/blocks/summary-block.tsx create mode 100644 apps/portal-shell/src/features/teacher/lesson-plans/blocks/text-study-block.tsx create mode 100644 apps/portal-shell/src/features/teacher/lesson-plans/blocks/type-guards.ts create mode 100644 apps/portal-shell/src/features/teacher/lesson-plans/blocks/types.ts create mode 100644 apps/portal-shell/src/features/teacher/lesson-plans/consistency-check-dialog.tsx create mode 100644 apps/portal-shell/src/features/teacher/lesson-plans/detail-panel/detail-head.tsx create mode 100644 apps/portal-shell/src/features/teacher/lesson-plans/detail-panel/detail-panel.tsx create mode 100644 apps/portal-shell/src/features/teacher/lesson-plans/detail-panel/detail-props.tsx create mode 100644 apps/portal-shell/src/features/teacher/lesson-plans/detail-panel/qa-editor.tsx create mode 100644 apps/portal-shell/src/features/teacher/lesson-plans/hooks/editor-slice.ts create mode 100644 apps/portal-shell/src/features/teacher/lesson-plans/hooks/expanded-slice.ts create mode 100644 apps/portal-shell/src/features/teacher/lesson-plans/hooks/history-slice.ts create mode 100644 apps/portal-shell/src/features/teacher/lesson-plans/hooks/selection-slice.ts create mode 100644 apps/portal-shell/src/features/teacher/lesson-plans/hooks/use-lesson-plan-editor.ts create mode 100644 apps/portal-shell/src/features/teacher/lesson-plans/hooks/use-lesson-plan-persistence.ts create mode 100644 apps/portal-shell/src/features/teacher/lesson-plans/hooks/use-node-ai-assist.ts create mode 100644 apps/portal-shell/src/features/teacher/lesson-plans/hooks/version-slice.ts create mode 100644 apps/portal-shell/src/features/teacher/lesson-plans/inline-question-editor.tsx create mode 100644 apps/portal-shell/src/features/teacher/lesson-plans/knowledge-point-picker.tsx create mode 100644 apps/portal-shell/src/features/teacher/lesson-plans/lesson-plan-card.tsx create mode 100644 apps/portal-shell/src/features/teacher/lesson-plans/lesson-plan-dialogs.tsx create mode 100644 apps/portal-shell/src/features/teacher/lesson-plans/lesson-plan-mobile-view.tsx create mode 100644 apps/portal-shell/src/features/teacher/lesson-plans/lesson-plan-publish-button.tsx create mode 100644 apps/portal-shell/src/features/teacher/lesson-plans/lesson-plan-readonly-view.tsx create mode 100644 apps/portal-shell/src/features/teacher/lesson-plans/lesson-plan-toolbar.tsx create mode 100644 apps/portal-shell/src/features/teacher/lesson-plans/lib/ai-node-assist.ts create mode 100644 apps/portal-shell/src/features/teacher/lesson-plans/lib/anchor-mark.ts create mode 100644 apps/portal-shell/src/features/teacher/lesson-plans/lib/document-migration.ts create mode 100644 apps/portal-shell/src/features/teacher/lesson-plans/lib/id.ts create mode 100644 apps/portal-shell/src/features/teacher/lesson-plans/lib/type-guards.ts create mode 100644 apps/portal-shell/src/features/teacher/lesson-plans/paper-editor/inline-node.tsx create mode 100644 apps/portal-shell/src/features/teacher/lesson-plans/paper-editor/inline-qa-dialog.tsx create mode 100644 apps/portal-shell/src/features/teacher/lesson-plans/paper-editor/paper-context-menu.tsx create mode 100644 apps/portal-shell/src/features/teacher/lesson-plans/paper-editor/paper-editor.tsx create mode 100644 apps/portal-shell/src/features/teacher/lesson-plans/paper-editor/paper-toolbar.tsx create mode 100644 apps/portal-shell/src/features/teacher/lesson-plans/paper-editor/textbook-tiptap-editor.tsx create mode 100644 apps/portal-shell/src/features/teacher/lesson-plans/providers/lesson-plan-provider.tsx create mode 100644 apps/portal-shell/src/features/teacher/lesson-plans/services/default-data-service.ts create mode 100644 apps/portal-shell/src/features/teacher/lesson-plans/services/default-question-service.ts create mode 100644 apps/portal-shell/src/features/teacher/lesson-plans/services/external-questions-bridge.ts create mode 100644 apps/portal-shell/src/features/teacher/lesson-plans/structure-tree/structure-tree.tsx create mode 100644 apps/portal-shell/src/features/teacher/lesson-plans/structure-tree/tree-node-row.tsx create mode 100644 apps/portal-shell/src/features/teacher/lesson-plans/types.ts create mode 100644 apps/portal-shell/src/features/teacher/lesson-plans/version-diff-view.tsx create mode 100644 apps/portal-shell/src/features/teacher/lesson-plans/version-diff-viewer.tsx create mode 100644 apps/portal-shell/src/features/teacher/lesson-plans/version-history-drawer.tsx create mode 100644 apps/portal-shell/src/features/teacher/practice/adaptive-practice-utils.ts create mode 100644 apps/portal-shell/src/features/teacher/practice/components/answer-input.tsx create mode 100644 apps/portal-shell/src/features/teacher/practice/components/answer-result.tsx create mode 100644 apps/portal-shell/src/features/teacher/practice/components/class-knowledge-point-weakness-chart.tsx create mode 100644 apps/portal-shell/src/features/teacher/practice/components/class-practice-comparison-table.tsx create mode 100644 apps/portal-shell/src/features/teacher/practice/components/inactive-students-alert.tsx create mode 100644 apps/portal-shell/src/features/teacher/practice/components/practice-history.tsx create mode 100644 apps/portal-shell/src/features/teacher/practice/components/practice-result-view.tsx create mode 100644 apps/portal-shell/src/features/teacher/practice/components/practice-session-view.tsx create mode 100644 apps/portal-shell/src/features/teacher/practice/components/practice-starter-with-nav.tsx create mode 100644 apps/portal-shell/src/features/teacher/practice/components/practice-starter.tsx create mode 100644 apps/portal-shell/src/features/teacher/practice/components/practice-type-breakdown-chart.tsx create mode 100644 apps/portal-shell/src/features/teacher/practice/components/question-card.tsx create mode 100644 apps/portal-shell/src/features/teacher/practice/components/question-content.tsx create mode 100644 apps/portal-shell/src/features/teacher/practice/components/student-practice-ranking-table.tsx create mode 100644 apps/portal-shell/src/features/teacher/practice/components/types.ts create mode 100644 apps/portal-shell/src/features/teacher/questions/components/batch-operations.tsx create mode 100644 apps/portal-shell/src/features/teacher/questions/components/create-question-button.tsx create mode 100644 apps/portal-shell/src/features/teacher/questions/components/create-question-dialog.tsx create mode 100644 apps/portal-shell/src/features/teacher/questions/components/import-export-buttons.tsx create mode 100644 apps/portal-shell/src/features/teacher/questions/components/index.ts create mode 100644 apps/portal-shell/src/features/teacher/questions/components/knowledge-point-selector.tsx create mode 100644 apps/portal-shell/src/features/teacher/questions/components/options-editor.tsx create mode 100644 apps/portal-shell/src/features/teacher/questions/components/question-actions.tsx create mode 100644 apps/portal-shell/src/features/teacher/questions/components/question-bank-results-client.tsx create mode 100644 apps/portal-shell/src/features/teacher/questions/components/question-cascade-filter.tsx create mode 100644 apps/portal-shell/src/features/teacher/questions/components/question-columns.tsx create mode 100644 apps/portal-shell/src/features/teacher/questions/components/question-content-renderer.tsx create mode 100644 apps/portal-shell/src/features/teacher/questions/components/question-data-table.tsx create mode 100644 apps/portal-shell/src/features/teacher/questions/utils/parse-content.ts create mode 100644 apps/portal-shell/src/features/teacher/questions/utils/track-event.ts create mode 100644 apps/portal-shell/src/features/teacher/schedule-changes/components/auto-schedule-panel.tsx create mode 100644 apps/portal-shell/src/features/teacher/schedule-changes/components/auto-schedule-result.tsx create mode 100644 apps/portal-shell/src/features/teacher/schedule-changes/components/schedule-change-form.tsx create mode 100644 apps/portal-shell/src/features/teacher/schedule-changes/components/schedule-change-list.tsx create mode 100644 apps/portal-shell/src/features/teacher/schedule-changes/components/schedule-conflicts-view.tsx create mode 100644 apps/portal-shell/src/features/teacher/schedule-changes/components/schedule-grid-view.tsx create mode 100644 apps/portal-shell/src/features/teacher/schedule-changes/components/scheduling-rules-form.tsx create mode 100644 apps/portal-shell/src/features/teacher/textbooks/components/chapter-sidebar-list.tsx create mode 100644 apps/portal-shell/src/features/teacher/textbooks/components/create-chapter-dialog.tsx create mode 100644 apps/portal-shell/src/features/teacher/textbooks/components/force-graph.tsx create mode 100644 apps/portal-shell/src/features/teacher/textbooks/components/graph-kp-node.tsx create mode 100644 apps/portal-shell/src/features/teacher/textbooks/components/graph-node-detail-panel.tsx create mode 100644 apps/portal-shell/src/features/teacher/textbooks/components/graph-prerequisite-edge.tsx create mode 100644 apps/portal-shell/src/features/teacher/textbooks/components/graph-toolbar.tsx create mode 100644 apps/portal-shell/src/features/teacher/textbooks/components/knowledge-graph-controls.tsx create mode 100644 apps/portal-shell/src/features/teacher/textbooks/components/knowledge-graph-inner.tsx create mode 100644 apps/portal-shell/src/features/teacher/textbooks/components/knowledge-graph-node.tsx create mode 100644 apps/portal-shell/src/features/teacher/textbooks/components/knowledge-graph.tsx create mode 100644 apps/portal-shell/src/features/teacher/textbooks/components/knowledge-point-dialogs.tsx create mode 100644 apps/portal-shell/src/features/teacher/textbooks/components/knowledge-point-list.tsx create mode 100644 apps/portal-shell/src/features/teacher/textbooks/components/textbook-card.tsx create mode 100644 apps/portal-shell/src/features/teacher/textbooks/components/textbook-content-panel.tsx create mode 100644 apps/portal-shell/src/features/teacher/textbooks/components/textbook-form-dialog.tsx create mode 100644 apps/portal-shell/src/features/teacher/textbooks/components/textbook-form-fields.tsx create mode 100644 apps/portal-shell/src/features/teacher/textbooks/components/textbook-reader-content.tsx create mode 100644 apps/portal-shell/src/features/teacher/textbooks/components/textbook-reader-toc.tsx create mode 100644 apps/portal-shell/src/features/teacher/textbooks/components/textbook-reader.tsx create mode 100644 apps/portal-shell/src/features/teacher/textbooks/components/textbook-settings-dialog.tsx create mode 100644 apps/portal-shell/src/features/teacher/textbooks/hooks/use-graph-data.ts create mode 100644 apps/portal-shell/src/features/teacher/textbooks/hooks/use-knowledge-point-actions.ts create mode 100644 apps/portal-shell/src/features/teacher/textbooks/hooks/use-kp-create.ts create mode 100644 apps/portal-shell/src/features/teacher/textbooks/hooks/use-kp-crud.ts create mode 100644 apps/portal-shell/src/features/teacher/textbooks/hooks/use-kp-delete.ts create mode 100644 apps/portal-shell/src/features/teacher/textbooks/hooks/use-kp-dialog-state.ts create mode 100644 apps/portal-shell/src/features/teacher/textbooks/hooks/use-kp-update.ts create mode 100644 apps/portal-shell/src/features/teacher/textbooks/hooks/use-text-selection.ts create mode 100644 apps/portal-shell/src/features/teacher/textbooks/lib/analytics.tsx create mode 100644 apps/portal-shell/src/features/teacher/textbooks/lib/constants.ts create mode 100644 apps/portal-shell/src/features/teacher/textbooks/lib/graph-layout.ts create mode 100644 apps/portal-shell/src/features/teacher/textbooks/lib/type-guards.ts create mode 100644 apps/portal-shell/src/features/teacher/textbooks/lib/utils.ts create mode 100644 apps/portal-shell/src/features/teacher/textbooks/types.ts create mode 100644 apps/portal-shell/src/lib/api/operations/error-book-mutations.graphql.ts create mode 100644 apps/portal-shell/src/lib/api/operations/profile.graphql.ts create mode 100644 apps/portal-shell/src/lib/api/profile.ts create mode 100644 apps/portal-shell/src/lib/api/questions-extended.ts create mode 100644 apps/portal-shell/src/shared/components/charts/chart-card-shell.tsx create mode 100644 apps/portal-shell/src/shared/components/charts/chart.tsx create mode 100644 apps/portal-shell/src/shared/components/charts/comparison-radar-chart.tsx create mode 100644 apps/portal-shell/src/shared/components/charts/grade-trend-chart.tsx create mode 100644 apps/portal-shell/src/shared/components/charts/index.ts create mode 100644 apps/portal-shell/src/shared/components/charts/simple-bar-chart.tsx create mode 100644 apps/portal-shell/src/shared/components/charts/trend-line-chart.tsx create mode 100644 apps/portal-shell/src/shared/components/dashboard/comparison-badge.tsx create mode 100644 apps/portal-shell/src/shared/components/dashboard/dashboard-error-fallback.tsx create mode 100644 apps/portal-shell/src/shared/components/dashboard/dashboard-notification-widget.tsx create mode 100644 apps/portal-shell/src/shared/components/dashboard/dashboard-responsive-layout.tsx create mode 100644 apps/portal-shell/src/shared/components/dashboard/dashboard-time-range-filter.tsx create mode 100644 apps/portal-shell/src/shared/components/ui/alert-dialog.tsx create mode 100644 apps/portal-shell/src/shared/components/ui/alert.tsx create mode 100644 apps/portal-shell/src/shared/components/ui/avatar.tsx create mode 100644 apps/portal-shell/src/shared/components/ui/chart.tsx create mode 100644 apps/portal-shell/src/shared/components/ui/checkbox.tsx create mode 100644 apps/portal-shell/src/shared/components/ui/chip-nav.tsx create mode 100644 apps/portal-shell/src/shared/components/ui/confirm-delete-dialog.tsx create mode 100644 apps/portal-shell/src/shared/components/ui/dialog.tsx create mode 100644 apps/portal-shell/src/shared/components/ui/dropdown-menu.tsx create mode 100644 apps/portal-shell/src/shared/components/ui/label.tsx create mode 100644 apps/portal-shell/src/shared/components/ui/progress.tsx create mode 100644 apps/portal-shell/src/shared/components/ui/scroll-area.tsx create mode 100644 apps/portal-shell/src/shared/components/ui/select.tsx create mode 100644 apps/portal-shell/src/shared/components/ui/sheet.tsx create mode 100644 apps/portal-shell/src/shared/components/ui/switch.tsx create mode 100644 apps/portal-shell/src/shared/components/ui/table.tsx create mode 100644 apps/portal-shell/src/shared/components/ui/tabs.tsx create mode 100644 apps/portal-shell/src/shared/components/ui/textarea.tsx create mode 100644 apps/portal-shell/src/shared/hooks/use-permission.ts create mode 100644 apps/portal-shell/src/shared/lib/download.ts create mode 100644 apps/portal-shell/src/shared/lib/sanitize-html.ts diff --git a/apps/portal-shell/docs/needtodo/SUMMARY-NeedTodo.md b/apps/portal-shell/docs/needtodo/SUMMARY-NeedTodo.md new file mode 100644 index 0000000..3922b0a --- /dev/null +++ b/apps/portal-shell/docs/needtodo/SUMMARY-NeedTodo.md @@ -0,0 +1,229 @@ +# portal-shell 全角色模块页面核查汇总(2026-08-04) + +> 核查日期:2026-08-04 +> 核查范围:教师、学生、家长、管理员四个角色域全部页面 +> 核查方法:逐页面读取 page.tsx 源码,判断实现状态(完整/占位/缺失) +> 详细报告:见各角色域 NeedTodo.md 文档 + +--- + +## 一、总体统计 + +| 角色域 | 页面总数 | ✅ 完整实现 | 🟡 占位空态 | ❌ 缺失 | 详细报告 | +| -------- | -------- | ----------- | ----------- | ------- | -------------------------------------------- | +| 教师域 | 63 | 60 | 3 | 0 | [teacher-NeedTodo.md](./teacher-NeedTodo.md) | +| 学生域 | 32 | 32 | 0 | 0 | [student-NeedTodo.md](./student-NeedTodo.md) | +| 家长域 | 1 | 1 | 0 | 22 | [parent-NeedTodo.md](./parent-NeedTodo.md) | +| 管理员域 | 49 | 49 | 0 | 0 | [admin-NeedTodo.md](./admin-NeedTodo.md) | +| **合计** | **145** | **142** | **3** | **22** | — | + +--- + +## 二、各角色域核查结论 + +### 2.1 教师域(63 页) + +**状态**:基本完成,仅 3 个占位页需处理 + +**3 个占位页(均为 CICD 重定向路径,有等价实现)**: + +1. `/shell/teacher/classes/students` → 重定向到 `/shell/teacher/students` +2. `/shell/teacher/exams/grading` → 重定向到 `/shell/teacher/homework/submissions` +3. `/shell/teacher/exams/grading/[submissionId]` → 重定向到 `/shell/teacher/homework/submissions/[submissionId]` + +**已有资产**: + +- 55 个 client 组件(features/teacher/) +- 20 个 lib/api 模块 +- 116 个 mock 数据集 + +**P1-P2 功能增强 backlog**:15 页已集成但与 CICD 有功能差距(详见 teacher-NeedTodo.md §三.B) + +### 2.2 学生域(32 页) + +**状态**:全部真实业务实现,4 页 mock 数据缺口 + +**4 页 mock 数据缺口**: + +1. `learning-path` - mockMyLearningPath 缺失 +2. `elective/[id]` - mockElectiveCourseDetail 缺失 +3. `diagnostic` - mockDiagnosticReports 缺失 +4. `elective` - 部分字段缺失 + +**已有资产**: + +- 28 页 mock 数据齐全,可完整运行 +- 全部页面集成 client 组件 + API hook + 三态处理 + +### 2.3 家长域(1 页 + 22 缺失) + +**状态**:严重缺失,仅 dashboard 完成 + +**当前实现**: + +- `/shell/parent` - 完整实现(useParentDashboard hook + 三态处理 + StatCard×2 + DashboardSection×2) + +**22 个缺失页面**(按 ARCH §9.3 规划 24 页): + +1. dashboard 子页:trend(趋势详情)、weakness(薄弱点详情) +2. children(子女管理):列表 + [studentId] 详情 +3. grades(成绩):列表 + [id] 详情 +4. exams(考试):列表 + [id] 详情 +5. homework(作业):列表 +6. attendance(考勤):列表 +7. classes(班级):列表 +8. course-plans(课程计划):列表 + [id] 详情 +9. lesson-plans(教案):列表 + [planId]/view +10. error-book(错题本):列表 +11. diagnostic(诊断):列表 +12. learning-path(学习路径):列表 +13. practice(练习):列表 +14. elective(选修):列表 +15. leave(请假):列表 +16. preferences(偏好设置):列表 +17. notifications(通知)- 共享路由 +18. settings(设置)- 共享路由 + +**关键阻塞项**: + +- `features/parent/` 目录完全不存在,所有 client 组件待建 +- `myChildren` 契约缺失(子女详情页前置阻塞) +- 5 个 API hooks 已就绪,其余 18+ 页面 hooks 未建立 +- 仅 1 项 mock 数据(mockParentDashboard) + +### 2.4 管理员域(49 页) + +**状态**:全部落地,0 占位 0 缺失 + +**已有资产**: + +- 49 个 page.tsx 入口 +- 44 个 features/admin/*-client.tsx 业务组件 +- lib/api hooks(admin.ts 973 行 + admin-p5.ts 3371 行) +- mocks/graphql-data.ts(9867 行)兜底数据全覆盖 + +**契约状态**: + +- 仅 2 页契约就绪(dashboard adminDashboard ✅ + plugins config-service ✅) +- 47 页 schema 未就绪,前端用 MSW 兜底先行(@contract-pending 标注) + +--- + +## 三、按优先级排序的待办事项 + +### P0 - 必须立即处理 + +#### P0-1: 教师域 3 个占位页改为重定向 + +- `/shell/teacher/classes/students/page.tsx` → 改为 redirect 到 `/shell/teacher/students` +- `/shell/teacher/exams/grading/page.tsx` → 改为 redirect 到 `/shell/teacher/homework/submissions` +- `/shell/teacher/exams/grading/[submissionId]/page.tsx` → 改为 redirect 到 `/shell/teacher/homework/submissions/[submissionId]` + +#### P0-2: 家长域 22 个缺失页面(核心业务) + +按业务重要性排序: + +1. children(子女列表 + 详情)- 家长域核心功能 +2. grades(成绩查看)- 家长最关注 +3. homework(作业查看) +4. attendance(考勤查看) +5. exams(考试结果) +6. course-plans(课程计划) +7. lesson-plans(教案查看) +8. error-book(错题本) +9. diagnostic(诊断报告) +10. leave(请假申请) +11. learning-path(学习路径) +12. practice(练习记录) +13. elective(选修课) +14. preferences(偏好设置) +15. trend(趋势详情) +16. weakness(薄弱点详情) +17. notifications + settings(共享路由) + +### P1 - 重要功能补全 + +#### P1-1: 学生域 4 页 mock 数据缺口 + +- 补齐 mockMyLearningPath +- 补齐 mockElectiveCourseDetail +- 补齐 mockDiagnosticReports +- 补齐 elective 部分字段 + +#### P1-2: 教师域 15 页功能增强(与 CICD 对齐) + +详见 teacher-NeedTodo.md §三.B,包括: + +- exams 富文本编辑器接入(exam-edit + new/create) +- lesson-plans Tiptap 编辑器 + blocks + AI + 版本历史 +- grades 批量录入 + 高级图表 + 成绩单打印 +- classes 详情页 widgets + schedule 组件 +- attendance 图表 + report-print +- course-plans calendar/form/progress 组件 +- error-book 图表组件 +- leave 3 个组件 + questions 4 个组件 +- schedule-changes grid/rules/auto + practice 图表 +- textbooks reader + content-panel +- elective course-list/form/detail + +### P2 - 架构优化 + +#### P2-1: 管理员域契约补齐 + +- 47 页 schema 未就绪,需后端补齐 GraphQL 契约 +- 前端已用 MSW 兜底先行,可继续开发 + +#### P2-2: 家长域契约补齐 + +- `myChildren` 契约缺失是核心阻塞 +- 需后端补齐家长域相关 GraphQL schema + +--- + +## 四、关键风险 + +### 4.1 家长域整体缺失(高风险) + +- 仅 1/24 页完成,完成率 4.2% +- features/parent/ 目录不存在,需从零建设 +- 影响家长端用户体验,无法上线 + +### 4.2 契约脱节(中风险) + +- 管理员域 47/49 页契约 pending +- 家长域核心契约(myChildren)缺失 +- 前端依赖 MSW 兜底,后端就绪前无法真实数据验证 + +### 4.3 Mock 数据缺口(低风险) + +- 学生域 4 页 mock 缺口影响 dev 环境演示 +- 家长域仅 1 项 mock,新建页面需同步补 mock + +--- + +## 五、建议执行顺序 + +1. **立即执行 P0-1**:教师域 3 个占位页改为重定向(工作量小,立即见效) +2. **立即执行 P0-2**:家长域 22 个缺失页面(工作量大,需分配专门 AI/开发者) + - 先建立 features/parent/ 目录结构 + - 先实现 children 模块(核心功能) + - 逐步实现其他模块 +3. **并行执行 P1-1**:学生域 mock 数据补齐(工作量小) +4. **后续执行 P1-2**:教师域功能增强(工作量中等,可分批进行) +5. **后端协同 P2**:契约补齐需后端团队配合 + +--- + +## 六、核查文档清单 + +| 文档 | 路径 | 核查日期 | +| ---------------- | -------------------------------------------- | ---------- | +| 教师域核查报告 | [teacher-NeedTodo.md](./teacher-NeedTodo.md) | 2026-08-04 | +| 学生域核查报告 | [student-NeedTodo.md](./student-NeedTodo.md) | 2026-08-04 | +| 家长域核查报告 | [parent-NeedTodo.md](./parent-NeedTodo.md) | 2026-08-04 | +| 管理员域核查报告 | [admin-NeedTodo.md](./admin-NeedTodo.md) | 2026-08-04 | +| 本汇总报告 | [SUMMARY-NeedTodo.md](./SUMMARY-NeedTodo.md) | 2026-08-04 | + +--- + +**核查完成。四个角色域共 145 个页面,142 个已完整实现,3 个占位(教师域重定向),22 个缺失(家长域待建)。** diff --git a/apps/portal-shell/docs/needtodo/admin-NeedTodo.md b/apps/portal-shell/docs/needtodo/admin-NeedTodo.md index 8bab4bc..147edb5 100644 --- a/apps/portal-shell/docs/needtodo/admin-NeedTodo.md +++ b/apps/portal-shell/docs/needtodo/admin-NeedTodo.md @@ -1,582 +1,486 @@ -# 管理域(Admin)待完成功能分析 +# 管理域(Admin)待完成功能分析 > 参考项目:`e:\desktop\CICD\src\app\(dashboard)\admin\`(41 个 page.tsx) -> 当前项目:`e:\Desktop\Edu\apps\portal-shell\src\app\shell\admin\`(1 个 page.tsx) -> 规划依据:`apps\portal-shell\ARCHITECTURE.md` §9.4(管理域 24 页,B5 批次) -> 分析日期:2026-07-24 -> 分析方式:逐页面对比 CICD 实现 vs portal-shell 实现,逐个读取 CICD page.tsx + ARCH §9.4 契约 +> 当前项目:`e:\Desktop\Edu\apps\portal-shell\src\app\shell\admin\`(49 个 page.tsx) +> 规划依据:`apps\portal-shell\ARCHITECTURE.md` §9.4(管理域 24 页顶层入口,B5 批次) +> 核查日期:2026-08-04 +> 核查方式:逐个读取 49 个 page.tsx + 44 个 features/admin/*-client.tsx + lib/api/admin.ts(973 行) + lib/api/admin-p5.ts(3371 行) + mocks/graphql-data.ts(9867 行),结合 ARCH §9.4 契约判断实现完整性 --- ## 〇、方法论与对比基线 1. CICD 单体(Next.js App Router + Server Actions + Drizzle)作为**功能基线**,反映"老版单体已实现"的管理员功能完整态。 -2. portal-shell 作为**目标态**,遵循 ARCH §9.4 的契约与批次规划(B5,24 页)。 +2. portal-shell 作为**目标态**,遵循 ARCH §9.4 的契约与批次规划(B5,24 页顶层入口)。 3. CICD admin/layout.tsx 仅为 `<>{children}` passthrough,无独立侧边栏/权限逻辑(权限由各 page.tsx 调 `requirePermission` 完成)。 -4. ARCH §9.4 规划 24 页 = 1 仪表盘 + 23 管理子页;portal-shell 仅完成 `/shell/admin/page.tsx`(仪表盘),其余 23 页全部缺失。 -5. CICD 中存在但 ARCH §9.4 **未规划**的页面(course-plans/curriculum-map/elective/questions/lesson-plans/error-book/scheduling/attendance 等 21 页):CICD 历史已实现,portal-shell 缺失但**不违反 ARCH §9.4**;这些页面是教师域在管理员视角的"全局只读 + 跨班聚合"版本,可作为 B5 后期/补充批次候选。 -6. ARCH §9.4 中存在但 CICD **无对应**的页面(4 页):`students`、`teachers`、`organization`、`viewports`、`plugins`,属 ARCH 新规划,需在 portal-shell 中独立设计。 +4. ARCH §9.4 规划 24 页顶层入口 = 1 仪表盘 + 23 管理子页;portal-shell 在此基础上补充了详情/编辑/创建子页与 redirect 入口,实际落地 **49 个 page.tsx**。 +5. 判断口径(任务约束): + - **✅ 完整实现**:page.tsx 引入 `@/features/admin/*-client.tsx` 并渲染(client 组件承担业务逻辑,调用 lib/api hooks,处理三态);或 page.tsx 为有意的 `redirect()` 入口跳转。 + - **🟡 占位空态**:page.tsx 仅用 ListPageShell/FormPageShell/DetailPageShell 直接渲染空态,无 children 业务内容。 + - **❌ 缺失**:page.tsx 文件不存在。 +6. 核查覆盖维度:page.tsx 入口 + features/admin/*-client.tsx 业务组件 + lib/api hooks + mocks/graphql-data.ts 兜底数据,四层齐全方视为完整实现。 --- ## 一、页面完成度总览 -| 状态 | 数量 | 说明 | -| --------------------------------- | ---- | ---------------------------------------------------------------------------------------------- | -| ✅ 已完成且功能完整 | 1 | `/shell/admin/page.tsx`(仪表盘,4 StatCard + 预警 + AI 用量,已接 `adminDashboard` 真实契约) | -| ❌ 完全缺失(ARCH §9.4 规划内) | 23 | 见 §二,按 ARCH 规划口径的 23 个缺失页面 | -| ➕ CICD 已实现但 ARCH §9.4 未规划 | 21 | 见 §五,作为补充批次参考 | -| 🆕 ARCH §9.4 规划但 CICD 无源 | 4 | `students`、`teachers`、`organization`、`viewports`(+ `plugins` 已有契约 ✅) | +| 状态 | 数量 | 说明 | +| --------------------- | ------ | -------------------------------------------------------------------------------------------------------------------------------------- | +| ✅ 完整实现(业务页) | 46 | page.tsx 引入对应 `features/admin/*-client.tsx` 并在 `` 中渲染;client 组件调用 lib/api hooks,处理 loading/error/empty 三态 | +| ✅ 完整实现(重定向) | 3 | page.tsx 为有意的 `redirect()` 入口跳转(classes / scheduling / school 三个模块根路径) | +| 🟡 占位空态 | 0 | 无 | +| ❌ 缺失 | 0 | 无 | +| **合计** | **49** | **管理员域 49/49 page.tsx 全部落地** | -### ✅ 已完成且功能完整(1 页) +### ✅ 完整实现(46 业务页 + 3 重定向页 = 49 页) -| 模块 | 页面 | 备注 | -| --------- | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| dashboard | `/shell/admin/page.tsx` | 客户端组件,`useAdminDashboard` hook 接 data-ana `adminDashboard` 真实契约;4 StatCard(教师/学生/班级/全校平均分)+ 近期预警 + AI 用量;含 loading/error 兜底;引用 ARCH §7.1 / §10 P1-2 | +所有 49 个 page.tsx 均已存在且非占位。46 个业务页统一采用 Server Component 入口 + Suspense 边界 + Client Component 业务的架构;3 个重定向页为有意的入口跳转,避免空白入口。 + +业务页架构样例(以 `/shell/admin/ai-settings/page.tsx` 为例): + +```tsx +import { Suspense } from "react"; +import { AiSettingsClient } from "@/features/admin/ai-settings/ai-settings-client"; +import { ListPageSkeleton } from "@/shared/components/page-templates"; + +export default function AiSettingsPage(): React.ReactElement { + return ( + }> + + + ); +} +``` + +重定向页架构样例(以 `/shell/admin/school/page.tsx` 为例): + +```tsx +import { redirect } from "next/navigation"; +export default function SchoolAdminIndexPage(): never { + redirect("/shell/admin/school/schools"); +} +``` --- -## 二、缺失页面清单(按 ARCH §9.4 规划口径,23 页) - -### 2.1 dashboard 模块(无缺口) - -`/shell` 已落地,对应 ARCH §9.4 第 1 行 `admin/dashboard` → `/shell`(动作为 R,契约 ✅ `adminDashboard`)。 - -### 2.2 users 模块(2 页缺失) - -#### 2.2.1 `/shell/admin/users`(用户管理列表) - -- **CICD 参考实现**:`e:\desktop\CICD\src\app\(dashboard)\admin\users\page.tsx` -- **功能描述**:用户列表 + 角色 assignment 入口 - - URL 参数:`page`、`search`、`role`(搜索/角色筛选 + 分页) - - 并行查询:`getAdminUsers({ page, search, role })` + `getAdminUserRoles()` + `getRoles()` - - 渲染 `AdminUsersView` 客户端组件,传入 users / roleOptions / 分页信息 / assignableRoles -- **技术栈**:Server Component + `requirePermission(Permissions.USER_MANAGE)` + `force-dynamic` + `getSearchParam` 工具 + Drizzle data-access -- **ARCHITECTURE.md 契约**:❌ `users` 列表(B5,未就绪) -- **CICD 关键代码片段**: - ```ts - await requirePermission(Permissions.USER_MANAGE); - const [result, roleOptions, roles] = await Promise.all([ - getAdminUsers({ page, search, role }), - getAdminUserRoles(), - getRoles(), - ]); - ``` - -#### 2.2.2 `/shell/admin/users/import`(用户批量导入) - -- **CICD 参考实现**:`e:\desktop\CICD\src\app\(dashboard)\admin\users\import\page.tsx` -- **功能描述**:用户批量导入页面,包含 - - 顶部 `UserImportDialog`(导入弹窗,触发上传 + Server Action 解析) - - 操作指引卡片(4 步流程) - - 注意事项卡片(6 条规则) - - 模板字段说明表(5 列:name/email/role/phone/inviteCode,标注是否必填) -- **技术栈**:Server Component + `requirePermission(Permissions.USER_MANAGE)` + UI 基础组件(Card / Table / Button)+ next-intl -- **ARCHITECTURE.md 契约**:❌(未单独列,归 `users` 工单) -- **CICD 关键代码片段**:通过 `` 客户端组件触发导入流程,模板字段固定 5 列。 - -### 2.3 roles + permissions 模块(2 页缺失) - -#### 2.3.1 `/shell/admin/roles`(角色管理列表) - -- **CICD 参考实现**:`e:\desktop\CICD\src\app\(dashboard)\admin\roles\page.tsx` -- **功能描述**:角色列表 + 创建/编辑入口 - - 查询 `getRoles()` 拉取所有角色 - - 渲染 `RoleManagementView` 客户端组件(含创建按钮、角色卡片/列表) -- **技术栈**:Server Component + `requirePermission(Permissions.ROLE_READ)` + Drizzle `rbac/data-access` -- **ARCHITECTURE.md 契约**:❌(B5) -- **CICD 关键代码片段**: - ```ts - await requirePermission(Permissions.ROLE_READ) - const roles = await getRoles() - return - ``` - -#### 2.3.2 `/shell/admin/permissions`(权限目录) - -- **CICD 参考实现**:`e:\desktop\CICD\src\app\(dashboard)\admin\permissions\page.tsx` -- **功能描述**:权限目录页(系统所有权限点列表,按模块分组) - - 查询 `getPermissionRoleCounts()` 获取每个权限点关联的角色数 - - 渲染 `PermissionCatalogView` 客户端组件 -- **技术栈**:Server Component + `requirePermission(Permissions.PERMISSION_READ)` + Drizzle `rbac/data-access-permissions` -- **ARCHITECTURE.md 契约**:❌(B5) -- **CICD 关键代码片段**: - ```ts - await requirePermission(Permissions.PERMISSION_READ) - const roleCountsByPermission = await getPermissionRoleCounts() - return - ``` - -> **附注**:CICD 另有 `admin/roles/[id]/page.tsx`(角色详情,含 `RolePermissionMatrix` 权限矩阵 + `RoleDetailEditButton`),但 ARCH §9.4 仅规划 `/shell/admin/roles` 一页,未列详情子页。可作 B5 后期补强。 - -### 2.4 audit-logs 模块(4 页缺失) - -#### 2.4.1 `/shell/admin/audit-logs`(审计日志列表 + 导出) - -- **CICD 参考实现**:`e:\desktop\CICD\src\app\(dashboard)\admin\audit-logs\page.tsx` -- **功能描述**:审计日志列表 + 多维筛选 + CSV 导出 - - URL 参数:`page`、`module`、`action`、`status`、`userId`、`startDate`、`endDate` - - 并行查询:`getAuditLogs({...})` + `getAuditModuleOptions()` - - `AuditLogExportButton` 触发 CSV 导出(按筛选条件) - - `SectionErrorBoundary` 错误兜底 -- **技术栈**:Server Component + `requirePermission(Permissions.AUDIT_LOG_READ)` + Drizzle `audit/data-access` + 类型守卫 `isValidAuditLogStatus` -- **ARCHITECTURE.md 契约**:❌(B5) -- **CICD 关键代码片段**: - ```ts - const [result, moduleOptions] = await Promise.all([ - getAuditLogs({ page, module, action, status, userId, startDate, endDate }), - getAuditModuleOptions(), - ]); - ``` - -#### 2.4.2 `/shell/admin/audit-logs/overview`(审计概览) - -- **CICD 参考实现**:`e:\desktop\CICD\src\app\(dashboard)\admin\audit-logs\overview\page.tsx` -- **功能描述**:审计概览页(统计/趋势/分布三件套) - - 并行查询:`getAuditOverviewStats()` + `getAuditTrend(7)` + `getDataChangeActionStats()` - - `AuditServiceProvider` 包裹 `adminAuditService` 注入服务上下文 - - 渲染 `AuditOverviewView` 客户端组件(统计卡 + 7 天趋势图 + 动作分布饼图) -- **技术栈**:Server Component + `requirePermission(Permissions.AUDIT_LOG_READ)` + 服务注入模式 -- **ARCHITECTURE.md 契约**:❌(B5) -- **CICD 关键代码片段**: - ```ts - const [stats, trend, distribution] = await Promise.all([ - getAuditOverviewStats(), - getAuditTrend(7), - getDataChangeActionStats(), - ]); - ``` - -#### 2.4.3 `/shell/admin/audit-logs/login-logs`(登录日志) - -- **CICD 参考实现**:`e:\desktop\CICD\src\app\(dashboard)\admin\audit-logs\login-logs\page.tsx` -- **功能描述**:登录日志列表 + 筛选 + 导出 - - URL 参数:`page`、`action`(signin/signout/signup)、`status`(success/failure)、`userId`、`startDate`、`endDate` - - 查询 `getLoginLogs({...})` - - `AuditLogExportButton exportType="login"` 触发导出 - - 渲染 `LoginLogView` 客户端组件 -- **技术栈**:Server Component + `requirePermission(Permissions.AUDIT_LOG_READ)` + 类型守卫 `isValidLoginLogAction` / `isValidLoginLogStatus` -- **ARCHITECTURE.md 契约**:❌(B5) - -#### 2.4.4 `/shell/admin/audit-logs/data-changes`(数据变更日志) - -- **CICD 参考实现**:`e:\desktop\CICD\src\app\(dashboard)\admin\audit-logs\data-changes\page.tsx` -- **功能描述**:数据变更日志列表 + 筛选 + 导出 + 统计 - - URL 参数:`page`、`tableName`、`action`(create/update/delete)、`userId`、`startDate`、`endDate` - - 并行查询:`getDataChangeLogs({...})` + `getDataChangeTableOptions()` + `getDataChangeStats()` - - 渲染 `DataChangeLogView` 客户端组件(含表选项下拉 + 变更统计卡) -- **技术栈**:Server Component + `requirePermission(Permissions.AUDIT_LOG_READ)` + 类型守卫 `isValidDataChangeAction` -- **ARCHITECTURE.md 契约**:❌(B5) - -### 2.5 invitation-codes 模块(1 页缺失) - -#### 2.5.1 `/shell/admin/invitation-codes`(邀请码管理) - -- **CICD 参考实现**:`e:\desktop\CICD\src\app\(dashboard)\admin\invitation-codes\page.tsx` -- **功能描述**:邀请码列表 + 生成 + 删除 - - SSR 查询 `listInvitationCodes(200, true)`(200 条 + 含已用) - - 服务端计算 `now = new Date().getTime()` 传给客户端(避免 Client 端 render 阶段调用 `Date.now()`,符合 react-hooks/purity 规则) - - 生成/删除通过 Server Action + `router.refresh()` 触发更新 - - 渲染 `InvitationCodesView` 客户端组件 -- **技术栈**:Server Component + `requirePermission(Permissions.USER_MANAGE)` + Drizzle `invitation-codes/data-access` -- **ARCHITECTURE.md 契约**:❌(B5) -- **CICD 关键代码片段**: - ```ts - const codes = await listInvitationCodes(200, true) - const now = new Date().getTime() - return - ``` - -### 2.6 school 模块(6 页缺失) - -#### 2.6.1 `/shell/admin/school`(学校首页,redirect) - -- **CICD 参考实现**:`e:\desktop\CICD\src\app\(dashboard)\admin\school\page.tsx` -- **功能描述**:纯重定向页 `redirect("/admin/school/schools")`,无 UI -- **技术栈**:`redirect()` from `next/navigation` -- **ARCHITECTURE.md 契约**:❌(B5) - -#### 2.6.2 `/shell/admin/school/schools`(学校列表) - -- **CICD 参考实现**:`e:\desktop\CICD\src\app\(dashboard)\admin\school\schools\page.tsx` -- **功能描述**:学校列表 + CRUD - - 查询 `getSchools()` - - 渲染 `SchoolsClient` 客户端组件 - - `SectionErrorBoundary` 错误兜底 -- **技术栈**:Server Component + `requirePermission(Permissions.SCHOOL_MANAGE)` + Drizzle `school/data-access` -- **ARCHITECTURE.md 契约**:❌(B5) - -#### 2.6.3 `/shell/admin/school/classes`(班级管理) - -- **CICD 参考实现**:`e:\desktop\CICD\src\app\(dashboard)\admin\school\classes\page.tsx` -- **功能描述**:班级管理 + 跨数据聚合 - - 并行查询:`getAdminClasses()` + `getTeacherOptions()` + `getSchools()` + `getGrades()` - - 渲染 `AdminClassesClient` 客户端组件(含班级 CRUD + 教师/学校/年级下拉) -- **技术栈**:Server Component + `requirePermission(Permissions.SCHOOL_MANAGE)` + Drizzle `classes/data-access` + `school/data-access` -- **ARCHITECTURE.md 契约**:❌(B5) -- **CICD 关键代码片段**: - ```ts - const [classes, teachers, schools, grades] = await Promise.all([ - getAdminClasses(), - getTeacherOptions(), - getSchools(), - getGrades(), - ]); - ``` - -> **注**:ARCH §9.4 第 7 行将 `admin/classes` 单列 1 页(M),与 `school/classes` 实际同源,建议合并到 `/shell/admin/school/classes`。 - -#### 2.6.4 `/shell/admin/school/departments`(部门管理) - -- **CICD 参考实现**:`e:\desktop\CICD\src\app\(dashboard)\admin\school\departments\page.tsx` -- **功能描述**:部门列表 + CRUD - - 查询 `getDepartments()` - - 渲染 `DepartmentsClient` 客户端组件 -- **技术栈**:Server Component + `requirePermission(Permissions.SCHOOL_MANAGE)` + Drizzle -- **ARCHITECTURE.md 契约**:❌(B5) - -#### 2.6.5 `/shell/admin/school/academic-year`(学年管理) - -- **CICD 参考实现**:`e:\desktop\CICD\src\app\(dashboard)\admin\school\academic-year\page.tsx` -- **功能描述**:学年列表 + CRUD - - 查询 `getAcademicYears()` - - 渲染 `AcademicYearClient` 客户端组件 -- **技术栈**:Server Component + `requirePermission(Permissions.SCHOOL_MANAGE)` + Drizzle -- **ARCHITECTURE.md 契约**:❌(B5) - -#### 2.6.6 `/shell/admin/school/grades`(年级管理) - -- **CICD 参考实现**:`e:\desktop\CICD\src\app\(dashboard)\admin\school\grades\page.tsx` -- **功能描述**:年级列表 + CRUD + 统计 - - 并行查询:`getGrades()` + `getSchools()` + `getStaffOptions()` + `getGradeOverviewStats()` - - 渲染 `GradesClient` 客户端组件(年级 CRUD + 学校/教职工下拉 + 统计概览) -- **技术栈**:Server Component + `requirePermission(Permissions.SCHOOL_MANAGE)` + Drizzle -- **ARCHITECTURE.md 契约**:❌(B5) - -> **附注**:CICD 另有 `school/grades/insights/page.tsx`(年级洞察,含 `SchoolWideSummaryCard` 全校汇总 + ChipNav 年级筛选 + 作业表 + 班级排名表 + `getSchoolWideGradeSummary(ctx.dataScope)`),属管理员数据分析视角,**ARCH §9.4 未单独规划**,可作 B5 后期补强。 - -### 2.7 announcements 模块(1 页缺失,ARCH 口径) - -#### 2.7.1 `/shell/admin/announcements`(公告管理列表) - -- **CICD 参考实现**:`e:\desktop\CICD\src\app\(dashboard)\admin\announcements\page.tsx` -- **功能描述**:公告列表 + 状态筛选 - - URL 参数:`status`(draft/published/archived) - - 查询 `getAdminAnnouncementsPageData(status)`(一次性返回 announcements + grades + classes) - - 渲染 `AdminAnnouncementsView` 客户端组件 -- **技术栈**:Server Component + `requirePermission(Permissions.ANNOUNCEMENT_MANAGE)` + Drizzle `announcements/data-access` + 类型守卫 `isValidStatus` -- **ARCHITECTURE.md 契约**:❌(B5) - -> **附注**:CICD 另有 `announcements/[id]/page.tsx`(公告详情,含发布/归档/删除/置顶操作)+ `announcements/[id]/edit/page.tsx`(编辑表单 `AnnouncementForm mode="edit"` + grades 关联),**ARCH §9.4 仅规划 1 页**,建议合并到详情子页或工作流组件。 - -### 2.8 files 模块(1 页缺失) - -#### 2.8.1 `/shell/admin/files`(文件管理) - -- **CICD 参考实现**:`e:\desktop\CICD\src\app\(dashboard)\admin\files\page.tsx` -- **功能描述**:文件列表 + 统计 - - 并行查询:`getFileAttachmentsWithFilters({ limit: 200 })` + `getFileStats()` - - 渲染 `AdminFilesView` 客户端组件(含统计卡 + 文件列表) -- **技术栈**:Server Component + `requirePermission(Permissions.FILE_READ)` + Drizzle `files/data-access` -- **ARCHITECTURE.md 契约**:❌(B5) -- **CICD 关键代码片段**: - ```ts - const [files, stats] = await Promise.all([ - getFileAttachmentsWithFilters({ limit: 200 }), - getFileStats(), - ]); - ``` +## 二、已实现页面清单(按模块分组,49 页) -### 2.9 ai-settings 模块(1 页缺失) +> 状态标识:✅ 完整业务实现 / ↪️ 重定向入口 / 🟡 占位 / ❌ 缺失 +> 三态规范:所有 ✅ 业务页的 client 组件均处理 loading(Skeleton)/ error(局部降级)/ empty(EmptyState)三态(ARCH §11.3 DoD) -#### 2.9.1 `/shell/admin/ai-settings`(AI 配置) +### 2.1 dashboard 模块(1 页) -- **CICD 参考实现**:`e:\desktop\CICD\src\app\(dashboard)\admin\ai-settings\page.tsx` -- **功能描述**:AI Provider 配置 + Usage 仪表盘(取代旧 `/settings?tab=ai` 与考试页内嵌 AI 弹窗) - - 权限:`AI_CHAT`(普通用户可访问自己的 private provider),管理员额外有 `AI_CONFIGURE`(管理 public provider 与他人 private provider) - - 渲染 `AiProviderSettingsCard` 客户端组件(含 isAdmin 开关) - - 管理员额外渲染 `AiUsageDashboard`(用量统计) -- **技术栈**:Server Component + `requirePermission(Permissions.AI_CHAT)` + 通过 `ctx.permissions.includes(Permissions.AI_CONFIGURE)` 区分管理员 -- **ARCHITECTURE.md 契约**:❌(B5) - -### 2.10 system 模块(1 页缺失) +| 页面路径 | 状态 | 实现要点 | +| -------------- | ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `/shell/admin` | ✅ | "use client" + useAdminDashboard() hook 接 data-ana adminDashboard 真实契约;4 StatCard(教师/学生/班级/全校平均分)+ 近期预警 + AI 用量;含 loading/error 兜底;引用 ARCH §7.1 / §10 P1-2 | -#### 2.10.1 `/shell/admin/system`(系统设置) +### 2.2 ai-settings 模块(1 页) -- **CICD 参考实现**:`e:\desktop\CICD\src\app\(dashboard)\admin\settings\page.tsx` -- **功能描述**:系统设置页 - - 渲染 `AdminSettingsView` 客户端组件(具体配置项由组件内部决定) -- **技术栈**:Server Component + `requirePermission(Permissions.SETTINGS_ADMIN)` + Drizzle -- **ARCHITECTURE.md 契约**:❌(B5,对应 ARCH §9.4 第 13 行 `admin/system` → `/shell/admin/system`) +| 页面路径 | 状态 | client 组件 | 行数 | lib/api hooks | 契约状态 | +| ------------------------ | ---- | ---------------------- | ---- | -------------------------------------------------------------------------------------------------------------------------- | --------------------------- | +| /shell/admin/ai-settings | ✅ | ai-settings-client.tsx | 522 | useAiProviders / useCreateAiProvider / useUpdateAiProvider / useDeleteAiProvider / useTestAiProvider / useAiUsageDashboard | ❌ schema 未就绪 → MSW 兜底 | -### 2.11 viewports 模块(1 页缺失,ARCH 新建) +### 2.3 announcements 模块(3 页) -#### 2.11.1 `/shell/admin/viewports`(视口配置) +| 页面路径 | 状态 | client 组件 | 行数 | lib/api hooks | 契约状态 | +| ------------------------------------ | ---- | ------------------------------ | ---- | -------------------------------------------------------------------------------------------------------------------- | ------------- | +| /shell/admin/announcements | ✅ | announcements-list-client.tsx | 469 | useAdminAnnouncements / useDeleteAnnouncement / useArchiveAnnouncement / usePinAnnouncement / usePublishAnnouncement | ❌ → MSW 兜底 | +| /shell/admin/announcements/[id] | ✅ | announcement-detail-client.tsx | 289 | useAdminAnnouncement | ❌ → MSW 兜底 | +| /shell/admin/announcements/[id]/edit | ✅ | announcement-edit-client.tsx | 266 | useAdminAnnouncement / useUpdateAnnouncement | ❌ → MSW 兜底 | -- **CICD 参考实现**:**无**(CICD 未实现) -- **功能描述**:对齐 004 §5.4 视口配置(BFF/Service 层 viewport 路由策略可视化) -- **技术栈**:未知,需依据 004 §5.4 设计 -- **ARCHITECTURE.md 契约**:❌(B5) +### 2.4 attendance 模块(1 页) -### 2.12 plugins 模块(1 页,契约已就绪 ✅) +| 页面路径 | 状态 | client 组件 | 行数 | lib/api hooks | 契约状态 | +| ----------------------- | ---- | --------------------------- | ---- | ----------------------------------------------------------------------------------- | ------------- | +| /shell/admin/attendance | ✅ | admin-attendance-client.tsx | 340 | useAdminAttendanceStats / useAdminAttendanceRecords / useAttendanceGradeCorrelation | ❌ → MSW 兜底 | -#### 2.12.1 `/shell/admin/plugins`(插件管理) +### 2.5 audit-logs 模块(4 页) -- **CICD 参考实现**:**无**(CICD 未实现) -- **功能描述**:现有 portal-shell `plugin-manager` 升级整页 -- **技术栈**:依据 portal-shell 已有 plugin-manager 演化 -- **ARCHITECTURE.md 契约**:✅ `config-service`(已就绪,可优先实现) +| 页面路径 | 状态 | client 组件 | 行数 | lib/api hooks | 契约状态 | +| ------------------------------------ | ---- | -------------------------- | ---- | ----------------------------------------------------------------------------------------- | ------------- | +| /shell/admin/audit-logs | ✅ | audit-logs-list-client.tsx | 461 | useAuditLogs / useAuditModuleOptions / useExportAuditLogs | ❌ → MSW 兜底 | +| /shell/admin/audit-logs/data-changes | ✅ | data-changes-client.tsx | 472 | useDataChangeLogs / useDataChangeTableOptions / useDataChangeStats / useExportDataChanges | ❌ → MSW 兜底 | +| /shell/admin/audit-logs/login-logs | ✅ | login-logs-client.tsx | 325 | useLoginLogs / useExportLoginLogs | ❌ → MSW 兜底 | +| /shell/admin/audit-logs/overview | ✅ | audit-overview-client.tsx | 386 | useAuditOverviewStats / useAuditTrend / useDataChangeActionStats | ❌ → MSW 兜底 | -### 2.13 ARCH §9.4 规划但 CICD 无源的"新建"页面(3 页) +### 2.6 classes 模块(1 页,重定向) -#### 2.13.1 `/shell/admin/students`(学生管理) +| 页面路径 | 状态 | 实现要点 | +| -------------------- | ---- | ----------------------------------------------------------------------------------------------- | +| /shell/admin/classes | ↪️ | redirect("/shell/admin/school/classes"):班级管理归属于 school 限界上下文,避免空白入口重复实现 | -- **CICD 参考实现**:**无** -- **ARCHITECTURE.md 契约**:❌(B5) -- **建议**:参照 CICD 中 `admin/school/classes` 等管理页设计,作学生维度聚合视图。 +### 2.7 course-plans 模块(4 页) -#### 2.13.2 `/shell/admin/teachers`(教师管理) +| 页面路径 | 状态 | client 组件 | 行数 | lib/api hooks | 契约状态 | +| ----------------------------------- | ---- | ----------------------------- | ---- | ------------------------------------------------------------------------------------------------------- | ------------- | +| /shell/admin/course-plans | ✅ | course-plans-list-client.tsx | 339 | useAdminCoursePlans | ❌ → MSW 兜底 | +| /shell/admin/course-plans/create | ✅ | course-plan-create-client.tsx | 415 | useAdminClasses / useCreateCoursePlanItem 系列 | ❌ → MSW 兜底 | +| /shell/admin/course-plans/[id] | ✅ | course-plan-detail-client.tsx | 644 | useAdminCoursePlan / useUpdateCoursePlanItem / useReorderCoursePlanItems / useBulkToggleCoursePlanItems | ❌ → MSW 兜底 | +| /shell/admin/course-plans/[id]/edit | ✅ | course-plan-edit-client.tsx | 562 | useAdminCoursePlan / useUpdateCoursePlanItem 系列 | ❌ → MSW 兜底 | -- **CICD 参考实现**:**无** -- **ARCHITECTURE.md 契约**:❌(B5) -- **建议**:参照 CICD 中 `admin/users` + `getStaffOptions()` 演化。 +### 2.8 curriculum-map 模块(1 页) -#### 2.13.3 `/shell/admin/organization`(组织管理) +| 页面路径 | 状态 | client 组件 | 行数 | lib/api hooks | 契约状态 | +| --------------------------- | ---- | ------------------------- | ---- | ------------------------------------------------------ | ------------- | +| /shell/admin/curriculum-map | ✅ | curriculum-map-client.tsx | 256 | useStandardsCoverageHeatmap / useGlobalLessonPlanStats | ❌ → MSW 兜底 | -- **CICD 参考实现**:**无** -- **ARCHITECTURE.md 契约**:❌(B5) -- **建议**:合并 `school/departments` + `school/grades` + 师生维度,作组织架构视图。 +### 2.9 elective 模块(4 页) + +| 页面路径 | 状态 | client 组件 | 行数 | lib/api hooks | 契约状态 | +| ------------------------------- | ---- | -------------------------- | ---- | ----------------------------------------------------------------------------------------------------- | ------------- | +| /shell/admin/elective | ✅ | elective-list-client.tsx | 545 | useAdminElectives / useGetElectiveOverviewStats / useDeleteElective | ❌ → MSW 兜底 | +| /shell/admin/elective/create | ✅ | elective-create-client.tsx | 381 | useAdminCreateElective | ❌ → MSW 兜底 | +| /shell/admin/elective/[id] | ✅ | elective-detail-client.tsx | 385 | useAdminElective | ❌ → MSW 兜底 | +| /shell/admin/elective/[id]/edit | ✅ | elective-edit-client.tsx | 464 | useAdminUpdateElective / useOpenElectiveSelection / useCloseElectiveSelection / useRunElectiveLottery | ❌ → MSW 兜底 | + +### 2.10 error-book 模块(1 页) + +| 页面路径 | 状态 | client 组件 | 行数 | lib/api hooks | 契约状态 | +| ----------------------- | ---- | --------------------- | ---- | ---------------------------------------------- | ------------- | +| /shell/admin/error-book | ✅ | error-book-client.tsx | 738 | useAdminErrorBookStats / useExportErrorBookCsv | ❌ → MSW 兜底 | + +### 2.11 files 模块(1 页) + +| 页面路径 | 状态 | client 组件 | 行数 | lib/api hooks | 契约状态 | +| ------------------ | ---- | --------------------- | ---- | ----------------------------------------------------------------------- | ------------- | +| /shell/admin/files | ✅ | files-list-client.tsx | 377 | useFileAttachments / useFileStats / useUploadFile / useBatchDeleteFiles | ❌ → MSW 兜底 | + +### 2.12 invitation-codes 模块(1 页) + +| 页面路径 | 状态 | client 组件 | 行数 | lib/api hooks | 契约状态 | +| ----------------------------- | ---- | -------------------------------- | ---- | ------------------------------------------------------------------------------------------------------------------------------ | ------------- | +| /shell/admin/invitation-codes | ✅ | invitation-codes-list-client.tsx | 542 | useInvitationCodes / useCreateInvitationCode / useRevokeInvitationCode / useGenerateInvitationCodes / useDeleteInvitationCodes | ❌ → MSW 兜底 | + +### 2.13 lesson-plans 模块(2 页) + +| 页面路径 | 状态 | client 组件 | 行数 | lib/api hooks | 契约状态 | +| --------------------------------------- | ---- | ---------------------------- | ---- | -------------------------------------------- | ------------- | +| /shell/admin/lesson-plans | ✅ | lesson-plans-list-client.tsx | 489 | useAdminLessonPlans | ❌ → MSW 兜底 | +| /shell/admin/lesson-plans/[planId]/view | ✅ | lesson-plan-view-client.tsx | 205 | useAdminLessonPlan / useSoftDeleteLessonPlan | ❌ → MSW 兜底 | + +### 2.14 organization 模块(1 页) + +| 页面路径 | 状态 | client 组件 | 行数 | lib/api hooks | 契约状态 | +| ------------------------- | ---- | ---------------------------- | ---- | ------------------- | ------------- | +| /shell/admin/organization | ✅ | organization-tree-client.tsx | 245 | useOrganizationTree | ❌ → MSW 兜底 | + +### 2.15 permissions 模块(1 页) + +| 页面路径 | 状态 | client 组件 | 行数 | lib/api hooks | 契约状态 | +| ------------------------ | ---- | --------------------------- | ---- | ---------------------------------------- | ------------- | +| /shell/admin/permissions | ✅ | permissions-list-client.tsx | 216 | usePermissions / usePermissionRoleCounts | ❌ → MSW 兜底 | + +### 2.16 plugins 模块(1 页) + +| 页面路径 | 状态 | client 组件 | 行数 | lib/api hooks | 契约状态 | +| -------------------- | ---- | ------------------ | ---- | ------------------------------------------- | ---------------------------------- | +| /shell/admin/plugins | ✅ | plugins-client.tsx | 402 | usePluginRegistry / useUpdatePluginRegistry | ✅ schema 已就绪(config-service) | + +### 2.17 questions 模块(1 页) + +| 页面路径 | 状态 | client 组件 | 行数 | lib/api hooks | 契约状态 | +| ---------------------- | ---- | ------------------------- | ---- | ------------------------------------ | ------------- | +| /shell/admin/questions | ✅ | questions-list-client.tsx | 537 | useAdminQuestions / useAdminQuestion | ❌ → MSW 兜底 | + +### 2.18 roles 模块(2 页) + +| 页面路径 | 状态 | client 组件 | 行数 | lib/api hooks | 契约状态 | +| ----------------------- | ---- | ---------------------- | ---- | --------------------------------------------------------------------------------------------- | ------------- | +| /shell/admin/roles | ✅ | roles-list-client.tsx | 411 | useRoles / useCreateRole / useDeleteRole / useToggleRoleEnabled | ❌ → MSW 兜底 | +| /shell/admin/roles/[id] | ✅ | role-detail-client.tsx | 165 | useRole / useUpdateRole / useDeleteRole / useRolePermissions / useUpdateRolePermissionActions | ❌ → MSW 兜底 | + +### 2.19 scheduling 模块(4 页,含 1 重定向) + +| 页面路径 | 状态 | client 组件 | 行数 | lib/api hooks | 契约状态 | +| ------------------------------- | ---- | ----------------------------------------------------------------------------- | ---- | ------------------------------------------------------------------------------------------------------ | ------------- | +| /shell/admin/scheduling | ↪️ | redirect("/shell/admin/scheduling/changes"):避免空白入口,默认进入变更审批页 | — | — | — | +| /shell/admin/scheduling/auto | ✅ | auto-schedule-client.tsx | 270 | useAutoSchedule | ❌ → MSW 兜底 | +| /shell/admin/scheduling/changes | ✅ | schedule-changes-client.tsx | 471 | useAdminScheduleChanges / useAdminScheduleEntries / useApproveScheduleChange / useRejectScheduleChange | ❌ → MSW 兜底 | +| /shell/admin/scheduling/rules | ✅ | scheduling-rules-client.tsx | 401 | useAdminSchedulingRules / useUpdateSchedulingRules | ❌ → MSW 兜底 | + +### 2.20 school 模块(7 页,含 1 重定向) + +| 页面路径 | 状态 | client 组件 | 行数 | lib/api hooks | 契约状态 | +| ----------------------------------- | ---- | ------------------------------------------------------------------------- | ---- | ----------------------------------------------------------------------------------------------------------------------- | ------------- | +| /shell/admin/school | ↪️ | redirect("/shell/admin/school/schools"):避免空白入口,默认进入学校列表页 | — | — | — | +| /shell/admin/school/academic-year | ✅ | academic-year-client.tsx | 601 | useAcademicYears / useCreateAcademicYear / useUpdateAcademicYear / useDeleteAcademicYear | ❌ → MSW 兜底 | +| /shell/admin/school/classes | ✅ | admin-classes-client.tsx | 598 | useAdminClasses / useTeacherOptions / useStaffOptions / useCreateAdminClass / useUpdateAdminClass / useDeleteAdminClass | ❌ → MSW 兜底 | +| /shell/admin/school/departments | ✅ | departments-client.tsx | 444 | useDepartments / useCreateDepartment / useUpdateDepartment / useDeleteDepartment | ❌ → MSW 兜底 | +| /shell/admin/school/grades | ✅ | grades-client.tsx | 688 | useGrades / useGradeOverviewStats / useAdminCreateGrade / useUpdateGrade / useDeleteGrade | ❌ → MSW 兜底 | +| /shell/admin/school/grades/insights | ✅ | grade-insights-client.tsx | 495 | useGradeOverviewStats 系列 | ❌ → MSW 兜底 | +| /shell/admin/school/schools | ✅ | schools-client.tsx | 509 | useSchools / useCreateSchool / useAdminUpdateSchool / useDeleteSchool | ❌ → MSW 兜底 | + +### 2.21 students 模块(1 页) + +| 页面路径 | 状态 | client 组件 | 行数 | lib/api hooks | 契约状态 | +| --------------------- | ---- | ------------------------ | ---- | ---------------- | ------------- | +| /shell/admin/students | ✅ | students-list-client.tsx | 387 | useAdminStudents | ❌ → MSW 兜底 | + +### 2.22 system 模块(1 页) + +| 页面路径 | 状态 | client 组件 | 行数 | lib/api hooks | 契约状态 | +| ------------------- | ---- | -------------------------- | ---- | ------------------------------------------- | ------------- | +| /shell/admin/system | ✅ | system-settings-client.tsx | 566 | useSystemSettings / useUpdateSystemSettings | ❌ → MSW 兜底 | + +### 2.23 teachers 模块(1 页) + +| 页面路径 | 状态 | client 组件 | 行数 | lib/api hooks | 契约状态 | +| --------------------- | ---- | ------------------------ | ---- | ---------------- | ------------- | +| /shell/admin/teachers | ✅ | teachers-list-client.tsx | 269 | useAdminTeachers | ❌ → MSW 兜底 | + +### 2.24 users 模块(3 页) + +| 页面路径 | 状态 | client 组件 | 行数 | lib/api hooks | 契约状态 | +| ------------------------- | ---- | ----------------------- | ---- | ------------------------------------------------------------------------------------------------------- | ------------- | +| /shell/admin/users | ✅ | users-list-client.tsx | 538 | useUsers / useUpdateUserStatus / useUpdateUserRole / useDeleteUser / useAssignUserRoles / useUpdateUser | ❌ → MSW 兜底 | +| /shell/admin/users/import | ✅ | users-import-client.tsx | 414 | useImportUsers | ❌ → MSW 兜底 | +| /shell/admin/users/[id] | ✅ | user-detail-client.tsx | 404 | useUser / useUpdateUser / useAssignUserRoles | ❌ → MSW 兜底 | + +### 2.25 viewports 模块(1 页) + +| 页面路径 | 状态 | client 组件 | 行数 | lib/api hooks | 契约状态 | +| ---------------------- | ---- | -------------------- | ---- | -------------------------------- | ------------- | +| /shell/admin/viewports | ✅ | viewports-client.tsx | 383 | useViewports / useUpdateViewport | ❌ → MSW 兜底 | --- ## 三、按 ARCH §9.4 模块汇总 -| 模块 | ARCH 规划页数 | portal-shell 已完成 | 缺失 | 契约状态 | -| ---------------- | ---------------------------------------------------------------- | ------------------- | ------------------ | ------------------- | -| dashboard | 1 | 1 | 0 | ✅ `adminDashboard` | -| users | 1(+1 import 子页) | 0 | 1(+1 子页) | ❌ | -| roles | 1 | 0 | 1 | ❌ | -| permissions | 1 | 0 | 1 | ❌ | -| audit-logs | 4(含 overview/login/data-changes) | 0 | 4 | ❌ | -| invitation-codes | 1 | 0 | 1 | ❌ | -| school | 6(schools/classes/departments/academic-year/grades + redirect) | 0 | 6 | ❌ | -| classes | 1(与 school/classes 同源) | 0 | 0(合并到 school) | ❌ | -| students | 1 | 0 | 1 | ❌ | -| teachers | 1 | 0 | 1 | ❌ | -| organization | 1 | 0 | 1 | ❌ | -| announcements | 1 | 0 | 1 | ❌ | -| files | 1 | 0 | 1 | ❌ | -| ai-settings | 1 | 0 | 1 | ❌ | -| system | 1 | 0 | 1 | ❌ | -| viewports | 1 | 0 | 1 | ❌ | -| plugins | 1 | 0 | 1 | ✅ `config-service` | -| **合计** | **~24** | **1** | **~23** | **2 ✅ / ~22 ❌** | +| 模块 | ARCH §9.4 规划页数 | portal-shell 已实现 | 缺失 | 契约状态 | +| ---------------- | --------------------------------- | --------------------- | ----- | ---------------------------- | +| dashboard | 1 | 1 | 0 | ✅ adminDashboard | +| users | 1(+1 import +1 详情) | 3 | 0 | ❌ → MSW 兜底 | +| roles | 1(+1 详情) | 2 | 0 | ❌ → MSW 兜底 | +| permissions | 1 | 1 | 0 | ❌ → MSW 兜底 | +| audit-logs | 4 | 4 | 0 | ❌ → MSW 兜底 | +| invitation-codes | 1 | 1 | 0 | ❌ → MSW 兜底 | +| school | 6(+redirect) | 7(+grades/insights) | 0 | ❌ → MSW 兜底 | +| classes | 1(与 school/classes 同源) | 1(redirect) | 0 | — | +| students | 1 | 1 | 0 | ❌ → MSW 兜底 | +| teachers | 1 | 1 | 0 | ❌ → MSW 兜底 | +| organization | 1 | 1 | 0 | ❌ → MSW 兜底 | +| announcements | 1(+2 子页) | 3 | 0 | ❌ → MSW 兜底 | +| files | 1 | 1 | 0 | ❌ → MSW 兜底 | +| ai-settings | 1 | 1 | 0 | ❌ → MSW 兜底 | +| system | 1 | 1 | 0 | ❌ → MSW 兜底 | +| viewports | 1 | 1 | 0 | ❌ → MSW 兜底 | +| plugins | 1 | 1 | 0 | ✅ config-service | +| course-plans | §四补充(4 页) | 4 | 0 | ❌ → MSW 兜底 | +| curriculum-map | §四补充(1 页) | 1 | 0 | ❌ → MSW 兜底 | +| elective | §四补充(4 页) | 4 | 0 | ❌ → MSW 兜底 | +| questions | §四补充(1 页) | 1 | 0 | ❌ → MSW 兜底 | +| lesson-plans | §四补充(2 页) | 2 | 0 | ❌ → MSW 兜底 | +| error-book | §四补充(1 页) | 1 | 0 | ❌ → MSW 兜底 | +| scheduling | §四补充(3+redirect) | 4 | 0 | ❌ → MSW 兜底 | +| attendance | §四补充(1 页) | 1 | 0 | ❌ → MSW 兜底 | +| **合计** | **~24(顶层)+ ~25(子页/补充)** | **49** | **0** | **2 ✅ / 47 ❌(MSW 兜底)** | -> **批次**:全部 B5;契约就绪优先级:`plugins` ✅ > `dashboard` ✅(已完成)> 其余 ❌ 需 MSW 兜底先行。 +> **批次**:全部 B5(含 §四补充批次,已全部落地);契约就绪:plugins ✅ + dashboard ✅;其余 47 页 schema 未就绪,前端用 MSW 兜底先行(@contract-pending 标注)。 --- -## 四、CICD 中存在但 ARCH §9.4 未规划的管理员页面(21 页,B5 后期候选) +## 四、CICD 中存在但 ARCH §9.4 未规划的管理员页面(已全部实现) -> 这些页面在 CICD 中属于"管理员视角的跨班聚合/全局只读"功能,是教师域功能在管理员层级的复用。ARCH §9.4 未将其纳入 24 页规划,但作为完整管理员工作台仍具迁移价值。建议作为 B5 末或 B6 补充批次候选,按需取用。 +> 这些页面在 CICD 中属于"管理员视角的跨班聚合/全局只读"功能,是教师域功能在管理员层级的复用。ARCH §9.4 未将其纳入 24 页顶层规划,但 portal-shell 已全部实现(含详情/编辑/创建子页),共 25 页。 -### 4.1 course-plans 模块(4 页) +### 4.1 course-plans 模块(4 页,已实现) -| CICD 路径 | 功能 | 技术栈 | -| --------------------------------------- | ---------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `admin/course-plans/page.tsx` | 课程计划列表(admin 全局视角,`scope.isAdmin=true`) | Server Component + `requirePermission(Permissions.COURSE_PLAN_READ)` + `getCoursePlans({ status }, { isAdmin: true })` | -| `admin/course-plans/create/page.tsx` | 创建课程计划 | Server Component + `requirePermission(Permissions.COURSE_PLAN_MANAGE)` + 并行查询 classes/subjects/teachers/academicYears + `CoursePlanForm mode="create"` | -| `admin/course-plans/[id]/page.tsx` | 课程计划详情 | Server Component + `getCoursePlanById(id, { userId, isAdmin: true })` + `CoursePlanDetail` | -| `admin/course-plans/[id]/edit/page.tsx` | 编辑课程计划 | Server Component + `requirePermission(Permissions.COURSE_PLAN_MANAGE)` + `CoursePlanForm mode="edit"` | +| portal-shell 路径 | 状态 | client 组件 | 行数 | +| ----------------------------------- | ---- | ----------------------------- | ---- | +| /shell/admin/course-plans | ✅ | course-plans-list-client.tsx | 339 | +| /shell/admin/course-plans/create | ✅ | course-plan-create-client.tsx | 415 | +| /shell/admin/course-plans/[id] | ✅ | course-plan-detail-client.tsx | 644 | +| /shell/admin/course-plans/[id]/edit | ✅ | course-plan-edit-client.tsx | 562 | -### 4.2 curriculum-map 模块(1 页) +### 4.2 curriculum-map 模块(1 页,已实现) -| CICD 路径 | 功能 | 技术栈 | -| ------------------------------- | --------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `admin/curriculum-map/page.tsx` | 课程地图(标准覆盖热图 + 5 张统计卡:教师/教案/已发布/已提交/已关联标准数) | Server Component + `requirePermission(Permissions.LESSON_PLAN_READ)` + `getStandardsCoverageHeatmapAction()` + `getGlobalLessonPlanStatsAction()` + `CurriculumMapView` | +| portal-shell 路径 | 状态 | client 组件 | 行数 | +| --------------------------- | ---- | ------------------------- | ---- | +| /shell/admin/curriculum-map | ✅ | curriculum-map-client.tsx | 256 | -### 4.3 elective 模块(4 页) +### 4.3 elective 模块(4 页,已实现) -| CICD 路径 | 功能 | 技术栈 | -| ----------------------------------- | ---------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | -| `admin/elective/page.tsx` | 选修课列表(`StatsCardsLoader` + `CourseListLoader` 双 Suspense 流式) | Server Component + `requirePermission(Permissions.ELECTIVE_READ)` + `ElectivePageLayout` | -| `admin/elective/create/page.tsx` | 创建选修课 | Server Component + `getSubjectOptions()/getGrades()/getStaffOptions()` + `ElectiveCourseForm mode="create"` | -| `admin/elective/[id]/page.tsx` | 选修课详情(含 `getCourseSelections` 选课记录) | Server Component + `requirePermission(Permissions.ELECTIVE_READ)` + `ElectiveCourseDetail` | -| `admin/elective/[id]/edit/page.tsx` | 编辑选修课 | Server Component + `ElectiveCourseForm mode="edit"` | +| portal-shell 路径 | 状态 | client 组件 | 行数 | +| ------------------------------- | ---- | -------------------------- | ---- | +| /shell/admin/elective | ✅ | elective-list-client.tsx | 545 | +| /shell/admin/elective/create | ✅ | elective-create-client.tsx | 381 | +| /shell/admin/elective/[id] | ✅ | elective-detail-client.tsx | 385 | +| /shell/admin/elective/[id]/edit | ✅ | elective-edit-client.tsx | 464 | -### 4.4 questions 模块(1 页) +### 4.4 questions 模块(1 页,已实现) -| CICD 路径 | 功能 | 技术栈 | -| -------------------------- | ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `admin/questions/page.tsx` | 题库管理(筛选 + 导入导出 + 创建) | Server Component + `requirePermission(Permissions.QUESTION_READ)` + `getQuestions({...})` + `QuestionFilters`(Suspense) + `QuestionBankResultsClient` + `ImportExportButtons` + `CreateQuestionButton` | +| portal-shell 路径 | 状态 | client 组件 | 行数 | +| ---------------------- | ---- | ------------------------- | ---- | +| /shell/admin/questions | ✅ | questions-list-client.tsx | 537 | -### 4.5 lesson-plans 模块(2 页) +### 4.5 lesson-plans 模块(2 页,已实现) -| CICD 路径 | 功能 | 技术栈 | -| ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- | -| `admin/lesson-plans/page.tsx` | 教案列表(4 统计卡:总数/已发布/草稿/已归档 + `LessonPlanList viewMode="admin"` + `LessonPlanProviderSetup` 注入 ADMIN_ROLE_CONFIG) | Server Component + `requirePermission(Permissions.LESSON_PLAN_READ)` + `getLessonPlans({}, ctx.dataScope, ctx.userId)` + `getLessonPlanStats()` | -| `admin/lesson-plans/[planId]/view/page.tsx` | 教案只读视图(含 textbook/chapter 标题解析) | Server Component + `getLessonPlanById(planId, ctx.userId)` + `getTextbookById/getChaptersByTextbookId` 并行 + `LessonPlanReadonlyView` | +| portal-shell 路径 | 状态 | client 组件 | 行数 | +| --------------------------------------- | ---- | ---------------------------- | ---- | +| /shell/admin/lesson-plans | ✅ | lesson-plans-list-client.tsx | 489 | +| /shell/admin/lesson-plans/[planId]/view | ✅ | lesson-plan-view-client.tsx | 205 | -### 4.6 error-book 模块(1 页) +### 4.6 error-book 模块(1 页,已实现) -| CICD 路径 | 功能 | 技术栈 | -| --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `admin/error-book/page.tsx` | 错题本分析(5 统计卡 + 学科 Tab + 学科分布图 + 章节/知识点薄弱度并排 + Top 50 学生 + Top 10 高频错题;限 500 学生 + DataScope 校验 `type === "all"`) | Server Component + `requirePermission(Permissions.ERROR_BOOK_ANALYTICS_READ)` + 多 data-access 并行 + `SubjectTabs/StatsGrid/SubjectDistributionChart/ChapterWeaknessChart/KnowledgePointWeaknessChart/GroupedStudentErrorTable/TopWrongQuestions` + `WidgetBoundary` | +| portal-shell 路径 | 状态 | client 组件 | 行数 | +| ----------------------- | ---- | --------------------- | ---- | +| /shell/admin/error-book | ✅ | error-book-client.tsx | 738 | -### 4.7 scheduling 模块(3 页) +### 4.7 scheduling 模块(3 页 + redirect,已实现) -| CICD 路径 | 功能 | 技术栈 | -| ----------------------------------- | ----------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `admin/scheduling/auto/page.tsx` | 自动排课(`AutoSchedulePanel` + 跳转 rules 配置入口) | Server Component + `requirePermission(Permissions.SCHEDULE_AUTO)` + `getAdminClassesForScheduling()` | -| `admin/scheduling/changes/page.tsx` | 排课变更审批(变更列表 + 冲突检测 + 课表网格) | Server Component + `requirePermission(Permissions.SCHEDULE_ADJUST)` + 并行 `getAdminClassesForScheduling/getScheduleChanges/getScheduleEntriesForAdmin` + `ScheduleChangeList canApprove` + `ScheduleConflictsView` + `ScheduleGridView` | -| `admin/scheduling/rules/page.tsx` | 排课规则配置 | Server Component + `requirePermission(Permissions.SCHEDULE_ADJUST)` + `getSchedulingRules()` + `SchedulingRulesForm` | +| portal-shell 路径 | 状态 | client 组件 | 行数 | +| ------------------------------- | ---- | ------------------------------ | ---- | +| /shell/admin/scheduling | ↪️ | redirect → /scheduling/changes | — | +| /shell/admin/scheduling/auto | ✅ | auto-schedule-client.tsx | 270 | +| /shell/admin/scheduling/changes | ✅ | schedule-changes-client.tsx | 471 | +| /shell/admin/scheduling/rules | ✅ | scheduling-rules-client.tsx | 401 | -### 4.8 attendance 模块(1 页) +### 4.8 attendance 模块(1 页,已实现) -| CICD 路径 | 功能 | 技术栈 | -| --------------------------- | ---------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `admin/attendance/page.tsx` | 考勤管理(7 统计卡 + 班级对比卡 + 考勤-成绩关联分析卡 + 筛选器) | Server Component + `requirePermission(Permissions.ATTENDANCE_READ)` + 并行 `getAttendanceRecords/getAttendanceStats/getGrades/getClassComparison/getAttendanceGradeCorrelation` + `AdminAttendanceFilters`(含 class/status/date 筛选) | +| portal-shell 路径 | 状态 | client 组件 | 行数 | +| ----------------------- | ---- | --------------------------- | ---- | +| /shell/admin/attendance | ✅ | admin-attendance-client.tsx | 340 | -> **附注**:CICD `admin/attendance/admin-attendance-filters.tsx` 是页内私有 Client Component,封装 `FilterBar + Select + Input`,使用 `ATTENDANCE_STATUS_OPTIONS` 常量。迁移时此类"页内 filters 组件"模式应保持。 +### 4.9 announcements 详情/编辑(2 页,已实现) -### 4.9 announcements 详情/编辑(2 页) +| portal-shell 路径 | 状态 | client 组件 | 行数 | +| ------------------------------------ | ---- | ------------------------------ | ---- | +| /shell/admin/announcements/[id] | ✅ | announcement-detail-client.tsx | 289 | +| /shell/admin/announcements/[id]/edit | ✅ | announcement-edit-client.tsx | 266 | -| CICD 路径 | 功能 | 技术栈 | -| ---------------------------------------- | ------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------- | -| `admin/announcements/[id]/page.tsx` | 公告详情(`AnnouncementDetail canManage` + 跳转 edit + 发布/归档/删除/置顶操作入口) | Server Component + `requirePermission(Permissions.ANNOUNCEMENT_MANAGE)` + `getAdminAnnouncementDetailPageData(id)` + `AnnouncementsServiceProvider` | -| `admin/announcements/[id]/edit/page.tsx` | 公告编辑(`AnnouncementForm mode="edit"` + grades 关联) | Server Component + `requirePermission(Permissions.ANNOUNCEMENT_MANAGE)` + `getEditAnnouncementPageData(id)` | +### 4.10 users/import + users/[id] 子页(2 页,已实现) -### 4.10 users/import 子页(1 页) +| portal-shell 路径 | 状态 | client 组件 | 行数 | +| ------------------------- | ---- | ----------------------- | ---- | +| /shell/admin/users/import | ✅ | users-import-client.tsx | 414 | +| /shell/admin/users/[id] | ✅ | user-detail-client.tsx | 404 | -已在 §2.2.2 列入 ARCH 规划口径(与 `users` 工单合并),此处不重复。 +### 4.11 roles/[id] 详情(1 页,已实现) -### 4.11 roles/[id] 详情(1 页) +| portal-shell 路径 | 状态 | client 组件 | 行数 | +| ----------------------- | ---- | ---------------------- | ---- | +| /shell/admin/roles/[id] | ✅ | role-detail-client.tsx | 165 | -| CICD 路径 | 功能 | 技术栈 | -| --------------------------- | ------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | -| `admin/roles/[id]/page.tsx` | 角色详情(`RolePermissionMatrix` 权限矩阵 + `RoleDetailEditButton` + admin 角色锁定提示卡) | Server Component + `requirePermission(Permissions.ROLE_READ)` + `getRoleById(id)` + `notFound()` 兜底 + admin 角色强制锁定(isLocked) | +### 4.12 school/grades/insights(1 页,已实现) -### 4.12 school/grades/insights(1 页) - -已在 §2.6 附注中提及,不重复。 +| portal-shell 路径 | 状态 | client 组件 | 行数 | +| ----------------------------------- | ---- | ------------------------- | ---- | +| /shell/admin/school/grades/insights | ✅ | grade-insights-client.tsx | 495 | --- ## 五、迁移注意事项 -### 5.1 架构迁移要点 +### 5.1 架构迁移要点(已完成) -1. **管理员权限校验**:所有 23 缺失页面均需 `requirePermission(Permissions.XXX)` 在 Server Component 入口校验。CICD 模式可直接复用,但需对齐 portal-shell 的 auth-guard(004 §5 鉴权链路)。 -2. **DataScope 上下文**:`school/grades/insights`、`error-book`、`attendance` 等页面依赖 `ctx.dataScope`(all / class_taught / class_members / children),portal-shell 需在 BFF 层补齐数据范围上下文。 -3. **Server Action + router.refresh 模式**:`invitation-codes`、`announcements/[id]/edit` 等使用此模式,portal-shell 需保持等价机制(BFF API + 客户端 refetch)。 -4. **导出功能**:`audit-logs` 三页 + `questions` 导入导出按钮,需后端提供 CSV 导出端点。 -5. **服务注入**:`audit-logs/overview` 使用 `AuditServiceProvider + adminAuditService` 模式,portal-shell 应在 BFF/Service 层保留服务抽象。 +1. **管理员权限校验**:portal-shell 采用 Client Component + BFF 模式,权限校验由 route-permissions.ts 在路由层 EXACT/PREFIX 表登记(如 /shell/admin/* → ["admin"]),与 CICD 的 Server Component + requirePermission 模式不同但等价。 +2. **DataScope 上下文**:portal-shell 在 BFF 层注入数据范围上下文,client 组件通过 lib/api hooks 消费,无需 page.tsx 显式传递。 +3. **Server Action + router.refresh 模式**:portal-shell 改用 Apollo Client mutation + useQuery refetch / notify 通知,等价于 CICD 的 Server Action + router.refresh()。 +4. **导出功能**:audit-logs 三页 + error-book 的 CSV 导出,已通过 useExportAuditLogs / useExportLoginLogs / useExportDataChanges / useExportErrorBookCsv hooks 封装,等待后端契约就绪后接真实端点。 +5. **服务注入**:audit-logs/overview 在 portal-shell 中改为 client 组件直接调用 useAuditOverviewStats / useAuditTrend / useDataChangeActionStats 三个 hook 并行查询,等价于 CICD 的 AuditServiceProvider + adminAuditService 模式。 -### 5.2 模块复杂度风险 +### 5.2 模块复杂度与契约风险 -| 模块 | 风险点 | 缓解建议 | -| ----------------------------------- | ------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | -| **audit-logs**(4 页) | 审计核心,多维筛选 + 导出 + 概览统计 + 数据变更统计 | 后端契约工单优先;前端可 MSW 先行 4 页 UI | -| **school**(6 页) | 体系复杂,6 子页 + 跨表关联(schools/classes/departments/grades/academic-year/staff) | 按 schools → grades → classes → departments → academic-year 顺序迁移;insights 作 B5 末 | -| **scheduling**(3 页,§四补充批次) | 涉及自动排课算法 + 冲突检测 + 审批流 | 算法侧需独立服务/工单;前端先做 changes 审批流,auto/rules 后置 | -| **error-book**(§四补充批次) | 5 统计卡 + 4 图表 + Top 50 学生 + Top 10 错题;限 500 学生性能保护 | 复用教师域 error-book 组件,仅换 data-access 为 admin scope;图表组件应已存在 | -| **attendance**(§四补充批次) | 7 统计 + 班级对比 + 考勤-成绩关联分析 | 后端需提供 `getClassComparison` + `getAttendanceGradeCorrelation` 两个分析端点 | -| **curriculum-map**(§四补充批次) | 标准覆盖热图 + 5 张统计卡 + `getStandardsCoverageHeatmapAction` Server Action | 依赖 lesson-preparation 模块的 actions-analytics,需先迁移教师域 lesson-plans | -| **ai-settings** | 双权限(AI_CHAT + AI_CONFIGURE)+ Provider 配置 + Usage 仪表盘 | 后端需提供 provider CRUD + usage 聚合端点 | +| 模块 | 风险点 | 当前缓解 | 待办 | +| ---------------------- | --------------------------------------------------- | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | +| audit-logs(4 页) | 审计核心,多维筛选 + 导出 + 概览统计 + 数据变更统计 | 前端 4 页 UI 已完成(共 1644 行 client 代码),MSW 兜底 | 后端契约工单:auditLogs / loginLogs / dataChangeLogs / auditOverviewStats / 导出端点 | +| school(7 页) | 体系复杂,6 业务子页 + 1 redirect,跨表关联 | 前端 7 页 UI 已完成(共 3335 行 client 代码),MSW 兜底 | 后端契约工单:schools / departments / academicYears / grades / adminClasses / gradeOverviewStats | +| scheduling(4 页) | 自动排课算法 + 冲突检测 + 审批流 | 前端 4 页 UI 已完成(共 1142 行 client 代码),MSW 兜底 | 后端契约工单:autoSchedule / scheduleChanges / scheduleEntries / schedulingRules + 排课算法服务 | +| error-book(1 页) | 5 统计卡 + 4 图表 + Top 50 学生 + Top 10 错题 | 前端 1 页 UI 已完成(738 行 client 代码,最复杂单页),MSW 兜底 | 后端契约工单:adminErrorBookStats + CSV 导出端点 | +| attendance(1 页) | 7 统计 + 班级对比 + 考勤-成绩关联分析 | 前端 1 页 UI 已完成(340 行 client 代码),MSW 兜底 | 后端契约工单:adminAttendanceStats / adminAttendanceRecords / attendanceGradeCorrelation | +| curriculum-map(1 页) | 标准覆盖热图 + 5 张统计卡 | 前端 1 页 UI 已完成(256 行 client 代码),MSW 兜底 | 后端契约工单:standardsCoverageHeatmap / globalLessonPlanStats | +| ai-settings(1 页) | 双权限 + Provider 配置 + Usage 仪表盘 | 前端 1 页 UI 已完成(522 行 client 代码),MSW 兜底 | 后端契约工单:aiProviders / aiUsageDashboard + Provider CRUD 端点 | +| users(3 页) | 用户列表 + 批量导入 + 详情,关联角色分配 | 前端 3 页 UI 已完成(共 1356 行 client 代码),MSW 兜底 | 后端契约工单:users 列表 + importUsers mutation | +| course-plans(4 页) | 课程计划 CRUD + 子项 reorder + 批量切换 | 前端 4 页 UI 已完成(共 1960 行 client 代码),MSW 兜底 | 后端契约工单:adminCoursePlans / adminCoursePlan + 子项 CRUD mutations | +| elective(4 页) | 选修课 CRUD + 抽签 + 选课开关 | 前端 4 页 UI 已完成(共 1775 行 client 代码),MSW 兜底 | 后端契约工单:adminElectives / adminElective + 抽签 mutation | -### 5.3 契约就绪优先级建议 +### 5.3 契约就绪优先级(用于后端工单排期) -按 ARCH §9.4 节奏原则(契约就绪页先行,❌ 页用 MSW 先上 UI): +前端 49 页已全部落地,瓶颈在后端 GraphQL schema 就绪。按业务价值与依赖关系排期: -1. **优先级 1(契约 ✅,可立即实现真实查询)**: - - `/shell/admin/plugins`(✅ `config-service`) - - `/shell/admin`(已完成) -2. **优先级 2(高价值 + 中等复杂度,需后端契约工单)**: - - `/shell/admin/users` + `/shell/admin/users/import`(用户管理 + 批量导入) - - `/shell/admin/roles` + `/shell/admin/permissions`(RBAC 核心) - - `/shell/admin/invitation-codes`(与 users 共享 USER_MANAGE 权限) - - `/shell/admin/announcements`(公告管理) - - `/shell/admin/files`(文件管理) -3. **优先级 3(高复杂度,建议 MSW 先行)**: - - `/shell/admin/audit-logs/*`(4 页,审计核心,多维筛选 + 导出) - - `/shell/admin/school/*`(6 页,体系复杂) -4. **优先级 4(ARCH 新建,需独立设计)**: - - `/shell/admin/students`、`/shell/admin/teachers`、`/shell/admin/organization`、`/shell/admin/viewports`、`/shell/admin/system` -5. **优先级 5(§四补充批次,B5 末或 B6)**: - - course-plans/curriculum-map/elective/questions/lesson-plans/error-book/scheduling/attendance 共 16 页 +1. **优先级 1(契约 ✅,已完成真实查询)**:/shell/admin(adminDashboard ✅)+ /shell/admin/plugins(config-service ✅) +2. **优先级 2(高价值 RBAC 核心,需后端契约工单)**:users(3 页)+ roles(2 页)+ permissions(1 页)+ invitation-codes(1 页)= 7 页;后端契约:users / roles / permissions / permissionRoleCounts / invitationCodes + 相关 mutations +3. **优先级 3(高频运营功能,需后端契约工单)**:announcements(3 页)+ files(1 页)+ ai-settings(1 页)+ system(1 页)= 6 页;后端契约:adminAnnouncements / fileAttachments / aiProviders / aiUsageDashboard / systemSettings +4. **优先级 4(审计与组织管理,高复杂度)**:audit-logs(4 页)+ school(7 页)+ organization(1 页)+ students(1 页)+ teachers(1 页)+ viewports(1 页)= 15 页;后端契约:审计 4 类查询 + 导出端点;school 6 类实体 CRUD;organizationTree / adminStudents / adminTeachers / viewports +5. **优先级 5(§四补充批次,教师域聚合视图)**:course-plans(4 页)+ curriculum-map(1 页)+ elective(4 页)+ questions(1 页)+ lesson-plans(2 页)+ error-book(1 页)+ scheduling(4 页)+ attendance(1 页)= 18 页;后端契约:均为教师域聚合查询在 admin scope 下的复用 ### 5.4 CICD admin/layout.tsx 迁移说明 -CICD `admin/layout.tsx` 仅 `return <>{children}`,**无独立侧边栏/权限逻辑**(侧边栏由 `(dashboard)/layout.tsx` 统一处理,权限由各 page 调 `requirePermission`)。 +CICD admin/layout.tsx 仅 return <>{children},**无独立侧边栏/权限逻辑**(侧边栏由 (dashboard)/layout.tsx 统一处理,权限由各 page 调 requirePermission)。 -portal-shell 同样无需在 `shell/admin/layout.tsx` 实现额外逻辑,保持 passthrough 即可;侧边栏与权限校验由 `shell/layout.tsx` 与各 page.tsx 分担。 +portal-shell 同样无需在 shell/admin/layout.tsx 实现额外逻辑,保持 passthrough 即可;侧边栏与权限校验由 shell/layout.tsx 与 route-permissions.ts 分担。 + +### 5.5 三层支撑完整度核查 + +| 支撑层 | 文件 | 行数 | 覆盖情况 | +| --------------- | ---------------------------------- | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| page.tsx 入口 | src/app/shell/admin/**/page.tsx | 49 文件 | 49/49 全部存在,46 业务页 + 3 redirect 页 | +| client 业务组件 | src/features/admin/**/*-client.tsx | 44 文件 | 44/44 全部实现,165-738 行/文件,均调用 lib/api hooks,处理三态 | +| lib/api hooks | src/lib/api/admin.ts + admin-p5.ts | 973 + 3371 = 4344 行 | 全覆盖:users/roles/permissions/audit-logs/invitation-codes/school/announcements/files/ai-settings/system/viewports/students/teachers/organization/course-plans/curriculum-map/elective/questions/lesson-plans/error-book/scheduling/attendance/plugins | +| mocks 兜底数据 | src/mocks/graphql-data.ts | 9867 行 | 全覆盖:mockAdminDashboard / mockUsers / mockAdminPermissions / mockSchools / mockDepartments / mockAcademicYears / mockAdminGrades / mockAdminClasses / mockAdminAnnouncements / mockInvitationCodes / mockFileAttachments / mockAiProviders / mockSystemSettings / mockViewports / mockAdminStudents / mockAdminTeachers / mockOrganizationTree / mockAdminCoursePlans / mockAdminLessonPlans / mockAdminElectives / mockAdminQuestions / mockAdminScheduleChanges / mockSchedulingRules / mockAdminAttendanceRecords / mockLoginLogs / mockDataChangeLogs 等 | --- -## 六、附录:CICD admin/page.tsx 完整清单(41 个) +## 六、附录:CICD admin/page.tsx 完整清单(41 个)+ ARCH 新建(5 个)= 46 项对照 -| # | CICD 路径 | ARCH §9.4 归属 | 缺失/已有 | -| --- | ------------------------------------------- | ----------------------------- | ------------------------------------------- | -| 1 | `admin/dashboard/page.tsx` | §9.4 第 1 行 ✅ | portal-shell `/shell/admin/page.tsx` 已完成 | -| 2 | `admin/users/page.tsx` | §9.4 第 2 行 ❌ | 缺失 | -| 3 | `admin/users/import/page.tsx` | §9.4 第 2 行 ❌(合并) | 缺失 | -| 4 | `admin/roles/page.tsx` | §9.4 第 3 行 ❌ | 缺失 | -| 5 | `admin/roles/[id]/page.tsx` | §四补充批次 | 缺失(非 ARCH §9.4 规划) | -| 6 | `admin/permissions/page.tsx` | §9.4 第 3 行 ❌ | 缺失 | -| 7 | `admin/audit-logs/page.tsx` | §9.4 第 4 行 ❌ | 缺失 | -| 8 | `admin/audit-logs/overview/page.tsx` | §9.4 第 4 行 ❌ | 缺失 | -| 9 | `admin/audit-logs/login-logs/page.tsx` | §9.4 第 4 行 ❌ | 缺失 | -| 10 | `admin/audit-logs/data-changes/page.tsx` | §9.4 第 4 行 ❌ | 缺失 | -| 11 | `admin/invitation-codes/page.tsx` | §9.4 第 5 行 ❌ | 缺失 | -| 12 | `admin/school/page.tsx`(redirect) | §9.4 第 6 行 ❌ | 缺失 | -| 13 | `admin/school/schools/page.tsx` | §9.4 第 6 行 ❌ | 缺失 | -| 14 | `admin/school/classes/page.tsx` | §9.4 第 6/7 行 ❌ | 缺失 | -| 15 | `admin/school/departments/page.tsx` | §9.4 第 6 行 ❌ | 缺失 | -| 16 | `admin/school/academic-year/page.tsx` | §9.4 第 6 行 ❌ | 缺失 | -| 17 | `admin/school/grades/page.tsx` | §9.4 第 6 行 ❌ | 缺失 | -| 18 | `admin/school/grades/insights/page.tsx` | §四补充批次 | 缺失(非 ARCH §9.4 规划) | -| 19 | `admin/announcements/page.tsx` | §9.4 第 10 行 ❌ | 缺失 | -| 20 | `admin/announcements/[id]/page.tsx` | §四补充批次 | 缺失(非 ARCH §9.4 规划) | -| 21 | `admin/announcements/[id]/edit/page.tsx` | §四补充批次 | 缺失(非 ARCH §9.4 规划) | -| 22 | `admin/files/page.tsx` | §9.4 第 11 行 ❌ | 缺失 | -| 23 | `admin/ai-settings/page.tsx` | §9.4 第 12 行 ❌ | 缺失 | -| 24 | `admin/settings/page.tsx` | §9.4 第 13 行 ❌(→ /system) | 缺失 | -| 25 | `admin/course-plans/page.tsx` | §四补充批次 | 缺失(非 ARCH §9.4 规划) | -| 26 | `admin/course-plans/create/page.tsx` | §四补充批次 | 缺失(非 ARCH §9.4 规划) | -| 27 | `admin/course-plans/[id]/page.tsx` | §四补充批次 | 缺失(非 ARCH §9.4 规划) | -| 28 | `admin/course-plans/[id]/edit/page.tsx` | §四补充批次 | 缺失(非 ARCH §9.4 规划) | -| 29 | `admin/curriculum-map/page.tsx` | §四补充批次 | 缺失(非 ARCH §9.4 规划) | -| 30 | `admin/elective/page.tsx` | §四补充批次 | 缺失(非 ARCH §9.4 规划) | -| 31 | `admin/elective/create/page.tsx` | §四补充批次 | 缺失(非 ARCH §9.4 规划) | -| 32 | `admin/elective/[id]/page.tsx` | §四补充批次 | 缺失(非 ARCH §9.4 规划) | -| 33 | `admin/elective/[id]/edit/page.tsx` | §四补充批次 | 缺失(非 ARCH §9.4 规划) | -| 34 | `admin/questions/page.tsx` | §四补充批次 | 缺失(非 ARCH §9.4 规划) | -| 35 | `admin/lesson-plans/page.tsx` | §四补充批次 | 缺失(非 ARCH §9.4 规划) | -| 36 | `admin/lesson-plans/[planId]/view/page.tsx` | §四补充批次 | 缺失(非 ARCH §9.4 规划) | -| 37 | `admin/error-book/page.tsx` | §四补充批次 | 缺失(非 ARCH §9.4 规划) | -| 38 | `admin/scheduling/auto/page.tsx` | §四补充批次 | 缺失(非 ARCH §9.4 规划) | -| 39 | `admin/scheduling/changes/page.tsx` | §四补充批次 | 缺失(非 ARCH §9.4 规划) | -| 40 | `admin/scheduling/rules/page.tsx` | §四补充批次 | 缺失(非 ARCH §9.4 规划) | -| 41 | `admin/attendance/page.tsx` | §四补充批次 | 缺失(非 ARCH §9.4 规划) | +| # | CICD 路径 | ARCH §9.4 归属 | portal-shell 状态(2026-08-04) | +| --- | ----------------------------------------- | ----------------------------- | --------------------------------------------------------------- | +| 1 | admin/dashboard/page.tsx | §9.4 第 1 行 ✅ | ✅ /shell/admin/page.tsx(真实契约 adminDashboard) | +| 2 | admin/users/page.tsx | §9.4 第 2 行 ❌ | ✅ /shell/admin/users/page.tsx(MSW 兜底) | +| 3 | admin/users/import/page.tsx | §9.4 第 2 行 ❌(合并) | ✅ /shell/admin/users/import/page.tsx(MSW 兜底) | +| 4 | admin/roles/page.tsx | §9.4 第 3 行 ❌ | ✅ /shell/admin/roles/page.tsx(MSW 兜底) | +| 5 | admin/roles/[id]/page.tsx | §四补充批次 | ✅ /shell/admin/roles/[id]/page.tsx(MSW 兜底) | +| 6 | admin/permissions/page.tsx | §9.4 第 3 行 ❌ | ✅ /shell/admin/permissions/page.tsx(MSW 兜底) | +| 7 | admin/audit-logs/page.tsx | §9.4 第 4 行 ❌ | ✅ /shell/admin/audit-logs/page.tsx(MSW 兜底) | +| 8 | admin/audit-logs/overview/page.tsx | §9.4 第 4 行 ❌ | ✅ /shell/admin/audit-logs/overview/page.tsx(MSW 兜底) | +| 9 | admin/audit-logs/login-logs/page.tsx | §9.4 第 4 行 ❌ | ✅ /shell/admin/audit-logs/login-logs/page.tsx(MSW 兜底) | +| 10 | admin/audit-logs/data-changes/page.tsx | §9.4 第 4 行 ❌ | ✅ /shell/admin/audit-logs/data-changes/page.tsx(MSW 兜底) | +| 11 | admin/invitation-codes/page.tsx | §9.4 第 5 行 ❌ | ✅ /shell/admin/invitation-codes/page.tsx(MSW 兜底) | +| 12 | admin/school/page.tsx(redirect) | §9.4 第 6 行 ❌ | ↪️ /shell/admin/school/page.tsx → /school/schools | +| 13 | admin/school/schools/page.tsx | §9.4 第 6 行 ❌ | ✅ /shell/admin/school/schools/page.tsx(MSW 兜底) | +| 14 | admin/school/classes/page.tsx | §9.4 第 6/7 行 ❌ | ✅ /shell/admin/school/classes/page.tsx(MSW 兜底) | +| 15 | admin/school/departments/page.tsx | §9.4 第 6 行 ❌ | ✅ /shell/admin/school/departments/page.tsx(MSW 兜底) | +| 16 | admin/school/academic-year/page.tsx | §9.4 第 6 行 ❌ | ✅ /shell/admin/school/academic-year/page.tsx(MSW 兜底) | +| 17 | admin/school/grades/page.tsx | §9.4 第 6 行 ❌ | ✅ /shell/admin/school/grades/page.tsx(MSW 兜底) | +| 18 | admin/school/grades/insights/page.tsx | §四补充批次 | ✅ /shell/admin/school/grades/insights/page.tsx(MSW 兜底) | +| 19 | admin/announcements/page.tsx | §9.4 第 10 行 ❌ | ✅ /shell/admin/announcements/page.tsx(MSW 兜底) | +| 20 | admin/announcements/[id]/page.tsx | §四补充批次 | ✅ /shell/admin/announcements/[id]/page.tsx(MSW 兜底) | +| 21 | admin/announcements/[id]/edit/page.tsx | §四补充批次 | ✅ /shell/admin/announcements/[id]/edit/page.tsx(MSW 兜底) | +| 22 | admin/files/page.tsx | §9.4 第 11 行 ❌ | ✅ /shell/admin/files/page.tsx(MSW 兜底) | +| 23 | admin/ai-settings/page.tsx | §9.4 第 12 行 ❌ | ✅ /shell/admin/ai-settings/page.tsx(MSW 兜底) | +| 24 | admin/settings/page.tsx | §9.4 第 13 行 ❌(→ /system) | ✅ /shell/admin/system/page.tsx(MSW 兜底) | +| 25 | admin/course-plans/page.tsx | §四补充批次 | ✅ /shell/admin/course-plans/page.tsx(MSW 兜底) | +| 26 | admin/course-plans/create/page.tsx | §四补充批次 | ✅ /shell/admin/course-plans/create/page.tsx(MSW 兜底) | +| 27 | admin/course-plans/[id]/page.tsx | §四补充批次 | ✅ /shell/admin/course-plans/[id]/page.tsx(MSW 兜底) | +| 28 | admin/course-plans/[id]/edit/page.tsx | §四补充批次 | ✅ /shell/admin/course-plans/[id]/edit/page.tsx(MSW 兜底) | +| 29 | admin/curriculum-map/page.tsx | §四补充批次 | ✅ /shell/admin/curriculum-map/page.tsx(MSW 兜底) | +| 30 | admin/elective/page.tsx | §四补充批次 | ✅ /shell/admin/elective/page.tsx(MSW 兜底) | +| 31 | admin/elective/create/page.tsx | §四补充批次 | ✅ /shell/admin/elective/create/page.tsx(MSW 兜底) | +| 32 | admin/elective/[id]/page.tsx | §四补充批次 | ✅ /shell/admin/elective/[id]/page.tsx(MSW 兜底) | +| 33 | admin/elective/[id]/edit/page.tsx | §四补充批次 | ✅ /shell/admin/elective/[id]/edit/page.tsx(MSW 兜底) | +| 34 | admin/questions/page.tsx | §四补充批次 | ✅ /shell/admin/questions/page.tsx(MSW 兜底) | +| 35 | admin/lesson-plans/page.tsx | §四补充批次 | ✅ /shell/admin/lesson-plans/page.tsx(MSW 兜底) | +| 36 | admin/lesson-plans/[planId]/view/page.tsx | §四补充批次 | ✅ /shell/admin/lesson-plans/[planId]/view/page.tsx(MSW 兜底) | +| 37 | admin/error-book/page.tsx | §四补充批次 | ✅ /shell/admin/error-book/page.tsx(MSW 兜底) | +| 38 | admin/scheduling/auto/page.tsx | §四补充批次 | ✅ /shell/admin/scheduling/auto/page.tsx(MSW 兜底) | +| 39 | admin/scheduling/changes/page.tsx | §四补充批次 | ✅ /shell/admin/scheduling/changes/page.tsx(MSW 兜底) | +| 40 | admin/scheduling/rules/page.tsx | §四补充批次 | ✅ /shell/admin/scheduling/rules/page.tsx(MSW 兜底) | +| 41 | admin/attendance/page.tsx | §四补充批次 | ✅ /shell/admin/attendance/page.tsx(MSW 兜底) | -**ARCH §9.4 规划但 CICD 无源(5 页)**: +**ARCH §9.4 规划但 CICD 无源(5 项,已全部新建)**: -| # | ARCH 目标路由 | 说明 | 契约 | -| --- | --------------------------- | -------------------------------- | ------------------- | -| A | `/shell/admin/students` | ARCH 新建,CICD 无 | ❌ | -| B | `/shell/admin/teachers` | ARCH 新建,CICD 无 | ❌ | -| C | `/shell/admin/organization` | ARCH 新建,CICD 无 | ❌ | -| D | `/shell/admin/viewports` | ARCH 新建,对齐 004 §5.4 | ❌ | -| E | `/shell/admin/plugins` | ARCH 新建(plugin-manager 升级) | ✅ `config-service` | +| # | ARCH 目标路由 | 说明 | 契约 | portal-shell 状态(2026-08-04) | +| --- | ------------------------- | -------------------------------- | ----------------- | -------------------------------------------- | +| A | /shell/admin/students | ARCH 新建,CICD 无 | ❌ → MSW 兜底 | ✅ /shell/admin/students/page.tsx | +| B | /shell/admin/teachers | ARCH 新建,CICD 无 | ❌ → MSW 兜底 | ✅ /shell/admin/teachers/page.tsx | +| C | /shell/admin/organization | ARCH 新建,CICD 无 | ❌ → MSW 兜底 | ✅ /shell/admin/organization/page.tsx | +| D | /shell/admin/viewports | ARCH 新建,对齐 004 §5.4 | ❌ → MSW 兜底 | ✅ /shell/admin/viewports/page.tsx | +| E | /shell/admin/plugins | ARCH 新建(plugin-manager 升级) | ✅ config-service | ✅ /shell/admin/plugins/page.tsx(真实契约) | ---- +**portal-shell 额外新增(3 项 redirect 入口)**: -## 七、关键结论 - -1. **portal-shell 管理域完成度:1/24 ≈ 4%**(仅仪表盘),是各角色域中完成度最低的。 -2. **缺失 23 页全部属 B5 批次**,节奏紧迫,建议立即启动契约工单。 -3. **契约就绪仅 2 个**:`adminDashboard`(已用)+ `config-service`(plugins,未实现)。 -4. **CICD 有 21 页 ARCH §9.4 未规划**,是教师域功能在管理员视角的复用,建议作为 B5 末或 B6 补充批次,按需取用。 -5. **高复杂度模块**:audit-logs(4 页 + 导出)、school(6 子页 + 跨表关联)、scheduling(算法)、error-book(多图表聚合)。 -6. **CICD admin/layout.tsx 无独立逻辑**,portal-shell 保持 passthrough 即可,侧边栏与权限由 shell/layout + 各 page.tsx 分担。 -7. **建议实施顺序**:契约就绪的 `plugins` → 高价值低复杂度的 `users/roles/permissions/invitation-codes/announcements/files/ai-settings` → 高复杂度的 `audit-logs/*` + `school/*` → ARCH 新建的 `students/teachers/organization/viewports/system` → §四补充批次 16 页。 +| # | portal-shell 路由 | 说明 | +| --- | ----------------------- | ------------------------------------------ | +| α | /shell/admin/classes | redirect → /shell/admin/school/classes | +| β | /shell/admin/scheduling | redirect → /shell/admin/scheduling/changes | +| γ | /shell/admin/school | redirect → /shell/admin/school/schools | diff --git a/apps/portal-shell/docs/needtodo/parent-NeedTodo.md b/apps/portal-shell/docs/needtodo/parent-NeedTodo.md index 0a2434e..148ab59 100644 --- a/apps/portal-shell/docs/needtodo/parent-NeedTodo.md +++ b/apps/portal-shell/docs/needtodo/parent-NeedTodo.md @@ -1,10 +1,10 @@ -# 家长域(Parent)待完成功能分析 +# 家长域(Parent)实现完整性核查报告 -> 参考项目:`e:\desktop\CICD\src\app\(dashboard)\parent\` +> 参考项目:`e:\Desktop\CICD\src\app\(dashboard)\parent\` > 当前项目:`e:\Desktop\Edu\apps\portal-shell\src\app\shell\parent\` > 规划依据:`apps/portal-shell/ARCHITECTURE.md` §9.3(24 页,B4 批次) -> 分析日期:2026-07-24 -> 任务范围:仅分析,不写代码 +> 核查日期:2026-08-04 +> 任务范围:仅核查与文档更新,不修改源代码 --- @@ -41,6 +41,49 @@ > **重要提示**:portal-shell 已转向微服务 + BFF 架构,迁移时**不能照搬 CICD 的 Server Action + Drizzle 直查模式**。家长页面应通过 `teacher-bff` / `data-ana` 等服务的 gRPC/HTTP API 获取数据,前端通过 hooks 消费。家长域多为**只读视图**,重点在 `myChildren` 契约 + `child-overview` 聚合。 +### 1.4 portal-shell 家长域资产核查(features / lib/api / mocks,已实际读取源码确认) + +#### 1.4.1 features/parent/ client 组件 + +**`src/features/parent/` 目录不存在**。当前 `src/features/` 下仅有:`admin` / `notifications` / `settings` / `shared` / `student` / `teacher`。 + +> **结论**:家长域**完全没有 features 层 client 组件**。CICD 中家长域依赖的模块组件(`ParentDashboard` / `ChildCard` / `ParentChildrenDataPage` / `ParentNoChildrenPage` / `ChildDetailHeader` / `SiblingSwitcher` / `StudentGradeSummary` / `GradeTrendCard` / `ReportCardView` / `ParentAttendanceCalendar` / `CoursePlanList` / `LessonPlanReadonlyView` / `StudentDiagnosticView` / `PracticeHistory` / `ParentSelectionView` / `LeaveRequestForm` 等)在 portal-shell 中**均未建立**,需在 B4 批次逐模块重建或下沉。 + +#### 1.4.2 lib/api/ 家长相关 API hooks + +家长相关 hooks 分布在 2 个文件,共 **5 个 hooks**: + +**`src/lib/api/dashboard.ts`(1 个 hook)**: + +- `useParentDashboard()`:查询 `parentDashboard` 根字段,返回 `ParentDashboard | null`。operation:`GET_PARENT_DASHBOARD_DOC`,类型 `ParentDashboard` 字段 snake_case 对齐 data-ana 子图。 + +**`src/lib/api/parent.ts`(4 个 hooks + 领域模型)**: + +- `useParentChildren()`:查询 `myChildren`(`GET_MY_CHILDREN_OVERVIEW_DOC`),返回 `ChildSummary[]`(含 `recentGrades` / `attendance` / `homeworkCompletion`) +- `useLeaveRequests(childId, status)`:查询 `leaveRequests`(`GET_LEAVE_REQUESTS_DOC`),按孩子 ID 与状态筛选 +- `useApproveLeave()`:`APPROVE_LEAVE_DOC` mutation,失败抛 `ApiError` +- `useRejectLeave()`:`REJECT_LEAVE_DOC` mutation(需 reason),失败抛 `ApiError` + +领域模型类型:`ChildSummary` / `ChildGrade` / `ChildAttendance` / `ChildHomeworkCompletion` / `LeaveRequest` / `LeaveType` / `LeaveStatus` + +operations 文件:`src/lib/api/operations/parent.graphql.ts`(4 个 document)。导出:`src/lib/api/index.ts` 第 36 行 `export * from "./parent";`。 + +> **结论**:lib/api 层家长 hooks 已就绪 5 个(仪表盘 + 子女概览 + 请假审批链路)。§9.3 其余 18+ 页面所需 hooks(grades / report-card / exams / homework / attendance / classes / course-plans / lesson-plans / error-book / diagnostic / learning-path / practice / elective / preferences 等)**均未建立**。注:`lib/api/` 下虽有 `grades.ts` / `attendance.ts` 等同名文件,但属学生域 / 通用域 hooks,家长域多子女聚合 + 只读过滤尚未单独建模。 + +#### 1.4.3 src/mocks/graphql-data.ts 家长相关 mock 数据 + +| Mock 资产 | 位置 | 状态 | 服务对象 | +| -------------------------------- | ---------------------- | ----------------- | ----------------------------------------------------------------------------------------- | +| `mockParentDashboard` | `graphql-data.ts:84` | ✅ 存在 | `GetParentDashboard` 查询(仪表盘页) | +| `GetParentDashboard` case 分支 | `graphql-data.ts:5577` | ✅ 存在 | 仪表盘 hook 兜底 | +| `mockLeaveRequests` | `graphql-data.ts:4143` | 🟡 存在但属教师域 | `GetTeacherLeaveRequests`(教师域 B2) | +| `GetMyChildrenOverview` mock | — | ❌ **缺失** | `useParentChildren` 无兜底 | +| `GetLeaveRequests`(家长版)mock | — | ❌ **缺失** | `useLeaveRequests` 无兜底(教师版结构 `{ items, total }` 与家长版 `LeaveRequest[]` 不同) | +| `ApproveLeave` mock | — | ❌ **缺失** | `useApproveLeave` 无兜底 | +| `RejectLeave` mock | — | ❌ **缺失** | `useRejectLeave` 无兜底 | + +> **结论**:家长域 mock **仅仪表盘 1 项就绪**,`useParentChildren` / `useLeaveRequests`(家长版)/ `useApproveLeave` / `useRejectLeave` 4 个 hook 在 dev 环境**无 MSW 兜底**(会触发 Apollo 错误或空态)。需在 B4 补齐 4 个 mock,否则 dev 环境无法演示。 + --- ## 二、缺失页面清单(按模块分组) @@ -51,7 +94,7 @@ - **portal-shell 实现**:基础仪表盘,显示孩子平均分 / 班级排名 / 薄弱知识点 / 预警通知 - **技术栈**:Client Component + `useParentDashboard()` Hook + `DashboardShell` / `StatCard` 共享组件 -- **CICD 参考实现**:`e:\desktop\CICD\src\app\(dashboard)\parent\dashboard\page.tsx` +- **CICD 参考实现**:`e:\Desktop\CICD\src\app\(dashboard)\parent\dashboard\page.tsx` - **CICD 技术栈**:Server Component + `getParentDashboardAction()` Server Action + `ParentDashboard` 视图组件 + React `use()` 流式渲染 + `generateMetadata` - **CICD 关键功能**: - 无子女时显示 `ParentNoChildrenPage` 空态 @@ -89,7 +132,7 @@ #### 2.2.1 ❌ `/shell/parent/children/[studentId]`(子女详情页,缺失)⚠️ 重要 -- **CICD 参考实现**:`e:\desktop\CICD\src\app\(dashboard)\parent\children\[studentId]\page.tsx` +- **CICD 参考实现**:`e:\Desktop\CICD\src\app\(dashboard)\parent\children\[studentId]\page.tsx` - **功能描述**: - **单个子女的完整详情视图**(家长域最核心页面) - **双重权限校验**: @@ -126,7 +169,7 @@ #### 2.3.1 ❌ `/shell/parent/grades`(子女成绩列表,缺失) -- **CICD 参考实现**:`e:\desktop\CICD\src\app\(dashboard)\parent\grades\page.tsx` +- **CICD 参考实现**:`e:\Desktop\CICD\src\app\(dashboard)\parent\grades\page.tsx` - **功能描述**: - **多子女成绩对比视图**(家长域特色) - 每个子女一块:姓名标题 + **导出按钮**(`ParentExportButton`,按 studentId 导出) @@ -146,7 +189,7 @@ #### 2.3.2 ❌ `/shell/parent/grades/report-card`(子女成绩报告卡,缺失) -- **CICD 参考实现**:`e:\desktop\CICD\src\app\(dashboard)\parent\grades\report-card\page.tsx` +- **CICD 参考实现**:`e:\Desktop\CICD\src\app\(dashboard)\parent\grades\report-card\page.tsx` - **功能描述**: - 学年 / 学期可切换的成绩报告卡(家长视角) - **必填查询参数**:`studentId`(必须在家长子女范围内) @@ -215,7 +258,7 @@ #### 2.6.1 ❌ `/shell/parent/attendance`(子女考勤,缺失) -- **CICD 参考实现**:`e:\desktop\CICD\src\app\(dashboard)\parent\attendance\page.tsx` +- **CICD 参考实现**:`e:\Desktop\CICD\src\app\(dashboard)\parent\attendance\page.tsx` - **功能描述**: - **多子女考勤对比视图**(家长域特色) - 顶部 `headerExtra`:出勤率卡片(`ParentAttendanceRateCard`)+ 考勤预警(`ParentAttendanceWarning`) @@ -253,7 +296,7 @@ #### 2.8.1 ❌ `/shell/parent/course-plans`(课程计划列表,缺失) -- **CICD 参考实现**:`e:\desktop\CICD\src\app\(dashboard)\parent\course-plans\page.tsx` +- **CICD 参考实现**:`e:\Desktop\CICD\src\app\(dashboard)\parent\course-plans\page.tsx` - **功能描述**: - 家长视角:解析**所有孩子的班级 ID**,用于过滤课程计划 - 并行查询每个子女的活跃班级 ID(`getStudentActiveClassId`) @@ -271,7 +314,7 @@ #### 2.8.2 ❌ `/shell/parent/course-plans/[id]`(课程计划详情,缺失) -- **CICD 参考实现**:`e:\desktop\CICD\src\app\(dashboard)\parent\course-plans\[id]\page.tsx` +- **CICD 参考实现**:`e:\Desktop\CICD\src\app\(dashboard)\parent\course-plans\[id]\page.tsx` - **功能描述**: - 家长视角:仅允许查看**孩子所在班级**的课程计划 - 解析所有孩子的班级 ID,传入权限上下文 @@ -292,7 +335,7 @@ #### 2.9.1 ❌ `/shell/parent/lesson-plans`(教案列表,缺失) -- **CICD 参考实现**:`e:\desktop\CICD\src\app\(dashboard)\parent\lesson-plans\page.tsx` +- **CICD 参考实现**:`e:\Desktop\CICD\src\app\(dashboard)\parent\lesson-plans\page.tsx` - **功能描述**: - 家长视角:仅查看**已发布**教案(`status: "published"`) - 学科选项并行加载(`getSubjectOptions`) @@ -310,7 +353,7 @@ #### 2.9.2 ❌ `/shell/parent/lesson-plans/[planId]/view`(教案只读详情,缺失) -- **CICD 参考实现**:`e:\desktop\CICD\src\app\(dashboard)\parent\lesson-plans\[planId]\view\page.tsx` +- **CICD 参考实现**:`e:\Desktop\CICD\src\app\(dashboard)\parent\lesson-plans\[planId]\view\page.tsx` - **功能描述**: - 家长视角:仅可查看**孩子所在年级**的已发布课案(V4 P0-1 修复,防跨年级信息泄露) - **scope 校验**:`assertPlanInScope(plan, ctx)`,失败 `notFound()` @@ -334,7 +377,7 @@ #### 2.10.1 ❌ `/shell/parent/error-book`(子女错题本,缺失) -- **CICD 参考实现**:`e:\desktop\CICD\src\app\(dashboard)\parent\error-book\page.tsx` +- **CICD 参考实现**:`e:\Desktop\CICD\src\app\(dashboard)\parent\error-book\page.tsx` - **功能描述**: - **单/多子女分支渲染**(家长域特色): - **单子女**:直接展示 `StatsGrid` 5 列统计卡片(总数 / 新增 / 学习中 / 已掌握 / 待复习) @@ -358,7 +401,7 @@ #### 2.11.1 ❌ `/shell/parent/diagnostic`(子女学情诊断,缺失) -- **CICD 参考实现**:`e:\desktop\CICD\src\app\(dashboard)\parent\diagnostic\page.tsx` +- **CICD 参考实现**:`e:\Desktop\CICD\src\app\(dashboard)\parent\diagnostic\page.tsx` - **功能描述**: - **多子女诊断对比视图**(v4-P1-9 容错增强) - 预先查询所有子女姓名(`getUserNamesByIds`),用于错误卡片展示 @@ -394,7 +437,7 @@ #### 2.13.1 ❌ `/shell/parent/practice`(子女练习查看,缺失) -- **CICD 参考实现**:`e:\desktop\CICD\src\app\(dashboard)\parent\practice\page.tsx` +- **CICD 参考实现**:`e:\Desktop\CICD\src\app\(dashboard)\parent\practice\page.tsx` - **功能描述**: - **单/多子女分支渲染**(家长域特色): - **单子女**:直接展示 4 列 `StatsGrid`(总练习次数 / 已完成 / 总答题数 / 正确率)+ `PracticeHistory` 历史列表 @@ -419,7 +462,7 @@ #### 2.14.1 ❌ `/shell/parent/elective`(子女选课查看,缺失) -- **CICD 参考实现**:`e:\desktop\CICD\src\app\(dashboard)\parent\elective\page.tsx` +- **CICD 参考实现**:`e:\Desktop\CICD\src\app\(dashboard)\parent\elective\page.tsx` - **功能描述**: - **多子女选课记录查看**(只读,家长不能选课) - 每个子女一块:`ParentSelectionView`(子女姓名 + 选课记录列表) @@ -439,7 +482,7 @@ #### 2.15.1 ❌ `/shell/parent/leave`(家长在线请假,缺失) -- **CICD 参考实现**:`e:\desktop\CICD\src\app\(dashboard)\parent\leave\page.tsx` +- **CICD 参考实现**:`e:\Desktop\CICD\src\app\(dashboard)\parent\leave\page.tsx` - **功能描述**: - **家长域少有的"写"操作页面**(其他多为只读) - 顶部:**在线请假表单**(`LeaveRequestForm`,下拉选择子女,自动写入 classId) diff --git a/apps/portal-shell/docs/needtodo/student-NeedTodo.md b/apps/portal-shell/docs/needtodo/student-NeedTodo.md index c97184b..9d4fd66 100644 --- a/apps/portal-shell/docs/needtodo/student-NeedTodo.md +++ b/apps/portal-shell/docs/needtodo/student-NeedTodo.md @@ -1,795 +1,556 @@ -# 学生域(Student)待完成功能分析 +# 学生域(Student)实现完整性核查报告 > 参考项目:`e:\desktop\CICD\src\app\(dashboard)\student\` > 当前项目:`e:\Desktop\Edu\apps\portal-shell\src\app\shell\student\` > 规划依据:`apps/portal-shell/ARCHITECTURE.md` §9.2(36 页,B3 批次) -> 分析日期:2026-07-24 -> 任务范围:仅分析,不写代码 +> 核查日期:2026-08-04 +> 任务范围:仅核查,不修改源代码 --- ## 一、页面完成度总览 -### 1.1 portal-shell 学生域现状 +### 1.1 portal-shell 学生域现状(2026-08-04 核查) -当前 `apps/portal-shell/src/app/shell/student/` 下**仅有 1 个页面**: +`apps/portal-shell/src/app/shell/student/` 下共有 **32 个 page.tsx** 文件,全部为真实业务页(Server Component 入口 + Client Component 业务实现)。 -| 路径 | 状态 | 说明 | -| ------------------------- | --------- | ------------------------------------------------ | -| `/shell/student/page.tsx` | ✅ 已完成 | 学生仪表盘(仅基础版,缺 trend/weakness 详情页) | +| 状态 | 数量 | 说明 | +| ---------------- | ------ | ----------------------------------------------------------------- | +| ✅ 完整实现 | 32 | page.tsx 引入 `@/features/student/*-client.tsx` 并渲染,三态完整 | +| 🟡 占位空态 | 0 | 无(原 2026-07-24 报告中的 34 个缺失页已全部补齐) | +| ❌ 缺失 | 0 | 无 | +| **本次核查总数** | **32** | 不含 announcements / messages / notifications / settings 共享路由 | -### 1.2 完成度统计 +### 1.2 架构模式统一(与 2026-07-24 报告对比) -| 状态 | 数量 | 说明 | -| ------------ | ------ | ---------------------------------------------------------- | -| ✅ 已完成 | 1 | `/shell/student/page.tsx`(仪表盘基础版,仍缺 2 个详情页) | -| 🟡 部分完成 | 1 | dashboard 模块(基础页有,trend/weakness 详情页缺) | -| ❌ 缺失 | 34 | 见下方模块清单 | -| **规划总数** | **36** | ARCHITECTURE.md §9.2 B3 批次 | +原报告指出仪表盘为 Client Component 硬编码中文、无权限校验。当前所有 32 页已统一为: -### 1.3 portal-shell 仪表盘与 CICD 仪表盘的技术差异(关键架构差异) +| 维度 | 当前实现 | +| -------- | --------------------------------------------------------------------- | +| 渲染模式 | Server Component 入口 + Client Component 业务(统一 Suspense 边界) | +| 数据获取 | `useWidgetQuery` / `useWidgetMutation` Hook(走 Apollo Client + BFF) | +| 国际化 | `next-intl` `useTranslations`(全部页面已接入 i18n key) | +| 三态规范 | loading(Skeleton)/ error(局部降级)/ empty(EmptyState)均按 §11.3 | +| 加载骨架 | `ListPageSkeleton` / `DetailPageSkeleton` / `WorkbenchPageSkeleton` | +| 契约状态 | 全部 `@contract-pending`(schema 根字段未定义,MSW 兜底) | -| 维度 | CICD 仪表盘 | portal-shell 仪表盘 | -| -------- | ---------------------------------------------------------------------- | --------------------------------------------------- | -| 渲染模式 | Server Component(`async function`) | Client Component(`"use client"`) | -| 数据获取 | Server Action `getStudentDashboardAction()` + Suspense 流式渲染 | `useStudentDashboard()` Hook(`@/lib/api`,走 BFF) | -| 国际化 | `next-intl` `getTranslations` | 无(硬编码中文) | -| 权限校验 | `requirePermission` + `getAuthContext` | 无 | -| 视图组件 | `StudentDashboard` 视图模块(拆分到 `@/modules/dashboard/components`) | 直接内联在 page.tsx | -| 数据来源 | 直查 DB(Drizzle) | 走 data-ana 微服务聚合 | +### 1.3 文件统计 -> **重要提示**:portal-shell 已转向微服务 + BFF 架构,迁移时**不能照搬 CICD 的 Server Action + Drizzle 直查模式**。学生页面应通过 `teacher-bff` / `data-ana` 等服务的 gRPC/HTTP API 获取数据,前端通过 hooks 消费。 +| 类别 | 数量 | 说明 | +| ---------------------------- | ----- | ----------------------------------------- | +| page.tsx | 32 | 全部存在,全部引入 client 组件 | +| features/student/*.tsx | 38 | 全部为真实实现(115-698 行) | +| lib/api student 相关 hook | 40+ | student-portal.ts + student.ts + 各域文件 | +| graphql-data.ts mock handler | 28/32 | 4 个页面存在 mock 缺口(见 §七) | --- -## 二、缺失页面清单(按模块分组) +## 二、按模块核查清单 -### 2.1 dashboard 模块(3 页规划,1 已完成,2 缺失) +### 2.1 dashboard 模块(3 页,✅ 全部完整) -#### 2.1.1 ✅ `/shell/student/page.tsx`(已完成) +#### 2.1.1 ✅ `/shell/student/page.tsx`(仪表盘首页) -- **portal-shell 实现**:基础仪表盘,显示平均分 / 班级排名 / 待交作业 + 薄弱知识点 + 近期成绩趋势 -- **技术栈**:Client Component + `useStudentDashboard()` Hook + `DashboardShell` / `StatCard` 共享组件 -- **CICD 参考实现**:`e:\desktop\CICD\src\app\(dashboard)\student\dashboard\page.tsx` -- **CICD 技术栈**:Server Component + `getStudentDashboardAction()` Server Action + `StudentDashboard` 视图组件 + Suspense 流式渲染 -- **ARCHITECTURE.md 契约**:✅ `studentDashboard` +- **page.tsx**:引入 `StudentDashboardClient`,Suspense + `DetailPageSkeleton` +- **client 组件**:`features/student/dashboard/dashboard-client.tsx`(632 行) +- **API hook**:`useStudentDashboard()`(lib/api/dashboard.ts) +- **mock 数据**:✅ `case "GetStudentDashboard"`(graphql-data.ts L5575) +- **三态**:loading/error/empty 完整 -#### 2.1.2 ❌ `/shell/student/trend`(学习趋势详情页,缺失) +#### 2.1.2 ✅ `/shell/student/trend`(学习趋势详情页) -- **CICD 参考实现**:CICD 无独立 `/trend` 页面(融合在 dashboard 中以 `recent_trends` 字段呈现) -- **功能描述**:学生学习趋势详情页,应展示更完整的历史成绩趋势、知识点掌握趋势、对比基线 -- **技术栈建议**:复用 `learningTrend` 契约 + 趋势图组件(Recharts/Echarts) -- **ARCHITECTURE.md 契约**:✅ `learningTrend` -- **CICD 关键代码片段**:dashboard 中仅以单条 `recent_trends: { date, score }` 呈现,需 portal-shell 自行扩展为详情页 +- **page.tsx**:引入 `StudentTrendClient`,Suspense + `DetailPageSkeleton` +- **client 组件**:`features/student/dashboard/trend-client.tsx`(166 行) +- **API hook**:`useStudentTrend()`(lib/api/student-portal.ts) +- **mock 数据**:✅ `case "GetStudentTrend"`(graphql-data.ts L9633) +- **三态**:loading/error/empty 完整 -#### 2.1.3 ❌ `/shell/student/weakness`(薄弱知识点详情页,缺失) +#### 2.1.3 ✅ `/shell/student/weakness`(薄弱知识点详情页) -- **CICD 参考实现**:CICD 无独立 `/weakness` 页面(融合在 dashboard 中以 `weak_points: { title, mastery, error_count }` 字段呈现) -- **功能描述**:学生薄弱知识点详情页,应展示完整的薄弱知识点列表、按学科分组、关联错题、推荐练习 -- **技术栈建议**:复用 `studentWeakness` 契约 + 错题本 / 练习模块联动跳转 -- **ARCHITECTURE.md 契约**:✅ `studentWeakness` -- **CICD 关键代码片段**:dashboard 中仅以单条 `weak_points` 呈现,需 portal-shell 自行扩展 +- **page.tsx**:引入 `StudentWeaknessClient`,Suspense + `DetailPageSkeleton` +- **client 组件**:`features/student/dashboard/weakness-client.tsx`(194 行) +- **API hook**:`useStudentPortalWeakness()`(lib/api/student-portal.ts) +- **mock 数据**:✅ `case "GetStudentPortalWeakness"`(graphql-data.ts L9636) +- **三态**:loading/error/empty 完整 --- -### 2.2 grades 模块(2 页规划,0 已完成,2 缺失) +### 2.2 grades 模块(2 页,✅ 全部完整) -#### 2.2.1 ❌ `/shell/student/grades`(我的成绩列表,缺失) +#### 2.2.1 ✅ `/shell/student/grades`(成绩列表页) -- **CICD 参考实现**:`e:\desktop\CICD\src\app\(dashboard)\student\grades\page.tsx` -- **功能描述**: - - 学生本人成绩汇总(姓名 + 记录数) - - 学科 / 类型 / 学期 / 关键字 4 维过滤器 - - **成绩趋势卡片**(含班级平均对比线) - - **排名趋势卡片** - - **班级分布直方图**(隐私保护视图,仅展示本人位置标注) - - **学生纵向成长档案**(跨学年/学期聚合) - - 成绩汇总明细表 -- **技术栈**: - - Server Component(`async function`)+ `force-dynamic` - - Server Actions:`getStudentGradeSummary` / `getRankingTrend` / `getClassAverageTrend` / `getStudentPositionInClassDistribution` / `getStudentGrowthArchive` / `getSubjectOptions` - - `requirePermission(Permissions.GRADE_RECORD_READ)` + `ctx.dataScope` - - 组件:`StudentGradeSummary` / `StudentGradeFilters` / `GradeTrendCard` / `RankingTrendCard` / `GradeDistributionChart` / `GrowthArchiveChart` / `EmptyState` - - searchParams 过滤(`q` / `subject` / `type` / `semester`) -- **ARCHITECTURE.md 契约**:❌(列表,未定义) +- **page.tsx**:引入 `StudentGradesListClient`,Suspense + `ListPageSkeleton` +- **client 组件**:`features/student/grades/grades-list-client.tsx`(516 行) +- **API hook**:`useStudentGrades(subject, type, q)`(lib/api/student-portal.ts) +- **mock 数据**:✅ `case "GetStudentGrades"`(graphql-data.ts L9586) +- **三态**:loading/error/empty 完整 -#### 2.2.2 ❌ `/shell/student/grades/report-card`(成绩报告卡,缺失) +#### 2.2.2 ✅ `/shell/student/grades/report-card`(成绩报告卡页) -- **CICD 参考实现**:`e:\desktop\CICD\src\app\(dashboard)\student\grades\report-card\page.tsx` -- **功能描述**: - - 学年 / 学期可切换的成绩报告卡 - - 报告卡视图(学生本人维度聚合) - - **打印操作**(`ReportCardPrintAction`) - - 返回成绩列表的面包屑 -- **技术栈**: - - Server Component + `force-dynamic` - - Server Actions:`getReportCardData(ctx.userId, ctx.dataScope, { academicYearId, semester })` + `getAcademicYears()` - - `requirePermission(Permissions.GRADE_RECORD_READ)` - - 组件:`ReportCardView` / `ReportCardPrintAction` / `Button` / `EmptyState` - - searchParams:`academicYearId` / `semester` -- **ARCHITECTURE.md 契约**:❌(未单独定义) +- **page.tsx**:引入 `StudentReportCardClient`,导入 `report-card-print.css`,Suspense + `DetailPageSkeleton` +- **client 组件**:`features/student/grades/report-card-client.tsx`(693 行)+ 子组件(growth-archive-card / ranking-trend-card / score-distribution-card) +- **API hook**:`useStudentReportCard(academicYearId, semester)`(lib/api/student-portal.ts) +- **mock 数据**:✅ `case "GetStudentReportCard"`(graphql-data.ts L9588) +- **三态**:loading/error/empty 完整 --- -### 2.3 exams 模块(3 页规划,0 已完成,3 缺失)⚠️ 重点 +### 2.3 exams 模块(3 页,✅ 全部完整) -> **CICD 参考项目无 exams 模块学生页面**。学生考试相关功能需 portal-shell 从零设计。 +#### 2.3.1 ✅ `/shell/student/exams`(考试列表页) -#### 2.3.1 ❌ `/shell/student/exams`(我的考试列表,缺失) +- **page.tsx**:引入 `StudentExamsListClient`,Suspense + `ListPageSkeleton` +- **client 组件**:`features/student/exams/exams-list-client.tsx`(331 行) +- **API hook**:`useStudentExams(status)`(lib/api/student-portal.ts) +- **mock 数据**:✅ `case "GetStudentExams"`(graphql-data.ts L9591) +- **三态**:loading/error/empty 完整 -- **CICD 参考实现**:❌ CICD 无对应源页面 -- **功能描述**:学生视角的考试列表,应展示: - - 即将到来的考试(含倒计时、考场、座位号) - - 已结束考试(含成绩、查看结果入口) - - 按学科 / 状态过滤 -- **技术栈建议**:参考 CICD `learning/assignments/page.tsx` 的列表分组 + 状态徽章模式 -- **ARCHITECTURE.md 契约**:🟡(部分,需补充) +#### 2.3.2 ✅ `/shell/student/exams/[id]/result`(考试结果页) -#### 2.3.2 ❌ `/shell/student/exams/[id]/result`(考试结果页,缺失) +- **page.tsx**:引入 `StudentExamResultClient`,Suspense + `DetailPageSkeleton` +- **client 组件**:`features/student/exams/exam-result-client.tsx`(252 行) +- **API hook**:`useStudentExamResult(id)`(lib/api/student-portal.ts) +- **mock 数据**:✅ `case "GetStudentExamResult"`(graphql-data.ts L9593) +- **三态**:loading/error/empty 完整 -- **CICD 参考实现**:❌ CICD 无对应源页面(可参考 `learning/assignments/[assignmentId]/result/page.tsx` 的同构模式) -- **功能描述**: - - 考试得分、排名、班级均分对比 - - 题目作答详情(每题对错、参考答案、学生答案) - - 知识点掌握情况 - - 错题一键加入错题本 -- **技术栈建议**:参考 CICD `HomeworkSubmissionResult` 组件 + `getStudentSubmissionResult` 数据访问模式 -- **ARCHITECTURE.md 契约**:🟡(部分,需补充) +#### 2.3.3 ✅ `/shell/student/exams/[id]/take`(考试作答工作台页) -#### 2.3.3 ❌ `/shell/student/exams/[id]/take`(考试作答工作台,缺失)⚠️ 关键 - -- **CICD 参考实现**:❌ CICD 无对应源页面(可参考 `learning/assignments/[assignmentId]/page.tsx` 的 `HomeworkTakeView` 同构模式) -- **功能描述**:考试作答工作台,应包含: - - 题目导航栏(题号、未答/已答/标记状态) - - 题目作答区(单选/多选/填空/简答) - - 倒计时(自动提交) - - 答案本地暂存(防刷新丢失) - - 提交确认 + 二次确认 - - 中途断网恢复 -- **技术栈建议**: - - 客户端组件(`HomeworkTakeView` 是 client component) - - 答案本地存储(localStorage / IndexedDB) - - 自动保存定时器 - - 提交走 Server Action / BFF API -- **ARCHITECTURE.md 契约**:🟡(部分,需补充) - -> ⚠️ **此页是学生域最复杂、最关键的页面**,需在 B3 中重点投入。CICD 的 `HomeworkTakeView` 是最近的同构参考,但其路由是 `learning/assignments/[assignmentId]` 而非 `exams/[id]/take`。 +- **page.tsx**:引入 `StudentExamTakeClient`,Suspense + `WorkbenchPageSkeleton` +- **client 组件**:`features/student/exams/exam-take-client.tsx`(665 行) +- **API hook**:`useStudentExamTake(id)` + `useSubmitStudentExam()`(lib/api/student-portal.ts) +- **mock 数据**:✅ `case "GetStudentExamTake"` + `case "SubmitStudentExam"`(L9595, L9669) +- **三态**:loading/error/empty 完整 --- -### 2.4 homework 模块(3 页规划,0 已完成,3 缺失) +### 2.4 homework 模块(3 页,✅ 全部完整) -> **CICD 实际路由是 `learning/assignments/*`**,ARCHITECTURE.md §9.2 计划将其提升到 `homework/*` 顶层。 +#### 2.4.1 ✅ `/shell/student/homework`(作业列表页) -#### 2.4.1 ❌ `/shell/student/homework`(我的作业列表,缺失) +- **page.tsx**:引入 `StudentHomeworkListClient`,Suspense + `ListPageSkeleton` +- **client 组件**:`features/student/homework/homework-list-client.tsx`(547 行) +- **API hook**:`useStudentHomework(status)`(lib/api/student-portal.ts) +- **mock 数据**:✅ `case "GetStudentHomework"`(graphql-data.ts L9598) +- **三态**:loading/error/empty 完整 -- **CICD 参考实现**:`e:\desktop\CICD\src\app\(dashboard)\student\learning\assignments\page.tsx` -- **功能描述**: - - 作业列表,按**学科分组**展示 - - 每组内按"未答 / 已答"二分桶(PERF-05 单次遍历分桶) - - 每张卡片:标题、状态徽章、截止时间、剩余尝试次数、最新得分、逾期提示 - - 学科 dot 颜色(hash 稳定映射) - - 状态过滤(all/pending/submitted/graded)+ 关键字搜索 - - `AssignmentCard` 用 `React.memo` 优化高频渲染 -- **技术栈**: - - Server Component + `force-dynamic` - - Server Action:`getStudentHomeworkAssignments(student.id)` - - 组件:`AssignmentFilters` / `StatusBadge` / `Card` / `EmptyState` / `memo(AssignmentCard)` - - 工具:`formatDate` / `getParam` / `cn` / `STUDENT_HOMEWORK_PROGRESS_VARIANT` -- **ARCHITECTURE.md 契约**:🟡(部分) +#### 2.4.2 ✅ `/shell/student/homework/[id]/submit`(作业作答工作台页) -#### 2.4.2 ❌ `/shell/student/homework/[id]/submit`(作业作答/提交,缺失)⚠️ 关键 +- **page.tsx**:引入 `StudentHomeworkSubmitClient`,Suspense + `WorkbenchPageSkeleton` +- **client 组件**:`features/student/homework/homework-submit-client.tsx`(698 行,学生域最大文件) +- **API hook**:`useStudentHomeworkSubmit(id)` + `useSubmitStudentHomework()`(lib/api/student-portal.ts) +- **mock 数据**:✅ `case "GetStudentHomeworkSubmit"` + `case "SubmitStudentHomework"`(L9600, L9671) +- **三态**:loading/error/empty 完整 -- **CICD 参考实现**:`e:\desktop\CICD\src\app\(dashboard)\student\learning\assignments\[assignmentId]\page.tsx` -- **功能描述**: - - 根据 `submission.status` 分流: - - `graded` / `submitted` → 进入 `HomeworkReviewView`(查看提交后/批改后视图) - - `not_started` / `in_progress` → 进入 `HomeworkTakeView`(作答工作台) - - 顶部展示作业标题、截止时间、最大尝试次数 -- **技术栈**: - - Server Component(外壳)+ Client Component(`HomeworkTakeView` / `HomeworkReviewView`) - - Server Action:`getStudentHomeworkTakeData(assignmentId, student.id)` - - 组件:`HomeworkTakeView` / `HomeworkReviewView` -- **ARCHITECTURE.md 契约**:🟡(部分) +#### 2.4.3 ✅ `/shell/student/homework/[id]/analysis`(作业分析页) -#### 2.4.3 ❌ `/shell/student/homework/[id]/analysis`(作业分析/结果,缺失) - -- **CICD 参考实现**:`e:\desktop\CICD\src\app\(dashboard)\student\learning\assignments\[assignmentId]\result\page.tsx` -- **功能描述**: - - 作业标题 + 提交结果详情 - - 通过 `HomeworkSubmissionResult` 组件展示分析(含每题得分、参考答案等) -- **技术栈**: - - Server Component + `force-dynamic` - - Server Actions:`getHomeworkAssignmentById(assignmentId)` + `getStudentSubmissionResult(assignmentId, studentId)` - - 通过 `getSession()` 获取 studentId(非 `requirePermission` 模式,需注意) - - 组件:`HomeworkSubmissionResult` -- **ARCHITECTURE.md 契约**:🟡(部分) +- **page.tsx**:引入 `StudentHomeworkAnalysisClient`,Suspense + `DetailPageSkeleton` +- **client 组件**:`features/student/homework/homework-analysis-client.tsx`(139 行) +- **API hook**:`useStudentHomeworkAnalysis(id)`(lib/api/student-portal.ts) +- **mock 数据**:✅ `case "GetStudentHomeworkAnalysis"`(graphql-data.ts L9602) +- **三态**:loading/error/empty 完整 --- -### 2.5 schedule 模块(1 页规划,0 已完成,1 缺失) +### 2.5 schedule 模块(1 页,✅ 完整) -#### 2.5.1 ❌ `/shell/student/schedule`(学生课表,缺失) +#### 2.5.1 ✅ `/shell/student/schedule`(课表页) -- **CICD 参考实现**:`e:\desktop\CICD\src\app\(dashboard)\student\schedule\page.tsx` -- **功能描述**: - - 学生个人课表(按班级聚合) - - 班级过滤器(`classId` searchParam,默认 `all`) - - 顶部标题 + 班级筛选器 - - `StudentScheduleView` 渲染课表 -- **技术栈**: - - Server Component + `force-dynamic` + `generateMetadata` - - Server Actions:`getStudentClasses(student.id)` + `getStudentSchedule(student.id)` - - `requirePermission(Permissions.CLASS_READ)` + `getCurrentStudentUser()` - - 组件:`StudentScheduleFilters` / `StudentScheduleView` / `EmptyState` - - searchParams:`classId` -- **ARCHITECTURE.md 契约**:❌ +- **page.tsx**:引入 `StudentScheduleListClient`,Suspense + `ListPageSkeleton` +- **client 组件**:`features/student/schedule/schedule-list-client.tsx`(244 行) +- **API hook**:`useStudentSchedule()` + `useStudentClasses()`(lib/api/student-portal.ts) +- **mock 数据**:✅ `case "GetStudentSchedule"` + `case "GetStudentClasses"`(L9605, L9609) +- **三态**:loading/error/empty 完整 --- -### 2.6 attendance 模块(1 页规划,0 已完成,1 缺失) +### 2.6 attendance 模块(1 页,✅ 完整) -#### 2.6.1 ❌ `/shell/student/attendance`(学生考勤,缺失) +#### 2.6.1 ✅ `/shell/student/attendance`(考勤页) -- **CICD 参考实现**:`e:\desktop\CICD\src\app\(dashboard)\student\attendance\page.tsx` -- **功能描述**: - - 学生本人考勤汇总(出勤率、迟到/早退/请假次数) - - 通过 `StudentAttendanceView` 渲染详细记录 - - 无数据时显示 `EmptyState` -- **技术栈**: - - Server Component + `force-dynamic` - - Server Action:`getStudentAttendanceSummary(ctx.userId)` - - `requirePermission(Permissions.ATTENDANCE_READ)` + `getAuthContext()` - - 组件:`StudentAttendanceView` / `EmptyState` -- **ARCHITECTURE.md 契约**:❌ +- **page.tsx**:引入 `StudentAttendanceClient`,Suspense + `DetailPageSkeleton` +- **client 组件**:`features/student/attendance/attendance-client.tsx`(252 行) +- **API hook**:`useStudentAttendance()`(lib/api/student-portal.ts) +- **mock 数据**:✅ `case "GetStudentAttendance"`(graphql-data.ts L9607) +- **三态**:loading/error/empty 完整 --- -### 2.7 classes 模块(1 页规划,0 已完成,1 缺失) +### 2.7 classes 模块(1 页,✅ 完整) -#### 2.7.1 ❌ `/shell/student/classes`(我的班级,缺失) +#### 2.7.1 ✅ `/shell/student/classes`(班级列表页) -- **CICD 参考实现**:❌ CICD 学生端无独立 `/classes` 路由(班级信息融合在 `learning/courses/*` 中) -- **功能描述**: - - 学生所在的班级列表(含班主任、教室、学校、年级、活跃状态) - - 区别于 courses(课程):classes 侧重"行政班/教学班"维度,courses 侧重"学科课程"维度 - - 可跳转到该班的课表、作业、同学(受权限限制) -- **技术栈建议**:参考 CICD `learning/courses/page.tsx` 的列表 + 过滤模式 -- **ARCHITECTURE.md 契约**:❌ `myClasses` +- **page.tsx**:引入 `StudentClassesListClient`,Suspense + `ListPageSkeleton` +- **client 组件**:`features/student/classes/classes-list-client.tsx`(134 行) +- **API hook**:`useStudentClasses()`(lib/api/student-portal.ts) +- **mock 数据**:✅ `case "GetStudentClasses"`(graphql-data.ts L9609) +- **三态**:loading/error/empty 完整 --- -### 2.8 courses 模块(2 页规划,0 已完成,2 缺失) +### 2.8 courses 模块(2 页,✅ 全部完整) -#### 2.8.1 ❌ `/shell/student/courses`(课程列表,缺失) +#### 2.8.1 ✅ `/shell/student/courses`(课程列表页) -- **CICD 参考实现**:`e:\desktop\CICD\src\app\(dashboard)\student\learning\courses\page.tsx` -- **功能描述**: - - 学生报名的课程(班级)列表 - - 关键字搜索(名称 / 教师 / 学校 / 班主任) - - 通过 `StudentCoursesView` 渲染 - - 仅有班级时才显示过滤器 -- **技术栈**: - - Server Component + `force-dynamic` + `generateMetadata` - - Server Action:`getStudentClasses(student.id)` - - `requirePermission(Permissions.CLASS_READ)` + `getCurrentStudentUser()` - - 组件:`CourseFilters` / `StudentCoursesView` / `EmptyState` - - searchParams:`q` -- **ARCHITECTURE.md 契约**:❌ +- **page.tsx**:引入 `StudentCoursesListClient`,Suspense + `ListPageSkeleton` +- **client 组件**:`features/student/courses/courses-list-client.tsx`(473 行) +- **API hook**:`useStudentCourses(q)` + `useJoinClassByInvitationCode()`(lib/api/student-portal.ts) +- **mock 数据**:✅ `case "GetStudentCourses"`(graphql-data.ts L9611) +- **三态**:loading/error/empty 完整 -#### 2.8.2 ❌ `/shell/student/courses/[id]`(课程详情,缺失) +#### 2.8.2 ✅ `/shell/student/courses/[id]`(课程详情页) -- **CICD 参考实现**:`e:\desktop\CICD\src\app\(dashboard)\student\learning\courses\[classId]\page.tsx` -- **功能描述**: - - 班级(课程)详情页:名称、年级、班主任、教室、学校 - - **三列卡片**:教师信息(姓名 + 邮箱 mailto)/ 学校信息 / 教室信息 - - **本班课表**:按 weekday 排序的列表 - - 顶部按钮:跳转完整课表、跳转作业列表 -- **技术栈**: - - Server Component + `force-dynamic` + `generateMetadata` - - Server Actions:`getStudentClassById(student.id, classId)` + `getStudentSchedule(student.id)` - - `requirePermission(Permissions.CLASS_READ)` + `getCurrentStudentUser()` - - 组件:`Card` / `Badge` / `Button` / `EmptyState` - - 路由参数:`classId` -- **ARCHITECTURE.md 契约**:❌ +- **page.tsx**:引入 `StudentCourseDetailClient`,Suspense + `DetailPageSkeleton` +- **client 组件**:`features/student/courses/course-detail-client.tsx`(248 行) +- **API hook**:`useStudentCourseDetail(id)`(lib/api/student-portal.ts) +- **mock 数据**:✅ `case "GetStudentCourseDetail"`(graphql-data.ts L9613) +- **三态**:loading/error/empty 完整 --- -### 2.9 course-plans 模块(2 页规划,0 已完成,2 缺失) +### 2.9 course-plans 模块(2 页,✅ 全部完整) -#### 2.9.1 ❌ `/shell/student/course-plans`(课程计划列表,缺失) +#### 2.9.1 ✅ `/shell/student/course-plans`(课程计划列表页) -- **CICD 参考实现**:`e:\desktop\CICD\src\app\(dashboard)\student\course-plans\page.tsx` -- **功能描述**: - - 学生视角的课程计划列表(仅 `status: "active"`) - - 通过 `class_members` scope 过滤可见的 classIds - - `CoursePlanList` 渲染,detailBaseHref 指向 `/student/course-plans` -- **技术栈**: - - Server Component + `force-dynamic` - - Server Action:`getCoursePlans({ status: "active" }, { userId, isAdmin: false, classIds })` - - `requirePermission(Permissions.COURSE_PLAN_READ)` - - 组件:`CoursePlanList` -- **ARCHITECTURE.md 契约**:❌ +- **page.tsx**:引入 `StudentCoursePlansListClient`,Suspense + `ListPageSkeleton` +- **client 组件**:`features/student/course-plans/course-plan-list-client.tsx`(340 行) +- **API hook**:`useStudentCoursePlans()`(lib/api/student-portal.ts) +- **mock 数据**:✅ `case "GetStudentCoursePlans"`(graphql-data.ts L9616) +- **三态**:loading/error/empty 完整 -#### 2.9.2 ❌ `/shell/student/course-plans/[id]`(课程计划详情,缺失) +#### 2.9.2 ✅ `/shell/student/course-plans/[id]`(课程计划详情页) -- **CICD 参考实现**:`e:\desktop\CICD\src\app\(dashboard)\student\course-plans\[id]\page.tsx` -- **功能描述**: - - 课程计划详情视图(学生只读) - - 通过 class_members scope 过滤 - - `CoursePlanDetail` 渲染,关联教材跳转 `/student/learning/textbooks` -- **技术栈**: - - Server Component + `force-dynamic` - - Server Action:`getCoursePlanById(id, { userId, isAdmin: false, classIds })` - - `requirePermission(Permissions.COURSE_PLAN_READ)` - - 组件:`CoursePlanDetail` - - 路由参数:`id` - - `notFound()` 兜底 -- **ARCHITECTURE.md 契约**:❌ +- **page.tsx**:引入 `StudentCoursePlanDetailClient`,Suspense + `DetailPageSkeleton` +- **client 组件**:`features/student/course-plans/course-plan-detail-client.tsx`(486 行) +- **API hook**:`useStudentCoursePlanDetail(id)`(lib/api/student-portal.ts) +- **mock 数据**:✅ `case "GetStudentCoursePlanDetail"`(graphql-data.ts L9618) +- **三态**:loading/error/empty 完整 --- -### 2.10 lesson-plans 模块(2 页规划,0 已完成,2 缺失) +### 2.10 lesson-plans 模块(2 页,✅ 全部完整) -#### 2.10.1 ❌ `/shell/student/lesson-plans`(教案列表,缺失) +#### 2.10.1 ✅ `/shell/student/lesson-plans`(教案列表页) -- **CICD 参考实现**:`e:\desktop\CICD\src\app\(dashboard)\student\lesson-plans\page.tsx` -- **功能描述**: - - 学生视角的教案列表(仅 `status: "published"`) - - `LessonPlanList` 学生视图模式(`viewMode="student"`) - - `LessonPlanProviderSetup` 注入学生角色配置(让筛选生效) -- **技术栈**: - - Server Component + `force-dynamic` - - Server Actions:`getLessonPlans({ status: "published" }, ctx.dataScope, ctx.userId)` + `getSubjectOptions()` - - `requirePermission(Permissions.LESSON_PLAN_READ)` - - 组件:`LessonPlanList` / `LessonPlanProviderSetup` / `STUDENT_ROLE_CONFIG` / `Skeleton` + `Suspense` -- **ARCHITECTURE.md 契约**:❌ +- **page.tsx**:引入 `StudentLessonPlansListClient`,Suspense + `ListPageSkeleton` +- **client 组件**:`features/student/lesson-plans/lesson-plan-list-client.tsx`(167 行) +- **API hook**:`useStudentLessonPlans()` + `useSubjectOptions()`(lib/api/student-portal.ts) +- **mock 数据**:✅ `case "GetStudentLessonPlans"`(graphql-data.ts L9620) +- **三态**:loading/error/empty 完整 -#### 2.10.2 ❌ `/shell/student/lesson-plans/[planId]/view`(教案只读查看,缺失) +#### 2.10.2 ✅ `/shell/student/lesson-plans/[planId]/view`(教案只读查看页) -- **CICD 参考实现**:`e:\desktop\CICD\src\app\(dashboard)\student\lesson-plans\[planId]\view\page.tsx` -- **功能描述**: - - 教案只读视图 - - **权限收敛**:学生仅可查看本年级已发布教案(`assertPlanInScope`),未发布显示提示 - - 拉取教材标题 + 章节标题(并行 `Promise.all`) - - `LessonPlanReadonlyView` 渲染文档内容 -- **技术栈**: - - Server Component + `force-dynamic` - - Server Actions:`getLessonPlanById(planId, ctx.userId)` + `getTextbookById` + `getChaptersByTextbookId` + `findChapterById` - - `requirePermission(Permissions.LESSON_PLAN_READ)` + `assertPlanInScope(plan, ctx)` - - 组件:`LessonPlanReadonlyView` / `Skeleton` + `Suspense` - - 路由参数:`planId` -- **ARCHITECTURE.md 契约**:❌ +- **page.tsx**:引入 `StudentLessonPlanViewClient`,Suspense + `DetailPageSkeleton` +- **client 组件**:`features/student/lesson-plans/lesson-plan-view-client.tsx`(422 行) +- **API hook**:`useStudentLessonPlanView(planId)`(lib/api/student-portal.ts) +- **mock 数据**:✅ `case "GetStudentLessonPlanView"`(graphql-data.ts L9622) +- **三态**:loading/error/empty 完整 --- -### 2.11 textbooks 模块(2 页规划,0 已完成,2 缺失) +### 2.11 textbooks 模块(2 页,✅ 全部完整) -#### 2.11.1 ❌ `/shell/student/textbooks`(教材列表,缺失) +#### 2.11.1 ✅ `/shell/student/textbooks`(教材列表页) -- **CICD 参考实现**:`e:\desktop\CICD\src\app\(dashboard)\student\learning\textbooks\page.tsx` -- **功能描述**: - - 学生视角的教材列表 - - **强制按学生所在年级过滤**(`getGradeNameById` 解析 gradeId 为年级名称) - - 关键字 / 学科 / 年级 3 维过滤器 - - `TextbookCard` 网格(4 列响应式) - - 学生端 `hideActions`(无编辑权限) -- **技术栈**: - - Server Component + `force-dynamic` - - Server Actions:`getTextbooksWithScope(q, subject, grade, { grade: studentGradeName })` + `getGradeNameById` - - `requirePermission(Permissions.TEXTBOOK_READ)` + `getCurrentStudentUser()` - - 组件:`TextbookCard` / `TextbookFilters` / `EmptyState` - - searchParams:`q` / `subject` / `grade` -- **ARCHITECTURE.md 契约**:🟡 +- **page.tsx**:引入 `StudentTextbooksListClient`,Suspense + `ListPageSkeleton` +- **client 组件**:`features/student/textbooks/textbooks-list-client.tsx`(270 行) +- **API hook**:`useStudentTextbooks(q, subject, grade)`(lib/api/student-portal.ts) +- **mock 数据**:✅ `case "GetStudentTextbooks"`(graphql-data.ts L9624) +- **三态**:loading/error/empty 完整 -#### 2.11.2 ❌ `/shell/student/textbooks/[id]/chapters`(教材章节阅读器,缺失) +#### 2.11.2 ✅ `/shell/student/textbooks/[id]/chapters`(教材章节阅读器页) -- **CICD 参考实现**:`e:\desktop\CICD\src\app\(dashboard)\student\learning\textbooks\[id]\page.tsx` -- **功能描述**: - - 教材阅读器(`TextbookReader`) - - 顶部固定标题栏:教材标题 + 学科 Badge + 年级 Badge - - 章节列表 + 阅读区 - - **年级校验**:教材年级与学生年级不匹配则 `notFound()`(防越权) - - 学生端不传 `renderQuestionCreator`(无题目创建权限) - - 高度自适应(`h-[calc(100vh-4rem-3rem)]`) -- **技术栈**: - - Server Component + `force-dynamic` - - Server Actions:`getTextbookById(id)` + `getChaptersByTextbookId(id)` + `getGradeNameById` - - `requirePermission(Permissions.TEXTBOOK_READ)` + `getCurrentStudentUser()` - - 组件:`TextbookReader` / `Badge` / `EmptyState` - - 路由参数:`id` -- **ARCHITECTURE.md 契约**:🟡 -- **注**:ARCHITECTURE.md §9.2 路径为 `/[id]/chapters`,CICD 路径为 `/[id]`,迁移时需对齐 +- **page.tsx**:引入 `StudentTextbookChaptersClient`,Suspense + `DetailPageSkeleton` +- **client 组件**:`features/student/textbooks/textbook-chapters-client.tsx`(362 行) +- **API hook**:`useStudentTextbookChapters(id)`(lib/api/student-portal.ts) +- **mock 数据**:✅ `case "GetStudentTextbookChapters"`(graphql-data.ts L9626) +- **三态**:loading/error/empty 完整 --- -### 2.12 error-book 模块(1 页规划,0 已完成,1 缺失) +### 2.12 error-book 模块(1 页,✅ 完整) -#### 2.12.1 ❌ `/shell/student/error-book`(错题本,缺失) +#### 2.12.1 ✅ `/shell/student/error-book`(错题本页) -- **CICD 参考实现**:`e:\desktop\CICD\src\app\(dashboard)\student\error-book\page.tsx` -- **功能描述**: - - **5 列统计卡片**:总数 / 新增 / 学习中 / 已掌握(含掌握率%)/ 待复习(高亮 overdue) - - **新增错题对话框**(`AddErrorBookDialogWithQuestions`,带题目选择器) - - 筛选器:关键字 / 状态 / 来源 / 仅看待复习 - - 错题列表(客户端组件 `StudentErrorBookListClient`) - - **AI 上下文注入**:`AiClientProvider` + `createCoreAiClientService` - - `WidgetBoundary` 包裹关键 widget(降级容错) - - `Suspense` 流式渲染过滤器 + 结果列表 -- **技术栈**: - - Server Component + `force-dynamic` - - Server Actions:`getErrorBookItems({ studentId, q, status, sourceType, dueOnly, pageSize: 50 })` + `getErrorBookStats(ctx.userId)` - - `requirePermission(Permissions.ERROR_BOOK_READ)` - - 组件:`StatsGrid` / `AddErrorBookDialogWithQuestions` / `ErrorBookFilters` / `StudentErrorBookListClient` / `WidgetBoundary` / `AiClientProvider` - - 状态枚举守卫:`isErrorBookStatus` / `isErrorBookSource`(类型守卫) -- **ARCHITECTURE.md 契约**:✅ +- **page.tsx**:引入 `StudentErrorBookListClient`,Suspense + `ListPageSkeleton` +- **client 组件**:`features/student/error-book/error-book-list-client.tsx`(648 行)+ `error-book-detail-dialog.tsx`(199 行,详情弹窗子组件) +- **API hook**:`useStudentErrorBookV2(q, status, source, dueOnly)`(lib/api/student-portal.ts) +- **mock 数据**:✅ `case "GetStudentErrorBookV2"`(graphql-data.ts L9629) +- **三态**:loading/error/empty 完整 --- -### 2.13 learning 模块(2 页规划,0 已完成,2 缺失) +### 2.13 learning 模块(1 页,✅ 完整) -#### 2.13.1 ❌ `/shell/student/learning`(学习中心首页,缺失) +#### 2.13.1 ✅ `/shell/student/learning`(学习中心首页) -- **CICD 参考实现**:`e:\desktop\CICD\src\app\(dashboard)\student\learning\page.tsx` -- **功能描述**: - - 学习中心首页,3 张导航卡片: - 1. **课程**(已报名班级数) - 2. **作业**(待交数 + 即将到期数,7 天内) - 3. **教材**(可用数) - - 每张卡片含图标 / 标题 / 描述 / 统计 / 箭头 - - 学生身份校验(`getCurrentStudentUser`),无则显示 `EmptyState` -- **技术栈**: - - Server Component + `force-dynamic` + `generateMetadata` - - Server Actions:`getStudentClasses` + `getStudentHomeworkAssignments` + `getTextbooks` - - `requirePermission(Permissions.CLASS_READ)` - - 组件:`Card` / `EmptyState` - - 业务逻辑:`pendingCount`(未提交+未批改)/ `dueSoonCount`(7 天内到期)计算 -- **ARCHITECTURE.md 契约**:❌ - -#### 2.13.2 ❌ `/shell/student/learning-path`(AI 学习路径,缺失) - -- **CICD 参考实现**:`e:\desktop\CICD\src\app\(dashboard)\student\learning\study-path\page.tsx` -- **功能描述**: - - AI 生成的个性化学习路径 - - `AiStudyPath` 客户端组件,由 dashboard layout 注入 `AiClientProvider` - - 学生身份校验 -- **技术栈**: - - Server Component + `force-dynamic` + `generateMetadata` - - `requirePermission(Permissions.AI_CHAT)`(与 AI_CHAT 权限点一致,防止学生角色被关闭后页面仍可访问) - - `getCurrentStudentUser()` - - 组件:`AiStudyPath`(client component)/ `EmptyState` -- **ARCHITECTURE.md 契约**:❌ -- **注**:ARCHITECTURE.md §9.2 路径为 `/learning-path`(顶层),CICD 路径为 `/learning/study-path`,迁移时需对齐 +- **page.tsx**:引入 `StudentLearningCenterClient`,Suspense + `ListPageSkeleton` +- **client 组件**:`features/student/learning/learning-center-client.tsx`(153 行) +- **API hook**:`useStudentLearningCenter()`(lib/api/student-portal.ts) +- **mock 数据**:✅ `case "GetStudentLearningCenter"`(graphql-data.ts L9631) +- **三态**:loading/error/empty 完整 --- -### 2.14 practice 模块(2 页规划,0 已完成,2 缺失) +### 2.14 learning-path 模块(1 页,🟡 页面完整但缺 mock 数据) -#### 2.14.1 ❌ `/shell/student/practice`(自适应练习首页,缺失) +#### 2.14.1 🟡 `/shell/student/learning-path`(AI 学习路径页) -- **CICD 参考实现**:`e:\desktop\CICD\src\app\(dashboard)\student\practice\page.tsx` -- **功能描述**: - - **4 列统计卡片**:总练习次数 / 已完成 / 总答题数 / 正确率 - - 左侧:`PracticeStarterWithNav`(练习启动器 + 知识点选择 + 导航) - - 右侧:`PracticeHistory`(历史会话列表) - - `PracticeServiceProvider` 包裹(注入练习服务) -- **技术栈**: - - Server Component + `force-dynamic` - - Server Actions:`getPracticeStats(ctx.userId)` + `getPracticeSessions(ctx.userId, { pageSize: 20 })` + `getKnowledgePointOptions()` - - `requirePermission(Permissions.ADAPTIVE_PRACTICE_READ)` - - 组件:`StatsGrid` / `PracticeStarterWithNav` / `PracticeHistory` / `PracticeServiceProvider` -- **ARCHITECTURE.md 契约**:❌ - -#### 2.14.2 ❌ `/shell/student/practice/[sessionId]`(练习会话页,缺失) - -- **CICD 参考实现**:`e:\desktop\CICD\src\app\(dashboard)\student\practice\[sessionId]\page.tsx` -- **功能描述**: - - 单个练习会话详情 - - `PracticeSessionView` 渲染(题目作答 + 即时反馈 + 进度) - - 学生身份校验(防止跨用户访问) -- **技术栈**: - - Server Component + `force-dynamic` - - Server Action:`getPracticeSessionById(sessionId, ctx.userId)` - - `requirePermission(Permissions.ADAPTIVE_PRACTICE_READ)` - - 组件:`PracticeSessionView` / `PracticeServiceProvider` - - 路由参数:`sessionId` - - `notFound()` 兜底 -- **ARCHITECTURE.md 契约**:❌ +- **page.tsx**:引入 `StudentLearningPathClient`,Suspense + `DetailPageSkeleton` ✅ +- **client 组件**:`features/student/learning-path/learning-path-client.tsx`(224 行)✅ +- **API hook**:`useLearningPath(subjectId)`(lib/api/student.ts)✅ +- **GraphQL operation**:`query MyLearningPath($subjectId: ID!)`(student.graphql.ts L37) +- **mock 数据**:❌ **缺失** — graphql-data.ts 中无 `case "MyLearningPath"` 处理器 +- **运行时影响**:MSW 未命中 → 返回 `{ data: null }` → 页面显示空态 +- **需补全**:在 graphql-data.ts `graphqlResponse()` 中添加 `case "MyLearningPath"` 返回 `mockLearningPath` 数据 --- -### 2.15 elective 模块(2 页规划,0 已完成,2 缺失) +### 2.15 practice 模块(2 页,✅ 全部完整) -#### 2.15.1 ❌ `/shell/student/elective`(选课列表,缺失) +#### 2.15.1 ✅ `/shell/student/practice`(自适应练习首页) -- **CICD 参考实现**:`e:\desktop\CICD\src\app\(dashboard)\student\elective\page.tsx` -- **功能描述**: - - 上下两段式布局: - 1. **我的选课**(`MySelectionsLoader`,Suspense 流式加载) - 2. **可选课程**(`AvailableCoursesLoader`,Suspense 流式加载 + searchParams 过滤) - - 学生身份校验 -- **技术栈**: - - Server Component + `force-dynamic` - - `requirePermission(Permissions.ELECTIVE_READ)` - - 子组件:`MySelectionsLoader` / `AvailableCoursesLoader` / `MySelectionsSkeleton` / `AvailableCoursesSkeleton`(均在 `_components/` 下) - - `Suspense` 双段流式渲染 - - searchParams 透传到 `AvailableCoursesLoader` -- **ARCHITECTURE.md 契约**:❌ +- **page.tsx**:引入 `StudentPracticeListClient`,Suspense + `ListPageSkeleton` +- **client 组件**:`features/student/practice/practice-list-client.tsx`(507 行) +- **API hook**:`useStudentPractice()` + `useStartPracticeSession()`(lib/api/student-portal.ts) +- **mock 数据**:✅ `case "GetStudentPractice"` + `case "StartPracticeSession"`(L9639, L9673) +- **三态**:loading/error/empty 完整 -#### 2.15.2 ❌ `/shell/student/elective/[id]`(选课详情,缺失) +#### 2.15.2 ✅ `/shell/student/practice/[sessionId]`(练习会话详情页) -- **CICD 参考实现**:`e:\desktop\CICD\src\app\(dashboard)\student\elective\[id]\page.tsx` -- **功能描述**: - - 选修课程详情视图 - - **学生视角隐私保护**:只展示课程信息,**不展示选课名单**(`selections=[]`),`showEditButton=false` - - 通过 `ElectivePageLayout` + `ElectiveCourseDetail` 组合渲染 -- **技术栈**: - - Server Component + `force-dynamic` - - Server Action:`getElectiveCourseById(id)` - - `requirePermission(Permissions.ELECTIVE_READ)` - - 组件:`ElectiveCourseDetail` / `ElectivePageLayout` - - 路由参数:`id` - - `notFound()` 兜底 -- **ARCHITECTURE.md 契约**:❌ +- **page.tsx**:引入 `StudentPracticeSessionClient`,Suspense + `DetailPageSkeleton` +- **client 组件**:`features/student/practice/practice-session-client.tsx`(191 行) +- **API hook**:`useStudentPracticeSession(sessionId)` + `useSubmitPracticeAnswer()`(lib/api/student-portal.ts) +- **mock 数据**:✅ `case "GetStudentPracticeSession"` + `case "SubmitPracticeAnswer"`(L9641, L9675) +- **三态**:loading/error/empty 完整 --- -### 2.16 ai-tutor 模块(1 页规划,0 已完成,1 缺失) +### 2.16 elective 模块(2 页,🟡 列表页部分缺 mock / 详情页缺 mock) -#### 2.16.1 ❌ `/shell/student/ai-tutor`(AI 辅导,缺失)⚠️ B3 末 +#### 2.16.1 🟡 `/shell/student/elective`(选课列表页) -- **CICD 参考实现**:❌ CICD 无对应源页面 -- **功能描述**: - - 学生专属 AI 辅导对话(区别于通用 AI 聊天) - - 应基于学生学情(薄弱点、近期错题、当前进度)提供个性化辅导 - - 可能的形态:聊天对话框 + 上下文卡片(学情摘要) -- **技术栈建议**:参考 CICD `learning/study-path/page.tsx` 的 `AiStudyPath` + `AiClientProvider` 模式 + `requirePermission(Permissions.AI_CHAT)` -- **ARCHITECTURE.md 契约**:❌ -- **批次**:B3 末 +- **page.tsx**:引入 `StudentElectiveListClient`,Suspense + `ListPageSkeleton` ✅ +- **client 组件**:`features/student/elective/elective-list-client.tsx`(636 行)✅ +- **API hook**:`useElectiveCourses(termId)` + `useStudentSelections(termId)` + `useEnrollCourse()` + `useDropCourse()`(lib/api/student.ts)✅ +- **GraphQL operations**:`query ElectiveCourses` + `query StudentSelections`(student.graphql.ts L53, L87) +- **mock 数据**: + - ✅ `case "ElectiveCourses"`(graphql-data.ts L9681)→ 可选课程列表有数据 + - ✅ `case "EnrollCourse"` + `case "DropCourse"`(L9683, L9685)→ 选退课 mutation 有数据 + - ❌ **缺失** `case "StudentSelections"` → 学生已选课程列表无数据 +- **运行时影响**:可选课程正常展示,但"我的选课"区域为空 +- **需补全**:在 graphql-data.ts 添加 `case "StudentSelections"` 返回学生已选课程 mock 数据 + +#### 2.16.2 🟡 `/shell/student/elective/[id]`(选课详情页) + +- **page.tsx**:引入 `StudentElectiveDetailClient`,Suspense + `DetailPageSkeleton` ✅ +- **client 组件**:`features/student/elective/elective-detail-client.tsx`(187 行)✅ +- **API hook**:`useStudentElectiveDetail(id)`(lib/api/student-portal.ts)✅ +- **GraphQL operation**:`query GetStudentElectiveDetail($id: ID!)`(student.graphql.ts L439) +- **mock 数据**:❌ **缺失** — graphql-data.ts 中无 `case "GetStudentElectiveDetail"` 处理器 +- **运行时影响**:MSW 未命中 → 返回 `{ data: null }` → 页面显示空态 +- **需补全**:在 graphql-data.ts 添加 `case "GetStudentElectiveDetail"` 返回 `mockStudentElectiveDetail` 数据 --- -### 2.17 announcements 模块(共享 2 页规划,0 已完成,2 缺失) +### 2.17 diagnostic 模块(1 页,🟡 页面完整但 mock handler 名不匹配) -#### 2.17.1 ❌ `/shell/announcements`(公告列表,缺失,共享) +#### 2.17.1 🟡 `/shell/student/diagnostic`(诊断报告页) -- **CICD 参考实现**:❌ CICD `student/` 下无对应源页面(应在 `(dashboard)/` 顶层共享路由中,但本次未读取) -- **功能描述**:公告列表(学生可见范围) -- **ARCHITECTURE.md 契约**:❌(列表未定义) -- **批次**:B3,共享路由 - -#### 2.17.2 ❌ `/shell/announcements/[id]`(公告详情,缺失,共享) - -- **CICD 参考实现**:❌ 同上 -- **功能描述**:单条公告详情 -- **ARCHITECTURE.md 契约**:❌ -- **批次**:B3,共享路由 - -> **注**:portal-shell 当前 `/shell/announcements/*` 路由不存在。需在共享层(非 student/ 下)创建。 +- **page.tsx**:引入 `StudentSelfDiagnosticClient`,Suspense + `DetailPageSkeleton` ✅ +- **client 组件**:`features/student/diagnostic/diagnostic-client.tsx`(603 行)✅ +- **API hook**:`useStudentSelfDiagnostic()`(lib/api/student-portal.ts)✅ +- **GraphQL operation**:`query GetStudentSelfDiagnostic`(student.graphql.ts L711) +- **mock 数据**:❌ **名称不匹配** — graphql-data.ts 中有 `case "GetStudentDiagnostic"`(L9804),但 hook 发送的 operationName 是 `GetStudentSelfDiagnostic` +- **运行时影响**:MSW case 不匹配 → 返回 `{ data: null }` → 页面显示空态 +- **需补全**:在 graphql-data.ts 将 `case "GetStudentDiagnostic"` 改为 `case "GetStudentSelfDiagnostic"`(或新增同名 case) --- -### 2.18 messages 模块(共享 1 页规划,0 已完成,1 缺失) +### 2.18 ai-tutor 模块(1 页,✅ 完整) -#### 2.18.1 ❌ `/shell/messages`(消息中心,缺失,共享)⚠️ B3 末 +#### 2.18.1 ✅ `/shell/student/ai-tutor`(AI 辅导页) -- **CICD 参考实现**:❌ CICD `student/` 下无对应源页面 -- **功能描述**:站内消息中心(学生收件箱) -- **ARCHITECTURE.md 契约**:❌ -- **批次**:B3 末,共享路由 - -> **注**:portal-shell 当前 `/shell/messages` 路由不存在。 +- **page.tsx**:引入 `StudentAiTutorClient`,Suspense + `DetailPageSkeleton` +- **client 组件**:`features/student/ai-tutor/ai-tutor-client.tsx`(241 行) +- **API hook**:`useAiTutorSessions(limit)` + `useSendAiTutorMessage()`(lib/api/student.ts) +- **mock 数据**:✅ `case "AiTutorSessions"` + `case "SendAiTutorMessage"`(graphql-data.ts L9687, L9689) +- **三态**:loading/error/empty 完整 --- -### 2.19 leave 模块(1 页规划,0 已完成,1 缺失) +### 2.19 leave 模块(1 页,✅ 完整) -#### 2.19.1 ❌ `/shell/student/leave`(学生在线请假,缺失) +#### 2.19.1 ✅ `/shell/student/leave`(在线请假页) -- **CICD 参考实现**:`e:\desktop\CICD\src\app\(dashboard)\student\leave\page.tsx` -- **功能描述**: - - 顶部:在线请假表单(`LeaveRequestForm`,`defaultStudentId` + `defaultClassId` 自动填入当前活跃班级) - - 底部:本人提交的请假申请列表(`LeaveRequestList`) - - 返回仪表盘的面包屑 - - 无活跃班级时显示提示卡片 -- **技术栈**: - - Server Component + `force-dynamic` - - Server Actions:`getStudentActiveClass(ctx.userId)` + `getLeaveRequests({ scope, currentUserId, page: 1, pageSize: 50 })` - - `getAuthContext()`(注意:未用 `requirePermission`,直接取 ctx) - - 组件:`LeaveRequestForm` / `LeaveRequestList` / `Card` / `Button` - - **scope 处理**:学生 scope 为 `class_members`,`buildScopeFilter` 返回 `1=0`,但通过 `currentUserId = requesterId` 过滤本人记录 -- **ARCHITECTURE.md 契约**:❌ - ---- - -### 2.20 diagnostic 模块(CICD 额外页面,ARCHITECTURE.md 未规划) - -#### 2.20.1 ⚠️ CICD `diagnostic/page.tsx`(学生诊断报告,ARCHITECTURE.md 未列入 36 页) - -- **CICD 参考实现**:`e:\desktop\CICD\src\app\(dashboard)\student\diagnostic\page.tsx` -- **功能描述**: - - 学生诊断学情(`getStudentMasterySummary`)+ 已发布诊断报告列表(`getDiagnosticReports`) - - `StudentDiagnosticView` 渲染 - - 学生仅可见 `status: "published"` 报告(防草稿泄露) -- **技术栈**:Server Component + `requirePermission(Permissions.DIAGNOSTIC_READ)` -- **ARCHITECTURE.md 契约**:❌ 未列入 §9.2 36 页清单 -- **建议**:若产品确认学生端诊断报告功能保留,需补充到 ARCHITECTURE.md §9.2;否则可在 dashboard/weakness 中替代承载 +- **page.tsx**:引入 `StudentLeaveClient`,Suspense + `DetailPageSkeleton` +- **client 组件**:`features/student/leave/leave-client.tsx`(115 行)+ `leave-request-form.tsx`(202 行)+ `leave-request-list.tsx`(253 行) +- **API hook**:`useStudentLeave(page, pageSize)` + `useSubmitLeaveRequest()` + `useStudentClasses()`(lib/api/student-portal.ts) +- **mock 数据**:✅ `case "GetStudentLeave"` + `case "SubmitLeaveRequest"`(graphql-data.ts L9643, L9677) +- **三态**:loading/error/empty 完整 --- ## 三、按模块汇总 -| 模块 | 规划页数 | 已完成 | 缺失 | 备注 | -| --------------------- | -------- | ------ | ------ | -------------------------------------------------------------- | -| dashboard | 3 | 1 | 2 | 仪表盘基础页有,trend/weakness 详情页缺 | -| grades | 2 | 0 | 2 | 列表 + 报告卡 | -| exams | 3 | 0 | 3 | ⚠️ CICD 无源页面,take 工作台最关键 | -| homework | 3 | 0 | 3 | ⚠️ CICD 路由为 `learning/assignments/*`,需迁移到 `homework/*` | -| schedule | 1 | 0 | 1 | | -| attendance | 1 | 0 | 1 | | -| classes | 1 | 0 | 1 | CICD 无独立路由(融合在 courses) | -| courses | 2 | 0 | 2 | CICD 路由为 `learning/courses/*` | -| course-plans | 2 | 0 | 2 | | -| lesson-plans | 2 | 0 | 2 | | -| textbooks | 2 | 0 | 2 | CICD 路由为 `learning/textbooks/*`,需对齐 `/[id]/chapters` | -| error-book | 1 | 0 | 1 | 含 AI 上下文 + 客户端列表 | -| learning | 2 | 0 | 2 | 学习中心首页 + AI 学习路径 | -| practice | 2 | 0 | 2 | 自适应练习 | -| elective | 2 | 0 | 2 | 含 Suspense 双段流式渲染 | -| ai-tutor | 1 | 0 | 1 | ⚠️ B3 末,CICD 无源页面 | -| announcements(共享) | 2 | 0 | 2 | 共享路由,非 student/ 下 | -| messages(共享) | 1 | 0 | 1 | ⚠️ B3 末,共享路由 | -| leave | 1 | 0 | 1 | | -| **合计** | **34** | **1** | **33** | 不含 notifications/settings 共享路由 | +| 模块 | 页数 | ✅ 完整 | 🟡 mock 缺口 | 备注 | +| ------------- | ------ | ------- | ------------ | -------------------------------------------------------------------------------- | +| dashboard | 3 | 3 | 0 | 仪表盘 + trend + weakness 全部完成 | +| grades | 2 | 2 | 0 | 列表 + 报告卡(含 3 个子组件 + 打印 CSS) | +| exams | 3 | 3 | 0 | 列表 + result + take 工作台(665 行最复杂之一) | +| homework | 3 | 3 | 0 | 列表 + submit 工作台(698 行最大文件)+ analysis | +| schedule | 1 | 1 | 0 | | +| attendance | 1 | 1 | 0 | | +| classes | 1 | 1 | 0 | | +| courses | 2 | 2 | 0 | | +| course-plans | 2 | 2 | 0 | | +| lesson-plans | 2 | 2 | 0 | | +| textbooks | 2 | 2 | 0 | | +| error-book | 1 | 1 | 0 | 列表 + 详情弹窗 | +| learning | 1 | 1 | 0 | | +| learning-path | 1 | 0 | 1 | ❌ 缺 `MyLearningPath` mock handler | +| practice | 2 | 2 | 0 | | +| elective | 2 | 0 | 2 | ❌ 列表缺 `StudentSelections` mock;详情缺 `GetStudentElectiveDetail` mock | +| ai-tutor | 1 | 1 | 0 | | +| leave | 1 | 1 | 0 | 表单 + 列表分页 | +| diagnostic | 1 | 0 | 1 | ❌ mock handler 名不匹配(`GetStudentDiagnostic` vs `GetStudentSelfDiagnostic`) | +| **合计** | **32** | **28** | **4** | 28 页完全可用,4 页存在 mock 缺口 | -> **加上 diagnostic(CICD 额外页)共 35**;ARCHITECTURE.md §9.2 规划 36 页(含 notifications/settings 共享路由)。 +> **结论**:32 个页面在结构上全部完整(page.tsx + client 组件 + API hook + 三态处理),其中 28 页 mock 数据齐全可完整运行,4 页存在 mock 数据缺口需补齐。 --- -## 四、关键技术差异 & 迁移注意事项 +## 四、关键技术实现差异 & 迁移完成情况 -### 4.1 架构模式差异(最重要) +### 4.1 架构模式迁移(已全部完成) -| 维度 | CICD(参考) | portal-shell(目标) | -| -------- | -------------------------------------- | ------------------------------------------------------------------- | -| 渲染模式 | Server Component 为主 | 当前仪表盘是 Client Component,需确定后续页面模式 | -| 数据获取 | Server Action + Drizzle 直查 DB | **必须改为** BFF / 微服务 API(`teacher-bff` / `data-ana` 等) | -| 权限校验 | `requirePermission` + `getAuthContext` | 需通过 Gateway + BFF 鉴权,前端用 `usePermission().hasPermission()` | -| 国际化 | `next-intl` `getTranslations` | portal-shell 当前仪表盘硬编码中文,需统一策略 | -| 共享 UI | `@/shared/components/ui/*` | `@/shared/components/ui/*`(已迁移部分) | -| 模块组件 | `@/modules//components/*` | 需在 portal-shell 重建或下沉到 BFF | +| 维度 | CICD(参考) | portal-shell(当前) | 迁移状态 | +| -------- | -------------------------------------- | ----------------------------------------------------------------------- | --------- | +| 渲染模式 | Server Component 为主 | Server Component 入口 + Client Component 业务 | ✅ 已统一 | +| 数据获取 | Server Action + Drizzle 直查 DB | `useWidgetQuery` Hook → Apollo Client → BFF / 微服务 | ✅ 已迁移 | +| 国际化 | `next-intl` `getTranslations` | `next-intl` `useTranslations`(全部页面接入) | ✅ 已统一 | +| 三态规范 | 各页不一致 | 统一 loading(Skeleton)/ error(局部降级)/ empty(EmptyState) | ✅ 已统一 | +| 权限校验 | `requirePermission` + `getAuthContext` | 前端 `usePermission().hasPermission()`,后端 Gateway + BFF | ✅ 已迁移 | +| 共享 UI | `@/modules//components/*` | `@/features/student//*-client.tsx` + `@/shared/components/ui/*` | ✅ 已迁移 | -### 4.2 路由路径对齐 +### 4.2 路由路径对齐(已全部完成) -| CICD 路径 | ARCHITECTURE.md §9.2 目标路径 | 备注 | -| -------------------------------------------- | ----------------------------- | --------------------------- | -| `learning/assignments` | `homework` | 提升到顶层 | -| `learning/assignments/[assignmentId]` | `homework/[id]/submit` | 路径参数改名 | -| `learning/assignments/[assignmentId]/result` | `homework/[id]/analysis` | 子路径改名 | -| `learning/courses` | `courses` | 提升到顶层 | -| `learning/courses/[classId]` | `courses/[id]` | 参数名 `classId` → `id` | -| `learning/textbooks` | `textbooks` | 提升到顶层 | -| `learning/textbooks/[id]` | `textbooks/[id]/chapters` | 增加子路径 `chapters` | -| `learning/study-path` | `learning-path` | 提升到顶层 | -| `(无)` | `exams/*` | 全新设计 | -| `(无)` | `ai-tutor` | 全新设计 | -| `(无)` | `classes` | 全新设计(与 courses 区分) | +| CICD 路径 | 目标路由 | 迁移状态 | +| -------------------------------------------- | ------------------------- | -------- | +| `learning/assignments` | `homework` | ✅ | +| `learning/assignments/[assignmentId]` | `homework/[id]/submit` | ✅ | +| `learning/assignments/[assignmentId]/result` | `homework/[id]/analysis` | ✅ | +| `learning/courses` | `courses` | ✅ | +| `learning/courses/[classId]` | `courses/[id]` | ✅ | +| `learning/textbooks` | `textbooks` | ✅ | +| `learning/textbooks/[id]` | `textbooks/[id]/chapters` | ✅ | +| `learning/study-path` | `learning-path` | ✅ | +| `(无)` | `exams/*` | ✅ 全新 | +| `(无)` | `ai-tutor` | ✅ 全新 | +| `(无)` | `classes` | ✅ 全新 | -### 4.3 权限点映射 +### 4.3 共享组件清单(已全部建立) -CICD 中学生页面用到的权限点(迁移时需在 portal-shell `Permissions` 常量中对应): +原报告列出的 40+ 共享组件已全部在 portal-shell 中以 client 组件形式实现: -- `GRADE_RECORD_READ`(grades / report-card) -- `CLASS_READ`(schedule / courses) -- `ATTENDANCE_READ`(attendance) -- `COURSE_PLAN_READ`(course-plans) -- `LESSON_PLAN_READ`(lesson-plans) -- `TEXTBOOK_READ`(textbooks) -- `ERROR_BOOK_READ`(error-book) -- `AI_CHAT`(study-path / ai-tutor) -- `ADAPTIVE_PRACTICE_READ`(practice) -- `ELECTIVE_READ`(elective) -- `DIAGNOSTIC_READ`(diagnostic,CICD 额外) -- leave 用 `getAuthContext` 而非 `requirePermission`(需统一) - -### 4.4 共享组件清单(需在 portal-shell 补齐或确认已有) - -- `DashboardShell` / `DashboardSection` / `StatCard`(仪表盘已有) -- `Card` / `CardContent` / `CardHeader` / `CardTitle`(基础) -- `Button` / `Badge` / `EmptyState` / `Skeleton`(基础) -- `StatsGrid`(多列统计,error-book/practice 用) -- `WidgetBoundary`(widget 降级容错) -- `StatusBadge` + `variantMap`(作业状态徽章) -- `TextbookReader` / `TextbookCard` / `TextbookFilters`(教材相关) -- `LessonPlanReadonlyView` / `LessonPlanList`(教案相关) -- `CoursePlanList` / `CoursePlanDetail`(课程计划) -- `StudentScheduleView` / `StudentScheduleFilters`(课表) -- `StudentCoursesView` / `CourseFilters`(课程) -- `StudentAttendanceView`(考勤) -- `StudentGradeSummary` / `GradeTrendCard` / `RankingTrendCard` / `GradeDistributionChart` / `GrowthArchiveChart` / `ReportCardView` / `ReportCardPrintAction` / `StudentGradeFilters`(成绩,组件最多) -- `HomeworkTakeView` / `HomeworkReviewView` / `HomeworkSubmissionResult` / `AssignmentFilters`(作业,⚠️ take 是最复杂) -- `PracticeStarterWithNav` / `PracticeHistory` / `PracticeSessionView` / `PracticeServiceProvider`(练习) -- `ElectiveCourseDetail` / `ElectivePageLayout` / `MySelectionsLoader` / `AvailableCoursesLoader`(选课) -- `LeaveRequestForm` / `LeaveRequestList`(请假) -- `StudentErrorBookListClient` / `AddErrorBookDialogWithQuestions` / `ErrorBookFilters`(错题本) -- `AiStudyPath` / `AiClientProvider` / `createCoreAiClientService`(AI) - -### 4.5 B3 实施优先级建议 - -按用户价值 + 实现复杂度排序: - -1. **P0(必做,核心学习闭环)**:homework 列表 + submit 工作台 + analysis、exams 列表 + take 工作台 + result -2. **P1(高频查看)**:grades 列表 + report-card、schedule、attendance、error-book -3. **P2(学习资源)**:learning 首页、courses 列表 + 详情、textbooks 列表 + chapters、course-plans + 详情、lesson-plans + view -4. **P3(辅助功能)**:classes、practice + session、elective + 详情、leave -5. **P4(增强)**:dashboard trend/weakness 详情页、learning-path -6. **P5(B3 末)**:ai-tutor、messages -7. **共享**:announcements 列表 + 详情 +- `ListPageShell` / `DetailPageShell` / `WorkbenchPageShell`(page-templates) +- `ListPageSkeleton` / `DetailPageSkeleton` / `WorkbenchPageSkeleton`(骨架) +- `EmptyState`(空态) +- `Button` / `Badge` / `Card` 等 UI 基础组件(`@/shared/components/ui/*`) +- 各域专用组件(`@/features/student//*-client.tsx`) --- -## 五、CICD 与 ARCHITECTURE.md §9.2 对照表 +## 五、CICD 与 ARCHITECTURE.md §9.2 对照表(2026-08-04 核查) -| ARCH §9.2 源路由 | ARCH §9.2 目标路由 | CICD 是否有源页面 | portal-shell 是否已有 | 契约状态 | 备注 | -| --------------------------- | ------------------------------------------- | -------------------------------------- | --------------------- | --------------------- | -------------------------------- | -| `dashboard` | `/shell/student` | ✅ | ✅ | ✅ `studentDashboard` | 已完成基础版 | -| `/trend` | `/shell/student/trend` | ❌(融合在 dashboard) | ❌ | ✅ `learningTrend` | 需新建详情页 | -| `/weakness` | `/shell/student/weakness` | ❌(融合在 dashboard) | ❌ | ✅ `studentWeakness` | 需新建详情页 | -| `my-grades` | `/shell/student/grades` | ✅ | ❌ | ❌ | | -| `/report-card` | `/shell/student/grades/report-card` | ✅ | ❌ | ❌ | | -| `my-exams` | `/shell/student/exams` | ❌ | ❌ | 🟡 | CICD 无,全新设计 | -| `/[id]/result` | `/shell/student/exams/[id]/result` | ❌ | ❌ | 🟡 | 参考作业 result | -| `/[id]/take` | `/shell/student/exams/[id]/take` | ❌ | ❌ | 🟡 | ⚠️ 最关键,参考 HomeworkTakeView | -| `my-homework` | `/shell/student/homework` | ✅(learning/assignments) | ❌ | 🟡 | 路径迁移 | -| `/[id]/submit` | `/shell/student/homework/[id]/submit` | ✅(learning/assignments/[id]) | ❌ | 🟡 | ⚠️ take 工作台 | -| `/[id]/analysis` | `/shell/student/homework/[id]/analysis` | ✅(learning/assignments/[id]/result) | ❌ | 🟡 | | -| `schedule` | `/shell/student/schedule` | ✅ | ❌ | ❌ | | -| `my-attendance` | `/shell/student/attendance` | ✅ | ❌ | ❌ | | -| `my-classes` | `/shell/student/classes` | ❌(融合在 courses) | ❌ | ❌ `myClasses` | 全新设计 | -| `courses` | `/shell/student/courses` | ✅(learning/courses) | ❌ | ❌ | 路径迁移 | -| `/[id]` | `/shell/student/courses/[id]` | ✅(learning/courses/[classId]) | ❌ | ❌ | 参数名 classId→id | -| `course-plans` | `/shell/student/course-plans` | ✅ | ❌ | ❌ | | -| `/[id]` | `/shell/student/course-plans/[id]` | ✅ | ❌ | ❌ | | -| `lesson-plans` | `/shell/student/lesson-plans` | ✅ | ❌ | ❌ | | -| `/[id]/view` | `/shell/student/lesson-plans/[planId]/view` | ✅ | ❌ | ❌ | | -| `textbooks` | `/shell/student/textbooks` | ✅(learning/textbooks) | ❌ | 🟡 | 路径迁移 | -| `/[id]/chapters` | `/shell/student/textbooks/[id]/chapters` | ✅(learning/textbooks/[id]) | ❌ | 🟡 | 增加子路径 | -| `error-book` | `/shell/student/error-book` | ✅ | ❌ | ✅ | 含 AI 上下文 | -| `learning` | `/shell/student/learning` | ✅ | ❌ | ❌ | | -| `learning-path` | `/shell/student/learning-path` | ✅(learning/study-path) | ❌ | ❌ | 路径迁移 | -| `practice` | `/shell/student/practice` | ✅ | ❌ | ❌ | | -| `/[sessionId]` | `/shell/student/practice/[sessionId]` | ✅ | ❌ | ❌ | | -| `elective` | `/shell/student/elective` | ✅ | ❌ | ❌ | | -| `/[id]` | `/shell/student/elective/[id]` | ✅ | ❌ | ❌ | | -| `ai-tutor` | `/shell/student/ai-tutor` | ❌ | ❌ | ❌ | B3 末,全新设计 | -| `announcements` | `/shell/announcements`(共享) | ❌(不在 student/ 下) | ❌ | ❌ | 共享路由 | -| `/[id]` | `/shell/announcements/[id]`(共享) | ❌ | ❌ | ❌ | 共享路由 | -| `messages` | `/shell/messages`(共享) | ❌ | ❌ | ❌ | B3 末,共享路由 | -| `leave` | `/shell/student/leave` | ✅ | ❌ | ❌ | | -| `notifications`、`settings` | 共享路由 | — | ❌ | ✅ | B1/B3,不在 36 页内 | -| `diagnostic`(CICD 额外) | —(未列入 §9.2) | ✅ | ❌ | — | 需产品决策是否保留 | +| ARCH §9.2 目标路由 | CICD 源页面 | portal-shell 实现状态 | mock 数据状态 | 备注 | +| ------------------------------------------- | ----------- | --------------------- | ------------- | ------------------ | +| `/shell/student` | ✅ | ✅ 完整 | ✅ | 仪表盘 | +| `/shell/student/trend` | ❌ | ✅ 完整 | ✅ | 全新设计 | +| `/shell/student/weakness` | ❌ | ✅ 完整 | ✅ | 全新设计 | +| `/shell/student/grades` | ✅ | ✅ 完整 | ✅ | | +| `/shell/student/grades/report-card` | ✅ | ✅ 完整 | ✅ | 含打印 CSS | +| `/shell/student/exams` | ❌ | ✅ 完整 | ✅ | 全新设计 | +| `/shell/student/exams/[id]/result` | ❌ | ✅ 完整 | ✅ | 全新设计 | +| `/shell/student/exams/[id]/take` | ❌ | ✅ 完整 | ✅ | 665 行,最复杂之一 | +| `/shell/student/homework` | ✅ | ✅ 完整 | ✅ | | +| `/shell/student/homework/[id]/submit` | ✅ | ✅ 完整 | ✅ | 698 行,最大文件 | +| `/shell/student/homework/[id]/analysis` | ✅ | ✅ 完整 | ✅ | | +| `/shell/student/schedule` | ✅ | ✅ 完整 | ✅ | | +| `/shell/student/attendance` | ✅ | ✅ 完整 | ✅ | | +| `/shell/student/classes` | ❌ | ✅ 完整 | ✅ | 全新设计 | +| `/shell/student/courses` | ✅ | ✅ 完整 | ✅ | | +| `/shell/student/courses/[id]` | ✅ | ✅ 完整 | ✅ | | +| `/shell/student/course-plans` | ✅ | ✅ 完整 | ✅ | | +| `/shell/student/course-plans/[id]` | ✅ | ✅ 完整 | ✅ | | +| `/shell/student/lesson-plans` | ✅ | ✅ 完整 | ✅ | | +| `/shell/student/lesson-plans/[planId]/view` | ✅ | ✅ 完整 | ✅ | | +| `/shell/student/textbooks` | ✅ | ✅ 完整 | ✅ | | +| `/shell/student/textbooks/[id]/chapters` | ✅ | ✅ 完整 | ✅ | | +| `/shell/student/error-book` | ✅ | ✅ 完整 | ✅ | | +| `/shell/student/learning` | ✅ | ✅ 完整 | ✅ | | +| `/shell/student/learning-path` | ✅ | ✅ 完整 | ❌ | **缺 mock** | +| `/shell/student/practice` | ✅ | ✅ 完整 | ✅ | | +| `/shell/student/practice/[sessionId]` | ✅ | ✅ 完整 | ✅ | | +| `/shell/student/elective` | ✅ | ✅ 完整 | 🟡 | **部分缺 mock** | +| `/shell/student/elective/[id]` | ✅ | ✅ 完整 | ❌ | **缺 mock** | +| `/shell/student/ai-tutor` | ❌ | ✅ 完整 | ✅ | 全新设计 | +| `/shell/student/leave` | ✅ | ✅ 完整 | ✅ | | +| `/shell/student/diagnostic`(CICD 额外) | ✅ | ✅ 完整 | ❌ | **mock 名不匹配** | --- ## 六、风险与建议 -### 6.1 高风险点 +### 6.1 当前风险点 -1. **exams/[id]/take 作答工作台**:CICD 完全无源可参考(仅有 `HomeworkTakeView` 同构参考),需从零设计考试特性(倒计时、断网恢复、批量提交、防作弊等) -2. **路由路径大调整**:6 类路径需迁移(learning/* → 顶层),需同步更新导航、面包屑、内链 -3. **架构模式转变**:portal-shell 已转向 BFF + 微服务,**不能照搬 CICD 的 Server Action + Drizzle 直查模式**,每个页面都需重新设计数据获取链路(BFF → data-ana / core-edu 等服务) -4. **国际化缺失**:当前仪表盘硬编码中文,36 页全部完成后需补 i18n,建议从一开始就用 `next-intl` -5. **权限校验缺失**:当前仪表盘无 `requirePermission`,需建立 portal-shell 学生域权限校验中间件/装饰器 +1. **Mock 数据缺口(4 页)**:learning-path / elective/[id] / diagnostic / elective(部分)的 MSW handler 缺失或名称不匹配,开发环境下这 4 页将显示空态而非 mock 数据。详见 §七。 +2. **契约全部 @contract-pending**:32 页的 GraphQL 查询字段在 schema 中均未定义根字段,全部依赖 MSW 兜底。后端补齐 schema 后需逐页切换 fetcher。 +3. **权限校验待确认**:前端 client 组件未见 `usePermission().hasPermission()` 调用,权限校验可能依赖 Gateway + BFF 层(需确认后端实现)。 ### 6.2 建议 -1. **先建契约**:开工前先在 `shared-proto` 中补齐 §9.2 标 ❌ / 🟡 的契约(特别是 exams 系列、my-classes、homework 等) -2. **统一渲染模式**:建议学生页面统一采用 Server Component + BFF 数据获取(与 CICD 一致),避免当前仪表盘的 Client Component 模式 -3. **统一权限模型**:建立 portal-shell 学生域 `RequirePermission` 装饰器/中间件,覆盖所有页面 -4. **共享组件下沉**:CICD 中 `@/modules//components/*` 的视图组件需评估是下沉到 BFF 返回结构化数据,还是在 portal-shell 重建 -5. **CICD diagnostic 决策**:产品需确认是否保留学生端诊断报告功能,若保留需补充 ARCHITECTURE.md §9.2 -6. **分批推进**:按本报告 §4.5 的 P0→P5 优先级分批实施,每批完成后跑 `pnpm run arch:scan` 同步 arch.db +1. **优先补齐 mock 缺口**:按 §七 列出的 4 个 mock 缺口补齐 graphql-data.ts 中的 case handler,使全部 32 页在开发环境可完整运行。 +2. **契约补齐计划**:按 §9.2 优先级(P0 exams/homework → P1 grades/schedule → P2 其余)逐步在 shared-proto / BFF schema 中补齐 `studentXxx` 根字段。 +3. **diagnostic 决策**:ARCHITECTURE.md §9.2 未将 diagnostic 列入 36 页清单,但页面已实现。需产品确认是否保留并补充到 §9.2。 +4. **权限点映射**:确认前端是否需要在 client 组件中添加 `usePermission().hasPermission()` 调用,或完全依赖 Gateway 层鉴权。 --- -**分析完成。共发现 35 个待完成页面(含 CICD 额外的 diagnostic 1 页),其中 33 个完全缺失、2 个为 dashboard 详情页缺失。最关键的 exams/[id]/take 作答工作台在 CICD 中无源页面,需重点设计。** +## 七、Mock 数据缺口清单(需补齐) + +以下 4 个页面的 page.tsx + client 组件 + API hook 均已完整实现,但 `src/mocks/graphql-data.ts` 中缺少对应的 MSW case handler,导致开发环境下页面无法展示 mock 数据: + +| # | 页面 | GraphQL operationName | student.graphql.ts 位置 | graphql-data.ts 状态 | 影响 | +| --- | --------------------------------- | -------------------------- | ----------------------- | ----------------------------------------------------- | -------------------------------- | +| 1 | `/shell/student/learning-path` | `MyLearningPath` | L37 | ❌ 无 case | 学习路径页全空 | +| 2 | `/shell/student/elective/[id]` | `GetStudentElectiveDetail` | L439 | ❌ 无 case | 选课详情页全空 | +| 3 | `/shell/student/diagnostic` | `GetStudentSelfDiagnostic` | L711 | ❌ case 名不匹配(现有 `GetStudentDiagnostic` L9804) | 诊断报告页全空 | +| 4 | `/shell/student/elective`(部分) | `StudentSelections` | L87 | ❌ 无 case | "我的选课"区域空(可选课程正常) | + +### 补齐方案 + +1. **`MyLearningPath`**:在 graphql-data.ts 添加 `case "MyLearningPath"` + 新建 `mockMyLearningPath` 数据(含 nodes/edges 结构) +2. **`GetStudentElectiveDetail`**:在 graphql-data.ts 添加 `case "GetStudentElectiveDetail"` + 新建 `mockStudentElectiveDetail` 数据(含课程详情 + 选课状态) +3. **`GetStudentSelfDiagnostic`**:将 graphql-data.ts L9804 的 `case "GetStudentDiagnostic"` 改为 `case "GetStudentSelfDiagnostic"`(或新增同名 case 保留原 case) +4. **`StudentSelections`**:在 graphql-data.ts 添加 `case "StudentSelections"` + 新建 `mockStudentSelections` 数据(含已选课程列表) + +--- + +## 八、与 2026-07-24 报告对比(变更总结) + +| 维度 | 2026-07-24 报告 | 2026-08-04 核查结果 | +| -------------- | -------------------------------- | ----------------------------------------------- | +| 完成页面数 | 1(仅 dashboard 基础版) | 32(全部页面结构完整) | +| 缺失页面数 | 34 | 0 | +| 部分完成页面数 | 1(dashboard 缺 trend/weakness) | 0(trend/weakness 已实现) | +| 架构模式 | Client Component + 硬编码中文 | Server Component + Client Component + next-intl | +| 路由路径对齐 | 6 类路径需迁移 | 全部已对齐 | +| 共享组件 | 40+ 组件需建立 | 全部已建立 | +| Mock 数据覆盖 | 未评估 | 28/32 页完整,4 页有缺口 | +| 最大风险 | 35 页未实现 | 4 页 mock 缺口(结构均完整) | + +> **总结**:原报告识别的 35 个待完成页面已全部实现。当前仅剩 4 个页面的 MSW mock 数据需补齐,不影响页面结构完整性,仅影响开发环境的 mock 数据展示。 + +--- + +**核查完成。32 个页面全部为真实业务实现(page.tsx + client 组件 + API hook + 三态处理),其中 28 页 mock 数据齐全可完整运行,4 页存在 mock 数据缺口需补齐(详见 §七)。** diff --git a/apps/portal-shell/docs/needtodo/teacher-NeedTodo.md b/apps/portal-shell/docs/needtodo/teacher-NeedTodo.md index 4bb135b..616bbd5 100644 --- a/apps/portal-shell/docs/needtodo/teacher-NeedTodo.md +++ b/apps/portal-shell/docs/needtodo/teacher-NeedTodo.md @@ -1,10 +1,10 @@ -# 教师域(Teacher)待完成功能分析 +# 教师域(Teacher)待完成功能分析 -> 参考项目:`e:\desktop\CICD\src\app\(dashboard)\teacher\`(53 个 page.tsx) -> 当前项目:`e:\Desktop\Edu\apps\portal-shell\src\app\shell\teacher\`(53 个 page.tsx) -> 规划依据:`apps\portal-shell\ARCHITECTURE.md` §9.1(教师域 56 页,B1+B2) -> 分析日期:2026-07-24 -> 分析方式:逐页面对比 CICD 实现 vs portal-shell 实现,逐个读取 page.tsx + features/*-client.tsx +> 参考项目:`e:\desktop\CICD\src\app\(dashboard)\teacher\`(CICD 原版,53 个 page.tsx) +> 当前项目:`e:\Desktop\Edu\apps\portal-shell\src\app\shell\teacher\`(63 个 page.tsx) +> 规划依据:`apps\portal-shell\ARCHITECTURE.md` §9.1(教师域 56 页规划,B1+B2+B2末) +> 分析日期:2026-08-04 +> 分析方式:逐页面读取 page.tsx 文件内容 + features/*-client.tsx 集成核查 + lib/api hooks + mocks/graphql-data.ts 覆盖核查 --- @@ -13,567 +13,467 @@ 1. CICD 单体(Next.js App Router + Server Actions + Drizzle)作为**功能基线**,反映"老版单体已实现"的教师功能完整态。 2. portal-shell 作为**目标态**,遵循 ARCH §9.1 的契约与批次规划(B1+B2+B2末)。 3. portal-shell 普遍采用「Server Component page.tsx 仅做 `` 边界包裹 + Client Component `*-client.tsx` 承载业务」的统一架构;CICD 多为「Server Component 直接拉数据 + Rich Client Component 渲染」。 -4. ARCH §9.1 规划 56 页,portal-shell 已实现 53 页,**3 页缺口** = `exams/[id]/proctoring`(B2末)+ `notifications`(B1 共享)+ `settings`(B1 共享)。 +4. ARCH §9.1 规划 56 页,portal-shell 已实现 **63 页**(含 5 个路径别名 `/classes/my`、`/classes/my/[id]`、`/exams/all`、`/exams/create`、`/homework/assignments/create` 复用同一 client,+ 3 个占位页 `/classes/students`、`/exams/grading`、`/exams/grading/[submissionId]`,+ 2 个 ARCH §9.1 原未规划但已补做 `/exams/[id]/proctoring`、`/diagnostic/student/[studentId]`)。 5. CICD 中 `exams/page.tsx`、`classes/page.tsx`、`exams/grading/*`、`exams/create` 等为**重定向/等价路径**,不算独立功能缺口。 -6. CICD 中 `diagnostic/student/[studentId]` 在 ARCH §9.1 中**未规划**(ARCH 仅规划 `diagnostic` + `diagnostic/class/[classId]` 共 2 页),属 CICD 历史功能,portal-shell 缺失但**不违反 ARCH**。 +6. CICD 中 `diagnostic/student/[studentId]` 在 ARCH §9.1 中**原未规划**(ARCH 仅规划 `diagnostic` + `diagnostic/class/[classId]` 共 2 页),属 CICD 历史功能,portal-shell 已补做(**待同步更新 ARCH §9.1 + 004 + arch.db**)。 + +### 0.1 三态分类严格定义(本次核查采用) + +| 状态 | 定义 | 判据 | +| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | +| ✅ 完整实现 | page.tsx 引入并渲染 `features/teacher/*-client.tsx` 客户端组件,或 page.tsx 自身包含真实业务逻辑(调用 lib/api hooks、渲染数据、处理 loading/error/empty 三态) | 文件内 `import` 了 `*-client.tsx` 并 `` 渲染;或直接调用 `useXxx` hook | +| 🟡 占位空态 | page.tsx 仅用 `ListPageShell` / `DetailPageShell` / `FormPageShell` / `WorkbenchPageShell` 等 page-templates 直接渲染空态(如 `empty` 属性或无 children 内容),**没有引入任何 `*-client.tsx`,也没有真实业务逻辑** | 文件内仅 `import { ListPageShell } from "@/shared/components/page-templates"`,无 client 组件 | +| ❌ 缺失 | 文件不存在 | 路径下无 page.tsx | + +> **重要说明**:本核查的"完整实现"指**页面骨架与数据流闭环**(client 组件已集成、hooks 已接入、三态已处理),**不代表与 CICD 功能完全对等**。与 CICD 的功能差距详见 §三.B(原 §3.4-§3.18 内容保留,归类为"功能差距"而非"占位空态")。 + +### 0.2 配套资产核查 + +| 资产 | 路径 | 覆盖情况 | +| ----------------------------- | -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| features/teacher/*-client.tsx | `src/features/teacher//` | **55 个页面级 client 组件**(不含 dashboard-cards-client 子组件),覆盖 20 个模块,详见 §七.2 | +| lib/api hooks | `src/lib/api/.ts` | **20 个模块文件**:ai/analytics/attendance/classes/course-plans/dashboard/diagnostic/elective/error-book/exams/grades/homework/knowledge-graph/leave/lesson-plans/practice/proctoring/questions/schedule-changes/students/textbooks(另含 admin/universal/sidebar/topbar/notifications/settings/profile 等共享域文件) | +| mocks/graphql-data.ts | `src/mocks/graphql-data.ts` | **269KB / 116 个 mock 数据集**(含教师域全部模块 + 学生/家长/管理域),所有占位页与功能差距页均有 MSW 兜底 | --- ## 一、页面完成度总览 -| 状态 | 数量 | 说明 | -| --------------------- | ---- | ---------------------------------------------------------------------------------------------------- | -| ✅ 已完成且功能完整 | 33 | 功能与 CICD 持平或为 ARCH 新建且实现完整(含 MSW 兜底契约页) | -| 🟡 已完成但功能不完整 | 14 | 页面存在但子功能/技术栈与 CICD 有差距 | -| ❌ 完全缺失 | 4 | `exams/[id]/proctoring`、`diagnostic/student/[studentId]`、`/shell/notifications`、`/shell/settings` | +| 状态 | 数量 | 说明 | +| ----------- | ------ | ---------------------------------------------------------------------------- | +| ✅ 完整实现 | **60** | page.tsx 集成 `*-client.tsx` 或自身含真实业务逻辑(lib/api hook + 三态处理) | +| 🟡 占位空态 | **3** | 仅用 page-templates 渲染空态,无 client 组件集成,无真实业务 | +| ❌ 完全缺失 | **0** | 教师域 63 个 page.tsx 全部存在 | -### ✅ 已完成且功能完整(33 页) +### 与原核查(2026-07-24)的差异 -| 模块 | 页面 | 备注 | -| ---------------- | -------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | -| dashboard | `/shell/teacher`(teacher/page.tsx) | 仪表盘,4 StatCard + 班级概况 + 预警(注:标题用硬编码中文,违反 i18n,但功能完整) | -| exams | `/shell/teacher/exams/[id]/analytics` | 汇总卡片 + 分数段分布图 + 每题正确率 + 学生排名,混合契约 ✅ `assignmentAnalysis` | -| homework | `/shell/teacher/homework/submissions/[submissionId]` | 单份提交批改,含题目分数/教师评语/AI 建议应用/上下导航/总反馈 | -| homework | `/shell/teacher/homework/submissions/[submissionId]/scan-grading` | 扫描批改 | -| homework | `/shell/teacher/homework/submissions` | 提交列表 | -| homework | `/shell/teacher/homework/assignments/[id]/submissions` | 作业子提交列表 | -| homework | `/shell/teacher/homework` | 作业列表 | -| grades | `/shell/teacher/grades` | 成绩列表 | -| grades | `/shell/teacher/grades/analytics` | 成绩分析,汇总 + 分布 + 排名,混合契约 | -| grades | `/shell/teacher/grades/stats` | 成绩统计 | -| grades | `/shell/teacher/grades/report-card` | 报告卡 | -| attendance | `/shell/teacher/attendance` | 考勤列表 | -| attendance | `/shell/teacher/attendance/report` | 考勤报告 | -| attendance | `/shell/teacher/attendance/stats` | 考勤统计 | -| classes | `/shell/teacher/classes` | 班级列表(含 gradeId/subjectId/q 筛选) | -| classes | `/shell/teacher/classes/schedule` | 班级课表 | -| students | (见 🟡 学生列表,因缺科目成绩) | — | -| course-plans | `/shell/teacher/course-plans`、`/[id]` | 2 页 | -| elective | `/shell/teacher/elective`、`/create`、`/[id]/edit` | 3 页 | -| error-book | `/shell/teacher/error-book` | ✅ `errorBookItems/Stats` 真实契约 | -| diagnostic | `/shell/teacher/diagnostic` | 诊断列表 | -| analytics | `/shell/teacher/analytics`、`/[studentId]` | ARCH 新建 2 页,MSW 兜底 | -| ai | `/shell/teacher/ai/ai-assist`、`/ai-lesson-plan`、`/ai-report` | ARCH 新建 B2末 3 页,全 MSW 兜底,工作台布局完整 | -| knowledge-graph | `/shell/teacher/knowledge-graph` | ARCH 新建 B2末,三栏工作台,`knowledgePoint(id)` ✅ 真实 + 列表 MSW | -| questions | `/shell/teacher/questions` | 题库列表,🟡 `question(id)` | -| textbooks | `/shell/teacher/textbooks`、`/[id]` | 2 页,🟡 `textbook(id)` | -| lesson-plans | `/shell/teacher/lesson-plans`、`/new`、`/calendar`、`/heatmap`、`/library` | 5 页(edit 见 🟡) | -| practice | `/shell/teacher/practice` | 练习 | -| leave | `/shell/teacher/leave` | 请假 | -| schedule-changes | `/shell/teacher/schedule-changes` | 调课 | +| 项目 | 原(07-24) | 现(08-04) | 变化 | +| -------- | ----------- | ----------- | ------------------------------------------------------------------------------------------------------------------------- | +| 总页面数 | 53 | 63 | +10(5 别名 + 3 占位 + 2 补做) | +| ✅ 完整 | 33 | 60 | +27(原 14 页"功能不完整"实为已集成 client 归入 ✅ + 新补做 2 页 + 新增别名 5 页 - 占位 3 页归 🟡 - 共享域 2 页非教师域) | +| 🟡 占位 | 14 | 3 | -11(原 14 页均集成 client 组件归 ✅;新增 3 个真占位页) | +| ❌ 缺失 | 4 | 0 | -4(proctoring 与 diagnostic/student 已补做;notifications/settings 属共享域非教师域核查范围) | -### 🟡 已完成但功能不完整(14 页) +### ✅ 完整实现(60 页) -详见 §三。 +按模块分布(含别名/复用): -### ❌ 完全缺失(4 页) +| 模块 | 页面数 | 页面路径 | +| ---------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| dashboard | 1 | `/shell/teacher`(page.tsx 含 useTeacherDashboard hook + 7 张卡片 + i18n + 三态) | +| ai | 3 | `/ai/ai-assist`、`/ai/ai-lesson-plan`、`/ai/ai-report`(全 MSW,B2末工作台) | +| analytics | 2 | `/analytics`、`/analytics/[studentId]`(MSW 兜底) | +| attendance | 4 | `/attendance`、`/report`、`/sheet`、`/stats` | +| classes | 5 | `/classes`、`/classes/my`(别名)、`/classes/[id]`、`/classes/my/[id]`(别名)、`/classes/schedule` | +| course-plans | 2 | `/course-plans`、`/course-plans/[id]` | +| diagnostic | 3 | `/diagnostic`、`/diagnostic/class/[classId]`、`/diagnostic/student/[studentId]`(已补做) | +| elective | 3 | `/elective`、`/elective/create`、`/elective/[id]/edit` | +| error-book | 1 | `/error-book`(✅ errorBookItems/Stats 真实契约) | +| exams | 9 | `/exams`、`/exams/all`(别名)、`/exams/new`、`/exams/create`(别名)、`/exams/[id]`、`/exams/[id]/analytics`、`/exams/[id]/build`、`/exams/[id]/edit`、`/exams/[id]/proctoring`(已补做) | +| grades | 5 | `/grades`、`/grades/analytics`、`/grades/entry`、`/grades/report-card`、`/grades/stats` | +| homework | 8 | `/homework`、`/homework/new`、`/homework/assignments/create`(别名)、`/homework/[id]`、`/homework/assignments/[id]/submissions`、`/homework/submissions`、`/homework/submissions/[submissionId]`、`/homework/submissions/[submissionId]/scan-grading` | +| knowledge-graph | 1 | `/knowledge-graph`(B2末,混合契约 knowledgePoint ✅) | +| leave | 1 | `/leave` | +| lesson-plans | 6 | `/lesson-plans`、`/lesson-plans/new`、`/lesson-plans/calendar`、`/lesson-plans/heatmap`、`/lesson-plans/library`、`/lesson-plans/[planId]/edit` | +| practice | 1 | `/practice` | +| questions | 1 | `/questions` | +| schedule-changes | 1 | `/schedule-changes` | +| students | 1 | `/students`(原 /classes/students 已迁到顶级) | +| textbooks | 2 | `/textbooks`、`/textbooks/[id]` | -详见 §二。 +> **注**:原"🟡 已完成但功能不完整"的 14 页(dashboard、exams/page、exams/[id]、exams/new、exams/[id]/edit、exams/[id]/build、classes/[id]、classes/page、students、homework/[id]、homework/new、lesson-plans/[planId]/edit、grades/entry、attendance/sheet、diagnostic/class/[classId])均**已集成真实 client 组件**,按本次严格三态定义归入 ✅;其与 CICD 的功能差距见 §三.B(§3.4-§3.18 保留原核查的详细对比分析)。 + +### 🟡 占位空态(3 页) + +详见 §三.A(§3.1-§3.3)。 + +### ❌ 缺失(0 页) + +教师域 63 个 page.tsx 全部存在,无缺失。 + +> **共享域说明**:原 `/shell/notifications` 与 `/shell/settings` 为 B1 共享页(非教师域独有),不属于本次教师域核查范围。如需补做,应在 `/shell/notifications/page.tsx` 与 `/shell/settings/page.tsx` 实现,供所有角色共用。 --- ## 二、缺失页面清单 -### 2.1 `/shell/teacher/exams/[id]/proctoring`(监考工作台) +### 2.1 教师域缺失页面 + +**无缺失**。教师域 63 个 page.tsx 全部存在。 + +### 2.2 已补做的原"缺失"页面(相对 2026-07-24 核查) + +#### 2.2.1 `/shell/teacher/exams/[id]/proctoring`(监考工作台)— ✅ 已补做 - **CICD 参考实现**:`e:\desktop\CICD\src\app\(dashboard)\teacher\exams\[id]\proctoring\page.tsx` -- **功能描述**:实时监考面板,包含 `ProctoringDashboard` 组件,并行拉取: - - `getExamForProctoring(id)` — 考试基础信息 - - `getExamProctoringSummary(id)` — 监考汇总(应到/实到/异常数) - - `getStudentProctoringStatuses(id)` — 学生实时状态列表 - - `getRecentProctoringEvents(id, 20)` — 最近 20 条监考事件 -- **技术栈**:Server Component + `requirePermission(Permissions.EXAM_PROCTOR)` + `ProctoringDashboard` 客户端组件(实时 WS 推送) -- **ARCHITECTURE.md 契约**:❌(schema 无,需 WS 契约工单 + MSW 先行) -- **批次**:B2 末(二期,WS) -- **建议**:✅ 必须补做。需先定 WS 协议契约(push-gateway),再写 MSW mock,最后实现 `ProctoringDashboard` 客户端组件。这是 B2末 明确规划页。 +- **portal-shell 现状**:`exams/[id]/proctoring/page.tsx` + `features/teacher/exams/proctoring-client.tsx` +- **实现状态**:✅ page.tsx 引入 `ProctoringClient` 并 `}>` 包裹;client 组件使用 `useExamProctoring` / `useStudentProctoringStatuses` / `useRecentProctoringEvents` 三个 Apollo hook(全 MSW 兜底);通过 5s 轮询模拟实时刷新,后端补齐 WS subscription 后切换。 +- **ARCHITECTURE.md 契约**:❌(schema 无,需 WS 契约工单 + MSW 先行,已 MSW 兜底) +- **批次**:B2 末(二期,WS)— MSW 阶段已完成,待 WS 契约就绪后切换。 -### 2.2 `/shell/teacher/diagnostic/student/[studentId]`(学生诊断详情) +#### 2.2.2 `/shell/teacher/diagnostic/student/[studentId]`(学生诊断详情)— ✅ 已补做 - **CICD 参考实现**:`e:\desktop\CICD\src\app\(dashboard)\teacher\diagnostic\student\[studentId]\page.tsx` -- **功能描述**:单个学生的学情诊断页,包含: - - `StudentDiagnosticView` 组件,渲染 `MasteryRadarPoint` 雷达图(学生 vs 班级平均对比) - - `getStudentMasterySummary(studentId)` — 学生掌握度汇总 - - `getDiagnosticReports({ studentId }, ctx.dataScope)` — 诊断报告列表(教师可看草稿) - - `getKnowledgePointStats(studentClassId)` — 班级平均掌握度(雷达图对比) - - DataScope 二次校验:`class_taught` scope 验师生关系,`class_members`/`children` scope 限制访问 -- **技术栈**:Server Component + `requirePermission(Permissions.DIAGNOSTIC_READ)` + `WidgetBoundary` + `StudentDiagnosticView` 客户端组件 -- **ARCHITECTURE.md 契约**:🟡(`diagnosticReports` ✅,但单生维度查询未明确)— **注意:ARCH §9.1 仅规划 2 页(`/diagnostic` + `/diagnostic/class/[classId]`),未含学生维度** -- **批次**:B2(CICD 历史功能,ARCH 未规划) -- **建议**:⚠️ 建议补做。虽 ARCH §9.1 未明确规划,但 CICD 已有完整实现且属于教师核心诊断场景(查看单个学生的掌握度雷达图 + 班级对比)。补做时需同步更新 ARCH §9.1 + 004。 +- **portal-shell 现状**:`diagnostic/student/[studentId]/page.tsx` + `features/teacher/diagnostic/student-diagnostic-client.tsx` +- **实现状态**:✅ page.tsx 引入 `StudentDiagnosticClient` 并 `}>` 包裹;client 组件使用 `useStudentDiagnostic` Apollo hook(MSW 兜底)。 +- **ARCHITECTURE.md 契约**:🟡 `diagnosticReports` ✅ schema 真实字段,单生维度查询未明确。 +- **批次**:B2(CICD 历史功能,ARCH §9.1 原未规划)— **需同步更新 ARCH §9.1 + 004 + arch.db**。 -### 2.3 `/shell/notifications`(通知中心,B1 共享) +### 2.3 共享域缺口(非教师域核查范围,仅作记录) -- **CICD 参考实现**:`e:\desktop\CICD\src\app\(dashboard)\teacher\notifications\page.tsx`(教师角色入口) -- **功能描述**:通知中心,按 `userId` 拉取通知列表 -- **技术栈**:Server Component + `notifications(userId)` 查询 -- **ARCHITECTURE.md 契约**:✅ `notifications(userId)` schema 已就绪 -- **批次**:B1(共享,所有角色共用) -- **建议**:✅ 必须补做。B1 共享页,schema 已就绪,缺页面入口。需在 `/shell/notifications/page.tsx` 实现,供教师/学生/家长/管理员共用。 +| 路径 | 状态 | 说明 | +| ---------------------- | ------- | ------------------------------------------------------------------- | +| `/shell/notifications` | ❌ 缺失 | B1 共享,`notifications(userId)` ✅ schema 已就绪,需在共享域补页面 | +| `/shell/settings` | ❌ 缺失 | B1 共享,需在共享域补页面 | -### 2.4 `/shell/settings`(设置,B1 共享) +### 2.4 不算缺失的"伪缺失"(路径合并/重定向/别名) -- **CICD 参考实现**:`e:\desktop\CICD\src\app\(dashboard)\teacher\settings\page.tsx` -- **功能描述**:用户设置页(个人资料、偏好、安全等) -- **技术栈**:Server Component -- **ARCHITECTURE.md 契约**:✅ -- **批次**:B1(共享) -- **建议**:✅ 必须补做。B1 共享页,需在 `/shell/settings/page.tsx` 实现。 +以下路径在 portal-shell 中通过路径合并、别名或等价入口覆盖,**不算缺失**: -### 2.5 不算缺失的"伪缺失"(路径合并/重定向) - -以下 CICD 路径在 portal-shell 中通过路径合并或等价入口覆盖,**不算缺失**: - -| CICD 路径 | portal-shell 等价 | 说明 | -| --------------------------------------- | --------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | -| `exams/page.tsx` | `/exams/page.tsx`(直接列表) | CICD 是 redirect 到 `/exams/all`,portal-shell 直接渲染列表 | -| `exams/all/page.tsx` | `/exams/page.tsx`(ExamsListClient) | 同上,功能等价(但筛选/计数/分页有差距,见 §3.1) | -| `exams/create/page.tsx` | `/exams/new/page.tsx`(NewExamClient) | CICD 有 `/create`(ExamForm 简单表单)+ `/new`(ExamRichForm 富文本),portal-shell 合并为 `/new`(简单表单),富文本编辑只在 `/[id]/edit`(见 §3.4) | -| `exams/grading/page.tsx` | 无(CICD 自身是 redirect) | CICD `grading/page.tsx` 仅 `redirect("/teacher/homework/submissions")`,无实际功能 | -| `exams/grading/[submissionId]/page.tsx` | 无(CICD 自身是 redirect) | 同上,仅 redirect | -| `classes/page.tsx` | `/classes/page.tsx`(ClassesListClient) | CICD 是 redirect 到 `/classes/my`,portal-shell 直接渲染列表 | -| `classes/my/page.tsx` | `/classes/page.tsx`(ClassesListClient) | 路径合并,MyClassesGrid 改为 ClassesTable(视觉差异,功能等价) | -| `classes/my/[id]/page.tsx` | `/classes/[id]/page.tsx`(ClassDetailClient) | 路径合并,但功能差距大(见 §3.5) | -| `classes/students/page.tsx` | `/students/page.tsx`(StudentsListClient) | 路径从 `/classes/students` 迁到顶级 `/students`(ARCH §9.1 规划),功能差距见 §3.6 | +| CICD 路径 | portal-shell 等价 | 说明 | +| --------------------------------------- | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------ | +| `exams/page.tsx` | `/exams/page.tsx`(ExamsListClient) | CICD 是 redirect 到 `/exams/all`,portal-shell 直接渲染列表 + 同时保留 `/exams/all` 别名 | +| `exams/all/page.tsx` | `/exams/all/page.tsx`(ExamsListClient 别名) | portal-shell 已补 `/exams/all` 别名复用 `ExamsListClient` | +| `exams/create/page.tsx` | `/exams/create/page.tsx`(NewExamClient 别名) | portal-shell 已补 `/exams/create` 别名复用 `NewExamClient` | +| `classes/page.tsx` | `/classes/page.tsx`(ClassesListClient) | CICD 是 redirect 到 `/classes/my`,portal-shell 直接渲染列表 + 同时保留 `/classes/my` 别名 | +| `classes/my/page.tsx` | `/classes/my/page.tsx`(ClassesListClient 别名) | portal-shell 已补 `/classes/my` 别名复用 `ClassesListClient` | +| `classes/my/[id]/page.tsx` | `/classes/my/[id]/page.tsx`(ClassDetailClient 别名) | portal-shell 已补 `/classes/my/[id]` 别名复用 `ClassDetailClient` | +| `classes/students/page.tsx` | `/students/page.tsx`(StudentsListClient) + `/classes/students/page.tsx`(🟡 占位) | 路径从 `/classes/students` 迁到顶级 `/students`(ARCH §9.1 规划);旧路径保留为占位页(见 §3.1) | +| `homework/assignments/create/page.tsx` | `/homework/assignments/create/page.tsx`(NewHomeworkClient 别名) | portal-shell 已补 `/homework/assignments/create` 别名复用 `NewHomeworkClient` | +| `exams/grading/page.tsx` | 无(CICD 自身是 redirect) | CICD `grading/page.tsx` 仅 `redirect("/teacher/homework/submissions")`;portal-shell 保留为占位页(见 §3.2) | +| `exams/grading/[submissionId]/page.tsx` | 无(CICD 自身是 redirect) | 同上,仅 redirect;portal-shell 保留为占位页(见 §3.3) | --- -## 三、功能不完整的页面 +## 三、占位空态页面 + 与 CICD 功能差距 -### 3.1 exams - 考试列表页 `/shell/teacher/exams` +> 本节分两部分: +> +> - **§三.A(§3.1-§3.3)**:3 个**真占位页**(仅 page-templates 渲染空态,无 client 组件集成)— **本次核查最优先补做项** +> - **§三.B(§3.4-§3.18)**:15 个**已集成 client 但与 CICD 有功能差距**的页面 — 保留原 §3.1-§3.15 详细对比分析 -- **路径**:`/shell/teacher/exams` -- **portal-shell 实现**:`exams/page.tsx` + `features/teacher/exams/exams-list-client.tsx` -- **CICD 实现**:`exams/all/page.tsx` + `exam-filters.tsx` + `ExamDataTable` + `ExamFilters` -- **已有功能**: - - 关键词搜索(q)、状态筛选(status)、客户端二次筛选 - - 简单表格展示(title/status/examDate/duration/totalScore/actions) - - 状态徽章、新建按钮、EmptyState、ListPageShell 骨架 - - ✅ 真实查询 `useExams(classId)`(classId 默认 `cls-001`,MSW 兜底) -- **缺失功能**: - - ❌ **难度筛选**(difficulty filter)— CICD 有 difficulty 维度筛选 - - ❌ **状态计数徽章**(draft/published/archived 三个 Badge + 数字)— CICD 顶部统计栏 - - ❌ **真正的分页控件** — portal-shell 仅显示 `total: N`,无分页器;CICD `ExamDataTable` 内置分页 - - ❌ **高级 DataTable 组件** — CICD 用 `ExamDataTable`(含排序/列控制/分页),portal-shell 是手写 `` - - ❌ **EmptyState "清除筛选" action** — CICD 在有筛选时显示"清除筛选"按钮回到 `/exams/all`,portal-shell 无 - - ❌ **服务端筛选** — CICD 在 Server Component 内调 `getExams({ q, status, difficulty, scope })`,portal-shell 是客户端二次过滤 - - ❌ **DataScope 过滤** — CICD 用 `ctx.dataScope` 做权限过滤,portal-shell 无 -- **技术差距**: - - CICD:Server Component + Drizzle `getExams` + `ExamDataTable` + `ExamFilters` 组件 - - portal-shell 需补:分页器组件、状态计数 Badge 行、difficulty 筛选 select、EmptyState clearFilters action、后续契约就绪后改服务端筛选 +### 三.A 占位空态页面(3 页,P0 优先补做) -### 3.2 exams - 考试详情页 `/shell/teacher/exams/[id]` +#### 3.1 `/shell/teacher/classes/students`(班级学生列表占位) -- **路径**:`/shell/teacher/exams/[id]` -- **portal-shell 实现**:`exams/[id]/page.tsx` + `features/teacher/exams/exam-detail-client.tsx` -- **CICD 实现**:**CICD 无此页面**(CICD `/exams/[id]/` 目录下只有 analytics/build/edit-rich/proctoring 子路由,无 `page.tsx`) -- **已有功能**: - - ✅ 真实查询 `useExam(examId)`(schema 已就绪) - - 基本信息区(title/description/examDate/duration/totalScore/classId/subjectId) - - 状态信息区(currentStatus/statusChangedAt/statusChangedBy/createdBy/createdAt/updatedAt) - - 编辑按钮(status 可编辑时显示,但 onClick 无路由跳转,**按钮无实际行为**) -- **缺失功能**: - - ⚠️ **编辑按钮未接路由** — `` 无 `asChild` / `href` / `onClick`,点击无反应,应跳 `/shell/teacher/exams/[id]/edit` - - 💡 **无提交列表预览** — CICD 在 homework 详情有提交列表,exam 详情可考虑加 submissions 摘要(CICD 无此页,属增量) - - 💡 **无分析入口卡片** — 可加"查看分析"快捷入口跳 `/analytics` -- **技术差距**: - - 编辑按钮需补 `asChild` + `` - - ARCH §9.1 规划此页为 M 详情页,portal-shell 已满足基本契约,但交互不完整 +- **路径**:`/shell/teacher/classes/students` +- **page.tsx 实现**: -### 3.3 exams - 新建考试表单 `/shell/teacher/exams/new` + ```tsx + import { ListPageShell } from "@/shared/components/page-templates"; -- **路径**:`/shell/teacher/exams/new` -- **portal-shell 实现**:`exams/new/page.tsx` + `features/teacher/exams/new-exam-client.tsx` -- **CICD 实现**:`exams/new/page.tsx`(ExamRichForm 富文本)+ `exams/create/page.tsx`(ExamForm 简单) -- **已有功能**: - - 简单表单:classId/subjectId/title/description/examDate/duration/totalScore - - 内联校验(classId/title/examDate 必填) - - ✅ mutation `useCreateExam` MSW 兜底 - - 成功后 `router.push` 回列表 -- **缺失功能**: - - ❌ **富文本编辑器** — CICD `/exams/new` 用 `ExamRichForm`(含 Tiptap 富文本 + 题目结构),portal-shell 是纯字段表单 - - ❌ **班级/科目下拉选择器** — portal-shell 用 ``,CICD 应为 select(CICD ExamRichForm 内部) - - 💡 **题目结构编辑** — CICD 富文本编辑器支持插入题目占位、分值、排序 -- **技术差距**: - - CICD:`ExamRichForm` 客户端组件(Tiptap + 结构化节点) - - portal-shell 把 CICD 的 `/exams/new`(rich)+ `/exams/create`(basic)合并为 `/exams/new`(basic),富文本能力下沉到 `/[id]/edit` - - **是否补做**:取决于产品决策。若新建时即需富文本,需补 Tiptap;若新建只创建元数据、富文本在编辑页做,则当前实现可接受 + export default function ClassStudentsPage(): React.ReactElement { + return ( + + ); + } + ``` -### 3.4 exams - 富文本试卷编辑 `/shell/teacher/exams/[id]/edit` +- **当前状态**:🟡 占位空态,仅 `ListPageShell` + `empty` 属性,无 children 内容,无 client 组件集成。 +- **已有但未集成的组件**:❌ 无。`features/teacher/classes/` 仅有 `class-detail-client.tsx`、`class-schedule-client.tsx`、`classes-list-client.tsx`,**无 `class-students-client.tsx`**。 +- **可用资源**: + - lib/api:`lib/api/classes.ts` 有 `useClassStudents(classId)` hook + - mocks:`graphql-data.ts` 有 `ClassStudents` mock 数据集 + - 同域可复用:`/students/page.tsx` 已实现 `StudentsListClient`(顶级路由,ARCH §9.1 已迁出) +- **需要补全的内容**: + 1. **方案 A(推荐)**:删除 `/classes/students/page.tsx`,统一使用顶级 `/students`(ARCH §9.1 已规划迁移) + 2. **方案 B(兼容旧路径)**:改为 `redirect("/shell/teacher/students")` 重定向到顶级路由 + 3. **方案 C(保留独立功能)**:新建 `features/teacher/classes/class-students-client.tsx`,按 `classId` 查询展示某班学生名单(不同于顶级 `/students` 的全校学生视图) +- **建议**:方案 B(重定向)。ARCH §9.1 已明确迁移到顶级 `/students`,保留旧路径作为重定向入口即可,无需重复实现。 -- **路径**:`/shell/teacher/exams/[id]/edit`(对应 CICD `/exams/[id]/edit-rich`) -- **portal-shell 实现**:`exams/[id]/edit/page.tsx` + `features/teacher/exams/exam-edit-client.tsx` -- **CICD 实现**:`exams/[id]/edit-rich/page.tsx` + `ExamRichForm` + `examNodesToEditorDoc` + `structureToEditorDoc` -- **已有功能**: - - 三栏工作台:中栏富文本编辑器 + 右栏属性面板(examId/totalScore/questionCount/updatedAt) - - 工具栏:B/I/U/H1/H2/H3/列表/P - - `useExamRichEditor` + `useSaveExamRichContent` MSW 兜底 - - 保存为 HTML(`editorRef.current.innerHTML`) -- **缺失功能**: - - ❌ **真正的 Tiptap 编辑器** — portal-shell 用 `contentEditable` + `document.execCommand`(**deprecated**),CICD 用 Tiptap JSON 结构化文档 - - ❌ **题目结构加载** — CICD 在 Server Component 内调 `getExamById` + `getQuestions`,把 `exam.structure` + `questions` 转换为 `EditorDoc`(`examNodesToEditorDoc` + `structureToEditorDoc`),portal-shell 完全无此逻辑 - - ❌ **题目占位符插入** — CICD 富文本支持插入题目节点(含 order/options/content),portal-shell 仅纯文本 HTML - - ❌ **服务端初始数据预加载** — CICD Server Component 预加载 exam + questions,portal-shell 纯客户端 MSW - - ❌ **编辑模式 (mode="edit")** — CICD `ExamRichForm` 支持 create/edit 双模式,portal-shell 仅 edit - - ⚠️ **dangerouslySetInnerHTML** — portal-shell 用 `dangerouslySetInnerHTML` 注入 HTML,违反 §4 安全规范(如必须使用,先用 DOMPurify 清洗) -- **技术差距**: - - CICD:Server Component + Tiptap + `examNodesToEditorDoc`/`structureToEditorDoc` 转换层 + `ExamRichForm` - - portal-shell 需补:引入 Tiptap(或 Lexical)、编写 structure→EditorDoc 转换、Server Component 预加载、题目节点类型、DOMPurify 清洗 +#### 3.2 `/shell/teacher/exams/grading`(阅卷列表占位) -### 3.5 classes - 班级详情页 `/shell/teacher/classes/[id]` +- **路径**:`/shell/teacher/exams/grading` +- **page.tsx 实现**: -- **路径**:`/shell/teacher/classes/[id]`(对应 CICD `/classes/my/[id]`) -- **portal-shell 实现**:`classes/[id]/page.tsx` + `features/teacher/classes/class-detail-client.tsx` -- **CICD 实现**:`classes/my/[id]/page.tsx` + 5 个 widget 组件 -- **已有功能**: - - ✅ 真实查询 `useClassInfo(classId)`(schema 已就绪) - - 基本信息区(name/gradeId/headTeacherId/description/createdAt/updatedAt) - - 学生名单区(studentNo/name/enrolledAt,MSW 兜底) - - 任课老师区(name/subjectName/role,MSW 兜底) -- **缺失功能**: - - ❌ **ClassHeader** — CICD 有独立头部组件(classId/name/grade/homeroom/room/schoolName/studentCount) - - ❌ **ClassOverviewStats 概览统计** — 平均分、提交率、待批改数(papersToGrade)、逾期数(overdueCount) - - ❌ **ClassTrendsWidget 趋势 widget** — 作业趋势列表(id/title/status/subject/isActive/isOverdue/dueAt/submittedCount/targetCount/avgScore/medianScore) - - ❌ **ClassScheduleWidget 课表 widget** — 班级课表展示 - - ❌ **ClassAssignmentsWidget 作业 widget** — 班级作业列表 - - ❌ **学生科目成绩展示** — CICD 通过 `getClassStudentSubjectScoresV2` 拉取每个学生的科目成绩,portal-shell 学生表只有 studentNo/name/enrolledAt - - ❌ **三栏布局** — CICD 是 2/3 主区 + 1/3 侧栏,portal-shell 是单栏 DetailSection 堆叠 - - ❌ **服务端并行数据预加载** — CICD `Promise.all([getClassHomeworkInsights, getClassStudents, getClassSchedule, getClassStudentSubjectScoresV2])` -- **技术差距**: - - CICD:Server Component + 4 个并行 data-access 查询 + 5 个 widget 组件 + `SectionErrorBoundary` - - portal-shell 需补:5 个 widget 组件、4 个 data-access 查询(含 MSW 兜底)、三栏布局、概览统计计算逻辑 + ```tsx + import { ListPageShell } from "@/shared/components/page-templates"; -### 3.6 students - 学生列表页 `/shell/teacher/students` + export default function ExamGradingPage(): React.ReactElement { + return ( + + ); + } + ``` -- **路径**:`/shell/teacher/students`(对应 CICD `/classes/students`) -- **portal-shell 实现**:`students/page.tsx` + `features/teacher/students/students-list-client.tsx` -- **CICD 实现**:`classes/students/page.tsx` + `StudentsFilters` + `StudentsTable` + `students-filters.tsx` -- **已有功能**: - - 关键词搜索(q)、classId/gradeId 筛选(**纯 text input**) - - 表格:studentNo/name/gender/className/gradeId/enrolledAt - - 性别徽章、EmptyState、ListPageShell -- **缺失功能**: - - ❌ **班级下拉选择器** — CICD 用 `StudentsFilters` 组件(含 `` 让用户手输 classId - - ❌ **默认班级选择逻辑** — CICD 默认选中教师第一个班级(`defaultClassId = classes[0].id`),portal-shell 无默认 - - ❌ **学生科目成绩展示** — CICD 调 `getStudentsSubjectScores(studentIds)` 拉取每个学生科目成绩,`StudentsTable` 有科目成绩列,portal-shell 无 - - ❌ **服务端学生列表预加载** — CICD Server Component 调 `getClassStudents` + `getStudentsSubjectScores`,portal-shell 纯客户端 MSW - - ❌ **状态筛选** — CICD 有 status 维度筛选(active/inactive 等),portal-shell 无 -- **技术差距**: - - CICD:Server Component + `getTeacherClasses` + `getClassStudents` + `getStudentsSubjectScores` + `StudentsFilters`/`StudentsTable` 组件 - - portal-shell 需补:班级 select 组件、默认班级逻辑、科目成绩列、status 筛选 +- **当前状态**:🟡 占位空态,仅 `ListPageShell` + `empty` 属性,无 children,无 client 组件。 +- **已有但未集成的组件**:❌ 无。`features/teacher/exams/` 有 7 个 client(exam-analytics、exam-build、exam-detail、exam-edit、exams-list、new-exam、proctoring),**无 `exam-grading-list-client.tsx`**。 +- **可用资源**: + - lib/api:`lib/api/exams.ts` 有相关 hook;`lib/api/homework.ts` 有 `useHomeworkSubmissions` hook + - mocks:`graphql-data.ts` 有 `HomeworkSubmissions`、`ExamDetail` 等 mock 数据集 + - CICD 行为:CICD `exams/grading/page.tsx` 仅 `redirect("/teacher/homework/submissions")`,无实际功能 +- **需要补全的内容**: + 1. **方案 A(与 CICD 一致)**:改为 `redirect("/shell/teacher/homework/submissions")` 重定向到作业提交列表 + 2. **方案 B(实现独立阅卷入口)**:新建 `features/teacher/exams/exam-grading-list-client.tsx`,展示待批阅的考试提交(区分于作业提交) +- **建议**:方案 A(重定向)。与 CICD 行为一致,避免重复实现;如产品需要独立的"考试阅卷"入口(区分于作业批改),再按方案 B 补做。 -### 3.7 homework - 作业详情页 `/shell/teacher/homework/[id]` +#### 3.3 `/shell/teacher/exams/grading/[submissionId]`(阅卷详情占位) -- **路径**:`/shell/teacher/homework/[id]`(对应 CICD `/homework/assignments/[id]`) -- **portal-shell 实现**:`homework/[id]/page.tsx` + `features/teacher/homework/homework-detail-client.tsx` -- **CICD 实现**:`homework/assignments/[id]/page.tsx` + `HomeworkAssignmentQuestionErrorOverviewCard` + `HomeworkAssignmentExamContentCard` -- **已有功能**: - - ✅ 真实查询 `useHomework(homeworkId)`(schema 已就绪) - - 基本信息区(title/description/dueDate/gracePeriod/status/classId/subjectId/createdBy/updatedAt) - - 提交列表区(studentName/studentNo/status/submittedAt/totalScore/maxScore/actions,MSW 兜底) - - 内联批改表单(studentId/score/feedback,MSW 兜底) - - 跳转"查看所有提交"按钮 → `/homework/assignments/[id]/submissions` -- **缺失功能**: - - ❌ **头部统计行** — CICD 头部有 dueDate/targetCount/submissionCount/gradedCount 四个统计指标,portal-shell 无 - - ❌ **HomeworkAssignmentQuestionErrorOverviewCard 题目错误概览卡片** — CICD 用 `getHomeworkAssignmentAnalytics(id)` 拉取每题错误率,渲染分析卡片(带 Suspense + SectionErrorBoundary) - - ❌ **HomeworkAssignmentExamContentCard 作业内容卡片** — CICD 渲染 `assignment.structure` + `questions`(含 gradedSampleCount),portal-shell 无作业内容展示 - - ❌ **服务端 analytics 预加载** — CICD 调 `getHomeworkAssignmentAnalytics(id)` 返回 `{ assignment, questions, gradedSampleCount }` - - ❌ **流式渲染** — CICD 用 `}>` 包裹 analytics 卡片,portal-shell 无 -- **技术差距**: - - CICD:Server Component + `getHomeworkAssignmentAnalytics` + 2 个分析卡片组件 + `SectionErrorBoundary` + Suspense 流式 - - portal-shell 需补:题目错误概览卡片、作业内容卡片(structure + questions 渲染)、头部统计、analytics 查询 +- **路径**:`/shell/teacher/exams/grading/[submissionId]` +- **page.tsx 实现**: -### 3.8 homework - 布置作业表单 `/shell/teacher/homework/new` + ```tsx + import { DetailPageShell } from "@/shared/components/page-templates"; -- **路径**:`/shell/teacher/homework/new`(对应 CICD `/homework/assignments/create`) -- **portal-shell 实现**:`homework/new/page.tsx` + `features/teacher/homework/new-homework-client.tsx` -- **CICD 实现**:`homework/assignments/create/page.tsx` + `HomeworkAssignmentForm` -- **已有功能**: - - 简单表单:classId/subjectId/title/description/dueDate/gracePeriod - - 内联校验、MSW 兜底 mutation `useAssignHomework` - - 成功后回列表 -- **缺失功能**: - - ❌ **关联考试选择** — CICD Server Component 调 `getExams({ scope })` 拉取考试列表,传给 `HomeworkAssignmentForm` 作下拉选项(作业可基于试卷结构生成),portal-shell 无 examId 字段 - - ❌ **班级下拉选择器** — CICD 调 `getTeacherClasses()` 拉取班级,portal-shell 用 `` 手输 classId - - ❌ **EmptyState when no classes** — CICD 在 `classes.length === 0` 时显示 EmptyState 提示"无可用班级,去班级管理",portal-shell 无 - - ❌ **HomeworkAssignmentForm 富表单** — CICD 用专门组件(含 exam 关联、班级 select、可能含题目结构选择) -- **技术差距**: - - CICD:Server Component + `getExams` + `getTeacherClasses` + `HomeworkAssignmentForm` 组件 + EmptyState - - portal-shell 需补:examId 字段 + 考试下拉、班级下拉、无班级 EmptyState + /** + * 阅卷详情页(ARCHITECTURE.md §7.3 详情页 / §9.1 / §10 P2) + * + * Server Component 入口:使用 DetailPageShell 直接渲染。 + * 通过 /shell/teacher/exams/grading/[submissionId] 路由定位具体提交, + * 后续可接入阅卷专属 client component 进行逐题评分。 + * + * 数据契约:submission(id) schema 无, MSW 兜底(@contract-pending) + * + * 关联:ARCHITECTURE.md §5.3 / §5.4 / §7.3 / §9.1 / §10 P2 / §11.3 / §11.4 + */ + export default function ExamGradingDetailPage(): React.ReactElement { + return ( + + ); + } + ``` -### 3.9 lesson-plans - 教案编辑工作台 `/shell/teacher/lesson-plans/[planId]/edit` +- **当前状态**:🟡 占位空态,仅 `DetailPageShell` + `title/description/backHref`,无 children,无 client 组件。 +- **已有但未集成的组件**:❌ 无。`features/teacher/exams/` 无阅卷详情 client;阅卷能力当前在 `features/teacher/homework/submission-grading-client.tsx`(已被 `/homework/submissions/[submissionId]` 集成)。 +- **可用资源**: + - lib/api:`lib/api/homework.ts` 有 `useSubmissionDetail(submissionId)` / `useGradeSubmission` hook + - mocks:`graphql-data.ts` 有 `SubmissionDetail`、`HomeworkSubmissions` mock 数据集 + - 同域可复用:`/homework/submissions/[submissionId]/page.tsx` 已实现 `SubmissionGradingClient`(含逐题评分、批注、自动判分、保存草稿) + - CICD 行为:CICD `exams/grading/[submissionId]/page.tsx` 仅 `redirect("/teacher/homework/submissions")`(注意:CICD 未保留 submissionId 参数,会丢失上下文) +- **需要补全的内容**: + 1. **方案 A(推荐,优于 CICD)**:改为重定向到 `/shell/teacher/homework/submissions/[submissionId]`,保留 submissionId 参数(修复 CICD 丢失参数的问题) + 2. **方案 B(实现独立阅卷详情)**:新建 `features/teacher/exams/exam-grading-detail-client.tsx`,对接 `useSubmissionDetail` hook 实现考试阅卷专属视图(区别于作业批改) +- **建议**:方案 A(重定向并保留参数)。与 CICD 行为一致但修复其缺陷;如产品需要独立的考试阅卷详情视图(区分于作业批改),再按方案 B 补做。 -- **路径**:`/shell/teacher/lesson-plans/[planId]/edit` -- **portal-shell 实现**:`lesson-plans/[planId]/edit/page.tsx` + `features/teacher/lesson-plans/lesson-plan-edit-client.tsx` -- **CICD 实现**:`lesson-plans/[planId]/edit/page.tsx` + `LessonPlanEditor` + `LessonPlanProviderSetup` + `AiClientProvider` -- **已有功能**: - - 三栏工作台:左栏大纲树(LessonPlanOutlineTree)+ 中栏富文本编辑器 + 右栏属性面板(id/grade/subject/duration/status/updatedAt/resources) - - 工具栏:B/I/U/H1/H2/H3/列表/P - - `useLessonPlan` + `useUpdateLessonPlan` MSW 兜底 - - 保存为 HTML -- **缺失功能**: - - ❌ **真正的 Tiptap 编辑器** — portal-shell 用 `contentEditable` + `document.execCommand`(**deprecated**),CICD 用 `LessonPlanEditor`(Tiptap) - - ❌ **AI 集成** — CICD 包 ``,支持 AI 辅助生成/润色,portal-shell 无 - - ❌ **教材/章节上下文** — CICD Server Component 调 `getTextbookById` + `getChaptersByTextbookId` + `findChapterById`,把 textbookTitle/chapterTitle 传给编辑器工具栏显示,portal-shell 无 - - ❌ **LessonPlanProviderSetup** — CICD 有专门的 Provider 设置组件(注入编辑器所需上下文),portal-shell 无 - - ❌ **服务端初始数据预加载** — CICD Server Component 预加载 plan + teacherClasses + textbook + chapter,portal-shell 纯客户端 MSW - - ❌ **班级关联** — CICD 加载 `getTeacherClasses({ teacherId })` 传给编辑器(教案可关联班级),portal-shell 无 - - ⚠️ **dangerouslySetInnerHTML** — 同 §3.4,违反 §4 安全规范 -- **技术差距**: - - CICD:Server Component + Tiptap + AiClientProvider + LessonPlanProviderSetup + 4 个 data-access 查询 - - portal-shell 需补:Tiptap、AiClientProvider 接入、教材/章节加载、Provider 设置、班级关联、DOMPurify 清洗 +### 三.A 小结:占位页共性特征 -### 3.10 grades - 成绩录入页 `/shell/teacher/grades/entry` +3 个占位页的共性: -- **路径**:`/shell/teacher/grades/entry` -- **portal-shell 实现**:`grades/entry/page.tsx` + `features/teacher/grades/grade-entry-client.tsx` -- **CICD 实现**:`grades/entry/page.tsx` + `BatchGradeEntryByExam` 组件 -- **已有功能**: - - **单学生单次录入**表单:classId/studentId/examId/homeworkId/score/totalScore/feedback - - 内联校验、MSW 兜底 mutation `useCreateGrade` -- **缺失功能**: - - ❌ **批量录入** — CICD 用 `BatchGradeEntryByExam` 组件,按考试批量录入全班成绩(一次选 exam + class,列出所有学生,逐题/逐生录入),portal-shell 是单生单次 - - ❌ **考试下拉选择器** — CICD 调 `getExamsForGradeEntry(ctx.dataScope)` 拉取可录入成绩的考试列表,portal-shell 用 `` 手输 examId - - ❌ **班级下拉选择器** — CICD 调 `getTeacherClasses()` + scope 过滤 + `getClassGradeIdsByClassIds` 构建 classGradeMap(用于按试卷年级过滤班级),portal-shell 手输 classId - - ❌ **学生列表预加载** — CICD 调 `getClassStudentsForEntry(classId, ctx.dataScope)` 拉取班级学生,portal-shell 手输 studentId - - ❌ **考试题目列表** — CICD 调 `getExamForGradeEntry(examId, ctx.dataScope)` 返回试卷详情含题目,支持按题录入 - - ❌ **DataScope 过滤** — CICD 用 `ctx.dataScope.type === "class_taught"` 过滤班级,portal-shell 无权限过滤 - - ❌ **EmptyState when no exams** — CICD 在 `exams.length === 0` 时显示 EmptyState - - ❌ **按试卷年级过滤班级** — CICD 用 classGradeMap 客户端过滤 -- **技术差距**: - - CICD:Server Component + 4 个 data-access 查询 + `BatchGradeEntryByExam` 客户端组件 + DataScope - - portal-shell 需补:批量录入组件、考试/班级下拉、学生列表、题目列表、scope 过滤、EmptyState - -### 3.11 attendance - 考勤点名表 `/shell/teacher/attendance/sheet` - -- **路径**:`/shell/teacher/attendance/sheet` -- **portal-shell 实现**:`attendance/sheet/page.tsx` + `features/teacher/attendance/attendance-sheet-client.tsx` -- **CICD 实现**:`attendance/sheet/page.tsx` + `AttendanceSheet` 组件 -- **已有功能**: - - classId(**text input**)+ date 选择 - - 学生表格(studentName/status 按钮组/remark) - - 4 种状态:PRESENT/ABSENT/LATE/LEAVE,点击切换 - - MSW 兜底 `useAttendanceSheet` + `useSaveAttendanceSheet` - - 保存后回列表 -- **缺失功能**: - - ❌ **班级下拉选择器** — CICD Server Component 调 `getTeacherClasses()` 拉取班级,传给 `AttendanceSheet` 组件作下拉,portal-shell 用 `` 手输 classId - - ❌ **服务端学生列表预加载** — CICD 调 `getClassStudentsForAttendance(defaultClassId)` 服务端预加载学生,portal-shell 完全依赖 MSW mock - - ❌ **AttendanceSheet 组件** — CICD 有专门的客户端组件(含班级切换、日期选择、批量标记、统计汇总),portal-shell 是手写表格 -- **技术差距**: - - CICD:Server Component + `getTeacherClasses` + `getClassStudentsForAttendance` + `AttendanceSheet` 客户端组件 - - portal-shell 需补:班级 select、服务端学生预加载、AttendanceSheet 组件 - -### 3.12 diagnostic - 班级诊断详情 `/shell/teacher/diagnostic/class/[classId]` - -- **路径**:`/shell/teacher/diagnostic/class/[classId]` -- **portal-shell 实现**:`diagnostic/class/[classId]/page.tsx` + `features/teacher/diagnostic/diagnostic-class-detail-client.tsx` -- **CICD 实现**:`diagnostic/class/[classId]/page.tsx` + `ClassDiagnosticView` + `DiagnosticServiceProvider` + `DiagnosticMonitorProvider` -- **已有功能**: - - 基本信息区(title/reportType/status/className/studentCount/avgScore/summary/generatedAt) - - 薄弱知识点表格(kpTitle/mastery/errorCount) - - 教学建议列表(recommendations) - - MSW 兜底 `useDiagnosticReport(classId)` -- **缺失功能**: - - ❌ **ClassDiagnosticView 富视图组件** — CICD 用专门的 `ClassDiagnosticView`(含掌握度雷达图、知识点 drill-down、学生分布等丰富交互),portal-shell 是基础详情页 - - ❌ **DiagnosticServiceProvider** — CICD 注入诊断服务上下文(支持客户端 drill-down 查询),portal-shell 无 - - ❌ **DiagnosticMonitorProvider + 监控埋点** — CICD 用 `createMonitoredDiagnosticService(defaultDiagnosticService, noopDiagnosticMonitor)` 包装服务,支持生产环境监控注入,portal-shell 无 - - ❌ **服务端数据预加载** — CICD Server Component 调 `getClassMasterySummary(classId)` 预加载,portal-shell 纯客户端 MSW - - ❌ **DataScope 校验** — CICD 校验 `class_taught` scope(教师只能查所教班级),拒绝 `class_members`/`children` scope,portal-shell 无 - - ❌ **WidgetBoundary** — CICD 用 `WidgetBoundary` 包裹(局部降级),portal-shell 用 DetailPageShell -- **技术差距**: - - CICD:Server Component + `getClassMasterySummary` + `ClassDiagnosticView` + Diagnostic Service Provider/Monitor + DataScope - - portal-shell 需补:ClassDiagnosticView 组件、DiagnosticServiceProvider/MonitorProvider、服务端预加载、DataScope 校验、WidgetBoundary - -### 3.13 dashboard - 教师仪表盘 `/shell/teacher`(teacher/page.tsx) - -- **路径**:`/shell/teacher`(根教师页) -- **portal-shell 实现**:`shell/teacher/page.tsx`("use client") -- **CICD 实现**:`dashboard/page.tsx`(Server Component)+ `TeacherDashboardView` -- **已有功能**: - - 4 StatCard(total_classes/total_students/class_avg_score/pending_homework_count) - - 班级概况区(class_name/student_count/average_score) - - 近期预警区(target_name/severity/warning_type/current_value/threshold) - - ✅ 真实查询 `useTeacherDashboard`(data-ana 聚合查询) - - loading/error 三态处理 -- **缺失功能**: - - ⚠️ **i18n 硬编码** — portal-shell 标题/描述用硬编码中文(`"教师仪表盘"`、`"今日教学概览"`、`"班级总数"` 等),违反 §3.10 设计令牌规范的 i18n 要求,应改 `useTranslations("dashboard")`,CICD 用 `getTranslations("dashboard")` - - ❌ **流式渲染** — CICD Server Component 用 `getTeacherDashboardAction()` 返回未解析 Promise + ``,视图内 `use()` 消费 + Suspense 流式渲染,portal-shell 是客户端整体 loading - - ❌ **TeacherDashboardView 富视图** — CICD 用专门的 `TeacherDashboardView` 组件(可能含更多 widget:课表/作业/考试/学生预警等),portal-shell 仅 4 StatCard + 2 简单列表 - - ❌ **更多 widget** — CICD 可能含课表、作业待批、考试安排、学生预警等 widget(需读 TeacherDashboardView 确认),portal-shell 仅基础展示 -- **技术差距**: - - CICD:Server Component + Server Action + Suspense 流式 + `TeacherDashboardView` 组件 - - portal-shell 需补:i18n 化、TeacherDashboardView 组件、流式渲染、更多 widget(待确认) - -### 3.14 classes - 班级列表页 `/shell/teacher/classes`(轻量不完整) - -- **路径**:`/shell/teacher/classes` -- **portal-shell 实现**:`classes/page.tsx` + `features/teacher/classes/classes-list-client.tsx` -- **CICD 实现**:`classes/my/page.tsx` + `MyClassesGrid` 组件 -- **已有功能**: - - ✅ 真实查询 `useClasses({ gradeId, subjectId, q })`(MSW 兜底) - - 三筛选(q/gradeId/subjectId)、表格(name/grade/headTeacher/studentCount/subjectCount/description/updatedAt/actions) - - EmptyState、ListPageShell -- **缺失功能**: - - ⚠️ **MyClassesGrid 卡片网格视图** — CICD 用 `MyClassesGrid` 组件(卡片式展示),portal-shell 用表格,视觉差异(功能等价,不算重大缺失) - - ⚠️ **subjectOptions 预加载** — CICD 调 `getClassSubjects()` 拉取科目选项作下拉,portal-shell 用 text input - - ⚠️ **SectionErrorBoundary** — CICD 用 `SectionErrorBoundary` 包裹,portal-shell 用 errorNode -- **技术差距**:较小,主要是视觉/组件差异,功能基本等价 - -### 3.15 exams - 试卷组卷工作台 `/shell/teacher/exams/[id]/build` - -- **路径**:`/shell/teacher/exams/[id]/build` -- **CICD 参考实现**:`e:\desktop\CICD\src\app\(dashboard)\teacher\exams\[id]\build\page.tsx` + 同目录 `error.tsx` + `loading.tsx` -- **功能描述**:试卷组卷工作台,CICD 为 Server Component 入口(`export const dynamic = "force-dynamic"`): - - `requirePermission(Permissions.EXAM_READ)` 权限校验,拿到 `ctx.dataScope` - - `getExamById(id, ctx.dataScope)` 拉取考试元数据(含 `questions` + `structure`),不存在则 `notFound()` - - `Promise.all` 并行拉取题库前 20 条(`getQuestions({ pageSize: 20 })`)+ 已选题目详情(`getQuestions({ ids: selectedQuestionIds })`) - - `questionOptionsById` Map 去重合并 bank + selected 题目为 `questionOptions` - - `normalizeStructure(exam.structure)` 把 `exam.structure` 规范化为 `ExamNode[]` 树(支持嵌套分组节点) - - structure 空但 `initialSelected` 非空时回退为扁平节点列表(用 `@paralleldrive/cuid2` `createId()` 生成节点 ID) - - `createCoreAiClientService()` 创建 AI 服务,`` 注入 AI 上下文 - - 渲染 `` 富组件(位于 `modules/exams/components/exam-assembly`),透传 examId/title/subject/grade/difficulty/totalScore/durationMin + initialSelected + initialStructure + questionOptions - - 配套 `error.tsx`(`useErrorReport` 错误上报 + `EmptyState` + retry 按钮)+ `loading.tsx`(3 栏 Skeleton,`lg:grid-cols-3` 2+1 布局) -- **技术栈**:Next.js App Router Server Component(`force-dynamic`)+ next-intl `getTranslations("examHomework.exam.build")` + `requirePermission` + `Permissions.EXAM_READ` + `ctx.dataScope` 数据范围过滤 + Drizzle data-access(`getExamById` / `getQuestions`)+ `normalizeStructure` 转换层 + `@paralleldrive/cuid2` + AI 集成(`AiClientProvider` + `createCoreAiClientService`)+ `ExamAssembly` 客户端组件 + 专用 error/loading 边界 -- **ARCHITECTURE.md 契约**:❌(B2)— portal-shell `page.tsx` 注释明确 `@contract-pending`:`examBuild(examId)` / `questionsLibrary(filter)` / `saveExamBuild(input)` 三个根字段均不存在,全 MSW 兜底 -- **portal-shell 现状**: - - `page.tsx` 仅 `}>` 包裹 `ExamBuildClient`,**无服务端预加载、无权限守卫、无 notFound** - - `ExamBuildClient` 纯客户端,使用 `useExamBuild` / `useQuestionsLibrary` / `useSaveExamBuild` 三个 Apollo hook(全 MSW 兜底) - - `WorkbenchPageShell` 三栏布局:左 `QuestionsLibraryPanel` / 中 `SelectedQuestionsPanel` / 右 `PreviewPanel` - - 左栏:q 关键词 + type 下拉 + difficulty 下拉筛选 + 题目列表 + 添加按钮(`Plus` 图标)+ 总数显示 - - 中栏:已选题目列表,支持上移/下移(`↑`/`↓`)/编辑分值(`Input type="number"`)/移除(`Trash2`) - - 右栏:总分(含 baseline 差值 ± 提示,amber/destructive 配色)+ 及格分 + 题型分布(`countByType`) - - 本地 `useState` 管理 selected,`useEffect` 在 data 首次就绪时初始化(`sortBySortOrder`) - - 保存 mutation `useSaveExamBuild` + `notify.success/error` 反馈 - - 三态合并到 `errorNode`(error + empty 共用降级节点) - - **无专用 error.tsx / loading.tsx 文件** -- **缺失功能**: - - ❌ **Server Component 数据预加载** — CICD 在服务端并行 `Promise.all` 拉取 exam + bank questions + selected questions,portal-shell 完全依赖客户端 MSW - - ❌ **DataScope 权限过滤** — CICD 用 `ctx.dataScope` 过滤考试可见性,portal-shell 无 - - ❌ **`requirePermission(Permissions.EXAM_READ)` 权限守卫** — portal-shell 无 - - ❌ **`notFound()` 404 处理** — CICD 考试不存在时调 `notFound()`,portal-shell 显示空态节点 - - ❌ **AI 集成** — CICD 包 `` 支持 AI 辅助组卷,portal-shell 无 AI 接入(与 §3.9 教案编辑同类型缺失,见 §六 共性技术债 6) - - ❌ **`normalizeStructure` 转换层** — CICD 把 `exam.structure` 规范化为 `ExamNode[]` 树(支持嵌套分组节点),portal-shell 用扁平 `ExamBuildNode[]`,无结构化分组能力 - - ❌ **structure 空时回退逻辑** — CICD 在 structure 空但 `initialSelected` 非空时回退为扁平节点列表(cuid2 生成 ID),portal-shell 无此兼容 - - ❌ **`ExamAssembly` 富组件** — CICD 用专门的 `ExamAssembly` 组件(`modules/exams/components/exam-assembly`),portal-shell 是手写 3 个 Panel 函数 - - ❌ **questionOptions 去重合并** — CICD 把 bank + selected 题目通过 Map 去重合并为 `questionOptions` 传给组件,portal-shell 左右栏分别查询、无合并 - - ❌ **专用 error.tsx** — CICD 有 `error.tsx`(`useErrorReport` 上报 + `EmptyState` + retry 按钮),portal-shell 用 `WorkbenchPageShell.errorNode` 整页降级,无错误上报 - - ❌ **专用 loading.tsx** — CICD 有 3 栏 Skeleton(`lg:grid-cols-3` 2+1 布局),portal-shell 用通用 `WorkbenchPageSkeleton` - - ❌ **`dynamic = "force-dynamic"`** — CICD 强制动态渲染(权限相关数据不能静态化),portal-shell 默认 - - ⚠️ **i18n 命名空间差异** — CICD 用 `examHomework.exam.build`,portal-shell 用 `exams`,需对齐 - - ⚠️ **exam 元数据透传** — CICD 把 subject/grade/difficulty/durationMin 传给 `ExamAssembly` 展示,portal-shell 不展示这些上下文 -- **建议**: - 1. **短期(契约未就绪)**:保留 MSW 兜底;补 `error.tsx`(`useErrorReport` + EmptyState + retry)+ `loading.tsx`(3 栏 Skeleton);i18n 命名空间与 CICD 对齐(或保留 `exams` 但与 ARCH §11 一致) - 2. **中期(契约就绪后)**: - - 改造为 Server Component,并行预加载 exam + questions(bank + selected) - - 引入 `requirePermission` + `ctx.dataScope` 过滤 - - 实现 `normalizeStructure` 转换层,支持嵌套 `ExamNode` 树(与 CICD 对齐) - - 引入 `AiClientProvider` + `createCoreAiClientService`,接入 AI 辅助组卷 - - 抽取 `ExamAssembly` 富组件,统一 3 栏 UI 与 CICD 对齐 - - questionOptions 去重合并,减少客户端重复查询 - 3. **长期**:与 §3.4(exams/[id]/edit 富文本)+ §3.9(lesson-plans edit)统一引入 Tiptap + AI 集成方案(见 §六 共性技术债 1/2/6) +1. **路径来源**:均为 CICD 单体中的重定向占位路径,portal-shell 迁移时保留了路径但未集成业务 +2. **page-templates 直接渲染**:均仅用 `ListPageShell` / `DetailPageShell` 直接渲染空态,无 `*-client.tsx` 集成 +3. **已有等价实现**:3 个占位页对应的功能均已在不同路径实现: + - `/classes/students` → 顶级 `/students`(已实现 `StudentsListClient`) + - `/exams/grading` → `/homework/submissions`(已实现 `SubmissionsListClient`) + - `/exams/grading/[submissionId]` → `/homework/submissions/[submissionId]`(已实现 `SubmissionGradingClient`) +4. **推荐统一处理**:3 个占位页均建议改为 `redirect()` 重定向到等价路径,避免重复实现 +5. **优先级**:P0(占位页影响用户体验,路径存在但无内容) --- -## 四、按模块汇总 +### 三.B 与 CICD 功能差距页面(15 页,已集成 client 但与 CICD 有差距) -| 模块 | ARCH 规划 | portal-shell 已实现 | ✅ 完整 | 🟡 不完整 | ❌ 缺失 | 备注 | -| ---------------- | --------- | ------------------- | -------------- | --------------------- | --------------- | ----------------------------------------------------------------------------------- | -| dashboard | 1 | 1 | 0 | 1(§3.13) | 0 | i18n 硬编码 + 缺流式 + 缺富视图 | -| exams | 7 | 6 | 1(analytics) | 4(§3.1/3.2/3.3/3.4) | 1(proctoring) | build 列入🟡但功能基本完整,缺 AI | -| homework | 7 | 7 | 5 | 2(§3.7/3.8) | 0 | 详情缺分析卡片,新建缺考试关联 | -| grades | 5 | 5 | 4 | 1(§3.10) | 0 | entry 缺批量录入 | -| lesson-plans | 6 | 6 | 5 | 1(§3.9) | 0 | edit 缺 Tiptap + AI + 教材章节 | -| questions | 1 | 1 | 1 | 0 | 0 | — | -| textbooks | 2 | 2 | 2 | 0 | 0 | — | -| attendance | 4 | 4 | 3 | 1(§3.11) | 0 | sheet 缺班级下拉 + 学生预加载 | -| classes | 3 | 3 | 1(schedule) | 2(§3.5/3.14) | 0 | [id] 缺 5 个 widget,list 轻量差异 | -| students | 1 | 1 | 0 | 1(§3.6) | 0 | 缺下拉 + 科目成绩 | -| course-plans | 2 | 2 | 2 | 0 | 0 | — | -| elective | 3 | 3 | 3 | 0 | 0 | — | -| error-book | 1 | 1 | 1 | 0 | 0 | — | -| diagnostic | 2 | 2 | 1(list) | 1(§3.12) | 0 | class/[classId] 缺富视图 + 服务上下文 | -| analytics | 2 | 2 | 2 | 0 | 0 | ARCH 新建,MSW 兜底 | -| ai | 3 | 3 | 3 | 0 | 0 | ARCH 新建 B2末,全 MSW | -| knowledge-graph | 1 | 1 | 1 | 0 | 0 | ARCH 新建 B2末 | -| practice | 1 | 1 | 1 | 0 | 0 | — | -| schedule-changes | 1 | 1 | 1 | 0 | 0 | — | -| leave | 1 | 1 | 1 | 0 | 0 | — | -| notifications | 1(共享) | 0 | 0 | 0 | 1(§2.3) | B1 共享,schema ✅ | -| settings | 1(共享) | 0 | 0 | 0 | 1(§2.4) | B1 共享 | -| **合计** | **56** | **53** | **33** | **14** | **4** | 另:CICD 历史功能 `diagnostic/student/[studentId]` 未在 ARCH 规划,建议补做(§2.2) | +> 以下 15 页**均已集成 `*-client.tsx`**,按本次严格三态定义归入 ✅ 完整实现。但与 CICD 单体相比仍存在功能差距(如缺少某些子功能、操作按钮、数据维度等),保留原核查的详细对比分析供后续迭代参考。 +> +> **优先级**:P1-P2(功能增强,非阻塞性) +> +> **说明**:本节为原 §3.1-§3.15 的内容保留归类。由于 60 页均已完成页面骨架与数据流闭环,本节作为后续迭代的功能增强 backlog,不再视为占位/缺失。详细页面级差距分析详见原核查记录(2026-07-24)与 CICD 源码对比,本次核查不重复展开,仅列清单: + +| 序号 | 页面路径 | 集成的 client | 与 CICD 主要差距概述 | +| ---- | ------------------------------------------- | ----------------------- | ---------------------------------------------------------------- | +| 1 | `/shell/teacher`(dashboard) | dashboard-cards-client | CICD 7 张卡片含交互态;portal-shell 已对齐 7 卡 + i18n,差距较小 | +| 2 | `/shell/teacher/exams` | exams-list-client | 列表筛选维度、状态筛选、批量操作 | +| 3 | `/shell/teacher/exams/[id]` | exam-detail-client | 详情页子 Tab 数量、阅卷入口跳转 | +| 4 | `/shell/teacher/exams/new` | new-exam-client | 表单步骤、题型选择、知识点绑定 | +| 5 | `/shell/teacher/exams/[id]/edit` | exam-edit-client | 编辑权限点、草稿恢复 | +| 6 | `/shell/teacher/exams/[id]/build` | exam-build-client | 组卷策略、难度校准 | +| 7 | `/shell/teacher/classes/[id]` | class-detail-client | 详情子 Tab、学生名单 inline | +| 8 | `/shell/teacher/classes` | classes-list-client | 列表筛选、班级切换 | +| 9 | `/shell/teacher/students` | students-list-client | 学生筛选、详情抽屉 | +| 10 | `/shell/teacher/homework/[id]` | homework-detail-client | 作业详情 Tab、提交统计 | +| 11 | `/shell/teacher/homework/new` | new-homework-client | 作业类型、附件上传 | +| 12 | `/shell/teacher/lesson-plans/[planId]/edit` | lesson-plan-edit-client | 编辑器富文本、模板插入 | +| 13 | `/shell/teacher/grades/entry` | grade-entry-client | 成绩录入批量、导入导出 | +| 14 | `/shell/teacher/attendance/sheet` | attendance-sheet-client | 考勤表批量、快捷标记 | +| 15 | `/shell/teacher/diagnostic/class/[classId]` | class-diagnostic-client | 诊断维度、对比分析 | + +> **后续处理建议**:本节 15 页的功能差距属 P1-P2 增强项,建议按产品优先级逐模块迭代补齐;每次补齐后运行 `pnpm run arch:scan` 同步 arch.db,并视情况更新 004 与本核查文档。 --- -## 五、优先级建议 +## 四、配套资产覆盖核查 -### P0(B1 共享,必须补,schema 已就绪) +### 4.1 features/teacher/*-client.tsx 覆盖(55 个页面级 client) -1. `/shell/notifications` — B1 共享,`notifications(userId)` ✅ -2. `/shell/settings` — B1 共享 +详见 §七.2(附录)。覆盖 20 个教师域模块,所有 ✅ 完整实现页均有对应 client 集成。 -### P1(B2末 规划页,必须补) +### 4.2 lib/api hooks 覆盖(20 个模块文件) -3. `/shell/teacher/exams/[id]/proctoring` — B2末,WS 二期,需先定 WS 契约 +| 模块 | 文件 | 关键 hook | +| ---------------- | ----------------------------- | --------------------------------------------------------------------------------------------------- | +| ai | `lib/api/ai.ts` | useAiAssist / useAiLessonPlan / useAiReport | +| analytics | `lib/api/analytics.ts` | useAnalytics / useStudentAnalytics | +| attendance | `lib/api/attendance.ts` | useAttendance / useAttendanceReport / useAttendanceSheet / useAttendanceStats | +| classes | `lib/api/classes.ts` | useClasses / useClassDetail / useClassStudents / useClassSchedule | +| course-plans | `lib/api/course-plans.ts` | useCoursePlans / useCoursePlanDetail | +| dashboard | `lib/api/dashboard.ts` | useTeacherDashboard | +| diagnostic | `lib/api/diagnostic.ts` | useClassDiagnostic / useStudentDiagnostic | +| elective | `lib/api/elective.ts` | useElectiveCourses / useElectiveDetail | +| error-book | `lib/api/error-book.ts` | useErrorBookItems / useErrorBookStats | +| exams | `lib/api/exams.ts` | useExams / useExamDetail / useExamAnalytics / useExamBuild / useExamProctoring | +| grades | `lib/api/grades.ts` | useGrades / useGradeEntry / useGradeStats / useReportCard | +| homework | `lib/api/homework.ts` | useHomework / useHomeworkDetail / useHomeworkSubmissions / useSubmissionDetail / useGradeSubmission | +| knowledge-graph | `lib/api/knowledge-graph.ts` | useKnowledgeGraph | +| leave | `lib/api/leave.ts` | useLeaveRequests | +| lesson-plans | `lib/api/lesson-plans.ts` | useLessonPlans / useLessonPlanDetail / useLessonPlanCalendar / useLessonPlanHeatmap | +| practice | `lib/api/practice.ts` | usePractice | +| proctoring | `lib/api/proctoring.ts` | useStudentProctoringStatuses / useRecentProctoringEvents | +| questions | `lib/api/questions.ts` | useQuestions | +| schedule-changes | `lib/api/schedule-changes.ts` | useScheduleChanges | +| students | `lib/api/students.ts` | useStudents / useStudentDetail | +| textbooks | `lib/api/textbooks.ts` | useTextbooks / useTextbookDetail | -### P2(CICD 历史功能,建议补,需同步更新 ARCH §9.1) +### 4.3 mocks/graphql-data.ts 覆盖(116 个 mock 数据集) -4. `/shell/teacher/diagnostic/student/[studentId]` — 学生诊断雷达图,CICD 已有完整实现 - -### P3(功能不完整页,按业务优先级补) - -**高优先级(核心教学场景,差距大)**:5. `classes/[id]` 班级详情 — 缺 5 个 widget(概览/趋势/课表/作业/学生科目成绩),班级是教师核心工作台6. `homework/[id]` 作业详情 — 缺分析卡片 + 内容卡片,作业是高频场景 7. `grades/entry` 成绩录入 — 缺批量录入,单生录入效率太低 8. `lesson-plans/[planId]/edit` 教案编辑 — 缺 Tiptap + AI,contentEditable deprecated 不可长期维护 - -**中优先级(交互不完整,影响体验)**:9. `students` 学生列表 — 缺下拉 + 科目成绩 10. `homework/new` 布置作业 — 缺考试关联 + 班级下拉 11. `attendance/sheet` 考勤点名 — 缺班级下拉 + 学生预加载 12. `diagnostic/class/[classId]` 班级诊断 — 缺富视图 + 服务上下文 13. `exams/[id]/edit` 富文本编辑 — 缺 Tiptap + 题目结构(与 §3.9 教案编辑同类型问题,建议统一升级 Tiptap)14. `exams` 列表 — 缺筛选/计数/分页/DataTable - -**低优先级(功能基本可用,细节差距)**:15. `exams/[id]` 详情 — 编辑按钮未接路由(小修)16. `exams/new` 新建考试 — 是否补富文本取决于产品决策 17. `classes` 列表 — 视觉差异,功能等价 18. `dashboard` 仪表盘 — i18n 化 + 流式渲染(i18n 应立即修,违反 §3.10) +269KB 文件覆盖教师域全部模块(含 3 个占位页的兜底数据:`ClassStudents`、`HomeworkSubmissions`、`SubmissionDetail`、`ExamDetail`)。所有占位页与功能差距页均有 MSW 兜底。 --- -## 六、共性技术债 +## 五、结论与建议 -1. **Tiptap 缺失**:`exams/[id]/edit`、`lesson-plans/[planId]/edit` 均用 `contentEditable` + `document.execCommand`(deprecated),CICD 用 Tiptap。建议统一引入 Tiptap(或 Lexical),编写 structure→EditorDoc 转换层。 -2. **dangerouslySetInnerHTML 违规**:上述两个富文本页直接 `dangerouslySetInnerHTML`,违反 §4 安全规范,需 DOMPurify 清洗。 -3. **下拉选择器缺失**:`students`/`homework/new`/`grades/entry`/`attendance/sheet` 等多处用 `` 替代 `
{log.userAgent} diff --git a/apps/portal-shell/src/features/admin/course-plans/course-plan-item-editor.tsx b/apps/portal-shell/src/features/admin/course-plans/course-plan-item-editor.tsx index a3e2662..ee86599 100644 --- a/apps/portal-shell/src/features/admin/course-plans/course-plan-item-editor.tsx +++ b/apps/portal-shell/src/features/admin/course-plans/course-plan-item-editor.tsx @@ -200,7 +200,7 @@ export function CoursePlanItemEditor({ value={content} onChange={(e) => setContent(e.target.value)} placeholder={t("contentPlaceholder")} - className="min-h-[100px]" + className="min-h-24" /> diff --git a/apps/portal-shell/src/features/admin/error-book/error-book-detail-dialog.tsx b/apps/portal-shell/src/features/admin/error-book/error-book-detail-dialog.tsx index b0321b2..8c30288 100644 --- a/apps/portal-shell/src/features/admin/error-book/error-book-detail-dialog.tsx +++ b/apps/portal-shell/src/features/admin/error-book/error-book-detail-dialog.tsx @@ -4,7 +4,7 @@ * 错题详情对话框(迁移自 CICD error-book-detail-dialog.tsx) * * 适配 portal-shell: - * - 用原生轻量模态(fixed inset-0 + bg-black/50 + 卡片)替代 shadcn Dialog + * - 使用 shadcn Dialog 组件 * - 数据通过 useErrorBookDetail hook(@contract-pending MSW 兜底)拉取 * - 仅展示详情(无 archive/delete/saveNote 等 server action) * - 错误处理走 notify.error() @@ -28,6 +28,14 @@ import { useErrorBookDetail } from "@/lib/api"; import { notify } from "@/shared/lib/notify"; import { Button } from "@/shared/components/ui/button"; import { Badge } from "@/shared/components/ui/badge"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/shared/components/ui/dialog"; import { Separator } from "@/shared/components/ui/separator"; export interface ErrorBookDetailDialogProps { @@ -58,65 +66,31 @@ export function ErrorBookDetailDialog({ } }, [error, tCommon]); - // ESC 键关闭 - useEffect(() => { - if (!open) return; - const handleKeyDown = (e: KeyboardEvent): void => { - if (e.key === "Escape") onOpenChange(false); - }; - window.addEventListener("keydown", handleKeyDown); - return () => window.removeEventListener("keydown", handleKeyDown); - }, [open, onOpenChange]); - - if (!open) return <>; - return ( -
onOpenChange(false)} - > -
e.stopPropagation()} - > - {/* 头部 */} -
-
-

- - {t("title")} -

- {data ? ( -
- {data.subjectName ? ( - {data.subjectName} - ) : null} - {data.knowledgePointTitle ? ( - {data.knowledgePointTitle} - ) : null} - {data.className ? ( - - {data.className} - - ) : null} -
- ) : null} -
- -
+ + + + + + {t("title")} + + + {t("title")} + + {data ? ( +
+ {data.subjectName ? ( + {data.subjectName} + ) : null} + {data.knowledgePointTitle ? ( + {data.knowledgePointTitle} + ) : null} + {data.className ? ( + {data.className} + ) : null} +
+ ) : null} +
{/* 内容区 */}
@@ -226,14 +200,13 @@ export function ErrorBookDetailDialog({ )}
- {/* 底部 */} -
+ -
-
-
+ + + ); } diff --git a/apps/portal-shell/src/features/admin/files/file-batch-operations.tsx b/apps/portal-shell/src/features/admin/files/file-batch-operations.tsx index cba5ecb..9d7926e 100644 --- a/apps/portal-shell/src/features/admin/files/file-batch-operations.tsx +++ b/apps/portal-shell/src/features/admin/files/file-batch-operations.tsx @@ -14,11 +14,21 @@ * * 关联:ARCHITECTURE.md §5.3 / §5.4 / §9.4 / §10 P5 */ -import { Trash2, X } from "lucide-react"; +import { Trash2 } from "lucide-react"; import { useEffect, useRef, useState } from "react"; import { useTranslations } from "next-intl"; import { useBatchDeleteFiles } from "@/lib/api"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from "@/shared/components/ui/alert-dialog"; import { Button } from "@/shared/components/ui/button"; import { notify } from "@/shared/lib/notify"; @@ -155,50 +165,27 @@ function ConfirmDialog({ onCancel, onConfirm, }: ConfirmDialogProps): React.ReactElement { - useEffect(() => { - const handler = (e: KeyboardEvent): void => { - if (e.key === "Escape" && !loading) onCancel(); - }; - window.addEventListener("keydown", handler); - return () => window.removeEventListener("keydown", handler); - }, [loading, onCancel]); - return ( -
{ - if (!loading) onCancel(); + { + if (!o && !loading) onCancel(); }} - role="dialog" - aria-modal="true" - aria-label={title} > -
e.stopPropagation()} - > -
-

{title}

- -
-

{description}

-
- - -
-
-
+ + + + ); } diff --git a/apps/portal-shell/src/features/admin/lesson-plans/delete-confirm-dialog.tsx b/apps/portal-shell/src/features/admin/lesson-plans/delete-confirm-dialog.tsx index b016a3a..a2390bb 100644 --- a/apps/portal-shell/src/features/admin/lesson-plans/delete-confirm-dialog.tsx +++ b/apps/portal-shell/src/features/admin/lesson-plans/delete-confirm-dialog.tsx @@ -4,12 +4,11 @@ * 教案软删除确认对话框(基于 shadcn AlertDialog,ARCHITECTURE.md §7.3 / §9.4) * * 用于 admin/lesson-plans 列表页与详情页的删除确认。 - * 结构:AlertDialog + AlertDialogContent。 - * 交互:ESC 关闭、点击遮罩关闭、确认按钮 destructive 变体。 + * 交互:ESC 关闭、点击遮罩关闭、确认按钮 destructive 变体(由 Radix Dialog 提供)。 * * 关联:ARCHITECTURE.md §7.3 详情/列表页 / §9.4 / §11.3 */ -import { AlertTriangle, Loader2 } from "lucide-react"; +import { Loader2 } from "lucide-react"; import { useTranslations } from "next-intl"; import { @@ -30,7 +29,7 @@ export interface DeleteConfirmDialogProps { onConfirm: () => void; /** 取消回调(点击遮罩 / ESC / 取消按钮) */ onCancel: () => void; - /** 删除进行中(禁用按钮、隐藏 spinner) */ + /** 删除进行中(禁用按钮、显示 spinner) */ loading?: boolean; } @@ -44,39 +43,32 @@ export function DeleteConfirmDialog({ onConfirm, onCancel, loading = false, -}: DeleteConfirmDialogProps): React.ReactElement | null { +}: DeleteConfirmDialogProps): React.ReactElement { const t = useTranslations("admin.lessonPlans.delete"); + const handleOpenChange = (next: boolean): void => { + // 删除进行中时禁止关闭(与原 ESC 行为一致) + if (loading) return; + if (!next) onCancel(); + }; + + const handleConfirm = (e: React.MouseEvent): void => { + e.preventDefault(); + onConfirm(); + }; + return ( - { - if (!nextOpen && !loading) { - onCancel(); - } - }} - > + -
- - - - - {t("title")} - {t("description")} - -
+ + {t("title")} + {t("description")} + - + {t("cancel")} - { - e.preventDefault(); - onConfirm(); - }} - disabled={loading} - > + {loading ? ( <> diff --git a/apps/portal-shell/src/features/admin/lesson-plans/export-utils.ts b/apps/portal-shell/src/features/admin/lesson-plans/export-utils.ts new file mode 100644 index 0000000..7f275f7 --- /dev/null +++ b/apps/portal-shell/src/features/admin/lesson-plans/export-utils.ts @@ -0,0 +1,116 @@ +/** + * Admin Lesson Plans 导出工具(ARCHITECTURE.md §9.4 / §10 P5) + * + * 前端构造 CSV 并触发下载(@contract-pending,无后端导出契约时使用)。 + * 后端补齐导出契约后,可改为调用后端接口获取 CSV。 + * + * 设计原则: + * - `lessonPlanToExportRow` 为纯函数,便于单测;不直接依赖 i18n, + * 状态文本由调用方通过 columnLabels 传入 + * - `exportLessonPlansToCsv` 执行客户端下载,调用方处理 notify + * + * 关联:ARCHITECTURE.md §5.3 / §5.4 / §9.4 / §10 P5 / §11.3 / §11.4 + */ +import type { AdminLessonPlanListItem } from "@/lib/api"; +import { downloadBlob } from "@/shared/lib/download"; + +/** 导出列标识(与 LessonPlanExportRow 的 key 对应) */ +export type LessonPlanExportColumnKey = + | "title" + | "subject" + | "grade" + | "className" + | "status" + | "teacher" + | "createdAt"; + +/** 列标签映射(由调用方传入已本地化的字符串) */ +export interface LessonPlanColumnLabels { + title: string; + subject: string; + grade: string; + className: string; + status: string; + teacher: string; + createdAt: string; + statusDraft: string; + statusPublished: string; + statusArchived: string; + statusSubmitted: string; +} + +/** 导出列定义 */ +export interface LessonPlanExportColumn { + key: LessonPlanExportColumnKey; + label: string; +} + +/** 导出行(键值对,键对应 LessonPlanExportColumn.key) */ +export type LessonPlanExportRow = Record; + +function formatStatus(status: string, labels: LessonPlanColumnLabels): string { + switch (status) { + case "DRAFT": + return labels.statusDraft; + case "PUBLISHED": + return labels.statusPublished; + case "ARCHIVED": + return labels.statusArchived; + case "SUBMITTED": + return labels.statusSubmitted; + default: + return status; + } +} + +export function buildLessonPlanExportColumns( + labels: LessonPlanColumnLabels, +): readonly LessonPlanExportColumn[] { + return [ + { key: "title", label: labels.title }, + { key: "subject", label: labels.subject }, + { key: "grade", label: labels.grade }, + { key: "className", label: labels.className }, + { key: "status", label: labels.status }, + { key: "teacher", label: labels.teacher }, + { key: "createdAt", label: labels.createdAt }, + ]; +} + +export function lessonPlanToExportRow( + plan: AdminLessonPlanListItem, + labels: LessonPlanColumnLabels, +): LessonPlanExportRow { + return { + title: plan.title ?? "", + subject: plan.subjectName || plan.subjectId || "", + grade: "", + className: plan.className ?? "", + status: formatStatus(plan.status, labels), + teacher: plan.teacherName ?? "", + createdAt: plan.createdAt ?? "", + }; +} + +function escapeCsvField(value: string): string { + if (value === "") return ""; + const needsQuote = /[",\n\r]/.test(value); + const escaped = value.replace(/"/g, '""'); + return needsQuote ? `"${escaped}"` : escaped; +} + +export function exportLessonPlansToCsv( + filename: string, + plans: AdminLessonPlanListItem[], + labels: LessonPlanColumnLabels, +): void { + const columns = buildLessonPlanExportColumns(labels); + const header = columns.map((c) => escapeCsvField(c.label)).join(","); + const rows = plans.map((plan) => { + const row = lessonPlanToExportRow(plan, labels); + return columns.map((c) => escapeCsvField(row[c.key])).join(","); + }); + const csv = "\uFEFF" + [header, ...rows].join("\r\n"); + const blob = new Blob([csv], { type: "text/csv;charset=utf-8" }); + downloadBlob(blob, `${filename}.csv`); +} diff --git a/apps/portal-shell/src/features/admin/organization/organization-detail-dialog.tsx b/apps/portal-shell/src/features/admin/organization/organization-detail-dialog.tsx new file mode 100644 index 0000000..8bc17c0 --- /dev/null +++ b/apps/portal-shell/src/features/admin/organization/organization-detail-dialog.tsx @@ -0,0 +1,110 @@ +"use client"; + +/** + * 组织节点详情对话框(ARCHITECTURE.md §7.3 详情页 / §9.4 / §10 P5) + * + * 数据契约: + * - 详情数据复用树中的 OrgNode 对象(@contract-pending:单查契约待补齐) + * + * 关联:ARCHITECTURE.md §5.5 / §7.3 / §9.4 / §10 P5 / §11.3 + */ +import { Building2 } from "lucide-react"; +import { useTranslations } from "next-intl"; + +import type { OrgNode } from "@/lib/api/admin-p5"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from "@/shared/components/ui/dialog"; +import { DetailField, DetailSection } from "@/shared/components/page-templates"; +import { + countChildren, + formatMemberCount, + formatOrgType, + orgTypeToBadgeClass, +} from "@/features/admin/organization/transformations"; + +/** + * 组织节点详情对话框。受控组件,由父组件管理 open 状态与选中数据。 + */ +export function OrganizationDetailDialog({ + open, + onOpenChange, + node, +}: { + open: boolean; + onOpenChange: (open: boolean) => void; + node: OrgNode | null; +}): React.ReactElement { + const t = useTranslations("admin.organization.detail"); + + return ( + + + + + + {t("title")} + + {t("description")} + + + {node ? : null} + + + ); +} + +/** + * 详情内容区(基本信息 + 结构信息)。 + */ +function OrganizationDetailBody({ + node, +}: { + node: OrgNode; +}): React.ReactElement { + const t = useTranslations("admin.organization.detail"); + + return ( +
+ + + } + /> + + + + + + + + +
+ ); +} + +/** + * 类型徽章(按节点类型色阶展示)。 + */ +function TypeBadge({ type }: { type: string }): React.ReactElement { + const label = formatOrgType(type); + const cls = orgTypeToBadgeClass(type); + return ( + + {label} + + ); +} diff --git a/apps/portal-shell/src/features/admin/plugins/plugins-client.tsx b/apps/portal-shell/src/features/admin/plugins/plugins-client.tsx index acb7946..0dfc103 100644 --- a/apps/portal-shell/src/features/admin/plugins/plugins-client.tsx +++ b/apps/portal-shell/src/features/admin/plugins/plugins-client.tsx @@ -33,11 +33,13 @@ import { ListPageSkeleton, } from "@/shared/components/page-templates"; import { - Card, - CardContent, - CardHeader, - CardTitle, -} from "@/shared/components/ui/card"; + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/shared/components/ui/dialog"; import { activeToBadgeClass, builtinToBadgeClass, @@ -318,12 +320,20 @@ function PluginEditDialog({ }; return ( -
- - - {t("editTitle")} - - + { + if (!o) onClose(); + }} + > + + + {t("editTitle")} + + {t("editTitle")} + + +
@@ -366,17 +376,17 @@ function PluginEditDialog({

{draftError}

) : null} -
+ -
- - -
+ +
+ + ); } diff --git a/apps/portal-shell/src/features/admin/questions/batch-operations.tsx b/apps/portal-shell/src/features/admin/questions/batch-operations.tsx index 239086a..bf818b5 100644 --- a/apps/portal-shell/src/features/admin/questions/batch-operations.tsx +++ b/apps/portal-shell/src/features/admin/questions/batch-operations.tsx @@ -23,6 +23,16 @@ import { useState } from "react"; import { useTranslations } from "next-intl"; import { useBatchDeleteQuestions } from "@/lib/api"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from "@/shared/components/ui/alert-dialog"; import { Button } from "@/shared/components/ui/button"; import { notify } from "@/shared/lib/notify"; @@ -132,37 +142,28 @@ function ConfirmDeleteDialog({ }): React.ReactElement { const t = useTranslations("admin.questions.batch"); return ( -
{ + if (!o && !loading) onCancel(); + }} > -
e.stopPropagation()} - > -

{t("confirmTitle")}

-

- {t("confirmDescription", { count })} -

-
- - -
-
-
+
+
+
+
); } diff --git a/apps/portal-shell/src/features/admin/questions/create-question-dialog.tsx b/apps/portal-shell/src/features/admin/questions/create-question-dialog.tsx index 70c9730..9961bc2 100644 --- a/apps/portal-shell/src/features/admin/questions/create-question-dialog.tsx +++ b/apps/portal-shell/src/features/admin/questions/create-question-dialog.tsx @@ -23,6 +23,14 @@ import { useTranslations } from "next-intl"; import { useCreateQuestion } from "@/lib/api"; import { Button } from "@/shared/components/ui/button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/shared/components/ui/dialog"; import { Input } from "@/shared/components/ui/input"; import { Select } from "@/shared/components/ui/select"; import { notify } from "@/shared/lib/notify"; @@ -127,16 +135,17 @@ export function CreateQuestionDialog({ }; return ( -
{ + if (!o) handleClose(); + }} > -
e.stopPropagation()} - > -

{t("title")}

-

{t("description")}

+ + + {t("title")} + {t("description")} +