前端完善考试答题功能

main
yuan 2 weeks ago
parent 11e604778c
commit ef4db46a90

@ -19,4 +19,6 @@ public class AnswerSheetDO {
private List<QuestionbankDO> list;
private double sumscore;
}

@ -11,5 +11,6 @@ public class QuestionbankDO {
private long id;
private String content;
private List<Map<String,String>> chance;
private double score;
}

@ -50,6 +50,7 @@ public class AnswerSheetDOServiceImpl extends ServiceImpl<AnswerSheetDOMapper, A
queryWrapper.eq(ExamCreate::getPid,pid);
List<ExamCreate> list = examCreateMapper.selectList(queryWrapper);
List<QuestionbankDO> l = new ArrayList<>();
double sum = 0;
Gson gson = new Gson();
for(ExamCreate examCreate : list){
LambdaQueryWrapper<Questionbank> queryWrapper1 = new LambdaQueryWrapper<>();
@ -58,17 +59,19 @@ public class AnswerSheetDOServiceImpl extends ServiceImpl<AnswerSheetDOMapper, A
QuestionbankDO questionbankDO = new QuestionbankDO();
questionbankDO.setId(questionbank.getId());
questionbankDO.setContent(questionbank.getContent());
questionbankDO.setScore(questionbank.getScore());
sum=sum+questionbank.getScore();
String chance = questionbank.getChance();
List<Map<String,String>> listMap = gson.fromJson(chance,List.class);
questionbankDO.setChance(listMap);
l.add(questionbankDO);
}
AnswerSheetDO answerSheetDO = new AnswerSheetDO();
answerSheetDO.setGrade(grade);
answerSheetDO.setSubject(subject);
answerSheetDO.setName(name);
answerSheetDO.setList(l);
answerSheetDO.setSumscore(sum);
return answerSheetDO;
}
}

