Sfoglia il codice sorgente

录音组件增加多次录音选项

natasha 1 mese fa
parent
commit
5bbbe0ee9b

+ 8 - 0
src/views/book/courseware/create/components/question/record_input/RecordInput.vue

@@ -9,6 +9,14 @@
             </el-radio>
           </el-radio-group>
         </el-form-item>
+
+        <el-form-item label="多次录音">
+          <el-radio-group v-model="data.is_enable_manyTimes">
+            <el-radio v-for="{ value, label } in switchOption" :key="value" :label="value">
+              {{ label }}
+            </el-radio>
+          </el-radio-group>
+        </el-form-item>
         <el-form-item label="录音评分">
           <el-radio-group v-model="data.is_enable_score">
             <el-radio v-for="{ value, label } in switchOption" :key="value" :label="value">

+ 1 - 0
src/views/book/courseware/data/recordInput.js

@@ -32,6 +32,7 @@ export function getRecordInputData() {
     size: 'promax',
     is_enable_score: switchOption[0].value,
     is_enable_input: switchOption[0].value,
+    is_enable_manyTimes:switchOption[0].value,
     answer: {
       answer_list: {
         answer_record_list: [],

+ 5 - 1
src/views/book/courseware/preview/components/record_input/RecordInputPreview.vue

@@ -34,7 +34,7 @@
 import { getRecordInputData } from '@/views/book/courseware/data/recordInput';
 
 import PreviewMixin from '../common/PreviewMixin';
-import SoundRecord from '../../common/SoundRecord.vue';
+import SoundRecord from './SoundRecord.vue';
 
 export default {
   name: 'RecordInputPreview',
@@ -89,5 +89,9 @@ export default {
     padding: 6px 12px;
     background-color: #fff;
   }
+
+  :deep .el-textarea .el-input__count {
+    background: transparent;
+  }
 }
 </style>

+ 463 - 0
src/views/book/courseware/preview/components/record_input/SoundRecord.vue

@@ -0,0 +1,463 @@
+<template>
+  <div class="record">
+    <template v-if="type === 'normal' || type === 'mini'">
+      <div :class="['record', microphoneStatus ? 'active' : '']" @click="microphone"></div>
+      <span
+        v-if="type && type == 'normal'"
+        :class="[
+          'record-time',
+          microphoneStatus ? 'record-ing' : '',
+          selectIndex || selectIndex == 0 ? 'record-black' : '',
+          type && type == 'normal' ? 'record-time-flex' : '',
+        ]"
+        >{{ isPlaying ? '-' : '' }}{{ handleDateTime(recordtime) }}</span
+      >
+      <div
+        :class="['playBack', hasMicro]"
+        @click="playmicrophone(selectIndex || selectIndex == 0 ? recordList[selectIndex].toltime : '')"
+      ></div>
+    </template>
+    <template v-else-if="type === 'pro'">
+      <div :class="['record', microphoneStatus ? 'active' : '']" @click="microphone"></div>
+      <el-select v-model="selectIndex" placeholder="无录音" class="proSelect" @change="handleChangeRecord">
+        <el-option v-for="(item, i) in recordList" :key="i + item.id" :label="item.name" :value="i" />
+      </el-select>
+      <div
+        :class="['playBack', hasMicro]"
+        @click="playmicrophone(selectIndex || selectIndex == 0 ? recordList[selectIndex].toltime : '')"
+      ></div>
+      <a :class="['record-delete', hasMicro ? 'record-delete-has' : '']" @click="handleDelete"></a>
+    </template>
+    <template v-else>
+      <div :class="['record', microphoneStatus ? 'active' : '']" @click="microphone"></div>
+      <span
+        :class="[
+          'record-time',
+          microphoneStatus ? 'record-ing' : '',
+          selectIndex || selectIndex == 0 ? 'record-black' : '',
+        ]"
+        >{{ isPlaying ? '-' : '' }}{{ handleDateTime(recordtime) }}</span
+      >
+      <el-select v-model="selectIndex" placeholder="无录音" @change="handleChangeRecord">
+        <el-option v-for="(item, i) in recordList" :key="i + item.id" :label="item.name" :value="i" />
+      </el-select>
+      <div
+        :class="['playBack', hasMicro]"
+        @click="playmicrophone(selectIndex || selectIndex == 0 ? recordList[selectIndex].toltime : '')"
+      ></div>
+      <a :class="['record-delete', hasMicro ? 'record-delete-has' : '']" @click="handleDelete"></a>
+    </template>
+  </div>
+</template>
+
+<script>
+import Recorder from 'js-audio-recorder'; // 录音插件
+
+export default {
+  props: {
+    wavData: {
+      type: Object,
+      default: () => {},
+    },
+    // 类型 normal pro mini promax
+    type: {
+      type: String,
+      default: '',
+    },
+    fileName: {
+      type: String,
+      default: '',
+    },
+    selectData: {
+      type: Object,
+      default: () => {},
+    },
+    answerRecordList: {
+      type: Array,
+      default: () => [],
+    },
+    index: {
+      type: Number,
+      default: 0,
+    },
+    taskModel: {
+      type: String,
+      default: '',
+    },
+    tmIndex: {
+      type: Number,
+      default: 0,
+    },
+  },
+  data() {
+    return {
+      recorder: new Recorder({
+        sampleBits: 16, // 采样位数,支持 8 或 16,默认是16
+        sampleRate: 16000, // 采样率,支持 11025、16000、22050、24000、44100、48000,根据浏览器默认值,我的chrome是48000
+        numChannels: 1, // 声道,支持 1 或 2, 默认是1
+      }),
+      microphoneStatus: false,
+      hasMicro: '', // 录音后的样式class
+      wavblob: null,
+      audio: new window.Audio(),
+      recordList: [], // 录音文件数组
+      recordtime: 0, // 录音时长
+      timer: null, // 计时器
+      recordFile: 1, // 录音文件名
+      selectIndex: null, // 选中的录音索引
+      oldIndex: null, // 存储播放录音索引
+      playtime: 0, // 播放时间
+      isPlaying: false,
+    };
+  },
+  computed: {
+    modelNotAnswer() {
+      return !this.taskModel || this.taskModel !== 'ANSWER';
+    },
+  },
+  watch: {
+    answerRecordList(newVal) {
+      this.recordList = JSON.parse(JSON.stringify(newVal));
+      this.oldIndex = null;
+      this.selectIndex = this.recordList.length > 0 ? this.recordList.length - 1 : null;
+      if (this.recordList.length > 0 && this.type === 'normal') {
+        this.recordtime = this.recordList[this.selectIndex].toltime;
+        this.wavblob = this.recordList[this.selectIndex].wavData;
+        this.changeStatus('normal');
+      }
+    },
+  },
+  created() {
+    this.handleActive();
+    window.stopAudioSound = () => {
+      if (this.audio) {
+        this.audio.pause();
+      }
+    };
+  },
+  mounted() {
+    this.recordList = this.answerRecordList ? JSON.parse(JSON.stringify(this.answerRecordList)) : [];
+    if (this.recordList.length > 0) {
+      this.selectIndex = 0;
+      this.$emit('getSelectData', this.recordList[0].selectData);
+      this.recordFile = this.recordList.length + 1;
+      this.handleChangeRecord(0);
+    }
+    this.audio.addEventListener('play', () => {
+      this.changeStatus('active');
+      this.isPlaying = true;
+    });
+    this.audio.addEventListener('pause', () => {
+      this.changeStatus('normal');
+    });
+    this.audio.addEventListener('ended', () => {
+      this.changeStatus('normal');
+      this.isPlaying = false;
+    });
+  },
+  beforeDestroy() {
+    this.audio.pause();
+  }, // 生命周期 - 销毁之前
+  methods: {
+    // 开始录音
+    microphone() {
+      if (!this.taskModel || this.taskModel !== 'ANSWER') {
+        if (this.microphoneStatus) {
+          this.hasMicro = 'normal';
+          this.recorder.stop();
+          this.$root.isRecording = false;
+          clearInterval(this.timer);
+          let toltime = this.recorder.duration; // 录音总时长
+          let fileSize = this.recorder.fileSize; // 录音总大小
+          // 录音结束,获取取录音数据
+          let wav = this.recorder.getWAVBlob(); // 获取 WAV 数据
+          // this.wavblob = wav;
+          this.microphoneStatus = false;
+          let reader = new window.FileReader();
+          reader.readAsDataURL(wav);
+          reader.onloadend = () => {
+            this.recordList[this.selectIndex].wavData = reader.result;
+            this.recordList[this.selectIndex].toltime = Math.floor(toltime);
+            this.recordList[this.selectIndex].fileSize = fileSize;
+            this.wavblob = this.recordList[this.selectIndex].wavData;
+            this.$emit('getWavblob', this.wavblob);
+            this.$emit('handleWav', JSON.parse(JSON.stringify(this.recordList)), this.tmIndex, this.index, this.indexs);
+            if (this.recordList[this.selectIndex].selectData) {
+              this.$emit('getSelectData', this.recordList[this.selectIndex].selectData);
+            }
+          };
+        } else {
+          if (this.recordList.length === 20) {
+            this.$message.warning('最多保存20条记录');
+            return false;
+          }
+          this.hasMicro = '';
+          this.$root.isRecording = true;
+          this.$emit('getWavblob', null);
+          this.$emit('getSelectData', { type: '' });
+          // 开始录音
+          this.recorder.start();
+          this.microphoneStatus = true;
+          this.recordtime = 0;
+          this.isPlaying = false;
+          clearInterval(this.timer);
+          this.timer = setInterval(() => {
+            if (this.recordtime < 300) {
+              this.recordtime += 1;
+            } else {
+              this.$message.warning('单条记录最多可以录音5分钟');
+              this.microphone();
+            }
+          }, 1000);
+          this.$emit('handleParentPlay');
+          let obj = {
+            name: this.fileName ? this.fileName + this.recordFile : `新录音${this.recordFile}`,
+            id: (this.recordFile + Math.round(Math.random() * 10)).toString(),
+          };
+          if (this.selectData) obj.selectData = this.selectData;
+          this.recordList.push(obj);
+          this.recordFile += 1;
+          this.selectIndex = this.recordList.length - 1;
+        }
+      }
+    },
+    playmicrophone(totalTimes) {
+      if (this.hasMicro) {
+        this.isPlaying = true;
+        if (this.selectIndex || this.selectIndex === 0) {
+          let totalTime = totalTimes;
+          if (!this.audio.paused) {
+            this.audio.pause();
+            clearInterval(this.timer);
+          } else if (this.audio.paused && this.oldIndex === this.selectIndex) {
+            this.audio.play();
+            if (this.recordtime === 0) {
+              this.recordtime = totalTimes;
+              this.playtime = 0;
+            }
+            this.timer = setInterval(() => {
+              if (this.playtime < totalTime) {
+                this.playtime += 1;
+                this.recordtime = totalTime - this.playtime;
+              } else {
+                clearInterval(this.timer);
+              }
+            }, 1000);
+          } else {
+            this.audio.pause();
+            this.audio.load();
+            this.audio.src = this.wavblob;
+            this.oldIndex = this.selectIndex;
+            this.audio.play();
+            this.playtime = 0;
+            this.recordtime = totalTime;
+            clearInterval(this.timer);
+            this.timer = setInterval(() => {
+              if (this.playtime < totalTime) {
+                this.playtime += 1;
+                this.recordtime = totalTime - this.playtime;
+              } else {
+                clearInterval(this.timer);
+              }
+            }, 1000);
+          }
+        }
+      }
+    },
+    // 高亮初始值
+    handleActive() {
+      if (this.wavData) {
+        this.hasMicro = 'normal';
+      } else {
+        this.hasMicro = '';
+      }
+    },
+    changeStatus(status) {
+      this.hasMicro = status;
+    },
+    // 格式化录音时长
+    handleDateTime(time) {
+      let _time = '';
+      if (parseInt(time / 60) < 10) {
+        _time = `${`0${parseInt(time / 60)}`.substring(`0${parseInt(time / 60)}`.length - 2)}:${`0${
+          time % 60
+        }`.substring(`0${time % 60}`.length - 2)}`;
+      } else {
+        _time = `${parseInt(time / 60)}:${`0${time % 60}`.substring(`0${time % 60}`.length - 2)}`;
+      }
+      return _time;
+    },
+    handleChangeRecord(index) {
+      this.recordtime = this.recordList[index].toltime;
+      this.wavblob = this.recordList[index].wavData;
+      this.hasMicro = 'normal';
+      clearInterval(this.timer);
+      this.audio.pause();
+      this.oldIndex = null;
+      this.$emit('getWavblob', this.wavblob);
+      if (this.recordList[index].selectData) this.$emit('getSelectData', this.recordList[index].selectData);
+      this.$emit('sentPause', false);
+    },
+    handleDelete() {
+      if (this.hasMicro && (!this.taskModel || this.taskModel !== 'ANSWER')) {
+        if (this.selectIndex || this.selectIndex === 0) {
+          this.recordList.splice(this.selectIndex, 1);
+          this.$emit('handleWav', JSON.parse(JSON.stringify(this.recordList)), this.tmIndex, this.index, this.indexs);
+          this.selectIndex = this.recordList.length > 0 ? this.recordList.length - 1 : null;
+
+          this.hasMicro = this.recordList.length > 0 ? 'normal' : '';
+          this.recordtime = this.recordList.length > 0 ? this.recordList[this.selectIndex].toltime : 0;
+          this.audio.pause();
+          this.audio = new window.Audio();
+          this.wavblob = this.recordList.length > 0 ? this.recordList[this.selectIndex].wavData : null;
+          this.oldIndex = null;
+          this.isPlaying = false;
+          clearInterval(this.timer);
+          this.audio.addEventListener('ended', () => {
+            this.changeStatus('normal');
+            this.isPlaying = false;
+          });
+        }
+      }
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.record {
+  display: flex;
+  grid-area: record;
+  align-items: center;
+  justify-content: center;
+  max-width: 160px;
+  height: 32px;
+
+  .playBack {
+    width: 16px;
+    height: 16px;
+    margin-left: 8px;
+    cursor: pointer;
+    background: url('@/assets/voice_matrix/luyin-play.png') center no-repeat;
+    background-size: 100%;
+
+    &.normal {
+      background: url('@/assets/voice_matrix/luyin-play-active.png') center no-repeat;
+      background-size: 100%;
+    }
+
+    &.active {
+      background: url('@/assets/voice_matrix/luyin-play-stop.png') center no-repeat;
+      background-size: 100%;
+    }
+  }
+
+  .line {
+    width: 1px;
+    height: 16px;
+    margin-left: 8px;
+    background: rgba(0, 0, 0, 85%);
+    opacity: 0.2;
+  }
+
+  .record {
+    width: 16px;
+    height: 16px;
+    cursor: pointer;
+    background: url('@/assets/voice_matrix/luyin.png') center no-repeat;
+    background-size: 100%;
+
+    &.active {
+      background: url('@/assets/voice_matrix/luyin-active.png') center no-repeat;
+      background-size: 100%;
+    }
+
+    &.active:hover {
+      background: url('@/assets/voice_matrix/luyin-stop.png') center no-repeat;
+      background-size: 100%;
+    }
+  }
+
+  .record-time {
+    margin-left: 8px;
+    font-family: 'robot';
+    font-size: 16px;
+    line-height: 150%;
+    color: rgba(0, 0, 0, 30%);
+
+    &.record-black {
+      color: #000;
+    }
+
+    &.record-ing {
+      color: #de4444;
+    }
+
+    &.record-time-flex {
+      flex: 1;
+    }
+  }
+
+  .el-select {
+    margin-left: 8px;
+  }
+
+  .record-delete {
+    display: block;
+    width: 16px;
+    height: 16px;
+    margin-left: 8px;
+    background: url('@/assets/voice_matrix/luyin-delete.png') center no-repeat;
+    background-size: 100%;
+
+    &.record-delete-has {
+      &:hover {
+        background: url('@/assets/voice_matrix/luyin-delete-active.png') center no-repeat;
+        background-size: 100%;
+      }
+    }
+  }
+}
+</style>
+
+<style lang="scss">
+.record {
+  .el-select {
+    flex: 1;
+    height: 24px;
+
+    &.proSelect {
+      width: 78px;
+    }
+
+    .el-input__inner {
+      box-sizing: border-box;
+      height: 24px;
+      padding: 0 11px;
+      font-family: 'Smartisan';
+      font-size: 14px;
+      line-height: 22px;
+      color: rgba(0, 0, 0, 85%);
+      border: 1px solid rgba(0, 0, 0, 10%);
+      border-radius: 4px;
+    }
+
+    .el-input {
+      .el-select__caret {
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        color: #000;
+      }
+    }
+
+    .el-input__icon {
+      width: 16px;
+    }
+
+    .el-input__suffix {
+      right: 3px;
+    }
+  }
+}
+</style>