392 lines
8.7 KiB
Vue
392 lines
8.7 KiB
Vue
<template>
|
|
<view class="talking-line">
|
|
<view class="talking-info" v-if="status === '02'">
|
|
<view :class="['talking-gif', isPlaying ? 'is-play' : '']">
|
|
<!-- <CommonLoading color="#fff" /> -->
|
|
</view>
|
|
<view class="right-time">{{ voiceTime }}</view>
|
|
<view class="talking-cont">
|
|
<text :class="['talking-text translate-text loading']" v-show="textShow">
|
|
{{ showContent }}
|
|
</text>
|
|
<view class="talking-text translate-text" v-if="textLoading">
|
|
<CommonLoading color="#fff" />
|
|
</view>
|
|
</view>
|
|
<view class="btns-cont">
|
|
<view class="play-btn">
|
|
<image
|
|
v-show="!isPlaying"
|
|
class="icon"
|
|
@click="playVoice"
|
|
src="@/static/images/course/play-blue.png"
|
|
style="width: 100%; height: 100%"
|
|
></image>
|
|
<image
|
|
v-show="isPlaying"
|
|
class="icon"
|
|
@click.stop="playVoice"
|
|
src="@/static/images/course/stop-blue.png"
|
|
style="width: 100%; height: 100%"
|
|
></image>
|
|
</view>
|
|
<view class="text-btn" v-if="!textShow" @click.stop="translateVoice()">文</view>
|
|
<view class="fl1"></view>
|
|
<view
|
|
v-show="!props.isPreview && props.button_again_show"
|
|
class="btns-cont-button-text"
|
|
@click.stop="buutton_click('again')"
|
|
>
|
|
重答
|
|
</view>
|
|
<view
|
|
v-show="!props.isPreview && props.button_complete_show"
|
|
class="btns-cont-button-text"
|
|
@click.stop="buutton_click('complete')"
|
|
>
|
|
完成
|
|
</view>
|
|
</view>
|
|
</view>
|
|
<!-- 01纯文字 -->
|
|
<view class="talking-info talking-info-text" v-else-if="status === '01'">
|
|
<view class="talking-cont">
|
|
<text :class="['talking-text translate-text loading']">
|
|
{{ showContent }}
|
|
</text>
|
|
</view>
|
|
<view class="btns-cont" v-show="!props.isPreview">
|
|
<view class="fl1"></view>
|
|
<view v-if="props.button_again_show" class="btns-cont-button-text" @click.stop="buutton_click('again')">
|
|
重答
|
|
</view>
|
|
<view
|
|
v-if="props.button_complete_show"
|
|
class="btns-cont-button-text"
|
|
@click.stop="buutton_click('complete')"
|
|
>
|
|
完成
|
|
</view>
|
|
</view>
|
|
</view>
|
|
<view class="talking-info talking-info-loading" v-else-if="status === '00'">
|
|
<view :class="['talking-gif']">
|
|
<CommonLoading color="#fff" />
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { computed, unref, ref, toRefs, watch, onMounted, nextTick } from 'vue';
|
|
import { onUnload } from '@dcloudio/uni-app';
|
|
import { useAudio } from './useAudio.js';
|
|
import { downloadFile } from '@/api/common.js';
|
|
const props = defineProps({
|
|
dataInfo: {
|
|
default: () => ({}),
|
|
type: Object
|
|
},
|
|
text: {
|
|
default: '',
|
|
type: String
|
|
},
|
|
voice: {
|
|
// 当前激活的语音
|
|
default: '',
|
|
type: String
|
|
},
|
|
activeVoiceId: {
|
|
default: '',
|
|
type: String
|
|
},
|
|
defaultDisplayText: {
|
|
default: false,
|
|
type: Boolean
|
|
},
|
|
status: {
|
|
default: '',
|
|
type: String
|
|
},
|
|
isPreview: {
|
|
// 是否是预览语音
|
|
default: false,
|
|
type: Boolean
|
|
},
|
|
button_again_show: {
|
|
// 是否显示重答按钮
|
|
default: true,
|
|
type: Boolean
|
|
},
|
|
button_complete_show: {
|
|
// 是否显示重答按钮
|
|
default: true,
|
|
type: Boolean
|
|
}
|
|
});
|
|
|
|
const innerAudio = useAudio();
|
|
|
|
const status = computed(() => props.status);
|
|
watch(
|
|
() => props.status,
|
|
(val) => {
|
|
if (val === '') {
|
|
textShow.value = false;
|
|
}
|
|
}
|
|
);
|
|
const emit = defineEmits(['changePlay', 'buutton_click']);
|
|
|
|
// 中间显示的文字
|
|
const showContent = computed(() => {
|
|
return props.text || props.dataInfo.anserResult || '';
|
|
});
|
|
|
|
const isPlaying = innerAudio.isPlaying;
|
|
const textLoading = ref(false);
|
|
const textShow = ref(props.defaultDisplayText); // 翻译文字展示状态
|
|
const voiceTime = ref('');
|
|
const itemVoice = ref('');
|
|
|
|
// 按钮点击
|
|
const buutton_click = (type) => {
|
|
if (type === 'again') {
|
|
nextTick(() => {
|
|
textShow.value = true;
|
|
});
|
|
setTimeout(() => {
|
|
voiceTime.value = '';
|
|
}, 0);
|
|
}
|
|
emit('buutton_click', type, props.dataInfo);
|
|
};
|
|
|
|
// 翻译语音
|
|
const translateVoice = (type) => {
|
|
if (!textShow.value) {
|
|
textLoading.value = true;
|
|
setTimeout(() => {
|
|
textShow.value = true;
|
|
textLoading.value = false;
|
|
}, 500);
|
|
} else {
|
|
textShow.value = false;
|
|
}
|
|
};
|
|
|
|
const playVoice = () => {
|
|
console.log('点击播放');
|
|
innerAudio.playAudio();
|
|
};
|
|
let unsubscribe = () => {};
|
|
onMounted(() => {
|
|
console.log('question的onMounted');
|
|
// 2. 监听 Store 中 isPlaying 的变化,实时同步
|
|
// unsubscribe = innerAudio.$subscribe((mutation, state) => {
|
|
// isPlaying.value = state.isPlaying;
|
|
// });
|
|
// 注册播放完成回调
|
|
innerAudio.onAudioEnded(() => {
|
|
console.log('播放完成(通过回调)');
|
|
setTimeout(() => {
|
|
isPlaying.value = false;
|
|
}, 60);
|
|
});
|
|
// 注册播放错误回调
|
|
innerAudio.onAudioError((error) => {
|
|
console.error('播放错误(通过回调):', error);
|
|
});
|
|
// 声音加载完毕
|
|
innerAudio.onAudioLoaded((e) => {
|
|
console.log('声音加载完毕', e.duration);
|
|
// 计算原始时长
|
|
const originalTime = Math.ceil(e.duration) || 1;
|
|
// 判断是否在118-123秒范围内
|
|
const displayTime = originalTime >= 118 && originalTime <= 124 ? 120 : originalTime;
|
|
// 赋值显示
|
|
console.log('displayTime', displayTime);
|
|
voiceTime.value = displayTime + 's';
|
|
});
|
|
watch(
|
|
() => props.dataInfo.voicePath,
|
|
(newVal) => {
|
|
console.log('去下载oss内容333', props.dataInfo);
|
|
if (newVal === '' && props.dataInfo.ossKey) {
|
|
// 去下载oss内容
|
|
console.log('真下载oss内容');
|
|
downloadFile(props.dataInfo.ossKey).then((res) => {
|
|
voiceTime.value = '';
|
|
innerAudio.setAudioSrc(res.tempFilePath);
|
|
});
|
|
} else if (newVal === undefined) {
|
|
innerAudio.stopAudio();
|
|
} else {
|
|
voiceTime.value = '';
|
|
innerAudio.setAudioSrc(newVal);
|
|
}
|
|
},
|
|
{ immediate: true }
|
|
);
|
|
});
|
|
onUnload(() => {
|
|
// 1. 停止当前播放(优先处理,避免音频继续播放)
|
|
innerAudio.stopAudio();
|
|
// 2. 移除当前组件注册的回调(避免内存泄漏)
|
|
innerAudio.removeCallbacks();
|
|
|
|
unsubscribe();
|
|
});
|
|
</script>
|
|
|
|
<style scoped lang="scss">
|
|
.talking-line {
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
}
|
|
.talking-info {
|
|
padding: 20rpx;
|
|
border-radius: 15rpx;
|
|
background-color: #06f;
|
|
padding-bottom: 30rpx;
|
|
position: relative;
|
|
color: #fff;
|
|
width: 100%;
|
|
|
|
.talking-gif {
|
|
width: 200rpx;
|
|
height: 38rpx;
|
|
background: url(@/static/images/course/voice-gray.png) no-repeat;
|
|
background-size: contain;
|
|
padding-left: 168rpx;
|
|
|
|
&.is-play {
|
|
background: url(@/static/images/course/voice-white.gif) no-repeat;
|
|
background-size: contain;
|
|
}
|
|
}
|
|
|
|
.right-time {
|
|
position: absolute;
|
|
right: 20rpx;
|
|
top: 20rpx;
|
|
font-size: 28rpx;
|
|
height: 38rpx;
|
|
line-height: 38rpx;
|
|
}
|
|
|
|
&.talking-info-loading {
|
|
padding-bottom: 20rpx;
|
|
}
|
|
|
|
&.talking-info-text {
|
|
padding-bottom: 20rpx;
|
|
width: auto;
|
|
min-width: calc(100vw * 0.45);
|
|
// float: right;
|
|
.talking-cont {
|
|
// margin-top: 0rpx;
|
|
}
|
|
}
|
|
|
|
.talking-cont {
|
|
// margin-top: 16rpx;
|
|
overflow: hidden;
|
|
|
|
.talking-text {
|
|
font-size: 28rpx;
|
|
line-height: 48rpx;
|
|
margin-bottom: 20rpx;
|
|
word-break: break-all;
|
|
white-space: pre-line;
|
|
overflow: hidden;
|
|
|
|
&.translate-text {
|
|
min-height: 48rpx;
|
|
}
|
|
}
|
|
}
|
|
|
|
.btns-cont {
|
|
overflow: hidden;
|
|
border-top: 3rpx solid #1f79ff;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding-top: 20rpx;
|
|
margin-top: 14rpx;
|
|
.replay-btn {
|
|
float: left;
|
|
width: 46rpx;
|
|
height: 46rpx;
|
|
overflow: hidden;
|
|
margin-right: 16rpx;
|
|
margin-top: 25rpx;
|
|
}
|
|
|
|
.play-btn {
|
|
width: 46rpx;
|
|
height: 46rpx;
|
|
margin-right: 16rpx;
|
|
}
|
|
|
|
.text-btn {
|
|
width: 46rpx;
|
|
height: 46rpx;
|
|
overflow: hidden;
|
|
line-height: 46rpx;
|
|
text-align: center;
|
|
color: #fff;
|
|
margin-right: 16rpx;
|
|
background-color: #67a4ff;
|
|
font-size: 24rpx;
|
|
font-weight: bold;
|
|
border-radius: 20rpx;
|
|
}
|
|
|
|
.text-right-btn {
|
|
float: right;
|
|
padding: 0 10rpx;
|
|
height: 46rpx;
|
|
overflow: hidden;
|
|
line-height: 46rpx;
|
|
text-align: center;
|
|
margin-left: 16rpx;
|
|
margin-top: 25rpx;
|
|
}
|
|
|
|
.next-btn {
|
|
float: right;
|
|
padding: 0 60rpx 0 20rpx;
|
|
height: 62rpx;
|
|
line-height: 62rpx;
|
|
font-size: 30rpx;
|
|
background-color: #06f;
|
|
color: #fff;
|
|
border-radius: 8rpx;
|
|
margin-top: 16rpx;
|
|
position: relative;
|
|
|
|
.icon-iamge {
|
|
width: 25rpx;
|
|
height: 25rpx;
|
|
position: absolute;
|
|
right: 20rpx;
|
|
top: 50%;
|
|
margin-top: -12rpx;
|
|
}
|
|
}
|
|
|
|
.btns-cont-button-text {
|
|
font-weight: 400;
|
|
font-size: 30rpx;
|
|
color: #ffffff;
|
|
line-height: 30rpx;
|
|
text-align: left;
|
|
font-style: normal;
|
|
margin-left: 18rpx;
|
|
}
|
|
}
|
|
}
|
|
</style>
|