From 59f45f63e84beb5e42cb23d73c90cb55097519b7 Mon Sep 17 00:00:00 2001 From: chenyuan Date: Wed, 16 Sep 2026 17:33:08 +0800 Subject: [PATCH] feat: add teacher and student exam pages --- src/api/exam.js | 36 ++++++ src/layout/components/Sidebar/index.vue | 4 + src/router/index.js | 72 ++++++++++- src/views/exam/index.vue | 33 ++++++ src/views/exam/result.vue | 19 +++ src/views/exam/take.vue | 55 +++++++++ src/views/teacherEnd/exam/analysis.vue | 29 +++++ src/views/teacherEnd/exam/editor.vue | 129 ++++++++++++++++++++ src/views/teacherEnd/exam/index.vue | 151 ++++++++++++++++++++++++ src/views/teacherEnd/exam/review.vue | 85 +++++++++++++ 10 files changed, 609 insertions(+), 4 deletions(-) create mode 100644 src/api/exam.js create mode 100644 src/views/exam/index.vue create mode 100644 src/views/exam/result.vue create mode 100644 src/views/exam/take.vue create mode 100644 src/views/teacherEnd/exam/analysis.vue create mode 100644 src/views/teacherEnd/exam/editor.vue create mode 100644 src/views/teacherEnd/exam/index.vue create mode 100644 src/views/teacherEnd/exam/review.vue diff --git a/src/api/exam.js b/src/api/exam.js new file mode 100644 index 0000000..3163111 --- /dev/null +++ b/src/api/exam.js @@ -0,0 +1,36 @@ +import request from "@/utils/request"; + +const base = "/api/exams"; + +export const getExamClasses = () => request({ url: `${base}/teacher/classes`, method: "get" }); +export const getQuestions = (params) => request({ url: `${base}/teacher/questions`, method: "get", params }); +export const createQuestion = (data) => request({ url: `${base}/teacher/questions`, method: "post", data }); +export const updateQuestion = (id, data) => request({ url: `${base}/teacher/questions/${id}`, method: "put", data }); +export const deleteQuestion = (id) => request({ url: `${base}/teacher/questions/${id}`, method: "delete" }); +export const uploadQuestionAttachment = (data) => request({ url: `${base}/teacher/questions/attachment`, method: "post", data, headers: { "Content-Type": "multipart/form-data" } }); +export const importQuestions = (data) => request({ url: `${base}/teacher/questions/import`, method: "post", data, headers: { "Content-Type": "multipart/form-data" } }); +export const downloadQuestionTemplate = () => request({ url: `${base}/teacher/questions/template`, method: "get", responseType: "blob" }); + +export const getTeacherExams = (params) => request({ url: `${base}/teacher`, method: "get", params }); +export const getTeacherExam = (id) => request({ url: `${base}/teacher/${id}`, method: "get" }); +export const createExam = (data) => request({ url: `${base}/teacher`, method: "post", data }); +export const updateExam = (id, data) => request({ url: `${base}/teacher/${id}`, method: "put", data }); +export const publishExam = (id) => request({ url: `${base}/teacher/${id}/publish`, method: "post" }); +export const withdrawExam = (id) => request({ url: `${base}/teacher/${id}/withdraw`, method: "post" }); +export const deleteExam = (id) => request({ url: `${base}/teacher/${id}`, method: "delete" }); +export const exportExamPaper = (id) => request({ url: `${base}/teacher/${id}/paper`, method: "get", responseType: "blob" }); +export const getReviewStudents = (id) => request({ url: `${base}/teacher/${id}/review`, method: "get" }); +export const getReviewDetail = (id, attemptId) => request({ url: `${base}/teacher/${id}/review/${attemptId}`, method: "get" }); +export const saveSubjectiveScores = (id, attemptId, data) => request({ url: `${base}/teacher/${id}/review/${attemptId}`, method: "put", data }); +export const rollbackAttempt = (id, attemptId) => request({ url: `${base}/teacher/${id}/review/${attemptId}/rollback`, method: "post" }); +export const exportExamScores = (id) => request({ url: `${base}/teacher/${id}/scores`, method: "get", responseType: "blob" }); +export const getExamAnalysis = (id, params) => request({ url: `${base}/teacher/${id}/analysis`, method: "get", params }); +export const exportExamSubmissions = (id) => request({ url: `${base}/teacher/${id}/submissions`, method: "get", responseType: "blob" }); + +export const getStudentExams = () => request({ url: `${base}/student`, method: "get" }); +export const enterExam = (id, deviceToken) => request({ url: `${base}/student/${id}/enter`, method: "post", data: { deviceToken } }); +export const getActivePaper = (id, deviceToken) => request({ url: `${base}/student/${id}/paper`, method: "get", params: { deviceToken } }); +export const saveExamAnswer = (id, questionId, data) => request({ url: `${base}/student/${id}/answers/${questionId}`, method: "put", data }); +export const uploadExamAnswerFile = (id, questionId, deviceToken, data) => request({ url: `${base}/student/${id}/answers/${questionId}/file`, method: "post", params: { deviceToken }, data, headers: { "Content-Type": "multipart/form-data" } }); +export const submitExam = (id, deviceToken) => request({ url: `${base}/student/${id}/submit`, method: "post", data: { deviceToken } }); +export const getExamResult = (id) => request({ url: `${base}/student/${id}/result`, method: "get" }); diff --git a/src/layout/components/Sidebar/index.vue b/src/layout/components/Sidebar/index.vue index cbb79ca..51719ee 100644 --- a/src/layout/components/Sidebar/index.vue +++ b/src/layout/components/Sidebar/index.vue @@ -363,6 +363,7 @@ const teacherManageNav = [ { title: "任务分配", path: "/task/index" }, { title: "任务评阅", path: "/task-review/index" }, { title: "成绩中心", path: "/score/index" }, + { title: "综合考核", path: "/exam/index" }, ], }, ]; @@ -394,6 +395,7 @@ const trainingTaskKeyByPath = { }; const studentCourseBaseNav = [ { title: "AI智能助手", path: "/assistant/index", activePrefix: "/assistant" }, + { title: "考试考核", path: "/examination/index", activePrefix: "/examination" }, ]; const studentTaskSections = ref([]); const currentCourseNav = computed(() => (isTeacherRole.value @@ -435,6 +437,8 @@ const studentIconByPath = { "/task/index": "Tickets", "/task-review/index": "DocumentChecked", "/score/index": "DataBoard", + "/exam/index": "Finished", + "/examination/index": "Finished", }; const studentIconBySectionStart = [ { path: "/training/", icon: "Document" }, diff --git a/src/router/index.js b/src/router/index.js index 76a41b6..425fb8a 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -495,9 +495,37 @@ export const dynamicRoutes = [ meta: { title: "BI可视化分析", icon: "dashboard", affix: true }, }, ], - }, -]; -// 教师端路由 + }, + { + path: "/examination", + component: Layout, + redirect: "/examination/index", + roles: ["student"], + children: [ + { + path: "index", + component: () => import("@/views/exam/index.vue"), + name: "StudentExaminations", + meta: { title: "考试考核", icon: "Finished", affix: true }, + }, + { + path: "take/:id", + component: () => import("@/views/exam/take.vue"), + name: "StudentExamTake", + hidden: true, + meta: { title: "考试作答", affix: false }, + }, + { + path: "result/:id", + component: () => import("@/views/exam/result.vue"), + name: "StudentExamResult", + hidden: true, + meta: { title: "考试成绩", affix: false }, + }, + ], + }, +]; +// 教师端路由 export const teacherRoutes = [ { path: "/task-review-preview", @@ -669,7 +697,43 @@ export const teacherRoutes = [ meta: { title: "成绩中心", icon: "成绩中心", affix: true }, }, ], - }, + }, + { + path: "/exam", + component: Layout, + redirect: "/exam/index", + roles: ["teacher"], + meta: { title: "综合考核", icon: "Finished", affix: true }, + children: [ + { + path: "index", + component: () => import("@/views/teacherEnd/exam/index.vue"), + name: "TeacherExams", + meta: { title: "综合考核", icon: "Finished", affix: true }, + }, + { + path: "manage/:id", + component: () => import("@/views/teacherEnd/exam/editor.vue"), + name: "TeacherExamEditor", + hidden: true, + meta: { title: "考试设置", affix: false }, + }, + { + path: "review/:id", + component: () => import("@/views/teacherEnd/exam/review.vue"), + name: "TeacherExamReview", + hidden: true, + meta: { title: "考试评阅", affix: false }, + }, + { + path: "analysis/:id", + component: () => import("@/views/teacherEnd/exam/analysis.vue"), + name: "TeacherExamAnalysis", + hidden: true, + meta: { title: "考试分析", affix: false }, + }, + ], + }, ]; export const platformAdminRoutes = [ { diff --git a/src/views/exam/index.vue b/src/views/exam/index.vue new file mode 100644 index 0000000..2ca9aa1 --- /dev/null +++ b/src/views/exam/index.vue @@ -0,0 +1,33 @@ + + + diff --git a/src/views/exam/result.vue b/src/views/exam/result.vue new file mode 100644 index 0000000..4cd47ce --- /dev/null +++ b/src/views/exam/result.vue @@ -0,0 +1,19 @@ + + + diff --git a/src/views/exam/take.vue b/src/views/exam/take.vue new file mode 100644 index 0000000..5c9a223 --- /dev/null +++ b/src/views/exam/take.vue @@ -0,0 +1,55 @@ + + + diff --git a/src/views/teacherEnd/exam/analysis.vue b/src/views/teacherEnd/exam/analysis.vue new file mode 100644 index 0000000..90d93ca --- /dev/null +++ b/src/views/teacherEnd/exam/analysis.vue @@ -0,0 +1,29 @@ + + + diff --git a/src/views/teacherEnd/exam/editor.vue b/src/views/teacherEnd/exam/editor.vue new file mode 100644 index 0000000..8e2c9ce --- /dev/null +++ b/src/views/teacherEnd/exam/editor.vue @@ -0,0 +1,129 @@ + + + + + diff --git a/src/views/teacherEnd/exam/index.vue b/src/views/teacherEnd/exam/index.vue new file mode 100644 index 0000000..3e6a898 --- /dev/null +++ b/src/views/teacherEnd/exam/index.vue @@ -0,0 +1,151 @@ + + + + + diff --git a/src/views/teacherEnd/exam/review.vue b/src/views/teacherEnd/exam/review.vue new file mode 100644 index 0000000..8171f5c --- /dev/null +++ b/src/views/teacherEnd/exam/review.vue @@ -0,0 +1,85 @@ + + + + +