<template>
|
<div
|
class="exam-container w-screen h-screen bg-slate-50 relative overflow-hidden"
|
>
|
<div class="top-bg bg-blue-500"></div>
|
<div class="exam-content">
|
<div class="exam-wrapper container mx-auto h-full flex flex-col">
|
<div class="exam-header flex items-center mt-12 mb-10">
|
<div class="title-container text-3xl font-semibold text-white mr-8">
|
{{ examInfo.examName }}
|
</div>
|
<AnswerTime @timeOut="timeOut" ref="answerTime"></AnswerTime>
|
|
<div class="return-container grow flex justify-end">
|
<el-button type="danger" size="large" circle @click="closeClick">
|
<template #icon>
|
<el-icon :size="20">
|
<Close />
|
</el-icon>
|
</template>
|
</el-button>
|
</div>
|
</div>
|
|
<div class="exam-main grow flex justify-between">
|
<!-- 答题卡区 -->
|
<div class="answer-wrapper answer-left mr-8 shadow-xl p-4 box-border">
|
<div class="wrapper h-full flex flex-col items-center">
|
<div
|
class="title-wrapper w-full flex justify-between items-center mb-5"
|
>
|
<div class="title text-xl font-semibold">答题卡</div>
|
<AnswerTag></AnswerTag>
|
</div>
|
|
<div class="progress-wrapper w-full">
|
<AnswerProgress></AnswerProgress>
|
</div>
|
|
<div class="sheet-wrapper w-full grow relative my-5">
|
<div class="sheet-content absolute top-0 bottom-0 w-full">
|
<AnswerSheet></AnswerSheet>
|
</div>
|
</div>
|
|
<div class="submit-wrapper">
|
<el-button
|
type="primary"
|
class="submit-button"
|
@click="submitExamHandle"
|
>提交试卷</el-button
|
>
|
</div>
|
</div>
|
</div>
|
|
<!-- 答题内容区 -->
|
<div class="answer-wrapper answer-right grow shadow-xl p-4">
|
<div class="wrapper h-full flex flex-col">
|
<div class="title-wrapper w-full flex mb-5">
|
<div class="title text-xl font-semibold">
|
{{ examType[currentType] }} ({{
|
examStore.getActiveQuestion.questionScore
|
}}分)
|
</div>
|
</div>
|
|
<div class="main-wrapper w-full grow relative my-5">
|
<div class="main-content absolute top-0 bottom-0 w-full">
|
<Transition appear name="fade-transform" mode="out-in">
|
<component
|
:is="typeComponent[currentType]"
|
:key="currentIndex"
|
></component>
|
</Transition>
|
</div>
|
</div>
|
|
<div class="tool-wrapper flex justify-end">
|
<div class="button-container flex items-center">
|
<div class="button-item">
|
<el-button class="tool-button" @click="prevQuestion"
|
>上一题</el-button
|
>
|
</div>
|
<div class="button-item">
|
<el-button
|
class="tool-button"
|
type="primary"
|
@click="nextQuestion"
|
>下一题</el-button
|
>
|
</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
|
<!-- 退出考试提示弹窗 -->
|
<el-dialog v-model="quitDialog" title="注意" width="500">
|
<div class="dialog-container">
|
<p>请确认是否退出当前考试</p>
|
<p>当前试卷会自动提交,后续将无法继续作答</p>
|
</div>
|
<template #footer>
|
<div class="dialog-footer">
|
<el-button @click="quitDialog = false">继续作答</el-button>
|
<el-button type="danger" @click="confirmQuit"> 确定退出 </el-button>
|
</div>
|
</template>
|
</el-dialog>
|
|
<!-- 交卷提示弹窗 -->
|
<el-dialog v-model="submitDialog" title="确认交卷?" width="500">
|
<div class="dialog-container">
|
<p>请确认是否提交试卷</p>
|
<p>一旦交卷将无法继续作答或修改答案</p>
|
</div>
|
<template #footer>
|
<div class="dialog-footer">
|
<el-button @click="submitDialog = false">继续作答</el-button>
|
<el-button type="primary" @click="confirmSubmit">
|
确认提交
|
</el-button>
|
</div>
|
</template>
|
</el-dialog>
|
|
<!-- 考试时间弹窗 -->
|
<el-dialog
|
v-model="timeDialog"
|
align-center
|
width="500"
|
:close-on-click-modal="false"
|
:close-on-press-escape="false"
|
:show-close="false"
|
>
|
<div class="dialog-container flex flex-col items-center">
|
<div class="icon-container">
|
<el-icon :size="50" color="#3680fa">
|
<Timer />
|
</el-icon>
|
</div>
|
<div class="dialog-info">考试结束,系统自动收卷中.......</div>
|
</div>
|
</el-dialog>
|
</div>
|
</template>
|
|
<script setup>
|
import { ref, watchEffect, watch, onMounted ,onUnmounted} from "vue";
|
import { storeToRefs } from "pinia";
|
import { Close, Timer } from "@element-plus/icons-vue";
|
import AnswerTag from "./components/answer-tag/index.vue";
|
import AnswerProgress from "./components/answer-progress/index.vue";
|
import AnswerSheet from "./components/answer-sheet/index.vue";
|
import AnswerTime from "./components/answer-time/index.vue";
|
|
import AnswerSingle from "./components/answer-main/answer-single/index.vue";
|
import AnswerMultiple from "./components/answer-main/answer-multiple/index.vue";
|
import AnswerAudio from "./components/answer-main/answer-audio/index.vue";
|
import AnswerFill from "./components/answer-main/answer-fill/index.vue";
|
import AnswerDetermine from "./components/answer-main/answer-determine/index.vue";
|
import AnswerShort from "./components/answer-main/answer-short/index.vue";
|
import AnswerCount from "./components/answer-main/answer-count/index.vue";
|
|
import { useExamStore, useUserStore } from "@/store/index.js";
|
import { useRouter } from "vue-router";
|
|
import { submitExam } from "@/api/modules/exam.js";
|
import useWebScoket from "@/hooks/useWebScoket.js";
|
|
import { ElMessage, ElMessageBox } from "element-plus";
|
|
const router = useRouter();
|
|
const examStore = useExamStore();
|
const userStore = useUserStore();
|
|
const { userInfo } = storeToRefs(userStore);
|
const { currentType, currentIndex, examDetail, examType, examInfo } =
|
storeToRefs(examStore);
|
|
const typeComponent = {
|
1: AnswerSingle,
|
2: AnswerMultiple,
|
3: AnswerDetermine,
|
4: AnswerFill,
|
5: AnswerShort,
|
6: AnswerAudio,
|
7: AnswerCount,
|
8: AnswerShort,
|
};
|
|
const quitDialog = ref(false);
|
const submitDialog = ref(false);
|
const timeDialog = ref(false);
|
|
const { status, message, error, connect, disconnect, sendMessage } = useWebScoket({
|
url: 'wss://www.kgmeet.com:18443/websocket/' + userInfo.value.id,
|
heartBeatData: 'ping'
|
});
|
|
// const { status, message, error, connect, disconnect, sendMessage } =
|
// useWebScoket({
|
// url: "//192.168.3.64:8000/websocket/" + userInfo.value.id,
|
// heartBeatData: "ping",
|
// });
|
|
// 上一题
|
const prevQuestion = () => {
|
currentIndex.value--;
|
checkList();
|
};
|
|
// 下一题
|
const nextQuestion = () => {
|
currentIndex.value++;
|
checkList();
|
};
|
|
// 查询题目
|
const checkList = () => {
|
let tempIndex = 0;
|
const typeQuestion = examDetail.value.find((typeItem, index) => {
|
if (typeItem.questionType === currentType.value) {
|
tempIndex = index;
|
return typeItem;
|
} else {
|
return false;
|
}
|
});
|
if (typeQuestion) {
|
if (currentIndex.value >= typeQuestion.questionList.length) {
|
tempIndex++;
|
if (examDetail.value[tempIndex]) {
|
currentType.value = examDetail.value[tempIndex].questionType;
|
currentIndex.value = 0;
|
} else {
|
currentType.value = typeQuestion.questionType;
|
currentIndex.value = typeQuestion.questionList.length - 1;
|
}
|
} else if (currentIndex.value < 0) {
|
tempIndex--;
|
if (examDetail.value[tempIndex]) {
|
currentType.value = examDetail.value[tempIndex].questionType;
|
currentIndex.value =
|
examDetail.value[tempIndex].questionList.length - 1;
|
} else {
|
currentType.value = typeQuestion.questionType;
|
currentIndex.value = 0;
|
}
|
}
|
}
|
};
|
|
// 退出考试
|
const closeClick = () => {
|
quitDialog.value = true;
|
};
|
|
const confirmQuit = () => {
|
timeOut();
|
};
|
|
// 交卷
|
const submitExamHandle = () => {
|
submitDialog.value = true;
|
};
|
|
const confirmSubmit = () => {
|
timeOut();
|
};
|
|
const resetAllDialog = () => {
|
quitDialog.value = false;
|
submitDialog.value = false;
|
};
|
|
// 时间结束
|
const timeOut = () => {
|
const temp = {
|
...examInfo.value,
|
titleList: examDetail.value,
|
};
|
timeDialog.value = true;
|
resetAllDialog();
|
|
disconnect();
|
|
submitExam(temp)
|
.then((res) => {
|
returnBack();
|
})
|
.catch(() => {
|
returnBack();
|
});
|
};
|
|
const returnBack = () => {
|
setTimeout(() => {
|
router.back();
|
}, 2000);
|
};
|
|
watchEffect(() => {
|
let progress = 0;
|
examDetail.value.forEach((item) => {
|
item.questionList.forEach((question) => {
|
if (
|
question.answer ||
|
(Array.isArray(question.answerList) && question.answerList.length)
|
) {
|
progress += 1;
|
}
|
});
|
});
|
examStore.setProgress(progress);
|
});
|
const answerTime = ref();
|
watch(
|
() => message.value,
|
(msg) => {
|
if (msg.commend == "delayed") {
|
answerTime.value.addTime(msg.data.addTimeM);
|
} else if (msg.commend == "forceSubmit") {
|
confirmSubmit();
|
}
|
}
|
);
|
// document.addEventListener('blur', function() {
|
// console.log('页面失去焦点');
|
// });
|
const warningNum = ref(0)
|
const handleBlur = () => {
|
if (document.visibilityState === "visible") {
|
} else if (document.visibilityState === "hidden") {
|
ElMessageBox.alert("考试过程中请勿离开考试页面!警告三次将强制收卷", "警告", {
|
confirmButtonText: "确定",
|
});
|
warningNum.value++
|
if (warningNum.value==3) {
|
timeOut();
|
}
|
}
|
};
|
|
// -----------------------------------生命周期
|
onMounted(() => {
|
// 连接webscoket
|
connect();
|
document.addEventListener("visibilitychange", handleBlur);
|
});
|
onUnmounted(()=>{
|
document.removeEventListener("visibilitychange", handleBlur);
|
})
|
</script>
|
|
<style lang="scss" scoped>
|
@media (min-width: 1836px) {
|
.container {
|
max-width: 1724px;
|
}
|
}
|
|
.top-bg {
|
width: 130%;
|
height: 200px;
|
position: absolute;
|
border-bottom-left-radius: 50%;
|
border-bottom-right-radius: 50%;
|
left: 50%;
|
transform: translateX(-50%);
|
}
|
|
.exam-content {
|
width: 100%;
|
height: 100%;
|
position: absolute;
|
top: 0;
|
left: 0;
|
z-index: 2;
|
}
|
|
.exam-header {
|
width: 100%;
|
}
|
|
.answer-wrapper {
|
background-color: #fff;
|
border-top-left-radius: 10px;
|
border-top-right-radius: 10px;
|
}
|
|
.answer-left {
|
width: 370px;
|
}
|
|
.submit-button,
|
.tool-button {
|
width: 160px;
|
height: 40px;
|
}
|
|
.tool-button {
|
margin: 0 20px;
|
}
|
|
.icon-container {
|
width: 70px;
|
height: 70px;
|
border-radius: 50%;
|
display: flex;
|
justify-content: center;
|
align-items: center;
|
background-color: rgba($color: #3680fa, $alpha: 0.2);
|
margin-bottom: 20px;
|
}
|
</style>
|