2025-08-18 22:09:42 +08:00

2585 lines
62 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="exam-page">
<!-- 考试中心标题 -->
<div class="banner-section">
<div class="banner-container">
<img src="/banners/考前须知.png" alt="考前须知" class="banner-image" />
</div>
<div class="banner-text">
<h2>考试中心</h2>
<p>涵盖多种题型全方位考核AI智能阅卷</p>
</div>
</div>
<!-- 考试说明页面 -->
<div class="exam-instructions" v-if="!examStarted">
<div class="container">
<div class="instructions-card">
<h3>考试说明</h3>
<ul>
<li>本次考试共{{ questions.length }}道题,总分{{ totalScore }}分</li>
<li>考试时长{{ examDuration }}分钟,请合理安排答题时间</li>
<li>考试开始后不能暂停,请确保网络连接稳定</li>
<li>每道题只能选择一个答案,提交后不能修改</li>
<li>考试结束后系统将自动提交答案</li>
</ul>
<div class="exam-actions">
<button class="btn-start-exam" @click="startExam">开始考试</button>
<button class="btn-back" @click="goBack">返回课程</button>
</div>
</div>
</div>
</div>
<!-- 课程信息导航 -->
<div class="course-info-nav" v-if="examStarted">
<div class="container">
<div class="nav-content">
<div class="nav-left">
<span class="nav-title">{{ courseName }}</span>
<span class="nav-separator">></span>
<span class="nav-item">{{ examName }}</span>
</div>
<div class="nav-right">
<span class="question-type-item" :class="{ 'active': currentQuestionType === '单选题' }">单选 ({{
questionsByType['单选题'].length }})</span>
<span class="type-separator">|</span>
<span class="question-type-item" :class="{ 'active': currentQuestionType === '多选题' }">多选 ({{
questionsByType['多选题'].length }})</span>
<span class="type-separator">|</span>
<span class="question-type-item" :class="{ 'active': currentQuestionType === '判断题' }">判断 ({{
questionsByType['判断题'].length }})</span>
<span class="type-separator">|</span>
<span class="question-type-item" :class="{ 'active': currentQuestionType === '填空题' }">填空 ({{
questionsByType['填空题'].length }})</span>
<span class="type-separator">|</span>
<span class="question-type-item" :class="{ 'active': currentQuestionType === '简答题' }">简答 ({{
questionsByType['简答题'].length }})</span>
</div>
</div>
</div>
</div>
<!-- 考试主体内容 -->
<div class="exam-main" v-if="examStarted && !examFinished">
<div class="container">
<div class="exam-layout">
<!-- 左侧导航 -->
<div class="exam-sidebar">
<!-- 考试时间 -->
<div class="exam-timer" :class="{ 'disabled': examSubmitted }">
<h4>剩余时间</h4>
<div class="timer-display"
:class="{ 'timer-warning': remainingTime <= 300 && !examSubmitted, 'disabled': examSubmitted }">
<span v-if="!examSubmitted">{{ Math.floor(remainingTime / 3600).toString().padStart(2, '0') }}</span>
<span v-else>00</span>
<span class="timer-separator">:</span>
<span v-if="!examSubmitted">{{ Math.floor((remainingTime % 3600) / 60).toString().padStart(2, '0')
}}</span>
<span v-else>00</span>
<span class="timer-separator">:</span>
<span v-if="!examSubmitted">{{ (remainingTime % 60).toString().padStart(2, '0') }}</span>
<span v-else>00</span>
</div>
<div class="timer-labels" :class="{ 'disabled': examSubmitted }">
<span>时</span>
<span>分</span>
<span>秒</span>
</div>
</div>
<!-- 答题卡 -->
<div class="answer-sheet" :class="{ 'disabled': examSubmitted }">
<div class="answer-sheet-header">
<h4>答题卡</h4>
<span class="progress-text">{{ String(currentQuestionIndex + 1).padStart(2, '0') }}<span>/{{
String(questions.length).padStart(2, '0') }}</span></span>
</div>
<div class="question-grid">
<!-- 单选题 -->
<div class="question-section">
<div class="section-header">
<h5>单选 ({{ questionsByType['单选题'].length }})</h5>
</div>
<div class="question-numbers">
<div v-for="question in questionsByType['单选题']" :key="question.id" class="answer-card-number"
:class="[getQuestionStatus(question.id - 1), { 'disabled': examSubmitted }]"
@click="handleQuestionClick(question.id - 1)">
{{ String(question.id).padStart(2, '0') }}
</div>
</div>
</div>
<!-- 多选题 -->
<div class="question-section">
<div class="section-header">
<h5>多选 ({{ questionsByType['多选题'].length }})</h5>
</div>
<div class="question-numbers">
<div v-for="question in questionsByType['多选题']" :key="question.id" class="answer-card-number"
:class="[getQuestionStatus(question.id - 1), { 'disabled': examSubmitted }]"
@click="handleQuestionClick(question.id - 1)">
{{ String(question.id).padStart(2, '0') }}
</div>
</div>
</div>
<!-- 判断题 -->
<div class="question-section">
<div class="section-header">
<h5>判断 ({{ questionsByType['判断题'].length }})</h5>
</div>
<div class="question-numbers">
<div v-for="question in questionsByType['判断题']" :key="question.id" class="answer-card-number"
:class="[getQuestionStatus(question.id - 1), { 'disabled': examSubmitted }]"
@click="handleQuestionClick(question.id - 1)">
{{ String(question.id).padStart(2, '0') }}
</div>
</div>
</div>
<!-- 填空题 -->
<div class="question-section">
<div class="section-header">
<h5>填空 ({{ questionsByType['填空题'].length }})</h5>
</div>
<div class="question-numbers">
<div v-for="question in questionsByType['填空题']" :key="question.id" class="answer-card-number"
:class="[getQuestionStatus(question.id - 1), { 'disabled': examSubmitted }]"
@click="handleQuestionClick(question.id - 1)">
{{ String(question.id).padStart(2, '0') }}
</div>
</div>
</div>
<!-- 简答题 -->
<div class="question-section">
<div class="section-header">
<h5>简答 ({{ questionsByType['简答题'].length }})</h5>
</div>
<div class="question-numbers">
<div v-for="question in questionsByType['简答题']" :key="question.id" class="answer-card-number"
:class="[getQuestionStatus(question.id - 1), { 'disabled': examSubmitted }]"
@click="handleQuestionClick(question.id - 1)">
{{ String(question.id).padStart(2, '0') }}
</div>
</div>
</div>
</div>
<!-- 答题状态说明 -->
<div class="answer-legend">
<div class="legend-item">
<div class="legend-icon unanswered"></div>
<span class="legend-text">未答</span>
</div>
<div class="legend-item">
<div class="legend-icon answered"></div>
<span class="legend-text">已答</span>
</div>
</div>
<!-- 交卷按钮 -->
<div class="submit-section">
<button class="btn-submit-exam" :class="{ 'disabled': examSubmitted }" :disabled="examSubmitted"
@click="submitExam">
交卷
</button>
</div>
</div>
</div>
<!-- 右侧题目内容 -->
<div class="exam-content">
<div class="question-card" v-if="currentQuestion && !examFinished">
<div class="question-header">
<span class="question-title-info">{{ String(currentQuestionIndex + 1).padStart(2, '0') }}【{{
getQuestionTypeShort(currentQuestion.type) }}】<span>{{ currentQuestion.score }}分</span></span>
</div>
<div class="question-content">
<div class="question-title">
{{ currentQuestion.title }}
</div>
<!-- 选择题选项 -->
<div
v-if="currentQuestion.type === '单选题' || currentQuestion.type === '多选题' || currentQuestion.type === '判断题'"
class="question-options">
<div v-for="(option, index) in currentQuestion.options" :key="index" class="option-item"
:class="{ 'selected': isOptionSelected(index) }" @click="selectOption(index)">
<div class="option-checkbox">
<input type="checkbox" :name="`question-${currentQuestionIndex}`"
:checked="isOptionSelected(index)" @change="handleCheckboxClick(index, $event)"
@click="handleCheckboxClick(index, $event)">
</div>
<span class="option-label">{{ String.fromCharCode(65 + index) }}.</span>
<span class="option-text">{{ option }}</span>
</div>
</div>
<!-- 填空题输入框 -->
<div v-else-if="currentQuestion.type === '填空题'" class="fill-blank">
<div class="fill-item" v-for="(_, index) in currentQuestion.blanks || [1]" :key="index">
<span class="fill-number">{{ index + 1 }}.</span>
<input type="text" :value="getFillAnswer(currentQuestionIndex, index)"
@input="setFillAnswer(currentQuestionIndex, index, $event.target.value)" placeholder=""
class="fill-input" />
</div>
<div class="fill-hint">
*请在上方输入框内输入填空内容
</div>
</div>
<!-- 简答题文本域 -->
<div v-else-if="currentQuestion.type === '简答题'" class="essay-answer">
<div class="essay-container">
<textarea v-model="essayAnswers[currentQuestionIndex]" placeholder="" class="essay-textarea"
rows="8" maxlength="500"></textarea>
</div>
<div class="essay-footer">
<div class="essay-hint">
*请在上方输入框内输入填空内容
</div>
<div class="essay-counter">
{{ getEssayLength(currentQuestionIndex) }}/500
</div>
</div>
</div>
</div>
<div class="question-navigation">
<button class="btn-nav btn-prev" @click="previousQuestion" :disabled="currentQuestionIndex === 0">
上一题
</button>
<button class="btn-nav btn-next" @click="nextQuestion">
下一题
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 确认交卷对话框 -->
<div class="confirm-dialog-overlay" v-if="showConfirmDialog" @click="cancelSubmit">
<div class="confirm-dialog" @click.stop>
<div class="dialog-content">
<p class="dialog-message">{{ confirmDialogMessage }}</p>
<div class="dialog-buttons">
<button class="btn-cancel" @click="cancelSubmit"></button>
<button class="btn-cancel" @click="confirmSubmit"></button>
</div>
</div>
</div>
</div>
<!-- 时间到提示对话框 -->
<div class="time-up-overlay" v-if="showTimeUpDialog">
<div class="time-up-dialog">
<div class="time-up-content">
<div class="time-up-image">
<img src="/images/examination/time-up.png" alt="时间到" class="time-up-img" />
</div>
<p class="time-up-message">时间到已自动为您提交答题卡</p>
<!-- <button class="btn-confirm" @click="goToResult">确定</button> -->
</div>
</div>
</div>
<!-- 考试结果 -->
<div class="exam-result" v-if="examFinished">
<div class="container">
<div class="exam-layout">
<!-- 左侧答题卡保持不变 -->
<div class="exam-sidebar">
<!-- 考试时间 -->
<div class="exam-timer" :class="{ 'disabled': examSubmitted }">
<h4>剩余时间</h4>
<div class="timer-display" :class="{ 'disabled': examSubmitted }">
<span>00</span>
<span class="timer-separator">:</span>
<span>00</span>
<span class="timer-separator">:</span>
<span>00</span>
</div>
<div class="timer-labels" :class="{ 'disabled': examSubmitted }">
<span></span>
<span></span>
<span></span>
</div>
</div>
<!-- 答题卡 -->
<div class="answer-sheet" :class="{ 'disabled': examSubmitted }">
<div class="answer-sheet-header">
<h4>答题卡</h4>
<span class="progress-text">
<span class="progress-current">{{ String(questions.length).padStart(2, '0') }}</span>
<span class="progress-total">/{{ String(questions.length).padStart(2, '0') }}</span>
</span>
</div>
<div class="question-grid">
<!-- 单选题 -->
<div class="question-section">
<div class="section-header">
<h5>单选 ({{ questionsByType['单选题'].length }})</h5>
</div>
<div class="question-numbers">
<div v-for="question in questionsByType['单选题']" :key="question.id"
class="answer-card-number answered" :class="{ 'disabled': examSubmitted }">
{{ String(question.id).padStart(2, '0') }}
</div>
</div>
</div>
<!-- 多选题 -->
<div class="question-section">
<div class="section-header">
<h5>多选 ({{ questionsByType['多选题'].length }})</h5>
</div>
<div class="question-numbers">
<div v-for="question in questionsByType['多选题']" :key="question.id"
class="answer-card-number answered" :class="{ 'disabled': examSubmitted }">
{{ String(question.id).padStart(2, '0') }}
</div>
</div>
</div>
<!-- 判断题 -->
<div class="question-section">
<div class="section-header">
<h5>判断 ({{ questionsByType['判断题'].length }})</h5>
</div>
<div class="question-numbers">
<div v-for="question in questionsByType['判断题']" :key="question.id"
class="answer-card-number answered" :class="{ 'disabled': examSubmitted }">
{{ String(question.id).padStart(2, '0') }}
</div>
</div>
</div>
<!-- 填空题 -->
<div class="question-section">
<div class="section-header">
<h5>填空 ({{ questionsByType['填空题'].length }})</h5>
</div>
<div class="question-numbers">
<div v-for="question in questionsByType['填空题']" :key="question.id"
class="answer-card-number answered" :class="{ 'disabled': examSubmitted }">
{{ String(question.id).padStart(2, '0') }}
</div>
</div>
</div>
<!-- 简答题 -->
<div class="question-section">
<div class="section-header">
<h5>简答 ({{ questionsByType['简答题'].length }})</h5>
</div>
<div class="question-numbers">
<div v-for="question in questionsByType['简答题']" :key="question.id"
class="answer-card-number answered" :class="{ 'disabled': examSubmitted }">
{{ String(question.id).padStart(2, '0') }}
</div>
</div>
</div>
</div>
<!-- 答题状态说明 -->
<div class="answer-legend">
<div class="legend-item">
<div class="legend-color legend-unanswered"></div>
<span>未答</span>
</div>
<div class="legend-item">
<div class="legend-color legend-answered"></div>
<span>已答</span>
</div>
</div>
<!-- 提交按钮区域 -->
<div class="submit-section">
<button class="btn-submit-exam" :class="{ 'disabled': examSubmitted }" disabled>
交卷
</button>
</div>
</div>
</div>
<!-- 右侧提交成功信息 -->
<div class="exam-content">
<div class="submit-success">
<div class="success-image">
<img src="/images/examination/submit-the-paper.png" alt="提交成功" class="success-img">
</div>
<div class="success-message">
<h3>答题卡已成功提交预计1-3个工作日完成批阅请您耐心等候查询成绩</h3>
</div>
<div class="success-actions">
<button class="btn-return-home" @click="goHome">返回课程</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
const route = useRoute()
const router = useRouter()
// 路由参数
const courseId = ref(Number(route.params.courseId))
const sectionId = ref(Number(route.params.sectionId))
const courseName = ref(route.query.courseName as string || '课程名称')
const examName = ref(route.query.examName as string || '考试')
// 考试状态
const examStarted = ref(false)
const examFinished = ref(false)
const examSubmitted = ref(false) // 新增:考试已提交状态
// 确认对话框状态
const showConfirmDialog = ref(false)
const confirmDialogMessage = ref('')
const confirmDialogType = ref('') // 'complete' 或 'incomplete'
const examDuration = ref(120) // 考试时长(分钟)- 2小时
const remainingTime = ref(0) // 剩余时间(秒)
const timer = ref<NodeJS.Timeout | null>(null)
const showTimeUpDialog = ref(false) // 时间到提示对话框状态
// 题目相关
const currentQuestionIndex = ref(0)
const selectedAnswers = ref<number[]>([])
const multipleAnswers = ref<number[][]>([]) // 多选题答案
const fillAnswers = ref<string[][]>([]) // 填空题答案
const essayAnswers = ref<string[]>([]) // 简答题答案
const isMarked = ref<boolean[]>([])
const finalScore = ref(0)
const correctCount = ref(0)
// 初始化答案数组
const initializeAnswers = () => {
selectedAnswers.value = new Array(questions.value.length).fill(undefined)
multipleAnswers.value = Array.from({ length: questions.value.length }, () => [])
fillAnswers.value = new Array(questions.value.length).fill('')
essayAnswers.value = new Array(questions.value.length).fill('')
isMarked.value = new Array(questions.value.length).fill(false)
}
// 模拟考试题目
const questions = ref([
// 单选题 (1-10)
{
id: 1,
type: '单选题',
score: 2,
title: '信息学奥赛中常用的编程语言是什么?',
options: ['Java', 'Python', 'C++', 'JavaScript'],
correctAnswer: 2
},
{
id: 2,
type: '单选题',
score: 2,
title: '以下哪个是正确的C++变量声明?',
options: ['int 2num;', 'int num_2;', 'int num-2;', 'int num 2;'],
correctAnswer: 1
},
{
id: 3,
type: '单选题',
score: 2,
title: '在C++中,以下哪个运算符用于取地址?',
options: ['*', '&', '%', '^'],
correctAnswer: 1
},
{
id: 4,
type: '单选题',
score: 2,
title: '以下哪个循环结构在执行前不检查条件?',
options: ['for循环', 'while循环', 'do-while循环', '以上都不是'],
correctAnswer: 2
},
{
id: 5,
type: '单选题',
score: 2,
title: '在C++中,数组下标从几开始?',
options: ['1', '0', '-1', '任意数'],
correctAnswer: 1
},
{
id: 6,
type: '单选题',
score: 2,
title: '以下哪个是C++的标准输入流?',
options: ['cout', 'cin', 'printf', 'scanf'],
correctAnswer: 1
},
{
id: 7,
type: '单选题',
score: 2,
title: '在C++中,以下哪个关键字用于定义常量?',
options: ['var', 'let', 'const', 'final'],
correctAnswer: 2
},
{
id: 8,
type: '单选题',
score: 2,
title: '以下哪个是正确的函数定义语法?',
options: ['function int add()', 'int add()', 'def add()', 'add() int'],
correctAnswer: 1
},
{
id: 9,
type: '单选题',
score: 2,
title: '在C++中,以下哪个符号用于单行注释?',
options: ['//', '/* */', '#', '--'],
correctAnswer: 0
},
{
id: 10,
type: '单选题',
score: 2,
title: '以下哪个是C++中的逻辑与运算符?',
options: ['&', '&&', 'and', '|'],
correctAnswer: 1
},
// 多选题 (11-20)
{
id: 11,
type: '多选题',
score: 3,
title: '以下哪些是C++的基本数据类型?',
options: ['int', 'float', 'string', 'char'],
correctAnswer: [0, 1, 3]
},
{
id: 12,
type: '多选题',
score: 3,
title: '以下哪些是面向对象编程的特征?',
options: ['封装', '继承', '多态', '抽象'],
correctAnswer: [0, 1, 2, 3]
},
{
id: 13,
type: '多选题',
score: 3,
title: '以下哪些是C++中的循环语句?',
options: ['for', 'while', 'do-while', 'foreach'],
correctAnswer: [0, 1, 2]
},
{
id: 14,
type: '多选题',
score: 3,
title: '以下哪些是C++中的条件语句?',
options: ['if', 'switch', 'case', 'else'],
correctAnswer: [0, 1]
},
{
id: 15,
type: '多选题',
score: 3,
title: '以下哪些是C++中的运算符?',
options: ['++', '--', '+=', '=='],
correctAnswer: [0, 1, 2, 3]
},
{
id: 16,
type: '多选题',
score: 3,
title: '以下哪些是C++中的存储类?',
options: ['auto', 'static', 'extern', 'register'],
correctAnswer: [0, 1, 2, 3]
},
{
id: 17,
type: '多选题',
score: 3,
title: '以下哪些是C++中的访问修饰符?',
options: ['public', 'private', 'protected', 'internal'],
correctAnswer: [0, 1, 2]
},
{
id: 18,
type: '多选题',
score: 3,
title: '以下哪些是C++中的标准库?',
options: ['iostream', 'vector', 'string', 'algorithm'],
correctAnswer: [0, 1, 2, 3]
},
{
id: 19,
type: '多选题',
score: 3,
title: '以下哪些是C++中的容器?',
options: ['vector', 'list', 'map', 'set'],
correctAnswer: [0, 1, 2, 3]
},
{
id: 20,
type: '多选题',
score: 3,
title: '以下哪些是C++中的智能指针?',
options: ['unique_ptr', 'shared_ptr', 'weak_ptr', 'auto_ptr'],
correctAnswer: [0, 1, 2]
},
// 判断题 (21-30)
{
id: 21,
type: '判断题',
score: 1,
title: 'C++是一种面向对象的编程语言。',
options: ['正确', '错误'],
correctAnswer: 0
},
{
id: 22,
type: '判断题',
score: 1,
title: 'C++中的数组下标从1开始。',
options: ['正确', '错误'],
correctAnswer: 1
},
{
id: 23,
type: '判断题',
score: 1,
title: 'C++中可以使用//进行单行注释。',
options: ['正确', '错误'],
correctAnswer: 0
},
{
id: 24,
type: '判断题',
score: 1,
title: 'C++中的变量必须先声明后使用。',
options: ['正确', '错误'],
correctAnswer: 0
},
{
id: 25,
type: '判断题',
score: 1,
title: 'C++中的函数可以重载。',
options: ['正确', '错误'],
correctAnswer: 0
},
{
id: 26,
type: '判断题',
score: 1,
title: 'C++中的类可以继承多个父类。',
options: ['正确', '错误'],
correctAnswer: 0
},
{
id: 27,
type: '判断题',
score: 1,
title: 'C++中的指针可以进行算术运算。',
options: ['正确', '错误'],
correctAnswer: 0
},
{
id: 28,
type: '判断题',
score: 1,
title: 'C++中的const关键字用于定义常量。',
options: ['正确', '错误'],
correctAnswer: 0
},
{
id: 29,
type: '判断题',
score: 1,
title: 'C++中的虚函数可以实现多态。',
options: ['正确', '错误'],
correctAnswer: 0
},
{
id: 30,
type: '判断题',
score: 1,
title: 'C++中的析构函数可以有参数。',
options: ['正确', '错误'],
correctAnswer: 1
},
// 填空题 (31-40)
{
id: 31,
type: '填空题',
score: 1,
title: '危险化学品生产企业应当提供危险化学品安全技术说明书并在包装______上标贴或者悬挂与包装内危险化学品相符的化学品______。',
options: [],
blanks: [1, 2],
correctAnswer: ['包装', '标签']
},
{
id: 32,
type: '填空题',
score: 4,
title: 'C++中定义整型变量的关键字是______。',
options: [],
correctAnswer: 'int'
},
{
id: 33,
type: '填空题',
score: 4,
title: 'C++中用于包含头文件的预处理指令是______。',
options: [],
correctAnswer: '#include'
},
{
id: 34,
type: '填空题',
score: 4,
title: 'C++中主函数的名称是______。',
options: [],
correctAnswer: 'main'
},
{
id: 35,
type: '填空题',
score: 4,
title: 'C++中用于定义类的关键字是______。',
options: [],
correctAnswer: 'class'
},
{
id: 36,
type: '填空题',
score: 4,
title: 'C++中用于动态分配内存的运算符是______。',
options: [],
correctAnswer: 'new'
},
{
id: 37,
type: '填空题',
score: 4,
title: 'C++中用于释放动态分配内存的运算符是______。',
options: [],
correctAnswer: 'delete'
},
{
id: 38,
type: '填空题',
score: 4,
title: 'C++中用于定义命名空间的关键字是______。',
options: [],
correctAnswer: 'namespace'
},
{
id: 39,
type: '填空题',
score: 4,
title: 'C++中用于继承的符号是______。',
options: [],
correctAnswer: ':'
},
{
id: 40,
type: '填空题',
score: 4,
title: 'C++中用于访问类成员的运算符是______。',
options: [],
correctAnswer: '.'
},
// 简答题 (41-44)
{
id: 41,
type: '简答题',
score: 10,
title: '请简述C++中面向对象编程的三大特征。',
options: [],
correctAnswer: '封装、继承、多态'
},
{
id: 42,
type: '简答题',
score: 10,
title: '请解释C++中指针和引用的区别。',
options: [],
correctAnswer: '指针是变量的地址,引用是变量的别名'
},
{
id: 43,
type: '简答题',
score: 10,
title: '请说明C++中构造函数和析构函数的作用。',
options: [],
correctAnswer: '构造函数用于初始化对象,析构函数用于清理对象'
},
{
id: 44,
type: '简答题',
score: 10,
title: '请解释C++中虚函数的概念和作用。',
options: [],
correctAnswer: '虚函数实现多态,允许在运行时确定调用哪个函数'
}
])
// 计算属性
const currentQuestion = computed(() => questions.value[currentQuestionIndex.value])
const currentQuestionType = computed(() => currentQuestion.value?.type || '')
const totalScore = computed(() => questions.value.reduce((sum, q) => sum + q.score, 0))
// const answeredCount = computed(() => {
// let count = 0
// questions.value.forEach((question, index) => {
// if (question.type === '单选题' || question.type === '判断题') {
// if (selectedAnswers.value[index] !== -1) count++
// } else if (question.type === '多选题') {
// if (multipleAnswers.value[index] && multipleAnswers.value[index].length > 0) count++
// } else if (question.type === '填空题') {
// const answers = fillAnswers.value[index] || []
// if (answers.some(answer => answer && answer.trim() !== '')) count++
// } else if (question.type === '简答题') {
// if (essayAnswers.value[index] && essayAnswers.value[index].trim() !== '') count++
// }
// })
// return count
// }) // 暂时注释,后续需要时再启用
// const unansweredCount = computed(() => questions.value.length - answeredCount.value)
// const markedCount = computed(() => isMarked.value.filter(marked => marked).length)
// 按题型分组统计
const questionsByType = computed(() => {
const groups = {
'单选题': questions.value.filter((q: any) => q.type === '单选题'),
'多选题': questions.value.filter((q: any) => q.type === '多选题'),
'判断题': questions.value.filter((q: any) => q.type === '判断题'),
'填空题': questions.value.filter((q: any) => q.type === '填空题'),
'简答题': questions.value.filter((q: any) => q.type === '简答题')
}
return groups
})
// 监听简答题答案变化,强制更新答题卡状态
// const essayAnswersWatcher = computed(() => {
// return essayAnswers.value.map((answer: string) => answer ? answer.trim() : '')
// })
// 开始考试
const startExam = () => {
examStarted.value = true
remainingTime.value = examDuration.value * 60
startTimer()
}
// 开始计时器
const startTimer = () => {
timer.value = setInterval(() => {
remainingTime.value--
if (remainingTime.value <= 0) {
// 时间到显示提示对话框并设置3秒后自动提交
showTimeUpAndSubmit()
if (timer.value) {
clearInterval(timer.value)
}
}
}, 1000)
}
// 选择选项(通用)
const selectOption = (optionIndex: number) => {
const question = currentQuestion.value
if (question.type === '多选题') {
const currentAnswers = multipleAnswers.value[currentQuestionIndex.value] || []
const answerIndex = currentAnswers.indexOf(optionIndex)
if (answerIndex > -1) {
// 取消选择
currentAnswers.splice(answerIndex, 1)
} else {
// 添加选择
currentAnswers.push(optionIndex)
}
multipleAnswers.value[currentQuestionIndex.value] = [...currentAnswers]
// 强制更新答题卡状态
multipleAnswers.value = [...multipleAnswers.value]
} else {
// 单选题、判断题
// 如果点击的是已选中的选项,则取消选择
if (selectedAnswers.value[currentQuestionIndex.value] === optionIndex) {
selectedAnswers.value[currentQuestionIndex.value] = -1
} else {
selectedAnswers.value[currentQuestionIndex.value] = optionIndex
}
// 强制更新答题卡状态
selectedAnswers.value = [...selectedAnswers.value]
}
}
// 检查选项是否被选中
const isOptionSelected = (optionIndex: number) => {
const question = currentQuestion.value
if (question.type === '多选题') {
const currentAnswers = multipleAnswers.value[currentQuestionIndex.value] || []
return currentAnswers.includes(optionIndex)
} else {
return selectedAnswers.value[currentQuestionIndex.value] === optionIndex
}
}
// 处理复选框点击
const handleCheckboxClick = (optionIndex: number, event: Event) => {
event.stopPropagation()
selectOption(optionIndex)
}
// 上一题
const previousQuestion = () => {
if (currentQuestionIndex.value > 0) {
currentQuestionIndex.value--
}
}
// 下一题
const nextQuestion = () => {
if (currentQuestionIndex.value < questions.value.length - 1) {
currentQuestionIndex.value++
}
}
// 跳转到指定题目
const goToQuestion = (index: number) => {
currentQuestionIndex.value = index
}
// 处理题号点击
const handleQuestionClick = (index: number) => {
if (!examSubmitted.value) {
goToQuestion(index)
}
}
// 检查是否所有题目都已作答
const checkAllQuestionsAnswered = () => {
let unansweredCount = 0
questions.value.forEach((question, index) => {
let isAnswered = false
if (question.type === '单选题' || question.type === '判断题') {
isAnswered = selectedAnswers.value[index] !== undefined && selectedAnswers.value[index] !== null
} else if (question.type === '多选题') {
const userAnswers = multipleAnswers.value[index] || []
isAnswered = userAnswers.length > 0
} else if (question.type === '填空题') {
const userAnswer = fillAnswers.value[index] || []
isAnswered = userAnswer.some(answer => answer && answer.trim() !== '')
} else if (question.type === '简答题') {
const userAnswer = essayAnswers.value[index] || ''
isAnswered = userAnswer.trim() !== ''
}
if (!isAnswered) {
unansweredCount++
}
})
return unansweredCount === 0
}
// 获取题目类型简称
const getQuestionTypeShort = (type: string) => {
switch (type) {
case '单选题': return '单选题'
case '多选题': return '多选题'
case '判断题': return '判断题'
case '填空题': return '填空题'
case '简答题': return '简答题'
default: return type
}
}
// 填空题答案处理
const getFillAnswer = (questionIndex: number, blankIndex: number) => {
if (!fillAnswers.value[questionIndex]) {
fillAnswers.value[questionIndex] = []
}
return fillAnswers.value[questionIndex][blankIndex] || ''
}
const setFillAnswer = (questionIndex: number, blankIndex: number, value: string) => {
if (!fillAnswers.value[questionIndex]) {
fillAnswers.value[questionIndex] = []
}
fillAnswers.value[questionIndex][blankIndex] = value
// 强制更新答题卡状态
fillAnswers.value = [...fillAnswers.value]
}
// 简答题字数统计
const getEssayLength = (questionIndex: number) => {
const answer = essayAnswers.value[questionIndex] || ''
return answer.length
}
// 获取题目状态
const getQuestionStatus = (index: number) => {
const classes = []
const question = questions.value[index]
// 当前题目
if (index === currentQuestionIndex.value) {
classes.push('current')
}
// 已答题目
let isAnswered = false
if (question.type === '单选题' || question.type === '判断题') {
isAnswered = selectedAnswers.value[index] !== undefined && selectedAnswers.value[index] !== null
} else if (question.type === '多选题') {
isAnswered = multipleAnswers.value[index] && multipleAnswers.value[index].length > 0
} else if (question.type === '填空题') {
const answers = fillAnswers.value[index] || []
isAnswered = answers.some(answer => answer && answer.trim() !== '')
} else if (question.type === '简答题') {
isAnswered = !!(essayAnswers.value[index] && essayAnswers.value[index].trim() !== '')
}
if (isAnswered) {
classes.push('answered')
}
// 标记题目
if (isMarked.value[index]) {
classes.push('marked')
}
return classes
}
// 提交考试
const submitExam = () => {
// 防止重复提交
if (examSubmitted.value) {
return
}
// 检查是否所有题目都已作答
const allAnswered = checkAllQuestionsAnswered()
if (allAnswered) {
// 所有题目都已作答
confirmDialogMessage.value = '您已完成所有题目的作答,是否确认交卷?交卷后不可再修改答题卡!'
confirmDialogType.value = 'complete'
} else {
// 还有未作答的题目
confirmDialogMessage.value = '检测到您当前还有未作答题目,是否确认交卷?'
confirmDialogType.value = 'incomplete'
}
showConfirmDialog.value = true
}
// 确认交卷
const confirmSubmit = () => {
showConfirmDialog.value = false
executeSubmit()
}
// 取消交卷
const cancelSubmit = () => {
showConfirmDialog.value = false
}
// 执行实际的交卷操作
const executeSubmit = () => {
if (timer.value) {
clearInterval(timer.value)
}
// 计算得分
correctCount.value = 0
finalScore.value = 0
questions.value.forEach((question, index) => {
let isCorrect = false
if (question.type === '单选题' || question.type === '判断题') {
isCorrect = selectedAnswers.value[index] === question.correctAnswer
} else if (question.type === '多选题') {
const userAnswers = multipleAnswers.value[index] || []
const correctAnswers = Array.isArray(question.correctAnswer) ? question.correctAnswer : [question.correctAnswer]
isCorrect = userAnswers.length === correctAnswers.length &&
userAnswers.every(answer => correctAnswers.includes(answer))
} else if (question.type === '填空题') {
const userAnswers = fillAnswers.value[index] || []
const correctAnswers = Array.isArray(question.correctAnswer) ? question.correctAnswer : [question.correctAnswer]
// 填空题需要所有空都填对
isCorrect = userAnswers.length === correctAnswers.length &&
userAnswers.every((answer, i) =>
answer && answer.trim().toLowerCase() === String(correctAnswers[i]).toLowerCase()
)
} else if (question.type === '简答题') {
// 简答题需要人工评分,这里暂时不计分
const userAnswer = essayAnswers.value[index] || ''
isCorrect = userAnswer.trim() !== '' // 只要有内容就算答题了
}
if (isCorrect) {
correctCount.value++
finalScore.value += question.score
}
})
// 先设置提交状态,让界面变灰
examSubmitted.value = true
console.log('考试完成,得分:', finalScore.value)
// 延迟显示提交成功页面,让用户看到灰色状态
setTimeout(() => {
examFinished.value = true
}, 1500) // 延迟1.5秒让用户看到交卷完成的灰色状态
}
// 查看答案
// const reviewAnswers = () => {
// // 这里可以跳转到答案解析页面
// alert('答案解析功能开发中...')
// }
// 返回课程
const goBack = () => {
router.push(`/course/${courseId.value}`)
}
// 返回课程列表
const goHome = () => {
router.push('/course/1')
}
// 时间到后前往结果页面
const goToResult = () => {
showTimeUpDialog.value = false
executeSubmit()
}
// 显示时间到对话框并设置3秒后自动提交
const showTimeUpAndSubmit = () => {
showTimeUpDialog.value = true
setTimeout(() => {
goToResult()
}, 3000)
}
// 格式化时间
// const formatTime = (seconds: number): string => {
// const minutes = Math.floor(seconds / 60)
// const remainingSeconds = seconds % 60
// return `${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`
// }
// 已移除未使用的时间格式化函数,避免构建时报未使用错误
// 组件卸载时清理计时器
onUnmounted(() => {
if (timer.value) {
clearInterval(timer.value)
}
})
onMounted(() => {
console.log('考试页面加载完成')
console.log('课程ID:', courseId.value)
console.log('章节ID:', sectionId.value)
console.log('考试名称:', examName.value)
// 初始化答案数组
initializeAnswers()
// 检查是否从考前须知页面跳转过来,如果是则直接开始考试
const fromNotice = route.query.fromNotice
if (fromNotice === 'true') {
startExam()
}
})
</script>
<style scoped>
.exam-page {
min-height: 100vh;
background: #F6F6F6;
}
.banner-section {
position: relative;
}
.banner-text {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
}
.banner-text h2 {
width: 128px;
height: 39px;
font-family: AlimamaShuHeiTi, AlimamaShuHeiTi;
font-weight: bold;
font-size: 32px;
color: #000000;
line-height: 39px;
text-align: center;
font-style: normal;
}
.banner-text p {
width: 252px;
height: 20px;
font-family: PingFangSC, PingFang SC;
font-weight: 400;
font-size: 14px;
color: #000000;
line-height: 20px;
text-align: center;
font-style: normal;
}
/* 考试中心标题 */
.exam-center-header {
background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);
padding: 40px 0;
text-align: center;
color: white;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
.banner-image {
width: 100%;
height: auto;
object-fit: cover;
}
.center-title {
font-size: 36px;
font-weight: 600;
margin: 0 0 12px 0;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
}
.center-subtitle {
font-size: 16px;
margin: 0;
opacity: 0.9;
}
/* 课程信息导航 */
.course-info-nav {
background: #F6F6F6;
padding: 12px 0;
}
.nav-content {
display: flex;
justify-content: space-between;
align-items: center;
margin-left: 390px;
padding-right: 20px;
}
.nav-left {
display: flex;
align-items: center;
font-size: 14px;
color: #666;
}
.nav-title {
color: #333333;
cursor: default;
}
.nav-item {
color: #999;
cursor: default;
}
.nav-separator {
margin: 0 8px;
color: #999;
}
.nav-right {
display: flex;
align-items: center;
font-size: 14px;
}
.question-type-item {
color: #999;
font-weight: 400;
transition: all 0.3s;
}
.question-type-item.active {
color: #1D94D5;
font-weight: 500;
}
.type-separator {
margin: 0 12px;
color: #999;
}
/* 面包屑导航 */
.container {
max-width: 1400px;
margin: 0 auto;
padding: 0 20px;
}
/* 考试主体布局 */
.exam-main {
padding: 0;
min-height: calc(100vh - 200px);
}
.exam-layout {
display: flex;
gap: 20px;
align-items: flex-start;
}
/* 左侧边栏 */
.exam-sidebar {
width: 370px;
flex-shrink: 0;
}
/* 考试时间 */
.exam-timer {
background: white;
padding: 20px;
margin-bottom: 20px;
text-align: center;
width: 370px;
height: 170px;
box-sizing: border-box;
display: flex;
flex-direction: column;
justify-content: center;
}
.exam-timer h4 {
text-align: left;
font-size: 16px;
color: #000;
margin: 0 0 10px 0;
font-weight: 700;
}
.timer-display {
font-size: 34px;
font-weight: 700;
color: #1D94D5;
font-family: 'Arial', sans-serif;
letter-spacing: 4px;
background: #EEF9FF;
padding: 15px 20px 0 20px;
border-radius: 4px;
display: flex;
justify-content: center;
}
.timer-display span {
display: inline-block;
width: 36px;
text-align: center;
}
.timer-warning {
color: #FF5722 !important;
animation: pulse 1.5s infinite;
}
.timer-separator {
color: #1D94D5;
}
.timer-labels {
margin-top: -5px;
padding-bottom: 10px;
background-color: #EEF9FF;
display: flex;
justify-content: center;
gap: 55px;
font-size: 13px;
color: #999999;
}
@keyframes pulse {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0.7;
}
}
/* 禁用状态样式 - 根据图片调整为更浅的灰色 */
.exam-timer.disabled {
background: #f5f5f5 !important;
border: 1px solid #e0e0e0 !important;
}
.exam-timer.disabled h4 {
color: #999 !important;
}
.timer-display.disabled {
color: #999 !important;
background: #f6f6f6 !important;
border-color: #ddd !important;
}
.timer-labels.disabled {
color: #999 !important;
background-color: #f6f6f6 !important;
}
/* 确保父级计时器容器背景保持白色 */
.exam-timer.disabled {
background: white !important;
}
/* 计时器分隔符在禁用状态下变灰 */
.timer-display.disabled .timer-separator {
color: #999 !important;
}
/* 确保"剩余时间"标题在禁用状态下保持黑色 */
.exam-timer.disabled h4 {
color: #000 !important;
}
.answer-sheet.disabled {
/* background: #f5f5f5 !important; */
border: 1px solid #e0e0e0 !important;
}
.answer-sheet.disabled h4 {
color: #999 !important;
}
.answer-sheet.disabled .progress-text {
color: #999 !important;
}
.answer-sheet.disabled .progress-text .progress-current {
color: #000 !important;
}
.answer-sheet.disabled .progress-text .progress-total {
color: #999 !important;
}
.answer-sheet.disabled .section-header h5 {
color: #999 !important;
}
.answer-sheet.disabled .answer-card-number {
background: white !important;
color: #999 !important;
border-color: #999 !important;
cursor: not-allowed !important;
}
/* 确保disabled状态覆盖其他状态 */
.answer-sheet.disabled .answer-card-number.answered {
background: white !important;
color: #999 !important;
border-color: #999 !important;
}
.answer-sheet.disabled .answer-card-number.current {
background: white !important;
color: #999 !important;
border-color: #999 !important;
}
.answer-sheet.disabled .answer-card-number.marked {
background: white !important;
color: #999 !important;
border-color: #999 !important;
}
.answer-sheet.disabled .btn-submit-exam {
background-color: #999 !important;
color: #fff !important;
cursor: not-allowed !important;
}
/* 交卷后计时器样式 */
.answer-sheet.disabled .timer-display {
background-color: #f6f6f6 !important;
color: #999 !important;
}
.answer-sheet.disabled .timer-labels {
color: #999 !important;
background-color: #f6f6f6 !important;
}
.answer-sheet.disabled .timer-separator {
color: #999 !important;
}
.answer-sheet.disabled .exam-timer {
background: white !important;
}
.answer-sheet.disabled .exam-timer h4 {
color: #000 !important;
}
/* 交卷后图例样式 */
.answer-sheet.disabled .legend-color.legend-unanswered {
background: white !important;
border-color: #999 !important;
}
.answer-sheet.disabled .legend-color.legend-answered {
background: #999 !important;
border-color: #999 !important;
display: block !important;
}
.answer-sheet.disabled .legend-color.legend-answered::after {
color: white !important;
font-size: 12px !important;
font-weight: bold !important;
}
/* 图例颜色块基础样式 */
.legend-color {
width: 16px;
height: 16px;
border-radius: 2px;
margin-right: 8px;
display: inline-block;
position: relative;
}
.legend-color.legend-unanswered {
background: white;
border: 2px solid #d9d9d9;
}
.legend-color.legend-answered {
background: #999;
border: 2px solid #999;
}
.legend-color.legend-answered::after {
content: '✓';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: white;
font-size: 12px;
font-weight: bold;
line-height: 1;
}
/* 答题卡 */
.answer-sheet {
background: white;
padding: 20px;
width: 370px;
height: auto;
box-sizing: border-box;
overflow-y: auto;
margin-bottom: 40px;
}
/* 答题卡标题区域 */
.answer-sheet-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
padding-bottom: 12px;
border-bottom: 1px solid #e8e8e8;
}
.answer-sheet h4 {
font-size: 16px;
color: #333;
margin: 0;
font-weight: 700;
}
.progress-text {
font-size: 16px;
color: #0088D1;
font-weight: 500;
}
.progress-text span {
color: #999;
}
.progress-text .progress-current {
color: #000;
}
.progress-text .progress-total {
color: #999;
}
.question-section {
margin-bottom: 20px;
}
.section-header {
padding-bottom: 8px;
}
.section-header h5 {
font-size: 16px;
color: #999;
margin: 0;
font-weight: 400;
}
.question-numbers {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 8px;
}
.answer-card-number {
width: 64px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
border: 1px solid #0088D1;
font-size: 24px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s;
color: #0088D1;
}
.answer-card-number:hover {
border-color: #0088d1;
color: #0088d1;
}
.answer-card-number.current {
background: #0088D1;
color: white;
border-color: #0088D1;
}
.answer-card-number.answered {
background: #E2F5FF;
color: #0088D1;
border-color: #0088D1;
}
.answer-card-number.marked {
background: #faad14;
color: white;
border-color: #faad14;
}
/* 答题状态说明 */
.answer-legend {
margin-top: 20px;
padding-top: 20px;
border-top: 1px solid #e8e8e8;
display: flex;
gap: 20px;
}
.legend-item {
display: flex;
align-items: center;
gap: 8px;
}
.legend-icon {
width: 16px;
height: 16px;
border-radius: 2px;
border: 1px solid #d9d9d9;
}
.legend-icon.unanswered {
background: #f5f5f5;
border-color: #d9d9d9;
}
.legend-icon.answered {
background: #e6f7ff;
border-color: #91d5ff;
}
.legend-text {
font-size: 14px;
color: #666;
}
/* 提交按钮区域 */
.submit-section {
padding-top: 20px;
}
.btn-submit-exam {
width: 100%;
background: #0088d1;
color: white;
border: none;
padding: 10px;
border-radius: 4px;
font-size: 16px;
font-weight: 500;
cursor: pointer;
transition: background-color 0.3s;
}
.btn-submit-exam:hover {
background: #0077bb;
}
/* 右侧题目内容 */
.exam-content {
flex: 1;
background: white;
padding: 24px;
min-height: 560px;
min-width: 950px;
}
.question-card {
height: 100%;
display: flex;
flex-direction: column;
}
.question-header {
margin-bottom: 12px;
}
.question-title-info {
color: #0088D1;
font-size: 16px;
font-weight: 500;
}
.question-title-info span {
background-color: #EEF9FF;
font-size: 12px;
padding: 2px 4px;
border-radius: 10px;
}
.question-content {
flex: 1;
margin-bottom: 24px;
}
.question-title {
font-size: 16px;
font-weight: 400;
color: #333;
margin: 0 0 24px 0;
line-height: 1.8;
}
.question-options {
margin-bottom: 24px;
}
.option-item {
display: flex;
align-items: center;
padding: 5px 20px;
border: none;
border-radius: 6px;
margin-bottom: 16px;
cursor: pointer;
transition: all 0.3s;
background: #EEF9FF;
min-height: 50px;
}
.option-item:hover {
background: #EEF9FF;
}
.option-item.selected {
background: #EEF9FF;
color: #333;
}
.option-checkbox {
margin-right: 12px;
position: relative;
}
.option-checkbox input[type="checkbox"] {
width: 18px;
height: 18px;
appearance: none;
border: 1.5px solid #0088D1;
border-radius: 3px;
cursor: pointer;
position: relative;
}
.option-checkbox input[type="checkbox"]:checked {
background: #0088d1;
border-color: #0088d1;
}
.option-checkbox input[type="checkbox"]:checked::after {
content: '✓';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: white;
font-size: 12px;
font-weight: bold;
}
.option-label {
font-weight: 500;
color: #333;
margin-right: 8px;
min-width: 24px;
}
.option-text {
flex: 1;
color: #333;
line-height: 1.6;
font-size: 15px;
}
.option-checkbox {
margin-right: 12px;
margin-top: 2px;
cursor: pointer;
padding: 2px;
display: flex;
align-items: center;
justify-content: center;
}
.option-checkbox input[type="checkbox"],
.option-checkbox input[type="radio"] {
width: 20px;
height: 20px;
accent-color: #0088D1;
cursor: pointer;
margin: 0;
}
.option-checkbox input[type="checkbox"]:checked,
.option-checkbox input[type="radio"]:checked {
background-color: #0088D1;
border-color: #0088D1;
}
/* 填空题样式 */
.fill-blank {
margin-bottom: 24px;
}
.fill-item {
display: flex;
align-items: center;
margin-bottom: 16px;
background: #EEF9FF;
padding: 8px 20px;
border-radius: 4px;
}
.fill-number {
font-size: 16px;
font-weight: 500;
color: #333;
min-width: 24px;
}
.fill-input {
flex: 1;
padding: 8px 2PX;
border: none;
border-radius: 4px;
font-size: 16px;
transition: all 0.3s;
background: #EEF9FF;
}
.fill-input:focus {
outline: none;
box-shadow: 0 0 0 2px rgba(0, 136, 209, 0.2);
}
.fill-hint {
margin-top: 12px;
padding: 8px 0;
color: #999;
font-size: 14px;
text-align: left;
}
/* 简答题样式 */
.essay-answer {
margin-bottom: 24px;
}
.essay-container {
background: #EEF9FF;
padding: 20px;
border-radius: 4px;
}
.essay-textarea {
width: 100%;
padding: 12px 16px;
border: none;
border-radius: 6px;
font-size: 16px;
font-family: inherit;
resize: vertical;
min-height: 120px;
transition: all 0.3s;
background: #EEF9FF;
box-sizing: border-box;
}
.essay-textarea:focus {
outline: none;
box-shadow: 0 0 0 2px rgba(0, 136, 209, 0.2);
}
.essay-footer {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 12px;
padding: 0;
}
.essay-hint {
color: #999;
font-size: 14px;
}
.essay-counter {
color: #999;
font-size: 14px;
font-weight: 500;
}
.question-navigation {
display: flex;
justify-content: center;
gap: 16px;
padding-top: 20px;
}
.btn-nav {
padding: 10px 32px;
border-radius: 4px;
font-size: 16px;
cursor: pointer;
transition: all 0.3s;
min-width: 200px;
font-weight: 400;
}
.btn-prev {
background: #E2F5FF;
color: #0088d1;
border: 1px solid #0088d1;
}
.btn-next {
background: #0088d1;
color: white;
border: 1px solid #0088d1;
}
.btn-prev:hover {
background: #f0f9ff;
border-color: #0088d1;
color: #0088d1;
}
.btn-next:hover {
background: #0077bb;
border-color: #0077bb;
}
.btn-prev:disabled {
background: #f5f5f5;
color: #bfbfbf;
border-color: #e8e8e8;
cursor: not-allowed;
}
/* 考试结果 */
.exam-result {
display: flex;
justify-content: center;
}
.result-card {
background: white;
border-radius: 8px;
padding: 32px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
text-align: center;
max-width: 500px;
width: 100%;
}
.result-header h2 {
font-size: 24px;
font-weight: 600;
color: #333;
margin: 0 0 20px 0;
}
.result-score {
margin-bottom: 24px;
}
.score-label {
font-size: 16px;
color: #666;
}
.score-value {
font-size: 36px;
font-weight: 600;
color: #52c41a;
margin: 0 8px;
}
.score-total {
font-size: 18px;
color: #666;
}
.result-details {
border-top: 1px solid #e8e8e8;
border-bottom: 1px solid #e8e8e8;
padding: 20px 0;
margin-bottom: 24px;
}
.result-item {
display: flex;
justify-content: space-between;
margin-bottom: 12px;
}
.result-item:last-child {
margin-bottom: 0;
}
.result-item .label {
color: #666;
}
.result-item .value {
font-weight: 500;
color: #333;
}
.result-actions {
display: flex;
gap: 16px;
justify-content: center;
}
.btn-review {
background: #1890ff;
color: white;
border: none;
padding: 12px 24px;
border-radius: 6px;
font-size: 16px;
cursor: pointer;
transition: background-color 0.3s;
}
.btn-review:hover {
background: #40a9ff;
}
/* 考试说明页面 */
.exam-instructions {
padding: 40px 0;
min-height: calc(100vh - 200px);
display: flex;
align-items: center;
justify-content: center;
}
.instructions-card {
background: white;
border-radius: 12px;
padding: 40px;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);
max-width: 600px;
width: 100%;
}
.instructions-card h3 {
font-size: 24px;
font-weight: 600;
color: #333;
margin: 0 0 24px 0;
text-align: center;
}
.instructions-card ul {
margin: 0 0 32px 0;
padding-left: 20px;
}
.instructions-card li {
margin-bottom: 12px;
color: #666;
line-height: 1.8;
font-size: 15px;
}
.exam-actions {
display: flex;
gap: 16px;
justify-content: center;
}
.btn-start-exam {
background: #1890ff;
color: white;
border: none;
padding: 14px 32px;
border-radius: 8px;
font-size: 16px;
font-weight: 500;
cursor: pointer;
transition: background-color 0.3s;
}
.btn-start-exam:hover {
background: #40a9ff;
}
.btn-back {
background: #f5f5f5;
color: #666;
border: 1px solid #d9d9d9;
padding: 14px 24px;
border-radius: 8px;
font-size: 16px;
cursor: pointer;
transition: all 0.3s;
}
.btn-back:hover {
background: #e6f7ff;
border-color: #1890ff;
color: #1890ff;
}
/* 考试结果 */
.exam-result {
min-height: 100vh;
background: #f5f5f5;
padding: 20px 0;
}
.exam-result .exam-layout {
display: flex;
gap: 24px;
margin: 0 auto;
}
.exam-result .exam-sidebar {
flex-shrink: 0;
}
.exam-result .exam-content {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
}
/* 提交成功页面样式 */
.submit-success {
background: white;
border-radius: 12px;
padding: 60px 40px;
text-align: center;
max-width: 970px;
width: 100%;
}
.success-image {
margin-bottom: 30px;
}
.icon-container {
position: relative;
display: inline-block;
font-size: 48px;
}
.lightbulb {
font-size: 48px;
margin-right: 20px;
}
.paper {
font-size: 48px;
margin-right: 20px;
}
.checkmark {
font-size: 32px;
color: #52c41a;
position: absolute;
top: -10px;
right: -10px;
background: white;
border-radius: 50%;
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.success-message {
margin: auto;
width: 372px;
height: 50px;
font-family: PingFangSC, PingFang SC;
font-weight: 400;
font-size: 18px;
color: #000000;
line-height: 25px;
text-align: center;
font-style: normal;
margin-bottom: 40px;
}
.success-message h3 {
color: #333;
font-size: 18px;
line-height: 1.6;
margin: 0;
font-weight: 500;
padding: 0 20px;
}
.success-actions {
display: flex;
justify-content: center;
}
.btn-return-home {
background: #0088D1;
color: white;
border: none;
border-radius: 4px;
padding: 11px 70px;
font-size: 16px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s;
}
.btn-return-home:hover {
background: #40a9ff;
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(24, 144, 255, 0.3);
}
/* 响应式设计 */
@media (max-width: 1024px) {
.exam-layout {
flex-direction: column;
}
.exam-sidebar {
width: 100%;
order: 2;
}
.exam-content {
order: 1;
margin-bottom: 20px;
}
.question-numbers {
grid-template-columns: repeat(4, 1fr);
}
}
@media (max-width: 1024px) {
.nav-content {
margin-left: 0;
padding: 0 20px;
}
}
@media (max-width: 768px) {
.nav-content {
flex-direction: column;
gap: 8px;
align-items: flex-start;
margin-left: 0;
padding: 0 20px;
}
.nav-right {
flex-wrap: wrap;
gap: 8px;
}
.type-separator {
margin: 0 8px;
}
.center-title {
font-size: 28px;
}
.center-subtitle {
font-size: 14px;
}
.exam-actions {
flex-direction: column;
}
.question-navigation {
flex-direction: column;
}
.btn-nav {
width: 100%;
}
.instructions-card {
margin: 20px;
padding: 24px;
}
}
/* 确认对话框样式 */
.confirm-dialog-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 9999;
}
.confirm-dialog {
background: white;
border-radius: 8px;
width: 320px;
height: 168px;
border-radius: 8px;
margin: 20px;
overflow: hidden;
}
.dialog-content {
text-align: center;
}
.dialog-message {
padding: 32px 24px 0 24px;
font-size: 16px;
color: #333;
line-height: 1.6;
margin: 0 0 39px 0;
font-weight: 400;
}
.dialog-buttons {
display: flex;
justify-content: center;
}
.btn-cancel,
.btn-confirm {
padding: 12px 32px;
font-size: 16px;
cursor: pointer;
border: 1px solid #E5E5E5;
min-width: 80px;
transition: all 0.2s;
}
.btn-cancel {
width: 50%;
background: white;
color: #666;
border-color: #ddd;
border-bottom: none;
}
.btn-cancel:hover {
background: #f5f5f5;
border-color: #ccc;
}
/* 响应式调整 */
@media (max-width: 768px) {
.confirm-dialog {
max-width: 320px;
}
.dialog-content {
padding: 24px 20px 20px 20px;
}
.dialog-message {
font-size: 14px;
margin-bottom: 24px;
}
.dialog-buttons {
flex-direction: column;
gap: 12px;
}
.btn-cancel,
.btn-confirm {
width: 100%;
padding: 12px 16px;
}
}
/* 时间到提示对话框样式 */
.time-up-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 9999;
}
.time-up-dialog {
background: white;
border-radius: 5px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
max-width: 320px;
min-height: 168px;
width: 90%;
margin: 20px;
padding: 20px;
position: relative;
}
.time-up-content {
margin-top: 65px;
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
}
.time-up-image {
margin-bottom: 24px;
}
.time-up-img {
position: absolute;
top: -55%;
left: 25%;
width: 178px;
height: 178px;
object-fit: contain;
}
.time-up-message {
font-size: 17px;
color: #000;
line-height: 1.6;
margin: 0;
font-weight: 500;
}
/* 时间到对话框响应式调整 */
@media (max-width: 768px) {
.time-up-dialog {
max-width: 320px;
}
}
/* 交卷后提交按钮背景变灰 - 样式已在上面定义 */
</style>