|
|
|
@ -0,0 +1,291 @@
|
|
|
|
|
<template>
|
|
|
|
|
<div class="exam-container">
|
|
|
|
|
<!-- 左侧栏 -->
|
|
|
|
|
<div class="sidebar">
|
|
|
|
|
<h2>{{ examTitle }}</h2>
|
|
|
|
|
<p>总分:{{ totalScore }} 时长:{{ duration }}</p>
|
|
|
|
|
<p class="time-remaining">剩余时间<br /><span>{{ formattedTime }}</span></p>
|
|
|
|
|
<button class="submit-button" @click="submitExam">提交试卷</button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 试题内容 -->
|
|
|
|
|
<div class="main-content" v-if="questions.length > 0">
|
|
|
|
|
<div v-for="(question, index) in questions" :key="question.id" class="question">
|
|
|
|
|
<p>{{ index + 1 }}. {{ question.text }}</p>
|
|
|
|
|
<div class="options">
|
|
|
|
|
<!-- 有选项时 -->
|
|
|
|
|
<template v-if="question.options.length > 0">
|
|
|
|
|
<label v-for="(option, optIndex) in question.options" :key="optIndex">
|
|
|
|
|
<input
|
|
|
|
|
type="radio"
|
|
|
|
|
:name="'question-' + question.id"
|
|
|
|
|
:value="option.value"
|
|
|
|
|
v-model="answers[question.id]"
|
|
|
|
|
/>
|
|
|
|
|
{{ option.label }}
|
|
|
|
|
</label>
|
|
|
|
|
</template>
|
|
|
|
|
<!-- 无选项时 -->
|
|
|
|
|
<template v-else>
|
|
|
|
|
<textarea
|
|
|
|
|
v-model="answers[question.id]"
|
|
|
|
|
placeholder="请填写答案"
|
|
|
|
|
rows="3"
|
|
|
|
|
></textarea>
|
|
|
|
|
</template>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 加载中提示 -->
|
|
|
|
|
<div class="loading" v-else>
|
|
|
|
|
正在加载试题,请稍候...
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
import axios from "axios";
|
|
|
|
|
|
|
|
|
|
export default {
|
|
|
|
|
name: "ExamPaper",
|
|
|
|
|
data() {
|
|
|
|
|
return {
|
|
|
|
|
examTitle: "考试中...", // 考试标题
|
|
|
|
|
totalScore: 0, // 总分
|
|
|
|
|
duration: "未知", // 考试时长
|
|
|
|
|
remainingTime: 0, // 剩余时间(秒)
|
|
|
|
|
questions: [], // 存储从后端获取的试题数据
|
|
|
|
|
answers: {}, // 存储用户选择的答案
|
|
|
|
|
examId: null, // 从路由获取的试卷 ID
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
computed: {
|
|
|
|
|
// 格式化剩余时间
|
|
|
|
|
formattedTime() {
|
|
|
|
|
const minutes = Math.floor(this.remainingTime / 60);
|
|
|
|
|
const seconds = this.remainingTime % 60;
|
|
|
|
|
return `${minutes}分${seconds}秒`;
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
methods: {
|
|
|
|
|
// 获取试题和试卷数据
|
|
|
|
|
async fetchQuestions() {
|
|
|
|
|
try {
|
|
|
|
|
const token = this.$store.state.token;
|
|
|
|
|
if (!token) {
|
|
|
|
|
alert("用户未登录,请重新登录!");
|
|
|
|
|
this.$router.push("/login");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const res = await axios.get('http://localhost:8080/student/examPaper/selectByUserId', {
|
|
|
|
|
headers: { Authorization: `Bearer ${token}`,
|
|
|
|
|
'Content-Type': 'application/json',}
|
|
|
|
|
});
|
|
|
|
|
// 打印返回的数据,确保 res.data 和 res.data.data 存在
|
|
|
|
|
console.log('请求返回的数据:', res.data);
|
|
|
|
|
|
|
|
|
|
// 确保 res.data 和 res.data.data 存在且有 grade 属性
|
|
|
|
|
if (res.data && res.data.data && res.data.data.grade) {
|
|
|
|
|
const grade = res.data.data.grade;
|
|
|
|
|
console.log('学生年级:', grade);
|
|
|
|
|
} else {
|
|
|
|
|
console.error("返回数据缺少 grade 字段!", res.data);
|
|
|
|
|
alert("返回数据缺少年级信息!");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const examRes = await axios.get(`http://localhost:8080/student/homepage/task_paper`, {
|
|
|
|
|
params: { name: this.name, subject: this.subject,grade: res.data.data.grade},
|
|
|
|
|
headers: { Authorization: `Bearer ${token}`,
|
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
console.log('examRes', examRes);
|
|
|
|
|
|
|
|
|
|
if (examRes.data.code === 200) {
|
|
|
|
|
const { name, grade, subject, list } = examRes.data.data;
|
|
|
|
|
this.examTitle = name;
|
|
|
|
|
this.totalScore = list.length * 10; // 假设每题10分
|
|
|
|
|
this.duration = "60分钟"; // 假设固定时长
|
|
|
|
|
this.remainingTime = 3600; // 假设默认1小时
|
|
|
|
|
this.questions = list.map(item => ({
|
|
|
|
|
id: item.id,
|
|
|
|
|
text: item.content,
|
|
|
|
|
options: item.chance ? item.chance.map(opt => ({ value: opt.label, label: opt.text })) : [],
|
|
|
|
|
}));
|
|
|
|
|
console.log("试题数据:", this.questions);
|
|
|
|
|
} else {
|
|
|
|
|
alert("加载试题失败:" + examRes.data.msg);
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error("加载试题时发生错误:"+ error);
|
|
|
|
|
alert("加载试题失败,请稍后再试!");
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 提交试卷
|
|
|
|
|
async submitExam() {
|
|
|
|
|
try {
|
|
|
|
|
// 检查未作答的题目
|
|
|
|
|
const unanswered = this.questions.filter(
|
|
|
|
|
(q) => !this.answers[q.id] || this.answers[q.id].trim() === ""
|
|
|
|
|
);
|
|
|
|
|
if (unanswered.length > 0) {
|
|
|
|
|
alert(`还有 ${unanswered.length} 道题未作答,请完成后再提交!`);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 提交答案
|
|
|
|
|
const response = await axios.post("http://localhost:8080/api/exam/submit", {
|
|
|
|
|
examId: this.examId,
|
|
|
|
|
answers: this.answers,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (response.data.code === 200) {
|
|
|
|
|
alert("考试提交成功!");
|
|
|
|
|
this.$router.push("/examList");
|
|
|
|
|
} else {
|
|
|
|
|
alert("提交失败:" + response.data.msg);
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error("提交考试时发生错误:", error);
|
|
|
|
|
alert("提交试卷失败,请稍后再试!");
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 倒计时逻辑
|
|
|
|
|
startCountdown() {
|
|
|
|
|
if (this.timer) clearInterval(this.timer);
|
|
|
|
|
this.timer = setInterval(() => {
|
|
|
|
|
if (this.remainingTime > 0) {
|
|
|
|
|
this.remainingTime -= 1;
|
|
|
|
|
} else {
|
|
|
|
|
clearInterval(this.timer);
|
|
|
|
|
alert("时间到,试卷将自动提交!");
|
|
|
|
|
this.submitExam();
|
|
|
|
|
}
|
|
|
|
|
}, 1000);
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
created() {
|
|
|
|
|
this.subject = this.$route.query.subject;
|
|
|
|
|
this.name = this.$route.query.name;
|
|
|
|
|
|
|
|
|
|
if (!this.subject && !this.name) {
|
|
|
|
|
alert("未获取到试卷,请重新选择试卷!");
|
|
|
|
|
this.$router.push("/exam");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 获取试题数据并启动倒计时
|
|
|
|
|
this.fetchQuestions();
|
|
|
|
|
},
|
|
|
|
|
mounted() {
|
|
|
|
|
this.startCountdown();
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
/* 页面布局 */
|
|
|
|
|
.exam-container {
|
|
|
|
|
display: flex;
|
|
|
|
|
height: 100vh;
|
|
|
|
|
background-color: #f4f6f9;
|
|
|
|
|
padding: 0; /* 确保没有额外的内边距 */
|
|
|
|
|
position: relative;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 左侧栏样式 */
|
|
|
|
|
.sidebar {
|
|
|
|
|
width: 20%;
|
|
|
|
|
padding: 20px;
|
|
|
|
|
background-color: #ffffff;
|
|
|
|
|
box-shadow: 2px 0 5px rgba(0, 0, 0, 0.1);
|
|
|
|
|
text-align: center;
|
|
|
|
|
position: sticky;
|
|
|
|
|
top: 0; /* 固定位置 */
|
|
|
|
|
height: 100vh; /* 确保左侧栏一直显示 */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 使左侧栏文字不会被遮挡 */
|
|
|
|
|
.sidebar h2 {
|
|
|
|
|
font-size: 20px;
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.time-remaining span {
|
|
|
|
|
color: red;
|
|
|
|
|
font-size: 24px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.submit-button {
|
|
|
|
|
padding: 10px 20px;
|
|
|
|
|
background-color: #007bff;
|
|
|
|
|
color: white;
|
|
|
|
|
border: none;
|
|
|
|
|
border-radius: 5px;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.submit-button:hover {
|
|
|
|
|
background-color: #0056b3;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 主内容区域 */
|
|
|
|
|
.main-content {
|
|
|
|
|
flex: 1;
|
|
|
|
|
padding: 30px;
|
|
|
|
|
background-color: #ffffff; /* 设置和左侧栏一致的背景 */
|
|
|
|
|
overflow-y: auto; /* 允许滚动 */
|
|
|
|
|
height: 100vh; /* 确保主内容区域可以填满视口高度 */
|
|
|
|
|
box-sizing: border-box; /* 包括内边距在内的大小计算 */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 题目区域 */
|
|
|
|
|
.question {
|
|
|
|
|
margin-bottom: 20px; /* 保证每道题目之间有间距 */
|
|
|
|
|
padding-bottom: 10px; /* 让横线不与题目文字紧贴 */
|
|
|
|
|
border-bottom: 1px solid #e0e0e0; /* 设置浅色横线 */
|
|
|
|
|
}
|
|
|
|
|
.question p {
|
|
|
|
|
font-size: 2rem; /* 默认字体大小 */
|
|
|
|
|
line-height: 1.5;
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* 选项样式 */
|
|
|
|
|
.options label {
|
|
|
|
|
font-size: 1.2rem; /* 默认字体大小 */
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
margin-bottom: 10px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.options input[type="radio"] {
|
|
|
|
|
margin-right: 10px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
textarea {
|
|
|
|
|
width: 100%;
|
|
|
|
|
border: 1px solid #ccc;
|
|
|
|
|
border-radius: 5px;
|
|
|
|
|
padding: 10px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 加载中样式 */
|
|
|
|
|
.loading {
|
|
|
|
|
text-align: center;
|
|
|
|
|
font-size: 18px;
|
|
|
|
|
color: #666;
|
|
|
|
|
margin-top: 50px;
|
|
|
|
|
}
|
|
|
|
|
/* 页面根元素字体大小 */
|
|
|
|
|
html {
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
}
|
|
|
|
|
</style>
|
|
|
|
|
|