创建ExamingView考试页模板
This commit is contained in:
parent
fc110beee0
commit
627ca9bc75
@ -1,6 +1,8 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import WelcomeView from '@/views/WelcomeView.vue'
|
||||
import ExamingView from '@/views/user/ExamingView.vue'
|
||||
import ExamineeHomeView from '@/views/user/ExamineeHomeView.vue'
|
||||
|
||||
import AdminHomeView from '@/views/admin/AdminHomeView.vue'
|
||||
// 导入QuestionManagementView
|
||||
import QuestionManagementView from '@/views/admin/QuestionManagementView.vue'
|
||||
@ -26,6 +28,12 @@ const router = createRouter({
|
||||
name: 'examinee-home',
|
||||
component: ExamineeHomeView,
|
||||
},
|
||||
// Modify the examing route to use lazy loading
|
||||
{
|
||||
path: '/examinee/examing',
|
||||
name: 'examinee-examing',
|
||||
component: ExamingView,
|
||||
},
|
||||
// admin/AdminHomeView路由
|
||||
{
|
||||
path: '/admin/home',
|
||||
|
@ -256,7 +256,7 @@ const startExam = async () => {
|
||||
}
|
||||
|
||||
console.log('开始考试 - 调用接口')
|
||||
isLoading.value = true
|
||||
// isLoading.value = true
|
||||
|
||||
try {
|
||||
// 这里可以添加开始考试的逻辑
|
||||
@ -268,7 +268,7 @@ const startExam = async () => {
|
||||
console.log('开始考试 - 成功')
|
||||
ElMessage.success('即将开始考试!')
|
||||
// 跳转到考试页面
|
||||
// router.push('/exam')
|
||||
router.push('/examinee/examing')
|
||||
isLoading.value = false
|
||||
}, 1000)
|
||||
} catch (error) {
|
||||
|
398
src/views/user/ExamingView.vue
Normal file
398
src/views/user/ExamingView.vue
Normal file
@ -0,0 +1,398 @@
|
||||
<template>
|
||||
<div v-if="isReady" class="examing-container">
|
||||
<el-container>
|
||||
<!-- 头部 -->
|
||||
<Header />
|
||||
|
||||
<!-- 主要内容区域 -->
|
||||
<el-main class="exam-content">
|
||||
<!-- 顶栏:考生信息和考试信息 -->
|
||||
<div class="exam-top-bar">
|
||||
<div class="left-info">
|
||||
<div class="info-item"><span class="label">考生姓名:</span><span class="value">张三</span></div>
|
||||
<div class="info-item"><span class="label">身份证号:</span><span class="value">2104XXXXXXXXXXXX1234</span></div>
|
||||
<div class="info-item"><span class="label">准考证号:</span><span class="value">20230001</span></div>
|
||||
</div>
|
||||
<div class="right-info">
|
||||
<div class="info-item"><span class="label">考试总分:</span><span class="value">100分</span></div>
|
||||
<div class="info-item"><span class="label">总时长:</span><span class="value">120分钟</span></div>
|
||||
<div class="info-item"><span class="label">最小时长:</span><span class="value">60分钟</span></div>
|
||||
<div class="info-item timer"><span class="label">倒计时:</span><span class="value">{{ countdown }}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="exam-main">
|
||||
<!-- 左侧边栏:试题序号列表 -->
|
||||
<div class="exam-sidebar">
|
||||
<div class="sidebar-title">试题列表</div>
|
||||
<div class="question-list">
|
||||
<div v-for="i in 20" :key="i" class="question-item" :class="{ 'current': i === currentQuestion }"
|
||||
@click="goToQuestion(i)">{{ i }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 主体部分:试题内容 -->
|
||||
<div class="exam-question-content">
|
||||
<div class="question-header">
|
||||
<span class="question-number">第 {{ currentQuestion }} 题</span>
|
||||
<span class="question-score">({{ questionScore }}分)</span>
|
||||
</div>
|
||||
<div class="question-body">
|
||||
<div class="question-type">选择题</div>
|
||||
<div class="question-text">
|
||||
以下关于统计法的描述,正确的是?
|
||||
</div>
|
||||
<div class="question-options" v-if="questionType === 'choice'">
|
||||
<div class="option-item" v-for="(option, index) in ['A', 'B', 'C', 'D']" :key="index"
|
||||
@click="selectOption(index)">
|
||||
<span class="option-letter">{{ option }}</span>
|
||||
<span class="option-text">{{ getOptionText(option) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="question-answer" v-else-if="questionType === 'fill_blank'">
|
||||
<input type="text" placeholder="请在此填写答案">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 底栏:操作按钮 -->
|
||||
<div class="exam-bottom-bar">
|
||||
<el-button type="default" @click="prevQuestion" :disabled="currentQuestion === 1">上一题</el-button>
|
||||
<el-button type="primary" @click="nextQuestion" :disabled="currentQuestion === totalQuestions">下一题</el-button>
|
||||
<el-button type="danger" @click="submitExam">交卷</el-button>
|
||||
</div>
|
||||
</el-main>
|
||||
|
||||
<!-- 底部 -->
|
||||
<Footer />
|
||||
</el-container>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// 导入组件
|
||||
import Header from '@/components/common/Header.vue'
|
||||
import Footer from '@/components/common/Footer.vue'
|
||||
import { ElButton } from 'element-plus'
|
||||
import { ref, onUnmounted, onMounted } from 'vue'
|
||||
|
||||
// 响应式数据
|
||||
const currentQuestion = ref(1)
|
||||
const totalQuestions = ref(20)
|
||||
const questionScore = ref(5)
|
||||
const questionType = ref('choice') // 'choice' 或 'fill_blank'
|
||||
const countdown = ref('01:59:59') // 初始倒计时120分钟
|
||||
|
||||
// 计时器逻辑
|
||||
let timer = null
|
||||
|
||||
// 格式化时间
|
||||
const formatTime = (seconds) => {
|
||||
const hours = Math.floor(seconds / 3600)
|
||||
const minutes = Math.floor((seconds % 3600) / 60)
|
||||
const secs = seconds % 60
|
||||
return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`
|
||||
}
|
||||
|
||||
// 模拟选项文本
|
||||
const getOptionText = (option) => {
|
||||
const options = {
|
||||
'A': '统计法是调整统计关系的法律规范的总称',
|
||||
'B': '统计法仅适用于政府统计活动',
|
||||
'C': '统计法不包括地方性统计法规',
|
||||
'D': '统计法制定的依据是宪法'
|
||||
}
|
||||
return options[option] || ''
|
||||
}
|
||||
|
||||
// 上一题
|
||||
const prevQuestion = () => {
|
||||
if (currentQuestion.value > 1) {
|
||||
currentQuestion.value--
|
||||
}
|
||||
}
|
||||
|
||||
// 下一题
|
||||
const nextQuestion = () => {
|
||||
if (currentQuestion.value < totalQuestions.value) {
|
||||
currentQuestion.value++
|
||||
}
|
||||
}
|
||||
|
||||
// 跳转到指定题目
|
||||
const goToQuestion = (index) => {
|
||||
currentQuestion.value = index
|
||||
}
|
||||
|
||||
// 选择选项
|
||||
const selectOption = (index) => {
|
||||
// 这里可以处理选项选择逻辑
|
||||
}
|
||||
|
||||
// 交卷
|
||||
const submitExam = () => {
|
||||
if (confirm('确定要交卷吗?交卷后将无法修改答案。')) {
|
||||
// 这里可以处理交卷逻辑
|
||||
alert('交卷成功!')
|
||||
}
|
||||
}
|
||||
|
||||
// 组件挂载时启动计时器
|
||||
// Add this line
|
||||
const isReady = ref(false)
|
||||
|
||||
// Modify onMounted
|
||||
onMounted(() => {
|
||||
// Set isReady to true after mount
|
||||
isReady.value = true
|
||||
|
||||
// 初始化为120分钟(7200秒)
|
||||
let remainingSeconds = 120 * 60
|
||||
|
||||
// 更新倒计时显示
|
||||
countdown.value = formatTime(remainingSeconds)
|
||||
|
||||
// 启动计时器
|
||||
timer = setInterval(() => {
|
||||
remainingSeconds--
|
||||
if (remainingSeconds <= 0) {
|
||||
clearInterval(timer)
|
||||
countdown.value = '00:00:00'
|
||||
alert('考试时间已结束,系统将自动交卷。')
|
||||
// 这里可以添加自动交卷逻辑
|
||||
} else {
|
||||
countdown.value = formatTime(remainingSeconds)
|
||||
}
|
||||
}, 1000)
|
||||
})
|
||||
|
||||
// 组件卸载时清除计时器
|
||||
onUnmounted(() => {
|
||||
if (timer) {
|
||||
clearInterval(timer)
|
||||
timer = null // Add this line to prevent memory leaks
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 自定义样式 */
|
||||
.examing-container, .el-container {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.exam-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 1rem;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 顶栏样式 */
|
||||
.exam-top-bar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 0.5rem 1rem;
|
||||
background-color: #f5f7fa;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 1rem;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.left-info, .right-info {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-weight: bold;
|
||||
margin-right: 0.5rem;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.value {
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.timer .value {
|
||||
color: #f56c6c;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* 主要内容区样式 */
|
||||
.exam-main {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
gap: 1rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 侧边栏样式 */
|
||||
.exam-sidebar {
|
||||
width: 240px;
|
||||
background-color: #f5f7fa;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.sidebar-title {
|
||||
padding: 0.75rem 1rem;
|
||||
font-weight: bold;
|
||||
color: #1e40af;
|
||||
border-bottom: 1px solid #e4e7ed;
|
||||
}
|
||||
|
||||
.question-list {
|
||||
padding: 1rem;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.question-item {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 4px;
|
||||
background-color: #fff;
|
||||
border: 1px solid #dcdfe6;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.question-item:hover {
|
||||
border-color: #409eff;
|
||||
}
|
||||
|
||||
.question-item.current {
|
||||
background-color: #409eff;
|
||||
color: #fff;
|
||||
border-color: #409eff;
|
||||
}
|
||||
|
||||
/* 试题内容区样式 */
|
||||
.exam-question-content {
|
||||
flex: 1;
|
||||
background-color: #fff;
|
||||
border-radius: 4px;
|
||||
padding: 1rem;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.question-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 1rem;
|
||||
padding-bottom: 0.5rem;
|
||||
border-bottom: 1px solid #e4e7ed;
|
||||
}
|
||||
|
||||
.question-number {
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.question-score {
|
||||
color: #f56c6c;
|
||||
}
|
||||
|
||||
.question-type {
|
||||
display: inline-block;
|
||||
padding: 0.25rem 0.5rem;
|
||||
background-color: #e6f7ff;
|
||||
color: #1890ff;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 1rem;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.question-text {
|
||||
margin-bottom: 1.5rem;
|
||||
line-height: 1.6;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
/* 选项样式 */
|
||||
.question-options {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.option-item {
|
||||
padding: 0.75rem 1rem;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #dcdfe6;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.option-item:hover {
|
||||
border-color: #409eff;
|
||||
background-color: #f0f8ff;
|
||||
}
|
||||
|
||||
.option-letter {
|
||||
display: inline-block;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
background-color: #f5f7fa;
|
||||
text-align: center;
|
||||
line-height: 24px;
|
||||
margin-right: 0.75rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* 填空题样式 */
|
||||
.question-answer input {
|
||||
width: 100%;
|
||||
padding: 0.75rem;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 底栏样式 */
|
||||
.exam-bottom-bar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 1rem;
|
||||
background-color: #f5f7fa;
|
||||
border-radius: 4px;
|
||||
margin-top: 1rem;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* 响应式样式 */
|
||||
@media (max-width: 992px) {
|
||||
.exam-main {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.exam-sidebar {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
}
|
||||
}
|
||||
</style>
|
Loading…
Reference in New Issue
Block a user