From a8c4d1497f36a343f52dab3d2d1ef6dea6f622dc Mon Sep 17 00:00:00 2001 From: ZhangXianQiang <1135831638@qq.com> Date: 星期三, 19 六月 2024 16:54:49 +0800 Subject: [PATCH] feat(成绩):查看考后试卷 --- src/views/grade/components/answer-main/answer-single/index.vue | 104 ++++++ src/store/index.js | 1 src/views/grade/components/answer-tag/index.vue | 46 +++ src/store/modules/grade.js | 252 ++++++++++++++++ src/views/grade/components/answer-main/answer-multiple/index.vue | 98 ++++++ src/views/grade/components/answer-main/answer-audio/index.vue | 104 ++++++ src/views/grade-list/data-list/index.vue | 7 src/router/index.js | 7 src/views/grade/components/answer-sheet/index.vue | 63 ++++ src/views/grade/index.vue | 210 ++++++++++++++ 10 files changed, 890 insertions(+), 2 deletions(-) diff --git a/src/router/index.js b/src/router/index.js index 5964487..7b03654 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -5,7 +5,7 @@ const routes = [ { path: '/', - redirect: '/login' + redirect: '/grade-list' }, { @@ -55,6 +55,11 @@ path: '/grade-list', component: () => import('@/views/grade-list/index.vue'), }, + // 璇曞嵎 + { + path: '/grade', + component: () => import('@/views/grade/index.vue'), + }, ]; const router = createRouter({ diff --git a/src/store/index.js b/src/store/index.js index c407325..aba1b6d 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -4,3 +4,4 @@ export * from './modules/user.js'; export * from './modules/exam.js'; +export * from './modules/grade.js'; diff --git a/src/store/modules/grade.js b/src/store/modules/grade.js new file mode 100644 index 0000000..1c21ed3 --- /dev/null +++ b/src/store/modules/grade.js @@ -0,0 +1,252 @@ +import { ref, computed } from 'vue'; +import { defineStore } from 'pinia'; +export const useGradeStore = defineStore('exam', () => { + const examInfo = ref({ + examId: 1, + examName: '鑰冭瘯鍚嶇О', + examType: '鑰冭瘯绫诲瀷', + examStatus: '鑰冭瘯鐘舵��', + examStartTime: '2021-01-01', + examEndTime: '2021-01-01', + examTime: 5, + }); + + const examType = ref({ + 1: '鍗曢�夐', + 2: '澶氶�夐', + 3: '闊抽棰�' + }); + + const currentType = ref(1); + const currentIndex = ref(0); + + + const examDetail = ref([ + { + questionType: 1, + questionList: [ + { + "id": null, + "questionType": 1, + "gradeLevel": null, + "subjectId": 2, + "title": "1+1=锛�", + "items": [ + { + "prefix": "A", + "content": "1" + }, + { + "prefix": "B", + "content": "2" + }, + { + "prefix": "C", + "content": "3" + }, + { + "prefix": "D", + "content": "4" + } + ], + "analyze": "闂皬鏈嬪弸", + "correct": "A", + "right": "B", + "score": "3", + "difficult": 5 + }, + { + "id": null, + "questionType": 1, + "gradeLevel": null, + "subjectId": 2, + "title": "1+1=锛�", + "items": [ + { + "prefix": "A", + "content": "1" + }, + { + "prefix": "B", + "content": "2" + }, + { + "prefix": "C", + "content": "3" + }, + { + "prefix": "D", + "content": "4" + } + ], + "analyze": "闂皬鏈嬪弸", + "correct": "A", + "right": "B", + "score": "3", + "difficult": 5 + } + ] + }, + { + questionType: 2, + questionList: [ + { + "id": null, + "questionType": 1, + "gradeLevel": null, + "subjectId": 2, + "title": "1+1=锛�", + "items": [ + { + "prefix": "A", + "content": "1" + }, + { + "prefix": "B", + "content": "2" + }, + { + "prefix": "C", + "content": "3" + }, + { + "prefix": "D", + "content": "4" + } + ], + "analyze": "闂皬鏈嬪弸", + "correct": "A,B", + "right": "A,B,C", + "score": "5", + "difficult": 5 + }, + { + "id": null, + "questionType": 1, + "gradeLevel": null, + "subjectId": 2, + "title": "1+1=锛�", + "items": [ + { + "prefix": "A", + "content": "1" + }, + { + "prefix": "B", + "content": "2" + }, + { + "prefix": "C", + "content": "3" + }, + { + "prefix": "D", + "content": "4" + } + ], + "analyze": "闂皬鏈嬪弸", + "correct": "A,D", + "right": "B,C", + "score": "5", + "difficult": 5 + } + ] + }, + { + questionType: 3, + questionList: [ + { + "id": null, + "questionType": 1, + "gradeLevel": null, + "subjectId": 2, + "title": "娴嬭瘯闊抽1", + "audioFile": '/test.mp3', + "items": [ + { + "prefix": "A", + "content": "1" + }, + { + "prefix": "B", + "content": "2" + }, + { + "prefix": "C", + "content": "3" + }, + { + "prefix": "D", + "content": "4" + } + ], + "analyze": "闂皬鏈嬪弸", + "correct": "", + "right": "B", + "score": "3", + "difficult": 5 + }, + { + "id": null, + "questionType": 1, + "gradeLevel": null, + "subjectId": 2, + "title": "娴嬭瘯闊抽2", + "audioFile": '/test.mp3', + "items": [ + { + "prefix": "A", + "content": "1" + }, + { + "prefix": "B", + "content": "2" + }, + { + "prefix": "C", + "content": "3" + }, + { + "prefix": "D", + "content": "4" + } + ], + "analyze": "闂皬鏈嬪弸", + "correct": "D", + "right": "A", + "score": "3", + "difficult": 5 + } + ] + }, + ]); + + const getActiveQuestion = computed(() => { + const temp = examDetail.value.find(item => item.questionType === currentType.value); + if (temp) { + return temp.questionList[currentIndex.value]; + } + }); + + const setExamInfo = (info) => { + examInfo.value = info; + }; + + const setExamDetail = (detail) => { + examDetail.value = detail; + }; + + return { + examInfo, + examDetail, + examType, + + currentType, + currentIndex, + + getActiveQuestion, + + setExamInfo, + setExamDetail, + }; +}); diff --git a/src/views/grade-list/data-list/index.vue b/src/views/grade-list/data-list/index.vue index 08620b2..661265f 100644 --- a/src/views/grade-list/data-list/index.vue +++ b/src/views/grade-list/data-list/index.vue @@ -37,7 +37,7 @@ </div> <div class="right-container"> <div class="button-container"> - <el-button type="primary" size="large">鏌ョ湅璇曞嵎</el-button> + <el-button type="primary" size="large" @click="checkExam">鏌ョ湅璇曞嵎</el-button> </div> </div> </div> @@ -50,7 +50,12 @@ <script setup> import {ref} from 'vue'; import { Timer } from '@element-plus/icons-vue'; +import {useRouter} from 'vue-router'; +const router = useRouter(); +const checkExam = () => { + router.push('/grade'); +} </script> diff --git a/src/views/grade/components/answer-main/answer-audio/index.vue b/src/views/grade/components/answer-main/answer-audio/index.vue new file mode 100644 index 0000000..705d1d0 --- /dev/null +++ b/src/views/grade/components/answer-main/answer-audio/index.vue @@ -0,0 +1,104 @@ +<template> + <div class="answer-container w-full h-full"> + <el-scrollbar> + <ExamInfo class="mb-5" :questionIndex="currentIndex" :activeQuestion="activeQuestion"></ExamInfo> + + <div class="answer-content"> + <div class="answer-item flex" v-for="item, index in activeQuestion.items" @click="answerClick(item)" + :class="answerState(item)"> + <div class="answer-icon flex flex-col justify-center items-center flex-shrink-0">{{ item.prefix }}</div> + <div class="answer-text text-gray-700">{{ item.content }}</div> + </div> + </div> + + </el-scrollbar> + </div> +</template> + +<script setup> +import { ref } from 'vue'; +import { storeToRefs } from 'pinia'; +import ExamInfo from '@/components/ExamInfo/index.vue'; +import { useExamStore } from '@/store/index.js'; +const examStore = useExamStore(); + +const { currentType, currentIndex } = storeToRefs(examStore); + +const activeQuestion = ref(examStore.getActiveQuestion); + +const answerClick = (item) => { + if (item) { + if (item.isActive) { + item.isActive = !item.isActive; + } else { + resetAnswer(); + item.isActive = true; + } + const answerList = filterAnswer(); + examStore.setQuestionAnswer(currentType.value, currentIndex.value, answerList.join(',')); + } +}; + +const answerState = (item) => { + return { + active: item.isActive + }; +}; + +const resetAnswer = () => { + activeQuestion.value.items.forEach(item => item.isActive = false); +}; + +const filterAnswer = () => { + return activeQuestion.value.items.filter(item => item.isActive); +} + + +</script> + +<style lang="scss" scoped> +.answer-item { + max-width: 500px; + border-radius: 10px; + border: 1px solid #DCDFE6; + overflow: hidden; + margin-bottom: 20px; + cursor: pointer; + + &:last-of-type { + margin-bottom: 0; + } + + &:hover { + border-color: #3680fa; + + .answer-icon { + color: #3680fa; + border-color: #3680fa; + } + } + + + .answer-icon { + width: 50px; + background-color: #F0F2F5; + border-right: 1px solid #ffffff; + } + + .answer-text { + min-height: 50px; + padding: 15px 0; + margin: 0 10px; + } +} + +.active { + border-color: #409EFF !important; + + .answer-icon { + color: #ffffff !important; + border-color: #409EFF !important; + background-color: #409EFF !important; + } +} +</style> \ No newline at end of file diff --git a/src/views/grade/components/answer-main/answer-multiple/index.vue b/src/views/grade/components/answer-main/answer-multiple/index.vue new file mode 100644 index 0000000..b127aff --- /dev/null +++ b/src/views/grade/components/answer-main/answer-multiple/index.vue @@ -0,0 +1,98 @@ +<template> + <div class="answer-container w-full h-full"> + <el-scrollbar> + <ExamInfo class="mb-5" :questionIndex="currentIndex" :activeQuestion="activeQuestion"></ExamInfo> + + <div class="answer-content"> + <div class="answer-item flex" v-for="item, index in activeQuestion.items" @click="answerClick(item)" + :class="answerState(item)"> + <div class="answer-icon flex flex-col justify-center items-center flex-shrink-0">{{ item.prefix }}</div> + <div class="answer-text text-gray-700">{{ item.content }}</div> + </div> + </div> + + </el-scrollbar> + </div> +</template> + +<script setup> +import { ref } from 'vue'; +import { storeToRefs } from 'pinia'; +import ExamInfo from '@/components/ExamInfo/index.vue'; +import { useExamStore } from '@/store/index.js'; + +const examStore = useExamStore(); + +const { currentType, currentIndex } = storeToRefs(examStore); + +const activeQuestion = ref(examStore.getActiveQuestion); + +const answerClick = (item) => { + if (item) { + // resetAnswer(); + item.isActive = !item.isActive; + const answerList = filterAnswer(); + examStore.setQuestionAnswer(currentType.value, currentIndex.value, answerList.join(',')); + + } +}; + +const answerState = (item) => { + return { + active: item.isActive + }; +}; + +const filterAnswer = () => { + return activeQuestion.value.items.filter(item => item.isActive); +} + + +</script> + +<style lang="scss" scoped> +.answer-item { + max-width: 500px; + border-radius: 10px; + border: 1px solid #DCDFE6; + overflow: hidden; + margin-bottom: 20px; + cursor: pointer; + + &:last-of-type { + margin-bottom: 0; + } + + &:hover { + border-color: #3680fa; + + .answer-icon { + color: #3680fa; + border-color: #3680fa; + } + } + + + .answer-icon { + width: 50px; + background-color: #F0F2F5; + border-right: 1px solid #ffffff; + } + + .answer-text { + min-height: 50px; + padding: 15px 0; + margin: 0 10px; + } +} + +.active { + border-color: #409EFF !important; + + .answer-icon { + color: #ffffff !important; + border-color: #409EFF !important; + background-color: #409EFF !important; + } +} +</style> \ No newline at end of file diff --git a/src/views/grade/components/answer-main/answer-single/index.vue b/src/views/grade/components/answer-main/answer-single/index.vue new file mode 100644 index 0000000..705d1d0 --- /dev/null +++ b/src/views/grade/components/answer-main/answer-single/index.vue @@ -0,0 +1,104 @@ +<template> + <div class="answer-container w-full h-full"> + <el-scrollbar> + <ExamInfo class="mb-5" :questionIndex="currentIndex" :activeQuestion="activeQuestion"></ExamInfo> + + <div class="answer-content"> + <div class="answer-item flex" v-for="item, index in activeQuestion.items" @click="answerClick(item)" + :class="answerState(item)"> + <div class="answer-icon flex flex-col justify-center items-center flex-shrink-0">{{ item.prefix }}</div> + <div class="answer-text text-gray-700">{{ item.content }}</div> + </div> + </div> + + </el-scrollbar> + </div> +</template> + +<script setup> +import { ref } from 'vue'; +import { storeToRefs } from 'pinia'; +import ExamInfo from '@/components/ExamInfo/index.vue'; +import { useExamStore } from '@/store/index.js'; +const examStore = useExamStore(); + +const { currentType, currentIndex } = storeToRefs(examStore); + +const activeQuestion = ref(examStore.getActiveQuestion); + +const answerClick = (item) => { + if (item) { + if (item.isActive) { + item.isActive = !item.isActive; + } else { + resetAnswer(); + item.isActive = true; + } + const answerList = filterAnswer(); + examStore.setQuestionAnswer(currentType.value, currentIndex.value, answerList.join(',')); + } +}; + +const answerState = (item) => { + return { + active: item.isActive + }; +}; + +const resetAnswer = () => { + activeQuestion.value.items.forEach(item => item.isActive = false); +}; + +const filterAnswer = () => { + return activeQuestion.value.items.filter(item => item.isActive); +} + + +</script> + +<style lang="scss" scoped> +.answer-item { + max-width: 500px; + border-radius: 10px; + border: 1px solid #DCDFE6; + overflow: hidden; + margin-bottom: 20px; + cursor: pointer; + + &:last-of-type { + margin-bottom: 0; + } + + &:hover { + border-color: #3680fa; + + .answer-icon { + color: #3680fa; + border-color: #3680fa; + } + } + + + .answer-icon { + width: 50px; + background-color: #F0F2F5; + border-right: 1px solid #ffffff; + } + + .answer-text { + min-height: 50px; + padding: 15px 0; + margin: 0 10px; + } +} + +.active { + border-color: #409EFF !important; + + .answer-icon { + color: #ffffff !important; + border-color: #409EFF !important; + background-color: #409EFF !important; + } +} +</style> \ No newline at end of file diff --git a/src/views/grade/components/answer-sheet/index.vue b/src/views/grade/components/answer-sheet/index.vue new file mode 100644 index 0000000..fef8708 --- /dev/null +++ b/src/views/grade/components/answer-sheet/index.vue @@ -0,0 +1,63 @@ +<template> + <div class="sheet-container w-full h-full"> + <el-scrollbar> + <el-collapse v-model="activeNames"> + <template v-for="item in examDetail"> + <el-collapse-item :title="examType[item.questionType]" :name="item.questionType"> + <div class="sheet-list grid grid-cols-5 gap-4 justify-items-center"> + <div class="sheet-item cursor-pointer flex justify-center items-center" v-for="question,index in item.questionList" @click="sheetClick(item.questionType,index)" :class="itemClass(question,item.questionType, index)"> + {{ index + 1 }} + </div> + </div> + </el-collapse-item> + </template> + </el-collapse> + </el-scrollbar> + </div> +</template> + +<script setup> +import { ref } from 'vue'; +import {storeToRefs} from 'pinia'; +import {useExamStore} from '@/store/index.js'; +const examStore = useExamStore(); + +const {examType, examDetail,currentType,currentIndex} = storeToRefs(examStore); + +const activeNames = ref(examDetail.value.map(item => item.questionType)); + +const itemClass = (question,type,index) => { + return { + answer: question.correct, + active: currentType.value === type && currentIndex.value === index + } +} + +const sheetClick = (type,index) => { + currentType.value = type; + currentIndex.value = index; +} + +</script> + +<style lang="scss" scoped> +.sheet-list { + width: 97%; +} +.sheet-item { + width: 35px; + height: 35px; + border: 1px solid #DCDFE6; + background-color: #fff; + color: #374151; +} + +.active { + border-color: #3680fa; +} +.answer { + border-color: #3680fa !important; + background-color: #3680fa !important; + color: #fff !important; +} +</style> \ No newline at end of file diff --git a/src/views/grade/components/answer-tag/index.vue b/src/views/grade/components/answer-tag/index.vue new file mode 100644 index 0000000..430559b --- /dev/null +++ b/src/views/grade/components/answer-tag/index.vue @@ -0,0 +1,46 @@ +<template> + <div class="tag-container flex flex-wrap"> + <div class="tag-item flex items-center" v-for="item in tagList"> + <div class="label text-xs text-gray-500 mr-2">{{ item.name }}</div> + <div class="tag flex-shrink-0" :style="{backgroundColor: item.bgColor, borderColor: item.borderColor}"></div> + </div> + </div> +</template> + +<script setup> +import {ref} from 'vue'; + +const tagList = ref([ + { + name: '宸茬瓟', + bgColor: '#3680fa', + borderColor: '#3680fa', + }, + { + name: '褰撳墠', + bgColor: '#ffffff', + borderColor: '#3680fa', + }, + { + name: '鏈瓟', + bgColor: '#ffffff', + borderColor: '#DCDFE6', + }, +]) + +</script> + +<style lang="scss" scoped> +.tag-item { + margin-right: 14px; + &:last-of-type { + margin: 0; + } +} +.tag { + width: 12px; + height: 12px; + border: 1px solid #fff; + background-color: #fff; +} +</style> \ No newline at end of file diff --git a/src/views/grade/index.vue b/src/views/grade/index.vue new file mode 100644 index 0000000..aa2f2dc --- /dev/null +++ b/src/views/grade/index.vue @@ -0,0 +1,210 @@ +<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> + + <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="sheet-wrapper w-full grow relative my-5"> + <div class="sheet-content absolute top-0 bottom-0 w-full"> + <!-- <AnswerSheet></AnswerSheet> --> + </div> + </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] }} ({{ + gradeStore.getActiveQuestion.score }}鍒�) + </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"> + </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> + + </div> +</template> + +<script setup> +import { ref, watchEffect } from 'vue'; +import { storeToRefs } from 'pinia'; +import { Close,Timer } from '@element-plus/icons-vue'; + +import AnswerTag from './components/answer-tag/index.vue'; +// import AnswerSheet from './components/answer-sheet/index.vue'; + +import { useGradeStore } from '@/store/index.js'; +import { useRouter } from 'vue-router'; + +const router = useRouter(); + +const gradeStore = useGradeStore(); +const { currentType, currentIndex, examDetail, examType, examInfo } = storeToRefs(gradeStore); + +// const typeComponent = { +// 1: AnswerSingle, +// 2: AnswerMultiple, +// 3: AnswerAudio, +// }; + + +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 = () => { + router.back(); +}; + +</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: 340px; +} + +.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> \ No newline at end of file -- Gitblit v1.8.0