| | |
| | | <template> |
| | | <div class="exam-container w-screen h-screen bg-slate-50 relative overflow-hidden"> |
| | | <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="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> |
| | | <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> |
| | | |
| | | <div class="submit-wrapper"> |
| | | <el-button type="primary" class="submit-button" @click="submitExamHandle">提交试卷</el-button> |
| | | <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 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> |
| | | <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> |
| | | <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> |
| | | <el-button |
| | | class="tool-button" |
| | | type="primary" |
| | | @click="nextQuestion" |
| | | >下一题</el-button |
| | | > |
| | | </div> |
| | | </div> |
| | | </div> |
| | |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | |
| | | <!-- 退出考试提示弹窗 --> |
| | | <el-dialog v-model="quitDialog" title="注意" width="500"> |
| | |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="quitDialog = false">继续作答</el-button> |
| | | <el-button type="danger" @click="confirmQuit"> |
| | | 确定退出 |
| | | </el-button> |
| | | <el-button type="danger" @click="confirmQuit"> 确定退出 </el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | |
| | | </el-dialog> |
| | | |
| | | <!-- 考试时间弹窗 --> |
| | | <el-dialog v-model="timeDialog" align-center width="500" :close-on-click-modal="false" |
| | | :close-on-press-escape="false" :show-close="false"> |
| | | <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 class="dialog-info">考试结束,系统自动收卷中.......</div> |
| | | </div> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, watchEffect, watch, onMounted } 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 { 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 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 { 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 { submitExam } from "@/api/modules/exam.js"; |
| | | import useWebScoket from "@/hooks/useWebScoket.js"; |
| | | |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | |
| | | const router = useRouter(); |
| | | |
| | |
| | | const userStore = useUserStore(); |
| | | |
| | | const { userInfo } = storeToRefs(userStore); |
| | | const { currentType, currentIndex, examDetail, examType, examInfo } = storeToRefs(examStore); |
| | | const { currentType, currentIndex, examDetail, examType, examInfo } = |
| | | storeToRefs(examStore); |
| | | |
| | | const typeComponent = { |
| | | 1: AnswerSingle, |
| | |
| | | // heartBeatData: 'ping' |
| | | // }); |
| | | |
| | | const { status, message, error, connect, disconnect, sendMessage } = useWebScoket({ |
| | | url: '//192.168.3.64:8000/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 = () => { |
| | |
| | | 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; |
| | | currentIndex.value = |
| | | examDetail.value[tempIndex].questionList.length - 1; |
| | | } else { |
| | | currentType.value = typeQuestion.questionType; |
| | | currentIndex.value = 0; |
| | | } |
| | | } |
| | | } |
| | | |
| | | }; |
| | | |
| | | // 退出考试 |
| | |
| | | const timeOut = () => { |
| | | const temp = { |
| | | ...examInfo.value, |
| | | titleList: examDetail.value |
| | | titleList: examDetail.value, |
| | | }; |
| | | timeDialog.value = true; |
| | | resetAllDialog(); |
| | | |
| | | disconnect(); |
| | | |
| | | submitExam(temp).then(res => { |
| | | returnBack(); |
| | | }).catch(() => { |
| | | returnBack(); |
| | | }); |
| | | submitExam(temp) |
| | | .then((res) => { |
| | | returnBack(); |
| | | }) |
| | | .catch(() => { |
| | | returnBack(); |
| | | }); |
| | | }; |
| | | |
| | | const returnBack = () => { |
| | |
| | | |
| | | watchEffect(() => { |
| | | let progress = 0; |
| | | examDetail.value.forEach(item => { |
| | | item.questionList.forEach(question => { |
| | | if (question.answer || (Array.isArray(question.answerList) && question.answerList.length)) { |
| | | 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() |
| | | 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> |