فهرست منبع

选择声调录入

natasha 1 سال پیش
والد
کامیت
4a3b42dd73

+ 48 - 41
package-lock.json

@@ -2406,6 +2406,49 @@
         "webpack-merge": "^5.7.3",
         "webpack-virtual-modules": "^0.4.2",
         "whatwg-fetch": "^3.6.2"
+      },
+      "dependencies": {
+        "@vue/vue-loader-v15": {
+          "version": "npm:vue-loader@15.11.1",
+          "resolved": "https://registry.npmmirror.com/vue-loader/-/vue-loader-15.11.1.tgz",
+          "integrity": "sha512-0iw4VchYLePqJfJu9s62ACWUXeSqM30SQqlIftbYWM3C+jpPcEHKSPUZBLjSF9au4HTHQ/naF6OGnO3Q/qGR3Q==",
+          "dev": true,
+          "requires": {
+            "@vue/component-compiler-utils": "^3.1.0",
+            "hash-sum": "^1.0.2",
+            "loader-utils": "^1.1.0",
+            "vue-hot-reload-api": "^2.3.0",
+            "vue-style-loader": "^4.1.0"
+          },
+          "dependencies": {
+            "hash-sum": {
+              "version": "1.0.2",
+              "resolved": "https://registry.npmmirror.com/hash-sum/-/hash-sum-1.0.2.tgz",
+              "integrity": "sha512-fUs4B4L+mlt8/XAtSOGMUO1TXmAelItBPtJG7CyHJfYTdDjwisntGO2JQz7oUsatOY9o68+57eziUVNw/mRHmA==",
+              "dev": true
+            }
+          }
+        },
+        "json5": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmmirror.com/json5/-/json5-1.0.2.tgz",
+          "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
+          "dev": true,
+          "requires": {
+            "minimist": "^1.2.0"
+          }
+        },
+        "loader-utils": {
+          "version": "1.4.2",
+          "resolved": "https://registry.npmmirror.com/loader-utils/-/loader-utils-1.4.2.tgz",
+          "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==",
+          "dev": true,
+          "requires": {
+            "big.js": "^5.2.2",
+            "emojis-list": "^3.0.0",
+            "json5": "^1.0.1"
+          }
+        }
       }
     },
     "@vue/cli-shared-utils": {
@@ -2593,47 +2636,6 @@
       "integrity": "sha512-RoorRB50WehYbsiWu497q8egZBYlrvOo9KBUG41uth4O023Cbs+7POLm9uw2CAiViBAIhvpw1Y4w4i+MZxOfXw==",
       "dev": true
     },
-    "@vue/vue-loader-v15": {
-      "version": "npm:vue-loader@15.10.2",
-      "resolved": "https://registry.npmmirror.com/vue-loader/-/vue-loader-15.10.2.tgz",
-      "integrity": "sha512-ndeSe/8KQc/nlA7TJ+OBhv2qalmj1s+uBs7yHDRFaAXscFTApBzY9F1jES3bautmgWjDlDct0fw8rPuySDLwxw==",
-      "dev": true,
-      "requires": {
-        "@vue/component-compiler-utils": "^3.1.0",
-        "hash-sum": "^1.0.2",
-        "loader-utils": "^1.1.0",
-        "vue-hot-reload-api": "^2.3.0",
-        "vue-style-loader": "^4.1.0"
-      },
-      "dependencies": {
-        "hash-sum": {
-          "version": "1.0.2",
-          "resolved": "https://registry.npmmirror.com/hash-sum/-/hash-sum-1.0.2.tgz",
-          "integrity": "sha512-fUs4B4L+mlt8/XAtSOGMUO1TXmAelItBPtJG7CyHJfYTdDjwisntGO2JQz7oUsatOY9o68+57eziUVNw/mRHmA==",
-          "dev": true
-        },
-        "json5": {
-          "version": "1.0.2",
-          "resolved": "https://registry.npmmirror.com/json5/-/json5-1.0.2.tgz",
-          "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
-          "dev": true,
-          "requires": {
-            "minimist": "^1.2.0"
-          }
-        },
-        "loader-utils": {
-          "version": "1.4.2",
-          "resolved": "https://registry.npmmirror.com/loader-utils/-/loader-utils-1.4.2.tgz",
-          "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==",
-          "dev": true,
-          "requires": {
-            "big.js": "^5.2.2",
-            "emojis-list": "^3.0.0",
-            "json5": "^1.0.1"
-          }
-        }
-      }
-    },
     "@vue/web-component-wrapper": {
       "version": "1.3.0",
       "resolved": "https://registry.npmmirror.com/@vue/web-component-wrapper/-/web-component-wrapper-1.3.0.tgz",
@@ -6602,6 +6604,11 @@
         "@sideway/pinpoint": "^2.0.0"
       }
     },
