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:
@@ -1544,6 +1544,23 @@
|
||||
"audit/components/data-change-log-table.tsx"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "ResizablePanel",
|
||||
"file": "components/ui/resizable-panel.tsx",
|
||||
"props": "{ minLeft?: number, minRight?: number, initialLeft?: number, left: ReactNode, right: ReactNode, className?: string }",
|
||||
"purpose": "2026-06-24 新增:自实现可拖拽分栏容器(左右两栏 + 中间分隔条 pointer 拖拽调整宽度,无新依赖)。用于试卷富文本编辑器左编辑右预览、阅卷式批改左题目右扫描图等场景。pointerdown/move/up 事件驱动,document.body cursor=userSelect=none 防选中,clamp 到 minLeft/minRight 范围。",
|
||||
"internalDeps": [
|
||||
"cn",
|
||||
"useState",
|
||||
"useRef",
|
||||
"useCallback",
|
||||
"useEffect"
|
||||
],
|
||||
"usedBy": [
|
||||
"exams/components/exam-rich-form.tsx",
|
||||
"homework/components/homework-scan-grading-view.tsx"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "StatusBadge",
|
||||
"file": "components/ui/status-badge.tsx",
|
||||
@@ -2337,7 +2354,8 @@
|
||||
"createdAt"
|
||||
],
|
||||
"usedBy": [
|
||||
"files"
|
||||
"files",
|
||||
"homework"
|
||||
]
|
||||
},
|
||||
"gradeRecords": {
|
||||
@@ -2906,6 +2924,44 @@
|
||||
"usedBy": [
|
||||
"management/grade/dashboard"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "autoMarkExamAction",
|
||||
"permission": "EXAM_AI_GENERATE",
|
||||
"signature": "(prevState: ActionState<AutoMarkResult> | null, formData: FormData) => Promise<ActionState<AutoMarkResult>>",
|
||||
"purpose": "2026-06-24 新增:AI 自动标记试卷文本。将粘贴的试卷文本交给 AI 解析为带题目块/分组/填空/加点字标记的 Tiptap JSONContent 文档,教师可在富文本编辑器中基于此结果继续微调。返回 { doc, title } 供编辑器载入。",
|
||||
"deps": [
|
||||
"requirePermission",
|
||||
"shared/lib/ai.createAiChatCompletion",
|
||||
"shared/lib/action-utils.handleActionError",
|
||||
"exams/ai-pipeline/parse.parseAiResponse",
|
||||
"exams/actions.buildTiptapDocFromAiResponse",
|
||||
"exams/actions.extractTitleFromAiResponse",
|
||||
"AutoMarkSchema"
|
||||
],
|
||||
"usedBy": [
|
||||
"exams/components/exam-rich-form.tsx"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "createExamFromRichEditorAction",
|
||||
"permission": "EXAM_CREATE",
|
||||
"signature": "(prevState: ActionState<string> | null, formData: FormData) => Promise<ActionState<string>>",
|
||||
"purpose": "2026-06-24 新增:从富文本编辑器保存试卷草稿。将 Tiptap JSONContent 通过 editor/editor-to-structure.editorDocToStructure 转换为 questions + structure 后复用 persistAiGeneratedExamDraft 持久化。revalidatePath('/teacher/exams/all')。",
|
||||
"deps": [
|
||||
"requirePermission",
|
||||
"shared/lib/action-utils.handleActionError",
|
||||
"shared/lib/action-utils.safeJsonParse",
|
||||
"exams/editor/editor-to-structure.editorDocToStructure",
|
||||
"exams/data-access.persistAiGeneratedExamDraft",
|
||||
"exams/actions.prepareExamCreateContext",
|
||||
"exams/actions.parseExamModeConfig",
|
||||
"RichExamCreateSchema",
|
||||
"revalidatePath"
|
||||
],
|
||||
"usedBy": [
|
||||
"exams/components/exam-rich-form.tsx"
|
||||
]
|
||||
}
|
||||
],
|
||||
"dataAccess": [
|
||||
@@ -3294,6 +3350,37 @@
|
||||
"grades/components/batch-grade-entry",
|
||||
"teacher/grades/entry/page.tsx"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "AutoMarkResult",
|
||||
"type": "interface",
|
||||
"file": "actions.ts",
|
||||
"definition": "{ doc: unknown; title: string }",
|
||||
"purpose": "2026-06-24 新增:AI 自动标记结果。doc 为 Tiptap JSONContent 文档(可直接载入富文本编辑器),title 为解析出的试卷标题(如有)",
|
||||
"usedBy": [
|
||||
"exams/actions.autoMarkExamAction",
|
||||
"exams/components/exam-rich-form.tsx"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "AutoMarkSchema",
|
||||
"type": "const",
|
||||
"file": "actions.ts",
|
||||
"definition": "z.object({ sourceText: z.string().min(1), aiProviderId: z.string().optional() })",
|
||||
"purpose": "2026-06-24 新增:autoMarkExamAction 的 Zod 输入校验 schema",
|
||||
"usedBy": [
|
||||
"exams/actions.autoMarkExamAction"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "RichExamCreateSchema",
|
||||
"type": "const",
|
||||
"file": "actions.ts",
|
||||
"definition": "z.object({ title, subject, grade, difficulty: z.coerce.number().int().min(1).max(5), totalScore: z.coerce.number().int().min(1), durationMin: z.coerce.number().int().min(1), scheduledAt: z.string().optional().nullable(), editorDoc: z.string().min(1) })",
|
||||
"purpose": "2026-06-24 新增:createExamFromRichEditorAction 的 Zod 输入校验 schema。editorDoc 为 Tiptap JSONContent 的 JSON 字符串",
|
||||
"usedBy": [
|
||||
"exams/actions.createExamFromRichEditorAction"
|
||||
]
|
||||
}
|
||||
],
|
||||
"components": [
|
||||
@@ -3510,6 +3597,11 @@
|
||||
"types": [
|
||||
"ExamAnalyticsSummary"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "ExamRichForm",
|
||||
"file": "exam-rich-form.tsx",
|
||||
"purpose": "2026-06-24 新增:富文本编辑器试卷创建表单(use client)。使用 ResizablePanel 左编辑右预览布局,集成 autoMarkExamAction 一键 AI 标记(粘贴试卷文本 → AI 解析为 Tiptap doc 载入编辑器)+ createExamFromRichEditorAction 保存草稿。调用 getSubjectsAction/getGradesAction 填充科目年级选项。包含 difficulty/totalScore/durationMin/scheduledAt 等基本信息字段。"
|
||||
}
|
||||
],
|
||||
"hooks": [
|
||||
@@ -3569,7 +3661,211 @@
|
||||
"exams/components/exam-analytics-dashboard.tsx"
|
||||
]
|
||||
}
|
||||
]
|
||||
],
|
||||
"editor": {
|
||||
"_description": "2026-06-24 新增:基于 Tiptap 的试卷富文本编辑器子模块(src/modules/exams/editor/)。提供自定义扩展(题目块/分组/填空/加点字/图片)、Tiptap JSONContent ↔ ExamStructure 双向转换、浮动选择工具栏。Barrel 导出位于 editor/index.ts。",
|
||||
"components": [
|
||||
{
|
||||
"name": "ExamRichEditor",
|
||||
"file": "editor/exam-rich-editor.tsx",
|
||||
"type": "component",
|
||||
"purpose": "富文本编辑器主体(use client + forwardRef)。基于 Tiptap StarterKit + 自定义扩展(DottedMark/BlankNode/ImageNode/QuestionBlock/GroupBlock)。通过 ExamRichEditorHandle 暴露 getJSON() 供父组件获取 JSONContent。集成 SelectionToolbar 浮动工具栏。",
|
||||
"usedBy": [
|
||||
"exams/components/exam-rich-form.tsx"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "ExamRichEditorHandle",
|
||||
"file": "editor/exam-rich-editor.tsx",
|
||||
"type": "interface",
|
||||
"definition": "{ getJSON: () => EditorJSONContent | null }",
|
||||
"purpose": "ExamRichEditor 的 ref 句柄类型,暴露 getJSON 方法获取当前编辑器内容的 Tiptap JSONContent",
|
||||
"usedBy": [
|
||||
"exams/components/exam-rich-form.tsx"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "SelectionToolbar",
|
||||
"file": "editor/selection-toolbar.tsx",
|
||||
"type": "component",
|
||||
"purpose": "浮动选择工具栏(use client)。当编辑器内有文本选中时显示,提供 DottedMark(加点字)等标记的快捷应用按钮。",
|
||||
"usedBy": [
|
||||
"exams/editor/exam-rich-editor.tsx"
|
||||
]
|
||||
}
|
||||
],
|
||||
"extensions": [
|
||||
{
|
||||
"name": "DottedMark",
|
||||
"file": "editor/extensions/dotted-mark.ts",
|
||||
"type": "tiptap-extension",
|
||||
"purpose": "加点字标记扩展(Mark)。用于拼音注音题中加点的字,渲染为带下点样式的 span。",
|
||||
"usedBy": [
|
||||
"exams/editor/exam-rich-editor.tsx",
|
||||
"exams/editor/selection-toolbar.tsx"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "BlankNode",
|
||||
"file": "editor/extensions/blank-node.tsx",
|
||||
"type": "tiptap-extension",
|
||||
"purpose": "填空空位节点扩展(Node,原子节点)。渲染为可填写的空位占位符,含 id/answer/score 属性。",
|
||||
"usedBy": [
|
||||
"exams/editor/exam-rich-editor.tsx"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "ImageNode",
|
||||
"file": "editor/extensions/image-node.ts",
|
||||
"type": "tiptap-extension",
|
||||
"purpose": "图片节点扩展(Node)。含 fileId/url/alt 属性,用于在题目中嵌入图片。",
|
||||
"usedBy": [
|
||||
"exams/editor/exam-rich-editor.tsx"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "GroupBlock",
|
||||
"file": "editor/extensions/group-block.tsx",
|
||||
"type": "tiptap-extension",
|
||||
"purpose": "大题分组节点扩展(Node,块级)。含 title 属性,用于「一、选择题」「二、填空题」等大题分组。",
|
||||
"usedBy": [
|
||||
"exams/editor/exam-rich-editor.tsx"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "QuestionBlock",
|
||||
"file": "editor/extensions/question-block.tsx",
|
||||
"type": "tiptap-extension",
|
||||
"purpose": "题目块节点扩展(Node,块级)。含 type(QuestionBlockType: single_choice|multiple_choice|judgment|text|composite)/score 等 attrs(QuestionBlockAttrs),用于包裹一道题目。",
|
||||
"usedBy": [
|
||||
"exams/editor/exam-rich-editor.tsx"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "QuestionBlockType",
|
||||
"file": "editor/extensions/question-block.tsx",
|
||||
"type": "type",
|
||||
"definition": "\"single_choice\" | \"multiple_choice\" | \"judgment\" | \"text\" | \"composite\"",
|
||||
"purpose": "题目块类型枚举",
|
||||
"usedBy": [
|
||||
"exams/editor/extensions/question-block.tsx"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "QuestionBlockAttrs",
|
||||
"file": "editor/extensions/question-block.tsx",
|
||||
"type": "interface",
|
||||
"definition": "{ type: QuestionBlockType; score: number; questionId?: string }",
|
||||
"purpose": "题目块属性接口",
|
||||
"usedBy": [
|
||||
"exams/editor/extensions/question-block.tsx"
|
||||
]
|
||||
}
|
||||
],
|
||||
"types": [
|
||||
{
|
||||
"name": "RichQuestionType",
|
||||
"file": "editor/exam-rich-editor-types.ts",
|
||||
"type": "type",
|
||||
"definition": "\"single_choice\" | \"multiple_choice\" | \"judgment\" | \"text\" | \"composite\"",
|
||||
"purpose": "富文本编辑器题目类型枚举",
|
||||
"usedBy": [
|
||||
"exams/editor/*",
|
||||
"exams/editor/editor-to-structure.ts",
|
||||
"exams/editor/structure-to-editor.ts"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "RichQuestionContent",
|
||||
"file": "editor/exam-rich-editor-types.ts",
|
||||
"type": "interface",
|
||||
"definition": "{ text: string; options?: Array<{ id, text, isCorrect? }>; blanks?: Array<{ id, answer?, score? }>; images?: Array<{ fileId, url, alt? }>; subQuestions?: Array<{ id, text, answer?, score? }>; correctAnswer?: unknown }",
|
||||
"purpose": "富文本编辑器题目内容结构(题干文本+选项+填空+图片+子题+正确答案)",
|
||||
"usedBy": [
|
||||
"exams/editor/exam-rich-editor-types.ts.EditorQuestion",
|
||||
"exams/editor/editor-to-structure.ts",
|
||||
"exams/editor/structure-to-editor.ts"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "EditorQuestion",
|
||||
"file": "editor/exam-rich-editor-types.ts",
|
||||
"type": "interface",
|
||||
"definition": "{ id: string; type: RichQuestionType; score: number; content: RichQuestionContent }",
|
||||
"purpose": "富文本编辑器题目结构",
|
||||
"usedBy": [
|
||||
"exams/editor/exam-rich-editor-types.ts.EditorDoc",
|
||||
"exams/editor/editor-to-structure.ts",
|
||||
"exams/editor/structure-to-editor.ts"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "EditorStructureNode",
|
||||
"file": "editor/exam-rich-editor-types.ts",
|
||||
"type": "interface",
|
||||
"definition": "{ id: string; type: \"group\" | \"question\"; title?: string; questionId?: string; score?: number; children?: EditorStructureNode[] }",
|
||||
"purpose": "富文本编辑器结构节点(递归,group|question)",
|
||||
"usedBy": [
|
||||
"exams/editor/exam-rich-editor-types.ts.EditorDoc",
|
||||
"exams/editor/editor-to-structure.ts",
|
||||
"exams/editor/structure-to-editor.ts"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "EditorDoc",
|
||||
"file": "editor/exam-rich-editor-types.ts",
|
||||
"type": "interface",
|
||||
"definition": "{ title: string; questions: EditorQuestion[]; structure: EditorStructureNode[] }",
|
||||
"purpose": "富文本编辑器文档结构(标题+题目列表+结构树),由 editorDocToStructure 产出",
|
||||
"usedBy": [
|
||||
"exams/editor/editor-to-structure.ts",
|
||||
"exams/editor/structure-to-editor.ts",
|
||||
"exams/components/exam-rich-form.tsx"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "EditorJSONContent",
|
||||
"file": "editor/exam-rich-editor-types.ts",
|
||||
"type": "type",
|
||||
"definition": "JSONContent (from @tiptap/react)",
|
||||
"purpose": "Tiptap JSONContent 别名,用于编辑器与试卷结构互转",
|
||||
"usedBy": [
|
||||
"exams/editor/exam-rich-editor.tsx",
|
||||
"exams/components/exam-rich-form.tsx"
|
||||
]
|
||||
}
|
||||
],
|
||||
"converters": [
|
||||
{
|
||||
"name": "editorDocToStructure",
|
||||
"file": "editor/editor-to-structure.ts",
|
||||
"type": "function",
|
||||
"signature": "(doc: EditorJSONContent, fallbackTitle: string) => EditorDoc",
|
||||
"purpose": "将 Tiptap JSONContent 转换为 EditorDoc 结构(title + questions + structure)。遍历 doc.nodes,识别 GroupBlock/QuestionBlock/BlankNode/DottedMark/ImageNode 等自定义节点,提取题目内容与结构树。供 createExamFromRichEditorAction 持久化使用。",
|
||||
"deps": [
|
||||
"@paralleldrive/cuid2.createId",
|
||||
"exams/editor/exam-rich-editor-types.ts"
|
||||
],
|
||||
"usedBy": [
|
||||
"exams/actions.createExamFromRichEditorAction",
|
||||
"exams/components/exam-rich-form.tsx"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "structureToEditorDoc",
|
||||
"file": "editor/structure-to-editor.ts",
|
||||
"type": "function",
|
||||
"signature": "(doc: EditorDoc) => EditorJSONContent",
|
||||
"purpose": "将 EditorDoc 结构转换回 Tiptap JSONContent,供编辑器回填已保存的试卷内容。",
|
||||
"deps": [
|
||||
"exams/editor/exam-rich-editor-types.ts"
|
||||
],
|
||||
"usedBy": [
|
||||
"exams/editor/exam-rich-editor.tsx"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"homework": {
|
||||
@@ -3659,6 +3955,39 @@
|
||||
"usedBy": [
|
||||
"homework-batch-grading-view.tsx"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "getScansAction",
|
||||
"permission": "HOMEWORK_GRADE | HOMEWORK_SUBMIT",
|
||||
"signature": "(submissionId: string) => Promise<ActionState<ScanAttachment[]>>",
|
||||
"purpose": "2026-06-24 新增:获取某次提交的所有答题扫描图。扫描图存储在 fileAttachments 表中 targetType=\"homework\"/targetId=submissionId。支持两类访问者:教师 HOMEWORK_GRADE(仅可访问自己创建的作业的提交,通过 getHomeworkSubmissionForGrading 校验 creatorId===ctx.userId 或 dataScope.type===\"all\")/ 学生 HOMEWORK_SUBMIT(仅可访问自己的提交,通过 getHomeworkSubmissionForPermission 校验 studentId===ctx.userId)。按 createdAt asc 排序,page 从 1 开始递增。",
|
||||
"deps": [
|
||||
"requirePermission",
|
||||
"shared/lib/action-utils.handleActionError",
|
||||
"data-access-write.getHomeworkSubmissionForGrading",
|
||||
"data-access-write.getHomeworkSubmissionForPermission",
|
||||
"shared/db",
|
||||
"shared/db.schema.fileAttachments",
|
||||
"drizzle-orm.eq/and/asc"
|
||||
],
|
||||
"usedBy": [
|
||||
"homework/components/homework-scan-grading-view.tsx"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "deleteScanAction",
|
||||
"permission": "HOMEWORK_SUBMIT",
|
||||
"signature": "(submissionId: string, fileId: string) => Promise<ActionState<null>>",
|
||||
"purpose": "2026-06-24 新增:删除答题扫描图。仅允许提交者本人删除(通过 getHomeworkSubmissionForPermission 校验 studentId===ctx.userId),且仅在提交状态为 started 时允许(已提交/已批改的提交锁定不可修改)。调用 files/data-access.deleteFileAttachment 删除 DB 记录(磁盘文件由 files 模块处理)。",
|
||||
"deps": [
|
||||
"requirePermission",
|
||||
"shared/lib/action-utils.handleActionError",
|
||||
"data-access-write.getHomeworkSubmissionForPermission",
|
||||
"files/data-access.deleteFileAttachment"
|
||||
],
|
||||
"usedBy": [
|
||||
"homework/components/scan-uploader.tsx"
|
||||
]
|
||||
}
|
||||
],
|
||||
"dataAccess": [
|
||||
@@ -4153,6 +4482,29 @@
|
||||
"usedBy": [
|
||||
"student/dashboard"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "ScanAttachment",
|
||||
"type": "interface",
|
||||
"file": "actions.ts",
|
||||
"definition": "{ fileId: string; url: string; filename: string; originalName: string; page: number }",
|
||||
"purpose": "2026-06-24 新增:扫描图附件结构(由 getScansAction 返回)。page 为页码(按 createdAt asc 排序,从 1 开始递增)。",
|
||||
"usedBy": [
|
||||
"homework/actions.getScansAction",
|
||||
"homework/components/homework-scan-grading-view.tsx"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "ScanImage",
|
||||
"type": "interface",
|
||||
"file": "components/scan-uploader.tsx",
|
||||
"definition": "{ fileId: string; url: string; filename: string; originalName?: string; page: number }",
|
||||
"purpose": "2026-06-24 新增:扫描图 UI 结构(由 ScanUploader 导出,供 ScanImageViewer 复用)。与 ScanAttachment 字段一致,但 originalName 可选。",
|
||||
"usedBy": [
|
||||
"homework/components/scan-uploader.tsx",
|
||||
"homework/components/scan-image-viewer.tsx",
|
||||
"homework/components/homework-scan-grading-view.tsx"
|
||||
]
|
||||
}
|
||||
],
|
||||
"components": [
|
||||
@@ -4215,6 +4567,62 @@
|
||||
"name": "HomeworkSubmissionResult",
|
||||
"file": "homework-submission-result.tsx",
|
||||
"purpose": "V3-9 新增:提交后即时反馈页。学生提交后立即看到分数汇总(总分/满分、得分率 Progress)、对错分布(正确/错误/部分正确/待批改)、错题预览(题目文本、学生答案、正确答案)。对标智学网/猿题库提交后反馈。"
|
||||
},
|
||||
{
|
||||
"name": "ScanUploader",
|
||||
"file": "scan-uploader.tsx",
|
||||
"purpose": "2026-06-24 新增:学生扫描图上传组件(use client)。学生在纸上作答后按页拍摄上传,调用 /api/upload 上传图片(targetType=\"homework\"/targetId=submissionId),返回 fileId+url。上传后将 fileId 列表通过 onChange 暴露给父组件。支持增删/排序,onDeleteScan 回调用于删除 fileAttachments 记录(调用 deleteScanAction)。已提交(disabled)时禁止操作。导出 ScanImage 类型供 ScanImageViewer 复用。",
|
||||
"deps": [
|
||||
"shared/components/ui/button",
|
||||
"shared/lib/utils.cn",
|
||||
"next-intl.useTranslations",
|
||||
"sonner.toast",
|
||||
"lucide-react (Upload/X/Loader2/ImageIcon)"
|
||||
],
|
||||
"usedBy": [
|
||||
"homework/components/homework-scan-grading-view.tsx"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "ScanImageViewer",
|
||||
"file": "scan-image-viewer.tsx",
|
||||
"purpose": "2026-06-24 新增:扫描图查看器(use client)。用于阅卷式批改时查看学生答题图片。支持翻页(上一页/下一页+页码)、缩放(ZoomIn/ZoomOut,0.25 步进,1-3 范围)、旋转(RotateCw,90° 步进)、全屏(Maximize2)。切换页面时重置缩放与旋转。",
|
||||
"deps": [
|
||||
"shared/components/ui/button",
|
||||
"shared/lib/utils.cn",
|
||||
"lucide-react (ChevronLeft/ChevronRight/ZoomIn/ZoomOut/Maximize2/RotateCw)",
|
||||
"homework/components/scan-uploader.ScanImage"
|
||||
],
|
||||
"usedBy": [
|
||||
"homework/components/homework-scan-grading-view.tsx"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "HomeworkScanGradingView",
|
||||
"file": "homework-scan-grading-view.tsx",
|
||||
"purpose": "2026-06-24 新增:教师阅卷式批改视图(use client)。使用 ResizablePanel 左侧题目+批改表单、右侧 ScanImageViewer 扫描图查看器。集成 getScansAction 拉取扫描图、gradeHomeworkSubmissionAction 批改。支持上一份/下一份提交快速切换(prevSubmissionId/nextSubmissionId)。显示学生姓名、作业标题、提交时间、状态、总分。左侧每题显示题干、学生答案、得分输入、反馈输入。",
|
||||
"deps": [
|
||||
"shared/components/ui/button",
|
||||
"shared/components/ui/card",
|
||||
"shared/components/ui/input",
|
||||
"shared/components/ui/label",
|
||||
"shared/components/ui/textarea",
|
||||
"shared/components/ui/badge",
|
||||
"shared/components/ui/scroll-area",
|
||||
"shared/components/ui/resizable-panel.ResizablePanel",
|
||||
"shared/lib/utils.formatDate",
|
||||
"shared/lib/utils.cn",
|
||||
"next-intl.useTranslations",
|
||||
"sonner.toast",
|
||||
"homework/actions.gradeHomeworkSubmissionAction",
|
||||
"homework/actions.getScansAction",
|
||||
"homework/components/question-renderer.QuestionRenderer",
|
||||
"homework/components/scan-image-viewer.ScanImageViewer",
|
||||
"lucide-react (Save/ChevronLeft/ChevronRight/User/Clock)"
|
||||
],
|
||||
"usedBy": [
|
||||
"app/(dashboard)/teacher/homework/submissions/[submissionId]/scan-grading/page.tsx"
|
||||
]
|
||||
}
|
||||
],
|
||||
"hooks": [
|
||||
@@ -9055,10 +9463,15 @@
|
||||
"name": "FileTargetType",
|
||||
"type": "type",
|
||||
"file": "types.ts",
|
||||
"definition": "\"exam\" | \"textbook\" | \"question\" | \"announcement\"",
|
||||
"definition": "\"exam\" | \"textbook\" | \"question\" | \"announcement\" | \"homework\"",
|
||||
"purpose": "文件附件关联资源类型枚举。2026-06-24 新增 \"homework\" 枚举值,用于学生答题扫描图附件(targetId = homeworkSubmissions.id)",
|
||||
"usedBy": [
|
||||
"types.FileAttachment.targetType",
|
||||
"file-upload.tsx"
|
||||
"file-upload.tsx",
|
||||
"app/api/upload/route.ts (VALID_TARGET_TYPES)",
|
||||
"homework/actions.getScansAction",
|
||||
"homework/actions.deleteScanAction",
|
||||
"homework/components/scan-uploader.tsx"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -16352,7 +16765,8 @@
|
||||
"exams",
|
||||
"classes",
|
||||
"school",
|
||||
"users"
|
||||
"users",
|
||||
"files"
|
||||
],
|
||||
"uses": {
|
||||
"shared": [
|
||||
@@ -16384,6 +16798,9 @@
|
||||
"users": [
|
||||
"data-access.getUserWithRole",
|
||||
"data-access.getUserNamesByIds"
|
||||
],
|
||||
"files": [
|
||||
"data-access.deleteFileAttachment"
|
||||
]
|
||||
}
|
||||
},
|
||||
@@ -18514,6 +18931,19 @@
|
||||
],
|
||||
"permission": "exam:create"
|
||||
},
|
||||
"/teacher/exams/new": {
|
||||
"component": "NewExamPage + ExamRichForm",
|
||||
"type": "server",
|
||||
"module": "exams",
|
||||
"actions": [
|
||||
"autoMarkExamAction",
|
||||
"createExamFromRichEditorAction",
|
||||
"getSubjectsAction",
|
||||
"getGradesAction"
|
||||
],
|
||||
"permission": "exam:create",
|
||||
"description": "2026-06-24 新增:富文本编辑器试卷创建页面。基于 Tiptap 的所见即所得试卷编辑器,支持 AI 自动标记(粘贴试卷文本 → AI 解析为 Tiptap doc)、手动编辑题目块/分组/填空/加点字/图片、左编辑右预览分栏布局(ResizablePanel)。保存时将 Tiptap JSONContent 转换为 questions + structure 后持久化为试卷草稿。"
|
||||
},
|
||||
"/teacher/questions": {
|
||||
"component": "QuestionDataTable",
|
||||
"type": "server",
|
||||
@@ -18645,6 +19075,20 @@
|
||||
],
|
||||
"permission": "homework:grade"
|
||||
},
|
||||
"/teacher/homework/submissions/[submissionId]/scan-grading": {
|
||||
"component": "HomeworkScanGradingPage + HomeworkScanGradingView",
|
||||
"type": "server",
|
||||
"module": "homework",
|
||||
"actions": [
|
||||
"gradeHomeworkSubmissionAction",
|
||||
"getScansAction"
|
||||
],
|
||||
"dataAccess": [
|
||||
"homework/data-access.getHomeworkSubmissionDetails"
|
||||
],
|
||||
"permission": "homework:grade",
|
||||
"description": "2026-06-24 新增:教师阅卷式批改页面。左侧显示题目+批改表单,右侧显示学生答题扫描图(ScanImageViewer,支持翻页/缩放/旋转/全屏)。使用 ResizablePanel 可拖拽分栏。支持上一份/下一份提交快速切换。适用于学生纸上作答后上传扫描图的场景。"
|
||||
},
|
||||
"/teacher/exams": {
|
||||
"component": "重定向",
|
||||
"type": "server",
|
||||
|
||||
Reference in New Issue
Block a user