@ -46,10 +46,10 @@
<p>学科{{ paper.subject }}</p>
<p>题目数{{ paper.questionCount }}</p>
<p>试卷总分{{ paper.totalScore }}</p>
<p>考试时长{{ paper.time }}</p>
<p>考试时长{{ paper.time + " 分钟"}}</p>
<p>开始时间{{ paper.startTime || "未设置" }}</p>
<p>结束时间{{ paper.endTime || "未设置" }}</p>
<button class="start-button" @click="startExam(paper.name, paper.subject)">开始答题</button>
<button class="start-button" @click="startExam(paper.id,paper.name, paper.subject,paper.time)">开始答题</button>
</div>
</div>
</div>
@ -69,6 +69,7 @@ export default {
components: {Aside, Header},
data() {
return {
collapse: false,
paperTypes: ["固定试卷", "时段试卷", "班级试卷"], //
subjects: ["语文", "数学"], //
selectedType: "班级试卷", //
@ -104,8 +105,8 @@ export default {
},
},
methods: {
startExam(name,subject) {
this.$router.push({ name: "ExamPaper", query: { name: name, subject: subject } });
startExam(id,name,subject,time) {
this.$router.push({ name: "ExamPaper", query: { id:id ,name: name, subject: subject ,time:time} });
},
handleTypeChange(type) {
this.selectedType = type; //
@ -149,7 +150,7 @@ export default {
subject: record.subject,
questionCount: record.totalquestion,
totalScore: record.totalscore,
time: record.time + "分钟",
time: record.time,
startTime: record.start_time || "未设置",
endTime: record.end_time || "未设置",
}));

@ -5,15 +5,34 @@
<h2>{{ examTitle }}</h2>
<p>总分{{ totalScore }} &nbsp; 时长{{ duration }}</p>
<p class="time-remaining">剩余时间<br /><span>{{ formattedTime }}</span></p>
<button class="submit-button" @click="submitExam"></button>
<button class="submit-button" @click="submitExam" :disabled="loading">提交试卷</button>
<div class="question-navigation">
<!-- Question navigation with clickable boxes -->
<div
v-for="(question, index) in questions"
:key="question.id"
class="question-box"
:class="{ answered: answers[question.id] }"
@click="scrollToQuestion(index)"
>
{{ index + 1 }}
</div>
</div>
</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="main-content" v-if="!loading && questions.length > 0">
<div
class="question"
v-for="(question, index) in questions"
:key="question.id"
:ref="'question-' + question.id"
>
<p>{{ index + 1 }}. {{ question.text }}
<span class="score" v-if="question.score">{{ question.score }} </span>
</p>
<div class="options">
<!-- 有选项时 -->
<!-- 有选项 -->
<template v-if="question.options.length > 0">
<label v-for="(option, optIndex) in question.options" :key="optIndex">
<input
@ -25,7 +44,7 @@
{{ option.label }}
</label>
</template>
<!-- 无选项 -->
<!-- 无选项 -->
<template v-else>
<textarea
v-model="answers[question.id]"
@ -38,7 +57,7 @@
</div>
<!-- 加载中提示 -->
<div class="loading" v-else>
<div class="loading" v-if="loading">
正在加载试题请稍候...
</div>
</div>
@ -54,18 +73,30 @@ export default {
examTitle: "考试中...", //
totalScore: 0, //
duration: "未知", //
remainingTime: 0, //
remainingTime: 5*60, //
questions: [], //
answers: {}, //
loading: true, //
examId: null, // ID
timer: null, //
refreshTimer: null, //
};
},
computed: {
//
formattedTime() {
const minutes = Math.floor(this.remainingTime / 60);
const seconds = this.remainingTime % 60;
const minutes = Math.floor(this.remainingTime / 60); //
const seconds = this.remainingTime % 60; //
return `${minutes}${seconds}`;
}
},
watch: {
questions(newVal) {
if (newVal.length > 0) {
//
this.startCountdown();
if (this.refreshTimer) clearTimeout(this.refreshTimer); //
}
},
},
methods: {
@ -80,72 +111,123 @@ export default {
}
const res = await axios.get('http://localhost:8080/student/examPaper/selectByUserId', {
headers: { Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',}
headers: { Authorization: `Bearer ${token}` },
});
// 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',
}
params: { name: this.name, subject: this.subject, grade },
headers: { Authorization: `Bearer ${token}` },
});
console.log('examRes', examRes);
if (examRes.data.code === 200) {
const { name, grade, subject, list } = examRes.data.data;
const { name, 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 => ({
this.totalScore = examRes.data.data.sumscore;
this.duration = this.time;
this.remainingTime = this.time * 60;
this.questions = list.map((item) => ({
id: item.id,
text: item.content,
options: item.chance ? item.chance.map(opt => ({ value: opt.label, label: opt.text })) : [],
options: item.chance
? item.chance.map((opt) => ({ value: opt.label, label: opt.text }))
: [],
score: item.score,
}));
console.log("试题数据:", this.questions);
} else {
alert("加载试题失败:" + examRes.data.msg);
}
} else {
alert("返回数据缺少年级信息!");
}
} catch (error) {
console.error("加载试题时发生错误:"+ error);
console.error("加载试题时发生错误:", error);
alert("加载试题失败,请稍后再试!");
} finally {
this.loading = false;
}
},
scrollToQuestion(index) {
const questionId = this.questions[index].id;
let questionElement = this.$refs[`question-${questionId}`];
// questionElement
if (Array.isArray(questionElement)) {
//
console.log(`Element found as array for question ID: ${questionId}, Element:`, questionElement[0]);
questionElement = questionElement[0];
}
//
console.log(`Trying to scroll to question ID: ${questionId}, Element found:`, questionElement);
if (questionElement && questionElement.scrollIntoView) {
questionElement.scrollIntoView({ behavior: "smooth", block: "start" });
} else {
console.warn(`Element for question ${index + 1} not found or invalid.`);
}
},
//
startRefreshTimer() {
this.refreshTimer = setTimeout(() => {
if (!this.questions.length) {
alert("加载超时,页面将刷新以重试...");
location.reload(); //
}
}, 10000); // 10
},
//
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} 道题未作答,请完成后再提交!`);
const confirmSubmit = confirm(`还有 ${unanswered.length} 道题未作答,是否仍然提交试卷?`);
if (!confirmSubmit) {
return; //
}
}
console.log('答案', this.answers);
//
const remainingTime = Math.round(Math.max(this.duration*60 - this.remainingTime, 0) / 60);
const token = this.$store.state.token;
if (!token) {
alert("用户未登录,请重新登录!");
this.$router.push("/login");
return;
}
//
const response = await axios.post("http://localhost:8080/api/exam/submit", {
examId: this.examId,
answers: this.answers,
// this.answers AnswerVo
const answerList = Object.keys(this.answers).map((questionId) => {
return {
id: Number(questionId), // id
answer: this.answers[questionId].trim() //
};
});
const response = await axios.post("http://localhost:8080/student/homepage/task_answer", {
testid: this.examId, // testid
list: answerList, // list AnswerVo
time: remainingTime // time
}, {
headers: {
Authorization: `Bearer ${token}`
}
});
if (response.data.code === 200) {
alert("考试提交成功!");
this.$router.push("/examList");
this.$router.push("/student/exam");
} else {
alert("提交失败:" + response.data.msg);
}
@ -157,48 +239,48 @@ export default {
//
startCountdown() {
if (this.timer) clearInterval(this.timer);
this.timer = setInterval(() => {
if (this.remainingTime > 0) {
this.remainingTime -= 1;
} else {
if (this.remainingTime <= 0) {
clearInterval(this.timer);
alert("时间到,试卷将自动提交");
this.submitExam();
alert("时间到!");
return;
}
}, 1000);
this.remainingTime--; //
}, 1000); //
},
},
created() {
this.subject = this.$route.query.subject;
this.name = this.$route.query.name;
this.examId = this.$route.query.id;
this.time = this.$route.query.time;
if (!this.subject && !this.name) {
alert("未获取到试卷,请重新选择试卷");
if (!this.subject || !this.name) {
alert("未获取到试卷,请重新选择");
this.$router.push("/exam");
return;
}
//
this.fetchQuestions();
this.startRefreshTimer();
},
mounted() {
this.startCountdown();
beforeDestroy() {
if (this.timer) clearInterval(this.timer);
if (this.refreshTimer) clearTimeout(this.refreshTimer);
},
};
</script>
<style scoped>
/* 页面布局 */
.exam-container {
display: flex;
height: 100vh;
background-color: #f4f6f9;
padding: 0; /* 确保没有额外的内边距 */
padding: 0;
position: relative;
}
/* 左侧栏样式 */
.sidebar {
width: 20%;
padding: 20px;
@ -206,11 +288,10 @@ export default {
box-shadow: 2px 0 5px rgba(0, 0, 0, 0.1);
text-align: center;
position: sticky;
top: 0; /* 固定位置 */
height: 100vh; /* 确保左侧栏一直显示 */
top: 0;
height: 100vh;
}
/* 使左侧栏文字不会被遮挡 */
.sidebar h2 {
font-size: 20px;
margin-bottom: 20px;
@ -234,35 +315,19 @@ export default {
background-color: #0056b3;
}
/* 主内容区域 */
.main-content {
flex: 1;
padding: 30px;
background-color: #ffffff; /* 设置和左侧栏一致的背景 */
overflow-y: auto; /* 允许滚动 */
height: 100vh; /* 确保主内容区域可以填满视口高度 */
box-sizing: border-box; /* 包括内边距在内的大小计算 */
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;
padding-bottom: 10px;
border-bottom: 1px solid #e0e0e0;
}
.options input[type="radio"] {
@ -274,18 +339,65 @@ textarea {
border: 1px solid #ccc;
border-radius: 5px;
padding: 10px;
font-size: 1.2rem; /* 调整字体大小 */
line-height: 1.5; /* 调整行高 */
}
/* 加载中样式 */
.question p {
font-size: 2.2rem; /* 调整题目字体大小 */
line-height: 1.5;
margin-bottom: 20px;
}
.options label {
font-size: 1.4rem; /* 调整选项字体大小 */
display: flex;
align-items: center;
margin-bottom: 10px;
}
.loading {
text-align: center;
font-size: 18px;
color: #666;
margin-top: 50px;
}
/* 页面根元素字体大小 */
html {
font-size: 16px;
.score {
font-size: 1.4rem;
color: #999;
margin-left: 10px;
}
/* Sidebar question navigation */
.question-navigation {
margin-top: 20px;
display: flex;
flex-wrap: wrap;
justify-content: center;
}
.question-box {
width: 40px;
height: 40px;
margin: 5px;
display: flex;
justify-content: center;
align-items: center;
background-color: #f0f0f0;
border-radius: 5px;
cursor: pointer;
font-size: 1.2rem;
}
</style>
.question-box:hover {
background-color: #007bff;
color: white;
}
.question-box.answered {
background-color: red; /* 已做题目的背景色 */
color: white; /* 文字颜色 */
}
</style>

Loading…
Cancel
Save