+    "js-audio-recorder": {
+      "version": "1.0.7",
+      "resolved": "https://registry.npmmirror.com/js-audio-recorder/-/js-audio-recorder-1.0.7.tgz",
+      "integrity": "sha512-JiDODCElVHGrFyjGYwYyNi7zCbKk9va9C77w+zCPMmi4C6ix7zsX2h3ddHugmo4dOTOTCym9++b/wVW9nC0IaA=="
+    },
     "js-base64": {
       "version": "2.6.4",
       "resolved": "https://registry.npmmirror.com/js-base64/-/js-base64-2.6.4.tgz",

+ 1 - 0
package.json

@@ -14,6 +14,7 @@
     "core-js": "^3.33.2",
     "dompurify": "^3.0.6",
     "element-ui": "^2.15.14",
+    "js-audio-recorder": "^1.0.7",
     "js-cookie": "^3.0.5",
     "md5": "^2.3.0",
     "nprogress": "^0.2.0",

BIN
src/assets/record-ing.png


BIN
src/assets/voice-play-gray.png


BIN
src/assets/voice-play-white.png


+ 2 - 2
src/icons/svg/audio.svg

@@ -1,10 +1,10 @@
 <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
 <g clip-path="url(#clip0_955_16891)">
-<path d="M5.50251 8.33366L8.3335 6.01738V13.9832L5.50251 11.667H2.50016V8.33366H5.50251ZM1.66683 13.3337H4.90757L9.31966 16.9435C9.39408 17.0044 9.48733 17.0377 9.5835 17.0377C9.81358 17.0377 10.0002 16.8512 10.0002 16.621V3.37957C10.0002 3.28339 9.96691 3.19016 9.906 3.11572C9.76025 2.93762 9.49775 2.91137 9.31966 3.05709L4.90757 6.66697H1.66683C1.2066 6.66697 0.833496 7.04006 0.833496 7.5003V12.5003C0.833496 12.9606 1.2066 13.3337 1.66683 13.3337ZM19.1668 10.0002C19.1668 12.7436 17.9617 15.2055 16.052 16.8854L14.8706 15.7039C16.48 14.3283 17.5002 12.2834 17.5002 10.0002C17.5002 7.71704 16.48 5.67215 14.8706 4.29655L16.052 3.11507C17.9617 4.79498 19.1668 7.25687 19.1668 10.0002ZM15.0002 10.0002C15.0002 8.40716 14.2552 6.98814 13.0946 6.07252L11.9037 7.26344C12.7679 7.8657 13.3335 8.86691 13.3335 10.0002C13.3335 11.1336 12.7679 12.1347 11.9037 12.737L13.0946 13.9279C14.2552 13.0123 15.0002 11.5932 15.0002 10.0002Z" fill="white"/>
+<path d="M5.50251 8.33366L8.3335 6.01738V13.9832L5.50251 11.667H2.50016V8.33366H5.50251ZM1.66683 13.3337H4.90757L9.31966 16.9435C9.39408 17.0044 9.48733 17.0377 9.5835 17.0377C9.81358 17.0377 10.0002 16.8512 10.0002 16.621V3.37957C10.0002 3.28339 9.96691 3.19016 9.906 3.11572C9.76025 2.93762 9.49775 2.91137 9.31966 3.05709L4.90757 6.66697H1.66683C1.2066 6.66697 0.833496 7.04006 0.833496 7.5003V12.5003C0.833496 12.9606 1.2066 13.3337 1.66683 13.3337ZM19.1668 10.0002C19.1668 12.7436 17.9617 15.2055 16.052 16.8854L14.8706 15.7039C16.48 14.3283 17.5002 12.2834 17.5002 10.0002C17.5002 7.71704 16.48 5.67215 14.8706 4.29655L16.052 3.11507C17.9617 4.79498 19.1668 7.25687 19.1668 10.0002ZM15.0002 10.0002C15.0002 8.40716 14.2552 6.98814 13.0946 6.07252L11.9037 7.26344C12.7679 7.8657 13.3335 8.86691 13.3335 10.0002C13.3335 11.1336 12.7679 12.1347 11.9037 12.737L13.0946 13.9279C14.2552 13.0123 15.0002 11.5932 15.0002 10.0002Z" fill="currentColor"/>
 </g>
 <defs>
 <clipPath id="clip0_955_16891">
-<rect width="20" height="20" fill="white"/>
+<rect width="20" height="20" fill="currentColor"/>
 </clipPath>
 </defs>
 </svg>

+ 5 - 0
src/icons/svg/delete-back-line.svg

@@ -0,0 +1,5 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g id="delete-back-2-line">
+<path id="Vector" d="M4.3565 2H13.9997C14.3679 2 14.6664 2.29848 14.6664 2.66667V13.3333C14.6664 13.7015 14.3679 14 13.9997 14H4.3565C4.1336 14 3.92544 13.8886 3.8018 13.7031L0.246244 8.3698C0.0969552 8.14587 0.0969552 7.85413 0.246244 7.6302L3.8018 2.29687C3.92544 2.1114 4.1336 2 4.3565 2ZM4.71329 3.33333L1.60218 8L4.71329 12.6667H13.333V3.33333H4.71329ZM8.66636 7.0572L10.552 5.17157L11.4948 6.11438L9.60916 8L11.4948 9.8856L10.552 10.8284L8.66636 8.9428L6.78076 10.8284L5.83795 9.8856L7.72356 8L5.83795 6.11438L6.78076 5.17157L8.66636 7.0572Z" fill="currentColor"/>
+</g>
+</svg>

+ 5 - 0
src/icons/svg/mic-line.svg

@@ -0,0 +1,5 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g id="mic-line">
+<path id="Vector" d="M8.00003 2.00033C6.89543 2.00033 6 2.89576 6 4.00033V6.66699C6 7.77159 6.89543 8.66699 8.00003 8.66699C9.10456 8.66699 10 7.77159 10 6.66699V4.00033C10 2.89576 9.10456 2.00033 8.00003 2.00033ZM8.00003 0.666992C9.84096 0.666992 11.3334 2.15938 11.3334 4.00033V6.66699C11.3334 8.50793 9.84096 10.0003 8.00003 10.0003C6.15905 10.0003 4.66667 8.50793 4.66667 6.66699V4.00033C4.66667 2.15938 6.15905 0.666992 8.00003 0.666992ZM2.03662 7.33366H3.38059C3.70408 9.59519 5.64902 11.3337 8.00003 11.3337C10.351 11.3337 12.2959 9.59519 12.6194 7.33366H13.9634C13.656 10.1147 11.4478 12.3229 8.66669 12.6304V15.3337H7.33336V12.6304C4.55225 12.3229 2.34405 10.1147 2.03662 7.33366Z" fill="currentColor"/>
+</g>
+</svg>

+ 27 - 2
src/views/exercise_questions/create/components/common/AudioPlay.vue

@@ -1,7 +1,16 @@
 <template>
   <div class="audio-wrapper">
-    <span class="audio-play" @click="playAudio">
-      <SvgIcon :size="audio.paused ? 20 : 14" :icon-class="iconClass" />
+    <span :class="[url ? 'audio-play' : 'audio-play not-url']" @click="playAudio">
+      <SvgIcon v-if="audio.paused" :size="audio.paused ? 20 : 14" :icon-class="iconClass" />
+      <img
+        v-else
+        :src="
+          themeColor === 'gray'
+            ? require('../../../../../assets/voice-play-gray.png')
+            : require('../../../../../assets/voice-play-white.png')
+        "
+        class="voice-play"
+      />
     </span>
     <audio ref="audio" :src="url" preload="metadata"></audio>
   </div>
@@ -17,6 +26,10 @@ export default {
       type: String,
       required: true,
     },
+    themeColor: {
+      type: String,
+      default: '',
+    },
   },
   data() {
     return {
@@ -55,6 +68,7 @@ export default {
   },
   methods: {
     playAudio() {
+      if (!this.url) return;
       const audio = this.$refs.audio;
       audio.paused ? audio.play() : audio.pause();
     },
@@ -70,9 +84,20 @@ export default {
     justify-content: center;
     width: 40px;
     height: 40px;
+    color: #fff;
     cursor: pointer;
     background-color: $main-color;
     border-radius: 50%;
+
+    &.not-url {
+      color: #a1a1a1;
+      cursor: not-allowed;
+    }
+
+    .voice-play {
+      width: 20px;
+      height: 20px;
+    }
   }
 }
 </style>

+ 2 - 0
src/views/exercise_questions/create/components/common/QuestionMixin.js

@@ -1,6 +1,7 @@
 // 题目混入
 import QuestionBase from './QuestionBase.vue';
 import RichText from '@/components/common/RichText.vue';
+import AudioPlay from '@/views/exercise_questions/create/components/common/AudioPlay.vue';
 
 import {
   stemTypeList,
@@ -23,6 +24,7 @@ const mixin = {
   components: {
     QuestionBase,
     RichText,
+    AudioPlay,
   },
   methods: {
     upload(file_id) {

+ 214 - 0
src/views/exercise_questions/create/components/common/SoundRecord.vue

@@ -0,0 +1,214 @@
+<template>
+  <div class="sound-record-wrapper">
+    <SvgIcon
+      v-if="audio.paused"
+      icon-class="audio"
+      :class="['audio-play-btn', wavBlob ? '' : 'not-url']"
+      @click="playMicrophone"
+    />
+    <img
+      v-else
+      :src="require('../../../../../assets/voice-play-gray.png')"
+      class="voice-play"
+      @click="playMicrophone"
+    />
+    <span :class="['record-time', microphoneStatus ? 'record-ing' : '']"
+      >{{ audio.paused ? '' : '-' }}{{ handleDateTime(recordTimes) }}</span
+    >
+    <img
+      v-if="microphoneStatus"
+      :src="require('../../../../../assets/record-ing.png')"
+      class="voice-play"
+      @click="microphone"
+    />
+    <SvgIcon v-else icon-class="mic-line" class="record" @click="microphone" />
+    <SvgIcon icon-class="delete-back-line" :class="['delete-btn', wavBlob ? '' : 'not-url']" @click="delectWav" />
+
+    <audio ref="audio" :src="wavBlob" preload="metadata"></audio>
+  </div>
+</template>
+
+<script>
+import Recorder from 'js-audio-recorder'; // 录音插件
+export default {
+  name: 'SoundRecord',
+  props: {
+    wavBlob: {
+      type: String,
+      default: '',
+    },
+    recordTime: {
+      type: Number,
+      default: 0,
+    },
+    itemIndex: {
+      type: Number,
+      default: null,
+    },
+  },
+  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
+      }),
+      timer: null, // 计时器
+      microphoneStatus: false, // 是否录音
+      hasMicro: '', // 录音后的样式class
+      audio: {
+        paused: true,
+      },
+      playtime: 0, // 播放时间
+      recordTimes: JSON.parse(JSON.stringify(this.recordTime)),
+    };
+  },
+  computed: {},
+  watch: {},
+  mounted() {
+    this.$refs.audio.addEventListener('ended', () => {
+      this.audio.paused = true;
+    });
+    this.$refs.audio.addEventListener('pause', () => {
+      this.audio.paused = true;
+    });
+    this.$refs.audio.addEventListener('play', () => {
+      this.audio.paused = false;
+    });
+  },
+  methods: {
+    playMicrophone() {
+      if (this.wavBlob) {
+        let totalTime = JSON.parse(JSON.stringify(this.recordTime));
+        if (this.audio.paused) {
+          this.hasMicro = 'active';
+          // this.$refs.audio.pause();
+          // this.$refs.audio.load();
+          this.$refs.audio.play();
+          if (this.recordTimes === 0) {
+            this.recordTimes = JSON.parse(JSON.stringify(this.recordTime));
+            this.playtime = 0;
+          }
+          clearInterval(this.timer);
+          this.timer = setInterval(() => {
+            if (this.playtime < totalTime) {
+              this.playtime += 1;
+              this.recordTimes = totalTime - this.playtime;
+            } else {
+              this.playtime = 0;
+              this.recordTimes = JSON.parse(JSON.stringify(this.recordTime));
+              clearInterval(this.timer);
+            }
+          }, 1000);
+        } else {
+          this.$refs.audio.pause();
+          this.hasMicro = 'normal';
+          clearInterval(this.timer);
+        }
+      }
+    },
+    // 格式化录音时长
+    handleDateTime(time) {
+      let times = 0;
+      if (parseInt(time / 60) < 10) {
+        times = `${`0${parseInt(time / 60)}`.substring(`0${parseInt(time / 60)}`.length - 2)}:${`0${
+          time % 60
+        }`.substring(`0${time % 60}`.length - 2)}`;
+      } else {
+        times = `${parseInt(time / 60)}:${`0${time % 60}`.substring(`0${time % 60}`.length - 2)}`;
+      }
+      return times;
+    },
+    // 开始录音
+    microphone() {
+      if (this.microphoneStatus) {
+        this.hasMicro = 'normal';
+        this.recorder.stop();
+        clearInterval(this.timer);
+        let tolTime = this.recorder.duration; // 录音总时长
+        // 录音结束,获取取录音数据
+        let wav = this.recorder.getWAVBlob(); // 获取 WAV 数据
+        this.microphoneStatus = false;
+        let reader = new window.FileReader();
+        reader.readAsDataURL(wav);
+        reader.onloadend = () => {
+          this.$emit('updataWav', this.itemIndex, reader.result, Math.floor(tolTime));
+        };
+      } else {
+        this.hasMicro = '';
+        this.$emit('updataWav', this.itemIndex, '', 0);
+        // 开始录音
+        this.recorder.start();
+        this.microphoneStatus = true;
+        this.recordTimes = 0;
+        clearInterval(this.timer);
+        this.timer = setInterval(() => {
+          this.recordTimes += 1;
+        }, 1000);
+      }
+    },
+    // 删除录音
+    delectWav() {
+      this.$refs.audio.pause();
+      this.hasMicro = '';
+      this.microphoneStatus = false;
+      this.playtime = 0;
+      this.recordTimes = 0;
+      clearInterval(this.timer);
+      this.$emit('deleteWav', this.itemIndex);
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.sound-record-wrapper {
+  display: flex;
+  align-items: center;
+  padding: 5px 12px;
+  background: #f2f3f5;
+  border-radius: 2px;
+
+  .audio-play-btn {
+    cursor: pointer;
+
+    &.not-url {
+      color: #a1a1a1;
+      cursor: not-allowed;
+    }
+  }
+
+  .record-time {
+    min-width: 52px;
+    margin: 0 12px;
+    font-size: 14px;
+    font-weight: 400;
+    line-height: 22px;
+    color: #a1a1a1;
+
+    &.record-ing {
+      color: #000;
+    }
+  }
+
+  .record {
+    cursor: pointer;
+  }
+
+  .voice-play {
+    width: 16px;
+    height: 16px;
+  }
+
+  .delete-btn {
+    margin-left: 12px;
+    color: #4e4e4e;
+    cursor: pointer;
+
+    &.not-url {
+      color: #a1a1a1;
+      cursor: not-allowed;
+    }
+  }
+}
+</style>

+ 80 - 10
src/views/exercise_questions/create/components/exercises/ChooseToneQuestion.vue

@@ -33,10 +33,26 @@
             <div class="option-content">
               <RichText v-model="item.content" placeholder="输入内容" :inline="true" />
             </div>
-            <UploadAudio :file-id="item.audio_file_id" />
-            <span v-for="({ img }, j) in toneList" :key="j" class="tone">
-              <SvgIcon :icon-class="img" />
-            </span>
+            <UploadAudio
+              v-if="data.property.audio_generation_method === 'upload'"
+              :key="item.audio_file_id || i"
+              :file-id="item.audio_file_id"
+              :item-index="i"
+              @upload="uploads"
+              @deleteFile="deleteFiles"
+            />
+            <div v-else-if="data.property.audio_generation_method === 'auto'" class="auto-matically">
+              <AudioPlay :file-id="item.audio_file_id" theme-color="gray" />
+              <span class="auto-btn" @click="handleMatically">自动生成</span>
+            </div>
+            <SoundRecord
+              v-else
+              :wav-blob="item.audio_wav"
+              :record-time="item.audio_wav_time"
+              :item-index="i"
+              @deleteWav="deleteWav"
+              @updataWav="updataWav"
+            />
             <SvgIcon icon-class="delete" class="delete pointer" @click="deleteOption(i)" />
           </li>
         </ul>
@@ -125,6 +141,7 @@
 <script>
 import QuestionMixin from '@/views/exercise_questions/create/components/common/QuestionMixin';
 import UploadAudio from '../common/UploadAudio.vue';
+import SoundRecord from '../common/SoundRecord.vue';
 
 import { changeOptionType } from '@/views/exercise_questions/data/common';
 import {
@@ -139,6 +156,7 @@ export default {
   name: 'ChooseToneQuestion',
   components: {
     UploadAudio,
+    SoundRecord,
   },
   mixins: [QuestionMixin],
   data() {
@@ -154,6 +172,24 @@ export default {
     addOption() {
       this.data.option_list.push(getOption());
     },
+    uploads(file_id, index) {
+      this.data.option_list[index].audio_file_id = file_id;
+    },
+    deleteFiles(file_id, itemIndex) {
+      this.data.option_list[itemIndex].audio_file_id = '';
+    },
+    // 自动生成音频
+    handleMatically() {},
+    // 清除录音
+    deleteWav(index) {
+      this.data.option_list[index].audio_wav = '';
+      this.data.option_list[index].audio_wav_time = 0;
+    },
+    // 更新录音内容和时间
+    updataWav(index, wav, time) {
+      this.data.option_list[index].audio_wav = wav;
+      this.data.option_list[index].audio_wav_time = time;
+    },
   },
 };
 </script>
@@ -174,14 +210,48 @@ export default {
       margin-top: 0;
     }
 
-    .tone {
+    :deep .file-name {
+      width: 140px;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+    }
+
+    .auto-matically {
       display: flex;
       align-items: center;
-      justify-content: center;
-      width: 32px;
-      height: 32px;
-      cursor: pointer;
-      background-color: $fill-color;
+      width: 233px;
+      padding: 5px 12px;
+      background: #f2f3f5;
+      border-radius: 2px;
+
+      .audio-wrapper {
+        margin-right: 12px;
+
+        :deep .audio-play {
+          width: 16px;
+          height: 16px;
+          color: #000;
+          background-color: initial;
+        }
+
+        :deep .audio-play.not-url {
+          color: #a1a1a1;
+        }
+
+        :deep .voice-play {
+          width: 16px;
+          height: 16px;
+        }
+      }
+
+      .auto-btn {
+        font-size: 14px;
+        font-weight: 400;
+        line-height: 22px;
+        color: #1d2129;
+        cursor: pointer;
+      }
     }
   }
 }

+ 1 - 1
src/views/exercise_questions/create/components/exercises/RepeatQuestion.vue

@@ -35,7 +35,7 @@
               <RichText v-model="item.content" placeholder="输入内容" :inline="true" />
             </div>
             <UploadAudio
-              :key="i + Math.random().toString(36).slice(-6)"
+              :key="item.audio_file_id || i"
               :file-id="item.file_id_list?.[0]"
               :item-index="i"
               @upload="uploads"

+ 1 - 1
src/views/exercise_questions/data/chooseTone.js

@@ -2,7 +2,7 @@ import { stemTypeList, questionNumberTypeList, scoreTypeList, optionTypeList } f
 import { getRandomNumber } from '@/utils/index';
 
 export function getOption(content = '') {
-  return { content, mark: getRandomNumber(), audio_file_id: '', tone: '' };
+  return { content, mark: getRandomNumber(), audio_file_id: '', tone: '', audio_wav: '', audio_wav_time: 0 };
 }
 
 export const toneList = [