| | |
| | | <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></AnswerTime> |
| | | <AnswerTime @timeOut="timeOut"></AnswerTime> |
| | | |
| | | <div class="return-container grow flex justify-end"> |
| | | <el-button type="danger" size="large" circle @click="closeClick"> |
| | |
| | | </div> |
| | | |
| | | <div class="submit-wrapper"> |
| | | <el-button type="primary" class="submit-button">提交试卷</el-button> |
| | | <el-button type="primary" class="submit-button" @click="submitExamHandle">提交试卷</el-button> |
| | | </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 ">单选题 (3分)</div> |
| | | <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> |
| | | |
| | | |
| | | <!-- 提示弹窗 --> |
| | | <el-dialog v-model="dialogVisible" title="注意" width="500"> |
| | | <!-- 退出考试提示弹窗 --> |
| | | <el-dialog v-model="quitDialog" title="注意" width="500"> |
| | | <div class="dialog-container"> |
| | | <p>请确认是否退出当前考试</p> |
| | | <p>当前考试试卷会自动提交,后续无法继续作答</p> |
| | | <p>当前试卷会自动提交,后续将无法继续作答</p> |
| | | </div> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="dialogVisible = false">取消</el-button> |
| | | <el-button type="primary" @click="confirmCancel"> |
| | | 确定 |
| | | <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 } from 'vue'; |
| | | import { ref, watchEffect, watch, onMounted } from 'vue'; |
| | | import { storeToRefs } from 'pinia'; |
| | | import { Close } from '@element-plus/icons-vue'; |
| | | 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 AnswerTime from './components/answer-time/index.vue'; |
| | | import { useExamStore } from '@/store/index.js'; |
| | | 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'; |
| | | |
| | | |
| | | const router = useRouter(); |
| | | |
| | | const examStore = useExamStore(); |
| | | const { currentType, currentIndex, activeQuestion, examDetail } = storeToRefs(examStore); |
| | | 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, |
| | | }; |
| | | examStore.setActiveQuestion(examDetail.value[0].questionList[0]); |
| | | |
| | | const dialogVisible = ref(false); |
| | | const quitDialog = ref(false); |
| | | const submitDialog = ref(false); |
| | | const timeDialog = ref(false); |
| | | |
| | | const { status, message, error, connect, disconnect, sendMessage } = useWebScoket({ |
| | | url: 'ws://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 (typeQuestion) { |
| | | if (currentIndex.value >= typeQuestion.questionList.length) { |
| | | tempIndex++; |
| | | if(examDetail.value[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]) { |
| | | if (examDetail.value[tempIndex]) { |
| | | currentType.value = examDetail.value[tempIndex].questionType; |
| | | currentIndex.value = examDetail.value[tempIndex].questionList.length - 1; |
| | | } else { |
| | |
| | | currentIndex.value = 0; |
| | | } |
| | | } |
| | | findQuestion(currentType.value, currentIndex.value); |
| | | } |
| | | |
| | | }; |
| | | |
| | | const findQuestion = (type, index) => { |
| | | const typeQuestion = examDetail.value.find(typeItem => typeItem.questionType === type); |
| | | if (typeQuestion) { |
| | | const question = typeQuestion.questionList[index]; |
| | | if (question) { |
| | | examStore.setActiveQuestion(question); |
| | | } |
| | | } |
| | | }; |
| | | |
| | | // 退出考试 |
| | | const closeClick = () => { |
| | | dialogVisible.value = true; |
| | | quitDialog.value = true; |
| | | }; |
| | | |
| | | const confirmCancel = () => { |
| | | dialogVisible.value = false; |
| | | router.back(); |
| | | 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); |
| | | }); |
| | | |
| | | watch(() => message.value, (msg) => { |
| | | console.log(msg); |
| | | }); |
| | | |
| | | |
| | | // -----------------------------------生命周期 |
| | | onMounted(() => { |
| | | // 连接webscoket |
| | | connect(); |
| | | }); |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | |
| | | } |
| | | |
| | | .answer-left { |
| | | width: 340px; |
| | | width: 370px; |
| | | } |
| | | |
| | | .submit-button, |
| | |
| | | .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> |