Files
tra-app/src/components/examination/question.vue
T

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>