feat(exams,homework): add rich text exam editor and scan-based grading

- Add Tiptap-based rich text editor with custom extensions (dotted-mark,
  blank-node, image-node, group-block, question-block) for exam creation
- Add AI auto-marking action to convert pasted exam text to structured editor doc
- Add resizable split-panel layout for editor + live preview
- Add student scan upload (photo of paper answers) with drag-drop and reorder
- Add scan image viewer with zoom/rotate/fullscreen for teachers
- Add scan grading view with side-by-side questions and scan images
- Add /teacher/exams/new and /teacher/homework/submissions/[id]/scan-grading routes
- Fix getScansAction to support both teacher (HOMEWORK_GRADE) and student
  (HOMEWORK_SUBMIT) permission scopes
- Add i18n keys for rich editor, scan upload, and scan grading (zh-CN/en)
- Sync architecture diagrams (004/005) with new modules, routes, and deps
This commit is contained in:
SpecialX
2026-06-24 13:16:33 +08:00
parent 0c64219cb8
commit 6114607c1e
30 changed files with 3548 additions and 26 deletions

View File

@@ -123,6 +123,57 @@
"highErrorWarningDesc": "The following questions have an error rate above 70% and are recommended for focused review",
"noData": "No analytics data yet. Data will be available after students submit and grading is complete.",
"viewAnalytics": "View Analytics"
},
"richEditor": {
"title": "Rich Text Exam Builder",
"description": "Paste exam text, use toolbar to mark questions/groups/blanks/dotted chars, live preview on the right",
"basicInfo": "Basic Info",
"sourceText": "Source Text",
"sourceTextPlaceholder": "Paste the full exam text here...",
"aiAutoMark": "AI Auto-Mark",
"aiMarking": "AI marking...",
"aiMarkSuccess": "AI auto-marking complete",
"aiMarkFailed": "AI auto-marking failed",
"aiMarkHint": "After pasting exam text, click this button to let AI auto-detect question structure",
"editorArea": "Editor",
"previewArea": "Preview",
"emptyEditor": "Enter or paste exam content in the editor on the left",
"emptyPreview": "Preview will appear here",
"markQuestion": "Mark Question",
"markGroup": "Mark Group",
"markDotted": "Dotted Char",
"markBlank": "Blank",
"insertImage": "Insert Image",
"questionType": "Type",
"score": "Score",
"groupTitle": "Group Title",
"saveDraft": "Save Draft",
"saving": "Saving...",
"saveSuccess": "Exam draft created",
"saveFailed": "Failed to create",
"loadFormFailed": "Failed to load form data",
"titleRequired": "Please fill in the exam title",
"subjectGradeRequired": "Please select subject and grade",
"emptyContent": "Exam content is empty",
"pasteSourceFirst": "Please paste exam text first",
"bold": "Bold",
"italic": "Italic",
"strike": "Strikethrough",
"dotted": "Dotted",
"bulletList": "Bullet List",
"orderedList": "Ordered List",
"quote": "Quote",
"undo": "Undo",
"redo": "Redo",
"questionTypes": {
"single_choice": "Single Choice",
"multiple_choice": "Multiple Choice",
"true_false": "True/False",
"fill_blank": "Fill in the Blank",
"short_answer": "Short Answer",
"essay": "Essay",
"text": "Text"
}
}
},
"homework": {
@@ -238,7 +289,18 @@
"autoSaveSaved": "Auto-saved",
"autoSaveError": "Auto-save failed. Will retry when network recovers.",
"autoSaveRestored": "Restored unsaved answers from offline cache",
"autoSaveCacheError": "Failed to restore offline cache"
"autoSaveCacheError": "Failed to restore offline cache",
"scanTitle": "Answer Scan Upload",
"scanDescription": "After answering on paper, photograph and upload the full paper",
"selectImageFiles": "Please select image files",
"uploadFailed": "Upload failed",
"uploadSuccess": "Uploaded {{count}} images",
"deleteScan": "Delete",
"moveUp": "Move Up",
"moveDown": "Move Down",
"dragDropHint": "Drag images here, or click to select files",
"pageLabel": "Page {{page}}",
"scanDisabled": "Submitted, scan images cannot be modified"
},
"grade": {
"title": "Grade",
@@ -286,7 +348,21 @@
"batchSelectAtLeastOne": "Please select at least one submission",
"batchFailed": "Batch grading failed",
"selectAll": "Select All",
"selectRow": "Select this row"
"selectRow": "Select this row",
"scanGrading": "Scan Grading",
"prevSubmission": "Previous",
"nextSubmission": "Next",
"saveGrading": "Save Grading",
"questionsAndGrading": "Questions & Grading",
"studentScanImages": "Student Scan Images",
"loadingScans": "Loading...",
"loadingImages": "Loading scan images...",
"noScans": "No scan images",
"saveFailed": "Save failed",
"scanFeedbackPlaceholder": "Grading feedback (optional)...",
"scoreOutOf": "/ {{max}} pts",
"questionsCount": "Questions & Grading ({{count}} items)",
"scanPagesCount": "Student Scan Images ({{count}} pages)"
},
"review": {
"title": "Review",