创建ExamingView考试页模板
This commit is contained in:
parent
fc110beee0
commit
627ca9bc75
@ -1,6 +1,8 @@
|
|||||||
import { createRouter, createWebHistory } from 'vue-router'
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
import WelcomeView from '@/views/WelcomeView.vue'
|
import WelcomeView from '@/views/WelcomeView.vue'
|
||||||
|
import ExamingView from '@/views/user/ExamingView.vue'
|
||||||
import ExamineeHomeView from '@/views/user/ExamineeHomeView.vue'
|
import ExamineeHomeView from '@/views/user/ExamineeHomeView.vue'
|
||||||
|
|
||||||
import AdminHomeView from '@/views/admin/AdminHomeView.vue'
|
import AdminHomeView from '@/views/admin/AdminHomeView.vue'
|
||||||
// 导入QuestionManagementView
|
// 导入QuestionManagementView
|
||||||
import QuestionManagementView from '@/views/admin/QuestionManagementView.vue'
|
import QuestionManagementView from '@/views/admin/QuestionManagementView.vue'
|
||||||
@ -26,6 +28,12 @@ const router = createRouter({
|
|||||||
name: 'examinee-home',
|
name: 'examinee-home',
|
||||||
component: ExamineeHomeView,
|
component: ExamineeHomeView,
|
||||||
},
|
},
|
||||||
|
// Modify the examing route to use lazy loading
|
||||||
|
{
|
||||||
|
path: '/examinee/examing',
|
||||||
|
name: 'examinee-examing',
|
||||||
|
component: ExamingView,
|
||||||
|
},
|
||||||
// admin/AdminHomeView路由
|
// admin/AdminHomeView路由
|
||||||
{
|
{
|
||||||
path: '/admin/home',
|
path: '/admin/home',
|
||||||
|
@ -256,7 +256,7 @@ const startExam = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
console.log('开始考试 - 调用接口')
|
console.log('开始考试 - 调用接口')
|
||||||
isLoading.value = true
|
// isLoading.value = true
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 这里可以添加开始考试的逻辑
|
// 这里可以添加开始考试的逻辑
|
||||||
@ -268,7 +268,7 @@ const startExam = async () => {
|
|||||||
console.log('开始考试 - 成功')
|
console.log('开始考试 - 成功')
|
||||||
ElMessage.success('即将开始考试!')
|
ElMessage.success('即将开始考试!')
|
||||||
// 跳转到考试页面
|
// 跳转到考试页面
|
||||||
// router.push('/exam')
|
router.push('/examinee/examing')
|
||||||
isLoading.value = false
|
isLoading.value = false
|
||||||
}, 1000)
|
}, 1000)
|
||||||
} catch (error) {
|
} 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