ZhangXianQiang
2024-06-19 f763e0bc88efa373ea0cedfdb1abbdc85046097b
feat:考试音频题
7个文件已修改
2个文件已添加
255 ■■■■■ 已修改文件
components.d.ts 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
public/test.mp3 补丁 | 查看 | 原始文档 | blame | 历史
src/components/ExamAudio/index.vue 157 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/ExamInfo/index.vue 15 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/modules/exam.js 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/exam/components/answer-main/answer-audio/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/exam/components/answer-main/answer-multiple/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/exam/components/answer-main/answer-single/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/exam/index.vue 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
components.d.ts
@@ -29,6 +29,7 @@
    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']
public/test.mp3
Binary files differ
src/components/ExamAudio/index.vue
New file
@@ -0,0 +1,157 @@
<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>
src/components/ExamInfo/index.vue
@@ -1,8 +1,13 @@
<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="">
@@ -12,13 +17,15 @@
</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
  }
})
@@ -27,6 +34,6 @@
<style lang="scss" scoped>
.info-img {
  max-height: 250px;
  margin: 20px 0;
  margin: 20px 10px;
}
</style>
src/store/modules/exam.js
@@ -14,6 +14,7 @@
  const examType = ref({
    1: '单选题',
    2: '多选题',
    3: '音频题'
  });
  const currentType = ref(1);
@@ -205,7 +206,72 @@
          "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(() => {
@@ -248,7 +314,7 @@
  const setProgress = (progress) => {
    answerProgress.value = progress;
  }
  };
  return {
src/views/exam/components/answer-main/answer-audio/index.vue
@@ -1,7 +1,7 @@
<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)"
src/views/exam/components/answer-main/answer-multiple/index.vue
@@ -1,7 +1,7 @@
<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)"
src/views/exam/components/answer-main/answer-single/index.vue
@@ -1,7 +1,7 @@
<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)"
src/views/exam/index.vue
@@ -103,9 +103,12 @@
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';
@@ -117,6 +120,7 @@
const typeComponent = {
  1: AnswerSingle,
  2: AnswerMultiple,
  3: AnswerAudio,
};
const dialogVisible = ref(false);