InternetCompetition/src/views/examModule/examPage.vue

902 lines
26 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script>
import {
getPracticeTopic,
getTopicDetail,
savaTestAnswer,
getTestCache,
saveTestCache,
} from "@/api/user";
import { sortString } from "@/util/tools";
export default {
name: "examPage",
data() {
return {
// 题目详情
testContent: {
testType: "多选题",
question:
"1、以社会主义核心价值观为引领发展社会主义先进文化弘扬革命文化传承中华优秀传统文化满足人民日益增长的精神文化需求巩固全党全国各族人民团结奋斗的共同思想基础不断提升 )。",
score: "10",
your_answer: "",
blanks_answer: [],
options: [
{
item_letter: "A",
item: "国家文化软实力和中华文化影响力",
},
{
item_letter: "B",
item: "国家文化硬实力和中华文化影响力",
},
{
item_letter: "C",
item: "国家文化硬实力和中华传统文化影响力",
},
{
item_letter: "D",
item: "国家文化硬实力和中华传统文化",
},
],
},
testList: [],
//题目数量
testTypeNum: [
{
type: "单选题",
children: [],
},
{
type: "多选题",
children: [],
},
{
type: "判断题",
children: [],
},
{
type: "填空题",
children: [],
},
{
type: "简答题",
children: [],
},
],
optionIndex: -1, // 选中的选项
examDialogShow: false,
// 考试请求参数
params: {
batch_id: "",
},
// 题量总数
questionTotal: 0,
passScore: 0, //及格分
totalScore: 0, //总分
minute: 0, //剩余时间
number: 0, //第几题
isCache: false, //是否获取完缓存
testTitle: "", //试卷名称
endTime: "",
countdown: "", //剩余时间
timer: "", //定时器
};
},
methods: {
// 选项点击事件
optionClick(event, index, item) {
// 单选题
if (this.testContent.testType === "多选题") {
// 多选题
if (event.target.className.includes("optionSelect")) {
event.target.className = "exam-left-test-option";
this.testList[this.number - 1].your_answer = this.testList[
this.number - 1
].your_answer.replace(item.item_letter + ",", "");
} else {
if (
this.testList[this.number - 1].your_answer.indexOf(item.item_letter) === -1
) {
this.testList[this.number - 1].your_answer += item.item_letter + ",";
}
event.target.className = "exam-left-test-option optionSelect";
}
} else {
if (event.target.className.includes("optionSelect")) {
event.target.className = "exam-left-test-option";
this.$set(this.testList[this.number - 1], "your_answer", "");
} else {
// 单选题、判断题
this.$refs.option.forEach((el) => {
el.className = "exam-left-test-option";
});
event.target.className = "exam-left-test-option optionSelect";
this.$set(this.testList[this.number - 1], "your_answer", item.item_letter);
}
// this.testList[this.number - 1].your_answer = item.item_letter
}
},
//输入框输入事件
handleInput(index) {
// let filteredArr = this.testContent.blanks_answer.filter(
// (item) => item !== undefined && item !== null
// );
console.log("value", index, this.testContent.blanks_answer,);
index.forEach(item => {
console.log(filteredArr[item-1]);
});
if (this.testContent.testType == "填空题") {
}
},
//交卷按钮
handPaper() {
this.examDialogShow = true;
},
//确定交卷按钮
submitTest() {
let list = this.testList.map((el) => {
console.log(el, "el");
if (el.question_kind == "填空题") {
let filteredArr = el.blanks_answer.filter(
(item) => item !== undefined && item !== null
);
return {
your_answer: filteredArr.join("__"), // 将数组元素通过 '__' 连接成字符串
theory_base_id: el.theory_base_id,
};
} else {
return {
your_answer: el.your_answer.includes(",")
? sortString(el.your_answer).slice(0, -1)
: el.your_answer,
theory_base_id: el.theory_base_id,
};
}
});
let params = sessionStorage.getItem("params");
let data = JSON.parse(params);
console.log(list);
data.list_item_answer = list;
let formData = new FormData();
formData.append("data", JSON.stringify(data));
savaTestAnswer(formData).then((res) => {
localStorage.removeItem("countdownEndTime");
this.$router.push("/practiceSuccess");
});
},
//答题卡数据获取
getCardData() {
// 考试信息
let testMsg = sessionStorage.getItem("testMsg");
this.testTitle = JSON.parse(testMsg).testTitle; //考试名称
this.endTime = JSON.parse(testMsg).endTime; //结束时间
// 试卷id
this.params.batch_id = this.$route.query.id;
this.totalScore = this.$route.query.totalScore; //总分
this.minute = this.$route.query.minute; //剩余时间
this.passScore = this.$route.query.passScore; //及格分
getPracticeTopic(this.params).then((res) => {
// 总数
this.questionTotal = res.data.data.length;
res.data.data.forEach((el) => {
this.testTypeNum.forEach((item) => {
if (el.question_kind === item.type) {
item.children.push(el);
}
});
});
this.testTypeNum.forEach((item, index) => {
this.testList.push(...item.children);
item.children.forEach((el) => {
// 回答的问题
el.your_answer = "";
el.blanks_answer = [];
if (el.question_kind === "单选题") {
el.number = el.no;
} else if (el.question_kind === "多选题") {
el.number = el.no + this.testTypeNum[0].children.length;
} else if (el.question_kind === "判断题") {
el.number =
el.no +
this.testTypeNum[0].children.length +
this.testTypeNum[1].children.length;
} else if (el.question_kind === "填空题") {
el.number =
el.no +
this.testTypeNum[0].children.length +
this.testTypeNum[1].children.length +
this.testTypeNum[2].children.length;
} else if (el.question_kind === "简答题") {
el.number =
el.no +
this.testTypeNum[0].children.length +
this.testTypeNum[1].children.length +
this.testTypeNum[2].children.length +
this.testTypeNum[3].children.length;
}
});
});
// 获取缓存数据
this.getCacheData();
this.testClick(this.testList[0]);
});
},
// 题目点击
testClick(item) {
console.log(item,'xxxxxxx');
// 保存缓存
this.saveCacheData();
this.testContent.options = [];
this.testContent.question = "";
// 第几个题目
this.number = item.number;
// 分数赋值
this.testContent.score = item.per_score;
this.testContent.testType = item.question_kind;
getTopicDetail({ theoryBaseId: item.theory_base_id }).then((res) => {
this.testContent.question = res.data.data.question;
this.testContent.options = res.data.data.items;
});
// 当前答案
this.testContent.your_answer = item.your_answer;
this.testContent.blanks_answer = item.blanks_answer;
},
// 获取缓存数据
getCacheData() {
let params = sessionStorage.getItem("params");
let data = JSON.parse(params);
// formData.append('user_id',JSON.stringify(data.user_id))
// formData.append('batch_id',JSON.stringify(data.batch_id))
let formData = {
user_id: data.user_id,
batch_id: data.batch_id,
};
getTestCache(formData).then((res) => {
if (res.data.state) {
res.data.data.list_item_answer.forEach((val) => {
this.testList.forEach((ele) => {
if (val.theory_base_id === ele.theory_base_id) {
this.$set(ele, "your_answer", val.your_answer);
// ele.your_answer = val.your_answer
}
});
});
this.testContent.your_answer = this.testList[0].your_answer;
} else {
}
this.isCache = true;
});
},
// 保存缓存数据
saveCacheData() {
let list = this.testList.map((el) => {
return {
your_answer: el.your_answer,
theory_base_id: el.theory_base_id,
};
});
let params = sessionStorage.getItem("params");
let data = JSON.parse(params);
data.list_item_answer = list.filter((el) => {
return el.your_answer;
});
let formData = new FormData();
formData.append("data", JSON.stringify(data));
// 保存缓存
saveTestCache(formData);
},
// 下一题点击
nextClick() {
if (this.number === this.questionTotal) {
return;
}
this.number++;
let data = this.testList[this.number - 1];
this.testClick(data);
},
// 上一题点击
preClick() {
if (this.number === 1 || this.number === 0) {
return;
}
this.number--;
let data = this.testList[this.number - 1];
this.testClick(data);
},
//获取剩余时间
initializeCountdown() {
// 检查 localStorage 中是否已有保存的结束时间
const storedEndTime = localStorage.getItem("countdownEndTime");
const currentTime = Date.now();
if (storedEndTime) {
// 如果存在,计算剩余时间
const remainingTime = Math.max(0, parseInt(storedEndTime) - currentTime);
this.countdown = Math.floor(remainingTime / 1000); // 转换为秒
} else {
// 如果没有结束时间设置新的结束时间30分钟后
const newEndTime = currentTime + this.minute * 60 * 1000; // 当前时间 + 30分钟
localStorage.setItem("countdownEndTime", newEndTime.toString());
this.countdown = this.minute * 60; // 设置初始倒计时时间30分钟
}
// 启动倒计时
this.timer = setInterval(this.updateCountdown, 1000);
},
updateCountdown() {
const currentTime = Date.now();
const storedEndTime = parseInt(localStorage.getItem("countdownEndTime"));
const remainingTime = Math.max(0, storedEndTime - currentTime);
this.countdown = Math.floor(remainingTime / 1000);
// 如果倒计时结束,清除定时器
if (remainingTime <= 0) {
clearInterval(this.timer);
this.timer = null;
localStorage.removeItem("countdownEndTime"); // 倒计时结束时清除存储
this.$message.warning("考试时间到,已自动交卷");
this.submitTest();
}
},
padZero(value) {
// 格式化为两位数
return value < 10 ? `0${value}` : value;
},
generateArray(n) {
let result = [];
for (let i = 1; i <= n; i++) {
result.push(i);
}
return result;
},
},
mounted() {
this.getCardData();
// 获取剩余时间
this.initializeCountdown();
},
beforeDestroy() {
// 清除定时器
if (this.timer) {
clearInterval(this.timer);
}
},
computed: {
// 将题目字符串按“-”分割,形成一个包含文本和输入框的数组
questionParts() {
const parts = this.testContent.question.split("_");
console.log(parts,'xxxxxxxxxxxx');
return parts
// return parts.map((part, index) => {
// return {
// type: index % 2 === 1 ? "input" : "text",
// value: part,
// index: this.generateArray(parts.filter((value, i) => i % 2 === 1).length),
// };
// });
},
timeFormatted() {
// 将剩余时间转换为时:分:秒
const hours = Math.floor(this.countdown / 3600);
const minutes = Math.floor((this.countdown % 3600) / 60);
const seconds = this.countdown % 60;
return `${this.padZero(hours)}:${this.padZero(minutes)}:${this.padZero(seconds)}`;
},
},
watch: {
"testContent.options"(newVal, oldVal) {},
},
};
</script>
<template>
<div class="exam">
<div class="exam-box">
<div class="exam-left">
<div class="exam-test-title">{{ testTitle }}</div>
<div class="exam-left-top">
<div class="exam-left-num">{{ number }}/{{ questionTotal }}</div>
<div class="exam-left-type">{{ testContent.testType }}</div>
<span>({{ testContent.score }})</span>
<div v-if="testContent.testType === '单选题'" class="exam-left-msc">
每题只有一个正确答案
</div>
<div v-else-if="testContent.testType === '多选题'" class="exam-left-msc">
每题只有两个以上正确答案
</div>
</div>
<!-- 题干 -->
<div class="exam-left-test">
<div class="exam-left-test-title">
<div v-if="testContent.testType === '填空题'" class="exam-left-test-blanks">
<div v-for="(char, index) in questionParts" :key="index">
<span v-if="char !== ''">{{ char }}</span>
<el-input
v-else
v-model="chart"
@input="handleInput"
type="text"
placeholder="请输入答案"
class="answer-input"
></el-input>
</div>
</div>
<div v-else>{{ testContent.question }}</div>
</div>
<div v-if="testContent.testType === '多选题'" class="exam-left-test-content">
<div
v-for="(item, index) in testContent.options"
ref="option"
:class="
testContent.your_answer.includes(item.item_letter) ? 'optionSelect' : ''
"
class="exam-left-test-option"
@click="optionClick($event, index, item)"
>
{{ item.item_letter }}{{ item.item }}
</div>
</div>
<div v-else class="exam-left-test-content">
<div
v-for="(item, index) in testContent.options"
ref="option"
:class="testContent.your_answer === item.item_letter ? 'optionSelect' : ''"
class="exam-left-test-option"
@click="optionClick($event, index, item)"
>
{{ item.item_letter }}{{ item.item }}
</div>
</div>
<!-- 按钮 -->
<div class="exam-left-btn">
<div
:class="number === 1 ? 'exam-left-btn-pre' : 'exam-left-btn-next'"
@click="preClick"
>
上一题
</div>
<div
:class="
number === questionTotal ? 'exam-left-btn-pre' : 'exam-left-btn-next'
"
style="margin-left: 164px"
@click="nextClick"
>
下一题
</div>
</div>
</div>
</div>
<div class="exam-right">
<div class="exam-right-title">答题卡</div>
<div class="exam-right-card">
<div class="exam-right-card-score">
<span>总分:{{ totalScore }}分</span>
<span>及格分:{{ passScore }}</span>
<span>题量:{{ questionTotal }}</span>
</div>
<div class="exam-right-card-time">
<span>剩余时间:</span>
<span>{{ timeFormatted }}</span>
</div>
<div class="exam-right-card-line"></div>
<div v-if="isCache" class="exam-right-card-box">
<div v-for="(item, index) in testTypeNum" class="exam-right-card-num">
<span class="exam-right-card-num-title"
><span class="type">{{ item.type }}</span>
<span>(共{{ item.children.length }}题)</span></span
>
<div class="exam-right-card-num-item">
<span
ref="answer"
v-for="s in item.children"
:class="{ selectQuestion: s.your_answer, border: s.number === number }"
:style="{
'background-color':
s.blanks_answer.length != 0 ? 'rgba(64, 253, 137, 0.2)' : '',
}"
@click="testClick(s)"
>{{ s.no }}</span
>
</div>
</div>
</div>
<!-- <div class="exam-right-card-lenged">-->
<!-- <div>-->
<!-- <span><i class="have"></i>已做</span>-->
<!-- <span><i class="notDone"></i>未做</span>-->
<!-- <span><i class="current"></i>当前</span>-->
<!-- </div>-->
<!-- </div>-->
<div class="submit" @click="handPaper"></div>
</div>
</div>
</div>
<div v-if="examDialogShow" class="exam-dialog">
<div class="exam-dialog-title">提示</div>
<div class="exam-dialog-content">确定要交卷吗?交卷之后将不能再继续答题。</div>
<div class="exam-dialog-btn">
<div
class="exam-dialog-btn-light"
style="margin-right: 82px"
@click="examDialogShow = false"
>
取消
</div>
<div class="exam-dialog-btn-light" @click="submitTest">确定</div>
</div>
</div>
</div>
</template>
<style lang="less" scoped>
.exam-test-title {
position: absolute;
top: -40px;
font-size: 24px;
color: rgba(255, 255, 255, 1);
font-weight: 700;
left: 0;
}
.exam {
width: 100%;
height: 100%;
display: flex;
align-items: center;
//提示弹窗
&-dialog {
width: 807px;
height: 391px;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: auto;
background-image: url("../../assets/image/prompt.png");
background-size: 100% 100%;
&-title {
text-align: center;
margin-right: 25px;
margin-top: 7px;
color: rgba(255, 255, 255, 1);
font-size: 24px;
}
&-content {
margin-top: 113px;
text-align: center;
font-size: 26px;
color: rgba(255, 255, 255, 1);
}
&-btn {
display: flex;
margin-top: 97px;
justify-content: center;
div {
width: 157px;
height: 44px;
line-height: 44px;
text-align: center;
background-size: 100% 100%;
}
&-light {
color: rgba(255, 255, 255, 1);
background-image: url("../../assets/image/highlight-btn.png");
}
&-dark {
margin-right: 82px;
color: rgba(255, 255, 255, 0.2);
background-image: url("../../assets/image/dark-btn.png");
}
}
}
&-box {
width: 100%;
display: flex;
align-items: center;
justify-content: space-evenly;
}
&-left::-webkit-scrollbar {
display: none;
}
&-left {
position: relative;
width: 1325px;
height: 858px;
background-size: 100% 100%;
background-image: url("../../assets/image/test.png");
&-top {
margin-top: 32px;
display: flex;
align-items: center;
}
&-type {
border: 2px solid rgba(101, 140, 246, 1);
color: rgba(101, 140, 246, 1);
padding: 0 7px;
margin-left: 20px;
font-size: 24px;
}
&-type + span {
margin: 0 10px;
color: rgba(255, 255, 255, 1);
font-size: 20px;
}
&-num {
text-align: center;
font-size: 26px;
line-height: 52px;
color: rgba(255, 255, 255, 1);
width: 114px;
height: 52px;
background-size: 100% 100%;
background-image: url("../../assets/image/test-num.png");
}
&-msc {
color: rgba(255, 255, 255, 0.6);
font-size: 18px;
}
// 试卷内容
&-test {
margin-top: 33px;
padding: 0 50px;
//题目
&-title {
font-size: 24px;
line-height: 44px;
color: rgba(255, 255, 255, 1);
}
&-blanks {
display: flex;
flex-wrap: wrap;
::v-deep .el-input__inner {
border: none;
border-radius: 0;
background: transparent;
color: #fff;
border-bottom: 1px solid #fff;
}
}
&-content::-webkit-scrollbar {
display: none;
}
// 选项
&-content {
margin-top: 40px;
height: 500px;
overflow-y: scroll;
}
.optionSelect {
background-color: rgba(101, 140, 246, 0.55);
}
&-option {
width: 100%;
height: 80px;
margin-bottom: 20px;
background-image: url("../../assets/image/test-option.png");
padding-left: 34px;
display: flex;
font-size: 22px;
color: #ffffff;
align-items: center;
box-sizing: border-box;
}
}
//按钮
&-btn {
position: absolute;
bottom: 50px;
display: flex;
margin-left: -239px;
left: 50%;
&-pre,
&-next {
width: 157px;
height: 44px;
text-align: center;
line-height: 44px;
background-size: 100% 100%;
cursor: pointer;
}
&-pre {
color: rgba(255, 255, 255, 0.5);
background-image: url("../../assets/image/dark-btn.png");
}
&-next {
color: rgba(255, 255, 255, 1);
background-image: url("../../assets/image/highlight-btn.png");
}
}
}
&-right {
position: relative;
width: 441px;
height: 858px;
background-size: 100% 100%;
background-image: url("../../assets/image/test-card.png");
//答题卡名称
&-title {
text-align: center;
font-size: 24px;
color: rgba(255, 255, 255, 1);
margin-top: 16px;
}
&-card {
font-size: 18px;
color: rgba(255, 255, 255, 1);
padding: 46px;
//提交按钮
.submit {
width: 157px;
height: 44px;
margin: 20px auto;
//left: 50%;
//margin-left: -78px;
//bottom: 50px;
background-image: url("../../assets/image/submit.png");
}
&-lenged {
width: 400px;
height: 80px;
background-color: rgba(149, 149, 149, 0.2);
position: absolute;
bottom: 10px;
left: 50%;
margin-left: -200px;
display: flex;
justify-content: center;
align-items: center;
div {
span {
font-size: 14px;
margin-right: 26px;
span:last-child {
margin-right: 0;
}
i {
width: 18px;
height: 10px;
margin-right: 10px;
display: inline-block;
box-sizing: border-box;
}
.current {
border: 2px solid rgba(101, 140, 246, 1);
}
.notDone {
background-color: rgba(0, 0, 0, 0.2);
}
.have {
background-color: rgba(64, 253, 137, 0.2);
}
}
}
}
&-score {
justify-content: space-between;
display: flex;
font-size: 18px;
}
&-line {
border: 1px solid rgba(255, 255, 255, 0.3);
margin: 22px 0;
}
&-time {
margin-top: 37px;
position: relative;
span:nth-child(2) {
color: rgba(255, 189, 34, 1);
font-size: 26px;
}
}
//题目容器
&-box {
position: relative;
height: 516px;
overflow-y: auto;
}
// 滚动条
&-box::-webkit-scrollbar {
position: absolute;
left: -50px;
width: 7px; //修改滚动条宽度
background-color: rgba(107, 126, 177, 0.2);
}
&-box::-webkit-scrollbar-thumb {
border-radius: 7px;
box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2);
background: rgba(107, 126, 177, 1);
}
// 题目数量
&-num {
&-title {
display: block;
margin-bottom: 16px;
.type {
border: 2px solid rgba(101, 140, 246, 1);
color: rgba(101, 140, 246, 1);
padding: 0 7px;
display: inline-block;
margin-right: 8px;
font-size: 24px;
}
}
&-item {
display: flex;
flex-wrap: wrap;
.border {
box-sizing: border-box;
border: 2px solid rgba(101, 140, 246, 1);
}
.selectQuestion {
background-color: rgba(64, 253, 137, 0.2);
}
span {
//display:inline-block ;
margin: 0 11px 14px 0;
width: 58px;
line-height: 38px;
text-align: center;
color: #fff;
background-color: rgba(0, 0, 0, 0.1);
height: 38px;
}
span:nth-child(5n) {
margin-right: 0 !important;
}
}
}
}
}
}
</style>