|
@@ -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>
|