From 590af0951f7ec358dccc3897cc298627720fb616 Mon Sep 17 00:00:00 2001 From: yuk255 Date: Sat, 30 Aug 2025 17:50:14 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AF=B9=E6=8E=A5=E9=83=A8=E5=88=86?= =?UTF-8?q?=E9=A2=98=E5=BA=93=E6=8E=A5=E5=8F=A3=EF=BC=9B=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E8=80=83=E8=AF=95=E7=95=8C=E9=9D=A2=EF=BC=9B=E9=83=A8=E5=88=86?= =?UTF-8?q?=E7=95=8C=E9=9D=A2=E6=A0=B7=E5=BC=8F=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/modules/exam.ts | 42 +- src/api/types.ts | 12 + src/data/mockExamData.ts | 301 +++++ src/router/index.ts | 9 +- src/views/Exam.vue | 13 +- src/views/Profile.vue | 11 +- .../ExamPages/ExamNoticeBeforeStart.vue | 333 +++++ src/views/teacher/ExamPages/ExamTaking.vue | 1123 ++++++++++++----- .../ExamPages/QuestionBankManagement.vue | 301 ++--- .../teacher/ExamPages/QuestionManagement.vue | 6 +- 10 files changed, 1678 insertions(+), 473 deletions(-) create mode 100644 src/data/mockExamData.ts create mode 100644 src/views/teacher/ExamPages/ExamNoticeBeforeStart.vue diff --git a/src/api/modules/exam.ts b/src/api/modules/exam.ts index 6b42605..75cd072 100644 --- a/src/api/modules/exam.ts +++ b/src/api/modules/exam.ts @@ -2,6 +2,7 @@ import { ApiRequest } from '../request' import type { ApiResponse, + ApiResponseWithResult, Repo, Question, CreateRepoRequest, @@ -36,19 +37,54 @@ export class ExamApi { /** * 获取课程题库 */ - static async getCourseRepoList(): Promise> { - const response = await ApiRequest.get(`/biz/repo/repoList`) + static async getCourseRepoList(): Promise> { + const response = await ApiRequest.get<{ result: Repo[] }>(`/biz/repo/repoList`) console.log('✅ 获取课程题库列表成功:', response) return response } + /** + * 获取课程列表(用于题库筛选) + */ + static async getCourseList(): Promise> { + try { + // 调用现有的课程列表API,但只返回id和name字段 + const response = await ApiRequest.get('/biz/course/list') + console.log('✅ 获取课程列表成功:', response) + + // 处理响应数据,只提取id和name + if (response.data && response.data.success && response.data.result) { + const courseList = response.data.result.map((course: any) => ({ + id: course.id, + name: course.name || course.title || '未命名课程' + })) + + return { + code: response.data.code || 200, + message: response.data.message || 'success', + data: courseList + } + } + + // 如果响应格式不符合预期,返回空数组 + return { + code: 200, + message: 'success', + data: [] + } + } catch (error) { + console.error('获取课程列表失败:', error) + throw error + } + } + /** * 删除题库 */ static async deleteRepo(id: string): Promise> { console.log('🚀 删除题库:', { id }) const response = await ApiRequest.delete('/gen/repo/repo/delete', { - params: { id } + id }) console.log('✅ 删除题库成功:', response) return response diff --git a/src/api/types.ts b/src/api/types.ts index cf332b8..04dafd4 100644 --- a/src/api/types.ts +++ b/src/api/types.ts @@ -8,6 +8,16 @@ export interface ApiResponse { timestamp?: string } +// 带有 result 包装的响应类型 +export interface ApiResponseWithResult { + code: number + message: string + data: { + result: T + } + timestamp?: string +} + // 分页响应类型 export interface PaginationResponse { list: T[] @@ -695,6 +705,8 @@ export interface Repo { createTime: string updateBy: string updateTime: string + courseId?: string // 所属课程ID + courseName?: string // 所属课程名称 } export interface Question { diff --git a/src/data/mockExamData.ts b/src/data/mockExamData.ts new file mode 100644 index 0000000..70f9fde --- /dev/null +++ b/src/data/mockExamData.ts @@ -0,0 +1,301 @@ +// 模拟考试数据 +export const mockExamData = { + id: "exam_001", + examName: "计算机基础知识测试", + duration: 90, // 考试时长(分钟) + totalScore: 100, + questions: [ + { + id: "section_1", + title: "第一大题:单选题", + type: "section", + description: "请从四个选项中选择一个正确答案", + subQuestions: [ + { + id: "q1", + type: "single_choice", + title: "在HTML中,用于定义无序列表的标签是?", + score: 2, + options: [ + { + id: "q1_a", + content: "
    " + }, + { + id: "q1_b", + content: "
      " + }, + { + id: "q1_c", + content: "
    • " + }, + { + id: "q1_d", + content: "
      " + } + ] + }, + { + id: "q2", + type: "single_choice", + title: "CSS中用于设置字体大小的属性是?", + score: 2, + options: [ + { + id: "q2_a", + content: "font-weight" + }, + { + id: "q2_b", + content: "font-size" + }, + { + id: "q2_c", + content: "font-family" + }, + { + id: "q2_d", + content: "font-style" + } + ] + }, + { + id: "q3", + type: "single_choice", + title: "JavaScript中声明变量使用的关键字是?", + score: 2, + options: [ + { + id: "q3_a", + content: "var" + }, + { + id: "q3_b", + content: "let" + }, + { + id: "q3_c", + content: "const" + }, + { + id: "q3_d", + content: "以上都是" + } + ] + }, + { + id: "q4", + type: "single_choice", + title: "HTTP协议默认端口号是?", + score: 2, + options: [ + { + id: "q4_a", + content: "21" + }, + { + id: "q4_b", + content: "80" + }, + { + id: "q4_c", + content: "443" + }, + { + id: "q4_d", + content: "8080" + } + ] + }, + { + id: "q5", + type: "single_choice", + title: "下列哪个不是Vue.js的生命周期钩子?", + score: 2, + options: [ + { + id: "q5_a", + content: "mounted" + }, + { + id: "q5_b", + content: "created" + }, + { + id: "q5_c", + content: "destroyed" + }, + { + id: "q5_d", + content: "rendered" + } + ] + } + ] + }, + { + id: "section_2", + title: "第二大题:多选题", + type: "section", + description: "请从选项中选择所有正确答案", + subQuestions: [ + { + id: "q6", + type: "multiple_choice", + title: "下列哪些是前端开发常用的框架?", + score: 3, + options: [ + { + id: "q6_a", + content: "React" + }, + { + id: "q6_b", + content: "Vue.js" + }, + { + id: "q6_c", + content: "Angular" + }, + { + id: "q6_d", + content: "Express.js" + } + ] + }, + { + id: "q7", + type: "multiple_choice", + title: "CSS布局方式包括哪些?", + score: 3, + options: [ + { + id: "q7_a", + content: "Flexbox" + }, + { + id: "q7_b", + content: "Grid" + }, + { + id: "q7_c", + content: "Float" + }, + { + id: "q7_d", + content: "Position" + } + ] + }, + { + id: "q8", + type: "multiple_choice", + title: "HTTP状态码中,表示成功的有哪些?", + score: 3, + options: [ + { + id: "q8_a", + content: "200" + }, + { + id: "q8_b", + content: "201" + }, + { + id: "q8_c", + content: "404" + }, + { + id: "q8_d", + content: "500" + } + ] + } + ] + }, + { + id: "section_3", + title: "第三大题:判断题", + type: "section", + description: "请判断下列说法是否正确", + subQuestions: [ + { + id: "q9", + type: "true_false", + title: "HTML是一种编程语言。", + score: 2 + }, + { + id: "q10", + type: "true_false", + title: "CSS可以用来控制网页的布局和样式。", + score: 2 + }, + { + id: "q11", + type: "true_false", + title: "JavaScript只能在浏览器中运行。", + score: 2 + }, + { + id: "q12", + type: "true_false", + title: "Git是一个版本控制系统。", + score: 2 + } + ] + }, + { + id: "section_4", + title: "第四大题:填空题", + type: "section", + description: "请在空白处填入正确答案", + subQuestions: [ + { + id: "q13", + type: "fill_blank", + title: "HTML文档的基本结构包括_____、_____和_____三个主要部分。", + score: 3, + fillBlanks: [ + { id: "blank1", answer: "DOCTYPE声明" }, + { id: "blank2", answer: "head" }, + { id: "blank3", answer: "body" } + ] + }, + { + id: "q14", + type: "fill_blank", + title: "CSS选择器中,_____选择器用于选择具有特定ID的元素,_____选择器用于选择具有特定类名的元素。", + score: 2, + fillBlanks: [ + { id: "blank4", answer: "ID" }, + { id: "blank5", answer: "类" } + ] + } + ] + }, + { + id: "section_5", + title: "第五大题:简答题", + type: "section", + description: "请根据题目要求作答", + subQuestions: [ + { + id: "q15", + type: "short_answer", + title: "请简述响应式网页设计的基本原理和实现方法。", + score: 5 + }, + { + id: "q16", + type: "short_answer", + title: "解释JavaScript中异步编程的概念,并举例说明。", + score: 5 + } + ] + } + ] +}; + +// 导出考试数据用于在组件中使用 +export default mockExamData; diff --git a/src/router/index.ts b/src/router/index.ts index 3d42579..da3f645 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -72,6 +72,7 @@ import AddQuestion from '@/views/teacher/ExamPages/AddQuestion.vue' import StudentList from '@/views/teacher/ExamPages/StudentList.vue' import GradingPage from '@/views/teacher/ExamPages/GradingPage.vue' import ExamTaking from '@/views/teacher/ExamPages/ExamTaking.vue' +import ExamNoticeBeforeStart from '@/views/teacher/ExamPages/ExamNoticeBeforeStart.vue' import ChapterEditor from '@/views/teacher/course/ChapterEditor.vue' @@ -366,6 +367,12 @@ const routes: RouteRecordRaw[] = [ ] }, + { + path: '/exam/notice/:id', + name: 'ExamNoticeBeforeStart', + component: ExamNoticeBeforeStart, + meta: { title: '考前须知' } + }, { path: '/taking/:id', name: 'ExamTaking', @@ -457,7 +464,7 @@ const routes: RouteRecordRaw[] = [ meta: { title: '学习中心', requiresAuth: true } }, { - path: '/profile', + path: '/profile/:tabKey?', name: 'Profile', component: Profile, meta: { title: '个人中心', requiresAuth: true } diff --git a/src/views/Exam.vue b/src/views/Exam.vue index 07d73ac..ff24090 100644 --- a/src/views/Exam.vue +++ b/src/views/Exam.vue @@ -11,8 +11,6 @@ - -
      @@ -881,8 +879,15 @@ const questionsByType = computed(() => { -// 开始考试 +// 开始考试(从考前须知页面跳转到这里) const startExam = () => { + // 跳转到考前须知页面 + const examId = sectionId.value || 1 // 使用 sectionId 作为考试ID,如果没有则默认为1 + router.push(`/exam/notice/${examId}`) +} + +// 实际开始考试 +const beginExam = () => { examStarted.value = true remainingTime.value = examDuration.value * 60 startTimer() @@ -1220,7 +1225,7 @@ onMounted(() => { // 检查是否从考前须知页面跳转过来,如果是则直接开始考试 const fromNotice = route.query.fromNotice if (fromNotice === 'true') { - startExam() + beginExam() } }) diff --git a/src/views/Profile.vue b/src/views/Profile.vue index 791eb7f..92720aa 100644 --- a/src/views/Profile.vue +++ b/src/views/Profile.vue @@ -1071,14 +1071,14 @@ import { ref, computed, onMounted, onActivated, reactive } from 'vue' import { useMessage, NInput, NForm, NFormItem } from 'naive-ui' import { useI18n } from 'vue-i18n' -import { useRouter } from 'vue-router' import { useUserStore } from '@/stores/user' import SafeAvatar from '@/components/common/SafeAvatar.vue' import QuillEditor from '@/components/common/QuillEditor.vue' +import { useRouter, useRoute } from 'vue-router' const { t, locale } = useI18n() const router = useRouter() - +const route = useRoute() // 轮播图根据语言动态切换 const bannerImage = computed(() => { return locale.value === 'zh' ? '/banners/banner8.png' : '/banners/banner1-en.png' @@ -2288,6 +2288,7 @@ const isMessageTab = computed(() => activeTab.value === 'message') // 处理菜单选择 const handleMenuSelect = (key: TabType) => { activeTab.value = key + router.push(`/profile/${key}`) // message.info(`切换到${getTabTitle(key)}`) } @@ -2392,7 +2393,8 @@ const startExam = (examId: number) => { // 继续考试 const continueExam = (examId: number) => { - message.info(`继续考试 ${examId}`) + // message.info(`继续考试 ${examId}`) + router.push(`/exam/notice/${examId}`) } // 查看考试结果 @@ -2759,6 +2761,9 @@ onMounted(() => { window.location.reload() }, 100) } + + const tabKey = route.params.tabKey || 'courses' + handleMenuSelect(tabKey) }) onActivated(() => { diff --git a/src/views/teacher/ExamPages/ExamNoticeBeforeStart.vue b/src/views/teacher/ExamPages/ExamNoticeBeforeStart.vue new file mode 100644 index 0000000..5c83acb --- /dev/null +++ b/src/views/teacher/ExamPages/ExamNoticeBeforeStart.vue @@ -0,0 +1,333 @@ + + + + + diff --git a/src/views/teacher/ExamPages/ExamTaking.vue b/src/views/teacher/ExamPages/ExamTaking.vue index 1acd038..f77e7e6 100644 --- a/src/views/teacher/ExamPages/ExamTaking.vue +++ b/src/views/teacher/ExamPages/ExamTaking.vue @@ -15,17 +15,32 @@
      剩余时间
      -
      {{ formatTime(remainingTime) }}
      +
      {{ formatTime(remainingTime) + }}
      +
      + + +
      +
      + ⚠️ + 违规次数: {{ violationCount }}/3 +
      -