| | |
| | | ElTabPane: typeof import('element-plus/es')['ElTabPane'] |
| | | ElTabs: typeof import('element-plus/es')['ElTabs'] |
| | | ElTag: typeof import('element-plus/es')['ElTag'] |
| | | ExamAudio: typeof import('./src/components/ExamAudio/index.vue')['default'] |
| | | ExamInfo: typeof import('./src/components/ExamInfo/index.vue')['default'] |
| | | ExamInfoDialog: typeof import('./src/components/ExamInfoDialog/index.vue')['default'] |
| | | Header: typeof import('./src/components/Header/index.vue')['default'] |
New file |
| | |
| | | <template> |
| | | <div class="audio-container"> |
| | | <div class="audio_wrap_content"> |
| | | <audio ref="audioRef" @play="playFunc" @pause="pauseFunc" @timeupdate="timeupdateFunc" |
| | | @loadedmetadata="onLoadedmetadata" @ended="handleEnd"> |
| | | <source :src="audioSrc" /> |
| | | </audio> |
| | | <div class="cudio_control_content"> |
| | | <el-icon :size="32" color="#3680fa" @click="startPlayOrPause" class="cursor-pointer"> |
| | | <VideoPlay v-show="!audio.playing" /> |
| | | <VideoPause v-show="audio.playing" /> |
| | | </el-icon> |
| | | <div class="slider"> |
| | | <span>{{ formattedCurrentTime }}</span> |
| | | <div @mousedown="audio.dragState = true" @mouseup="audio.dragState = false" |
| | | @mouseleave="audio.dragState = false"><el-slider v-model="sliderTime" size="small" :max="audio.maxTime" |
| | | :show-tooltip="false" @change="onChange"></el-slider></div> |
| | | <span>{{ formattedMaxTime }}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, computed, onBeforeUnmount } from 'vue'; |
| | | import { VideoPlay, VideoPause } from '@element-plus/icons-vue'; |
| | | |
| | | const props = defineProps({ |
| | | audioSrc: { |
| | | type: String, |
| | | required: true, |
| | | default: '' |
| | | } |
| | | }); |
| | | const sliderTime = ref(0); |
| | | const audioRef = ref(null); |
| | | |
| | | const audio = ref({ |
| | | maxTime: 0, |
| | | currentTime: 0, |
| | | playing: false, |
| | | dragState: false |
| | | }); |
| | | |
| | | |
| | | const formatTime = (second) => { |
| | | let m = parseInt(second / 60); |
| | | let s = parseInt(second % 60); |
| | | let time = ""; |
| | | if (second == 0) { |
| | | return "0'00''"; |
| | | } |
| | | if (m == 0) { |
| | | if (s >= 10) { |
| | | time = "0'" + s + "''"; |
| | | } else { |
| | | time = "0'0" + s + "''"; |
| | | } |
| | | } else { |
| | | if (s >= 10) { |
| | | time = m + "'" + s + "''"; |
| | | } else { |
| | | time = m + "'0" + s + "''"; |
| | | } |
| | | } |
| | | return time; |
| | | }; |
| | | |
| | | const formattedCurrentTime = computed(() => { |
| | | return formatTime(audio.value.currentTime); |
| | | }); |
| | | const formattedMaxTime = computed(() => { |
| | | return formatTime(audio.value.maxTime); |
| | | }); |
| | | |
| | | const play = () => { |
| | | audioRef.value.play(); |
| | | }; |
| | | const pause = () => { |
| | | audioRef.value.pause(); |
| | | }; |
| | | const playFunc = () => { |
| | | audio.value.playing = true; |
| | | }; |
| | | const pauseFunc = () => { |
| | | audio.value.playing = false; |
| | | }; |
| | | const handleEnd = () => { |
| | | sliderTime.value = 0; |
| | | audio.value.playing = false; |
| | | audio.value.currentTime = 0; |
| | | }; |
| | | const timeupdateFunc = (res) => { |
| | | if (!audio.value.dragState) { |
| | | audio.value.currentTime = res.target.currentTime; |
| | | sliderTime.value = res.target.currentTime; |
| | | } |
| | | }; |
| | | const onLoadedmetadata = (res) => { |
| | | audio.value.maxTime = parseInt(res.target.duration); |
| | | }; |
| | | const startPlayOrPause = () => { |
| | | audio.value.playing ? pause() : play(); |
| | | }; |
| | | const onChange = (value) => { |
| | | audioRef.value.currentTime = value; |
| | | }; |
| | | |
| | | onBeforeUnmount(() => { |
| | | pause(); |
| | | }); |
| | | |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .audio-container { |
| | | width: 400px; |
| | | border: 1px solid #3680fa; |
| | | height: 50px; |
| | | border-radius: 50px; |
| | | } |
| | | |
| | | .audio_wrap_content { |
| | | height: 100%; |
| | | } |
| | | |
| | | .cudio_control_content { |
| | | margin: 0 auto; |
| | | width: 90%; |
| | | height: 100%; |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | |
| | | .slider { |
| | | flex: 1; |
| | | width: 100%; |
| | | display: flex; |
| | | align-items: center; |
| | | |
| | | } |
| | | |
| | | .slider div { |
| | | flex: 1; |
| | | } |
| | | |
| | | .slider span { |
| | | margin: 0 15px; |
| | | font-size: 12px; |
| | | color: rgba(34, 34, 34, 0.5); |
| | | } |
| | | |
| | | |
| | | } |
| | | </style> |
| | |
| | | <template> |
| | | <div class="info-container w-full"> |
| | | <div class="exam-title break-all mb-4 text-base text-gray-700"> |
| | | {{ title }} |
| | | 第{{questionIndex + 1}}题: {{ activeQuestion.title }} |
| | | </div> |
| | | |
| | | <div class="audio-container" v-if="activeQuestion.audioFile"> |
| | | <ExamAudio :audioSrc="activeQuestion.audioFile"></ExamAudio> |
| | | </div> |
| | | |
| | | <div class="img-container flex"> |
| | | <div class="img-item"> |
| | | <img src="@/assets/test.png" class="info-img" alt=""> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import ExamAudio from '@/components/ExamAudio/index.vue'; |
| | | |
| | | const props = defineProps({ |
| | | questionIndex: { |
| | | type: Number, |
| | | required: true |
| | | }, |
| | | title: { |
| | | type: String, |
| | | activeQuestion: { |
| | | type: Object, |
| | | required: true |
| | | } |
| | | }) |
| | |
| | | <style lang="scss" scoped> |
| | | .info-img { |
| | | max-height: 250px; |
| | | margin: 20px 0; |
| | | margin: 20px 10px; |
| | | } |
| | | </style> |
| | |
| | | const examType = ref({ |
| | | 1: '单选题', |
| | | 2: '多选题', |
| | | 3: '音频题' |
| | | }); |
| | | |
| | | const currentType = ref(1); |
| | |
| | | "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": "", |
| | | "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": "", |
| | | "score": "3", |
| | | "difficult": 5 |
| | | } |
| | | ] |
| | | }, |
| | | ]); |
| | | |
| | | const getActiveQuestion = computed(() => { |
| | |
| | | |
| | | const setProgress = (progress) => { |
| | | answerProgress.value = progress; |
| | | } |
| | | }; |
| | | |
| | | |
| | | return { |
| | |
| | | <template> |
| | | <div class="answer-container w-full h-full"> |
| | | <el-scrollbar> |
| | | <ExamInfo class="mb-5" :questionIndex="currentIndex" :title="activeQuestion.title"></ExamInfo> |
| | | <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)" |
| | |
| | | <template> |
| | | <div class="answer-container w-full h-full"> |
| | | <el-scrollbar> |
| | | <ExamInfo class="mb-5" :questionIndex="currentIndex" :title="activeQuestion.title"></ExamInfo> |
| | | <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)" |
| | |
| | | <template> |
| | | <div class="answer-container w-full h-full"> |
| | | <el-scrollbar> |
| | | <ExamInfo class="mb-5" :questionIndex="currentIndex" :title="activeQuestion.title"></ExamInfo> |
| | | <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)" |
| | |
| | | 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 AnswerAudio from './components/answer-main/answer-audio/index.vue'; |
| | | |
| | | import { useExamStore } from '@/store/index.js'; |
| | | import { useRouter } from 'vue-router'; |
| | | |
| | |
| | | const typeComponent = { |
| | | 1: AnswerSingle, |
| | | 2: AnswerMultiple, |
| | | 3: AnswerAudio, |
| | | }; |
| | | |
| | | const dialogVisible = ref(false); |