Files
tra-app/src/pages/example/components/answer.vue
T

414 lines
9.2 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="answer-content" v-if="props.isLoading">
<view class="talking-cont">
<CommonLoading color="#06f" />
</view>
</view>
<view class="answer-content" v-else>
<view :class="['talking-gif', !voiceLoading&&isPlaying ? 'is-play' : '']">
</view>
<view class="talking-cont">
<MarkdownPreview :mdString="mdText" />
</view>
<view class="talking-link">
<view class="link-cont-desc">语音转文字的耗时{{ dataInfo.asrTime }}ms</view>
<!-- <view class="link-cont-desc">请求的文字{{ dataInfo.reqContent }}</view> -->
<view class="link-cont-desc">请求内容的字符长度{{ dataInfo.reqContSize }}</view>
<!-- <view class="link-cont-desc">AI问答返回内容{{ dataInfo.aiContent }}</view> -->
<view class="link-cont-desc">AI问答返回内容字符的长度{{ dataInfo.aiContentSize }}</view>
<view class="link-cont-desc">AI问答总耗时{{ dataInfo.aiAskTime }}ms</view>
<view class="link-cont-desc">文字转语音消耗时长{{ dataInfo.transVoice }}ms</view>
<!-- <view class="link-cont-desc">oss 文件id{{ dataInfo.fileId }}</view> -->
<view class="link-cont-desc">上传OSS文件服务消耗时长{{ dataInfo.ossUpTime }}ms</view>
<view class="link-cont-desc">AI回答语音长度时长{{ dataInfo.playVoiceTime }}s</view>
<!-- <view class="link-cont-desc">本次回答参考资料来源于网络</view> -->
</view>
<view class="btns-cont">
<view class="replay-btn" >
<image class="icon" src="@/static/images/course/play.png" style="width: 100%; height: 100%"
v-show="!isPlaying"
@click.stop="playVoice()" ></image>
<image class="icon"src="@/static/images/course/pause.png"
v-show="isPlaying"
style="width: 100%; height: 100%" @click.stop="pauseVoice"></image>
</view>
</view>
</view>
</template>
<script setup>
import { onMounted, computed, ref, watch, toRefs } from 'vue'
import { onUnload } from '@dcloudio/uni-app';
import MarkdownPreview from '@/components/markdown-preview/markdown-preview.vue'
import common from '@/common/common'
import {
downloadVoiceFile,
downloadFile
} from '@/api/common.js';
const emit = defineEmits(['handleEvents', 'changePlay'])
const props = defineProps({
dataInfo: {
default: () => ({}),
type: Object
},
isLoading: {
default: false,
type: Boolean
},
isAnswering: {
default: false,
type: Boolean
},
activeVoiceId:{
default:'',
type: String
},
isLast: {
default: false,
type: Boolean
}
})
const { activeVoiceId, dataInfo } = toRefs(props)
watch(
() => props.dataInfo,
() => {},
{
deep: true,
immediate: true
}
)
watch(
() => props.isLoading,
(val) => {
if(!val){
setTimeout(()=> {
playVoice()
},500)
}
},
{
deep: true,
immediate: true
}
)
const isPlaying = ref(false)
watch(
() => props.activeVoiceId,
(val) => {
if(val === props.dataInfo.id){
isPlaying.value = true
}else{
isPlaying.value = false
}
},
{
deep: true,
immediate: true
}
)
const mdText = computed(() => {
let _text = (props.dataInfo?.talkingText|| '').replace(/\\n/g, `\n`);
return _text
})
const fileList = computed(() => props.dataInfo?.talkingFiles || [])
const crsList = computed(() => props.dataInfo?.talkingCrsList || [])
const activeNames = ref([1, 2])
const talkVoiceObj = ref(null)
const voiceLoading = ref(false)
const voiceTime = ref('')
const itemVoice = ref('')
const initVoice = () => {
talkVoiceObj.value = uni.createInnerAudioContext()
talkVoiceObj.value.onEnded(()=>{
emit('changePlay', '')
talkVoiceObj.value.src = ''
isPlaying.value = false
})
talkVoiceObj.value.onError(()=>{
talkVoiceObj.value.src = ''
isPlaying.value = false
})
}
const setVoiceTime = (sum) => {
sum += 1
if(Math.ceil(talkVoiceObj.value.duration)>0){
voiceTime.value = (Math.ceil(talkVoiceObj.value.duration) || 1 ) + 's'
}else{
if(sum === 20) {
voiceTime.value = 0 + 's'
return
}
setTimeout(()=>{
setVoiceTime(sum)
},200)
}
}
const playVoice = (readType = 'pgth') => {
let _content = mdText.value;
if (!_content) {
common.msg('没有可以播放的文字信息!');
return;
}
isPlaying.value = true
voiceLoading.value = true;
setTimeout(() => {
downloadFile(props.dataInfo?.talkingVoiceId)
.then((res) => {
if (res?.data) {
common.msg(res.data.message);
return;
}
voiceLoading.value = false;
itemVoice.value = res.tempFilePath;
talkVoiceObj.value.src = res.tempFilePath;
talkVoiceObj.value.play();
})
.catch((err) => {
voiceLoading.value = false;
});
}, 100);
};
const pauseVoice = () => {
if(!talkVoiceObj.value) return
talkVoiceObj.value.pause()
isPlaying.value = false
emit('changePlay', '')
}
onMounted(()=>{
initVoice()
})
onUnload(()=>{
pauseVoice()
})
watch(() => props.dataInfo.talkingVoice,(val)=>{
if(val){
initVoice()
}
},{
deep: true,
immediate: true
})
const changeActive = val => {
if (activeNames.value.indexOf(val) >= 0) {
activeNames.value = activeNames.value.filter(t => t !== val)
} else {
activeNames.value.push(val)
}
}
const reAnswer = () => {
emit('handleEvents', 'reAnswer')
}
const stopAnswer = () => {
emit('handleEvents', 'stop')
}
const zanAnswer = () => {
emit('handleEvents', 'great')
}
const feedbackAnswer = () => {
emit('handleEvents', 'feedback')
}
const previewFile = item => {
emit('handleEvents', 'preview', item)
}
const previewCrs = item => {
common.navigateTo('/pages/courseDetail/index?crsId=' + item.crsId)
}
</script>
<style scoped lang="scss">
.answer-content {
background-color: #f5f5f5;
padding: 20rpx;
border-radius: 15rpx;
padding-bottom: 10rpx;
position: relative;
overflow: hidden;
float: left;
width: 100%;
min-height: 80rpx;
.talking-gif{
width: 200rpx;
height: 38rpx;
margin-bottom: 20rpx;
background: url(@/static/images/course/voice-gray.png) no-repeat;
background-size: contain;
padding-left: 168rpx;
&.is-play{
background: url(@/static/images/course/voice-gif.gif) no-repeat;
background-size: contain;
}
}
.talking-cont {
padding-bottom: 20rpx;
}
.btns-cont {
overflow: hidden;
border-top: 3rpx solid #eee;
padding-bottom: 10rpx;
.replay-btn {
float: left;
width: 46rpx;
height: 46rpx;
overflow: hidden;
margin-right: 20rpx;
margin-top: 20rpx;
}
.play-btn {
float: left;
width: 46rpx;
height: 46rpx;
overflow: hidden;
margin-right: 20rpx;
margin-top: 20rpx;
}
.next-learn-btn {
float: left;
padding: 0 60rpx 0 20rpx;
height: 62rpx;
line-height: 62rpx;
font-size: 30rpx;
background-color: #06f;
color: #fff;
border-radius: 8rpx;
margin-top: 16rpx;
margin-right: 16rpx;
position: relative;
&.replay-study-btn {
float: right;
}
&.restart {
background-color: #eaf2ff;
color: #06f;
}
.icon-iamge {
width: 25rpx;
height: 25rpx;
position: absolute;
right: 20rpx;
top: 50%;
margin-top: -12rpx;
}
}
.replay-learn-btn {
float: left;
padding: 0 60rpx 0 20rpx;
height: 62rpx;
line-height: 62rpx;
font-size: 30rpx;
color: #06f;
background-color: #eaf2ff;
border-radius: 8rpx;
margin-top: 16rpx;
margin-right: 16rpx;
position: relative;
.icon-iamge {
width: 25rpx;
height: 25rpx;
position: absolute;
right: 20rpx;
top: 50%;
margin-top: -12rpx;
}
}
.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;
}
}
}
.link-cont-desc {
margin-bottom: 12rpx;
font-size: 24rpx;
color: #999;
}
.link-cont {
margin-bottom: 12rpx;
font-size: 28rpx;
.link-info {
height: 50rpx;
line-height: 50rpx;
overflow: hidden;
margin-bottom: 12rpx;
.info-text {
float: left;
padding-right: 20rpx;
}
&::after {
transition: all 0.3s ease-in-out;
content: '';
display: block;
float: left;
position: relative;
top: 12rpx;
width: 12rpx;
height: 12rpx;
border-right: 4rpx solid #333;
border-bottom: 4rpx solid #333;
transform: rotate(135deg);
}
}
&.is-active {
.link-info {
&::after {
top: 18rpx;
transform: rotate(45deg);
}
}
.link-items {
max-height: 600rpx;
}
}
.link-items {
max-height: 0rpx;
overflow: hidden;
transition: all 0.3s ease-in-out;
.link-item {
line-height: 48rpx;
color: #06f;
}
}
}
}
</style>