|
|
|
@ -0,0 +1,328 @@
|
|
|
|
|
<template>
|
|
|
|
|
<div class="exam-container">
|
|
|
|
|
<!-- 左侧栏 -->
|
|
|
|
|
<div class="sidebar">
|
|
|
|
|
<!-- 返回按钮 -->
|
|
|
|
|
<div class="back-button">
|
|
|
|
|
<button @click="goBack">
|
|
|
|
|
<span>返回</span>
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
<h2>{{ examTitle }}</h2>
|
|
|
|
|
<p>答题人:{{ sender }}</p>
|
|
|
|
|
<p>得分:{{ currentScore }} 总分:{{ totalScore }}</p>
|
|
|
|
|
<p>已答对:{{ correctQuestions }} 总题数:{{ totalQuestions }}</p>
|
|
|
|
|
<div class="question-navigation">
|
|
|
|
|
<div
|
|
|
|
|
v-for="(question, index) in questions"
|
|
|
|
|
:key="question.id"
|
|
|
|
|
class="question-box"
|
|
|
|
|
:class="{ answered: answers[question.id]?.length > 0, active: currentQuestionIndex === index, correct: question.tf === 'true', incorrect: question.tf === 'false' }"
|
|
|
|
|
@click="scrollToQuestion(index)"
|
|
|
|
|
>
|
|
|
|
|
{{ index + 1 }}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 主内容 -->
|
|
|
|
|
<div class="main-content" v-if="!loading && questions.length">
|
|
|
|
|
<div
|
|
|
|
|
v-for="(question, index) in questions"
|
|
|
|
|
:key="question.id"
|
|
|
|
|
class="question"
|
|
|
|
|
:ref="'question-' + question.id"
|
|
|
|
|
:class="{ correct: question.tf === 'true', incorrect: question.tf === 'false', active: currentQuestionIndex === index }"
|
|
|
|
|
>
|
|
|
|
|
<p>{{ index + 1 }}. {{ question.content }}</p>
|
|
|
|
|
<div class="options">
|
|
|
|
|
<!-- 单选题 (Single Choice) -->
|
|
|
|
|
<template v-if="question.type === '单选题'">
|
|
|
|
|
<div v-for="(option, optIndex) in question.chance" :key="optIndex"
|
|
|
|
|
class="option"
|
|
|
|
|
:class="{ selected: answers[question.id] === option.label }">
|
|
|
|
|
{{ option.label }}: {{ option.text }}
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<!-- 多选题 (Multiple Choice) -->
|
|
|
|
|
<template v-else-if="question.type === '多选题'">
|
|
|
|
|
<div v-for="(option, optIndex) in question.chance" :key="optIndex" class="option">
|
|
|
|
|
{{ option.label }}: {{ option.text }}
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<!-- 判断题 (True/False) -->
|
|
|
|
|
<template v-else-if="question.type === '判断题'">
|
|
|
|
|
<div>
|
|
|
|
|
<input
|
|
|
|
|
type="radio"
|
|
|
|
|
:name="'question-' + question.id"
|
|
|
|
|
value="true"
|
|
|
|
|
:checked="answers[question.id] === 'true'"
|
|
|
|
|
disabled
|
|
|
|
|
/>
|
|
|
|
|
T
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<input
|
|
|
|
|
type="radio"
|
|
|
|
|
:name="'question-' + question.id"
|
|
|
|
|
value="false"
|
|
|
|
|
:checked="answers[question.id] === 'false'"
|
|
|
|
|
disabled
|
|
|
|
|
/>
|
|
|
|
|
F
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
</div>
|
|
|
|
|
<p>你的答案:<span>{{ question.answer }}</span></p>
|
|
|
|
|
<p>正确答案:<span>{{ question.idanswer }}</span></p>
|
|
|
|
|
<p>分数:{{ question.score }} 分</p>
|
|
|
|
|
<p v-if="question.analysis">解析:{{ question.analysis }}</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 加载中 -->
|
|
|
|
|
<div v-if="loading" class="loading">
|
|
|
|
|
正在加载试题,请稍候...
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 空状态 -->
|
|
|
|
|
<div v-if="!loading && !questions.length" class="empty">
|
|
|
|
|
未找到试题,请联系管理员!
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
import axios from "axios";
|
|
|
|
|
import exam from "@/views/Student/Exam.vue";
|
|
|
|
|
|
|
|
|
|
export default {
|
|
|
|
|
name: "ViewExamPaper",
|
|
|
|
|
data() {
|
|
|
|
|
return {
|
|
|
|
|
examTitle: this.examname, // 试卷标题
|
|
|
|
|
sender: "", // 发送人
|
|
|
|
|
totalScore: 0, // 总分
|
|
|
|
|
duration: "", // 时长
|
|
|
|
|
questions: [], // 试题数据
|
|
|
|
|
answers: {}, // 用户答案
|
|
|
|
|
correctQuestions: 0, // 答对的题目数
|
|
|
|
|
currentScore: 0, // 当前得分
|
|
|
|
|
totalQuestions: 0, // 总题数
|
|
|
|
|
loading: true, // 加载状态
|
|
|
|
|
submitted: false, // 是否已提交
|
|
|
|
|
currentQuestionIndex: 0, // 当前题目索引
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
computed: {
|
|
|
|
|
totalQuestions() {
|
|
|
|
|
return this.questions.length;
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
methods: {
|
|
|
|
|
async fetchExamData() {
|
|
|
|
|
try {
|
|
|
|
|
const token = this.$store.state.tokens[this.userId];
|
|
|
|
|
if (!token) {
|
|
|
|
|
alert("用户未登录,请重新登录!");
|
|
|
|
|
this.$router.push("/login");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const response = await axios.get(
|
|
|
|
|
"http://localhost:8080/teacher/answerSheet/selectQuestionanswer",
|
|
|
|
|
{
|
|
|
|
|
params: { senderId: this.studentId,testid: this.testid },
|
|
|
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
const { sender, list, testid } = response.data.data;
|
|
|
|
|
this.examTitle = this.examname;
|
|
|
|
|
this.sender = sender;
|
|
|
|
|
this.questions = list;
|
|
|
|
|
console.log('list', list);
|
|
|
|
|
this.totalScore = list.reduce((sum, q) => sum + q.score, 0);
|
|
|
|
|
this.correctQuestions = list.filter((q) => q.tf === "true").length;
|
|
|
|
|
this.loading = false;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error("获取试题失败", error);
|
|
|
|
|
this.loading = false;
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
async getTest() {
|
|
|
|
|
try {
|
|
|
|
|
const token = this.$store.state.tokens[this.userId];
|
|
|
|
|
if (!token) {
|
|
|
|
|
alert("用户未登录,请重新登录!");
|
|
|
|
|
this.$router.push("/login");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const response = await axios.get(
|
|
|
|
|
"http://localhost:8080/teacher/answerSheet/getScoreByTestId",
|
|
|
|
|
{
|
|
|
|
|
params: { studentId:this.studentId, testId: this.testId },
|
|
|
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
const data = response.data.data;
|
|
|
|
|
this.sender = data.sender;
|
|
|
|
|
this.currentScore = data.score;
|
|
|
|
|
this.totalScore = data.totalscore;
|
|
|
|
|
this.correctQuestions = data.tquestions;
|
|
|
|
|
this.totalQuestions = data.totalquestions;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error("获取试卷信息失败", error);
|
|
|
|
|
this.loading = false;
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
scrollToQuestion(index) {
|
|
|
|
|
this.$nextTick(() => {
|
|
|
|
|
const questionRef = this.$refs[`question-${this.questions[index].id}`];
|
|
|
|
|
if (questionRef && questionRef[0]) {
|
|
|
|
|
questionRef[0].scrollIntoView({ behavior: "smooth", block: "start" });
|
|
|
|
|
this.currentQuestionIndex = index;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
// 返回上一页的方法
|
|
|
|
|
goBack() {
|
|
|
|
|
this.$router.go(-1); // 返回上一页
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
mounted() {
|
|
|
|
|
this.fetchExamData();
|
|
|
|
|
},
|
|
|
|
|
created() {
|
|
|
|
|
this.userId = this.$route.query.userId;
|
|
|
|
|
this.examname = this.$route.query.name;
|
|
|
|
|
this.testid = this.$route.query.testid;
|
|
|
|
|
this.studentId = this.$route.query.studentId;
|
|
|
|
|
this.getTest();
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
.back-button {
|
|
|
|
|
padding-bottom: 10px;
|
|
|
|
|
text-align: center;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.back-button button {
|
|
|
|
|
background-color: #1976d2;
|
|
|
|
|
color: white;
|
|
|
|
|
padding: 8px 15px;
|
|
|
|
|
border: none;
|
|
|
|
|
border-radius: 5px;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
font-size: 18px;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.back-button button:hover {
|
|
|
|
|
background-color: #1565c0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.exam-container {
|
|
|
|
|
display: flex;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.sidebar {
|
|
|
|
|
position: fixed; /* 固定左侧栏 */
|
|
|
|
|
top: 0;
|
|
|
|
|
left: 0;
|
|
|
|
|
width: 20%;
|
|
|
|
|
height: 100%;
|
|
|
|
|
padding: 20px;
|
|
|
|
|
background-color: #f4f4f4;
|
|
|
|
|
overflow-y: auto; /* 允许左侧栏滚动 */
|
|
|
|
|
border-right: 1px solid #ddd;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.sidebar h2,
|
|
|
|
|
.sidebar p {
|
|
|
|
|
margin-bottom: 10px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.question-navigation {
|
|
|
|
|
display: grid;
|
|
|
|
|
grid-template-columns: repeat(5, 1fr); /* Adjust number of columns */
|
|
|
|
|
gap: 10px;
|
|
|
|
|
margin-top: 20px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.question-box {
|
|
|
|
|
padding: 15px;
|
|
|
|
|
background-color: #f1f1f1;
|
|
|
|
|
text-align: center;
|
|
|
|
|
border: 1px solid #ddd;
|
|
|
|
|
border-radius: 5px;
|
|
|
|
|
font-size: 18px;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
transition: background-color 0.3s;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.question-box:hover {
|
|
|
|
|
background-color: #e0e0e0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.question-box.active {
|
|
|
|
|
background-color: #bbdefb; /* Highlight active question */
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.question-box.answered {
|
|
|
|
|
background-color: #c8e6c9; /* Highlight answered question */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.question-box.correct {
|
|
|
|
|
background-color: #e0f7fa; /* Correct answer color */
|
|
|
|
|
color: #00796b; /* Text color for correct answers */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.question-box.incorrect {
|
|
|
|
|
background-color: #ffebee; /* Incorrect answer color */
|
|
|
|
|
color: #d32f2f; /* Text color for incorrect answers */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.main-content {
|
|
|
|
|
margin-left: 20%; /* 右侧主内容的宽度根据左侧栏的宽度调整 */
|
|
|
|
|
padding: 20px;
|
|
|
|
|
width: 80%;
|
|
|
|
|
overflow-y: auto; /* 使右侧内容可滚动 */
|
|
|
|
|
height: 100vh;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.question {
|
|
|
|
|
padding: 10px;
|
|
|
|
|
margin-bottom: 15px;
|
|
|
|
|
border: 1px solid #ddd;
|
|
|
|
|
border-radius: 5px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.correct {
|
|
|
|
|
background-color: #e0f7fa;
|
|
|
|
|
color: #00796b;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.incorrect {
|
|
|
|
|
background-color: #ffebee;
|
|
|
|
|
color: #d32f2f;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.loading,
|
|
|
|
|
.empty {
|
|
|
|
|
text-align: center;
|
|
|
|
|
margin-top: 50px;
|
|
|
|
|
font-size: 18px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.empty {
|
|
|
|
|
color: #888;
|
|
|
|
|
}
|
|
|
|
|
</style>
|