Files
tra-app/src/pages/course/components/talkingItem.vue
T
2025-07-01 16:21:01 +08:00

550 lines
14 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.
<!-- type: 3, 段落信息 4 问题信息 5 统计信息 1语音回答 2 文本回答 0纯文本信息 不翻译 -->
<template>
<view class="ai-answer" v-if="[3,4,5,6].indexOf(type) >= 0">
<view class="user-info">
<view class="avatar-box">
<ImagePreview class="img-box" :imgId="dataInfo?.userInfo?.imgAddr"></ImagePreview>
</view>
<view class="ai-name">
{{ dataInfo?.userInfo?.teacherName }}
<view class="ai-name-desc">{{dataInfo?.userInfo?.teacherDesc}}</view>
</view>
</view>
<view :class="['talking-info', [4].indexOf(type) >= 0 ? 'talking-info-less':''] ">
<view :class="['talking-gif', activeVoiceId === dataInfo.id?'is-play':'']"></view>
<view class="talking-cont">
<view class="talking-text" v-if="[3, 5, 6].indexOf(type) >= 0">
{{ dataInfo.pgrphContent }}
</view>
<view class="talking-text" v-if="[4].indexOf(type) >= 0">
请您思考并作答:<br>
{{ dataInfo.qnsContent }}
</view>
<view v-show="type === 3">
<ImagePreview class="cont-img-box" v-if="(dataInfo.imageAddrs||[]).length>0" :imgId="dataInfo.imageAddrs[0]"></ImagePreview>
<VideoPreview class="cont-img-box" v-if="(dataInfo.vidoAddrs||[]).length>0" :imgId="dataInfo.vidoAddrs[0]"></VideoPreview>
</view>
<view v-show="type === 5">
<ChartFour/>
<ChartThree/>
</view>
</view>
<!-- <view class="talking-cont">
<view class="talking-text">
阿列克山大是快乐的你艾德卡省的mask安东拉森扣篮大赛了快递那里是啊克里斯蒂娜老师的阿萨是看
</view>
</view> -->
<view class="btns-cont">
<view class="replay-btn" @click="restartPlay">
<image class="icon" src="@/static/images/course/replay.png" style="width: 100%;height: 100%;"></image>
</view>
<view class="play-btn">
<image
class="icon"
src="@/static/images/course/play.png"
style="width: 100%;height: 100%;"
@click="playVoice()"
v-show="!isPlaying"
></image>
<image
class="icon"
v-show="isPlaying"
src="@/static/images/course/pause.png"
style="width: 100%;height: 100%;"
@click="pauseVoice"
></image>
</view>
<!-- <view class="next-learn-btn">继续学<image class="icon icon-iamge" src="@/static/images/course/goon.png" ></image></view>
<view class="replay-study-btn"><image class="icon icon-iamge" src="@/static/images/course/restudy.png" ></image></view> -->
<view class="next-btn" @click="goOnGetQues" v-if="showNextBtn && [3, 5, 6].indexOf(type) >= 0">继续
<image class="icon icon-iamge" src="@/static/images/course/goon.png" ></image></view>
</view>
</view>
</view>
<view class="user-answer" v-else-if="[1, 2].indexOf(type) >= 0">
<view class="user-info">
<view class="avatar-box">
<!-- <ImagePreview class="img-box" :imgId="''"></ImagePreview> -->
<img class="img-box" :src="userInfo.imagePath" alt="" fit="contain" style="width: 100%; height: 100%;"/>
</view>
<view class="ai-name">
{{ userInfo.userName }}
</view>
</view>
<view class="talking-info">
<view :class="['talking-gif', activeVoiceId === dataInfo.id?'is-play':'']"></view>
<view class="talking-cont">
<view class="talking-text">
{{ dataInfo.talkingContent }}
</view>
</view>
<view class="btns-cont">
<!-- <view class="replay-btn">
<image class="icon" src="@/static/images/course/replay.png" style="width: 100%;height: 100%;"></image>
</view> -->
<view class="play-btn">
<image class="icon"
@click="playQnsVoice('qns', dataInfo)"
v-show="!isPlaying"
src="@/static/images/course/play-blue.png"
style="width: 100%;height: 100%;"
></image>
<image class="icon"
v-show="isPlaying"
@click="pauseVoice"
src="@/static/images/course/stop-blue.png"
style="width: 100%;height: 100%;"
></image>
</view>
<view class="icon text-btn" v-if="type === 1" @click="translateVoice(dataInfo)"></view>
<view class="icon text-right-btn" @click="translateVoice(dataInfo)" v-if="isLast">完成</view>
<view class="icon text-right-btn" @click="translateVoice(dataInfo)">撤回</view>
</view>
</view>
</view>
<view class="user-answer" v-else-if="type === 0">
<view class="user-info">
<view class="avatar-box">
<!-- <ImagePreview class="img-box" :imgId="''"></ImagePreview> -->
<img class="img-box" :src="userInfo.imagePath" alt="" fit="contain" style="width: 100%; height: 100%;"/>
</view>
<view class="ai-name">
{{ userInfo.userName }}
</view>
</view>
<view class="talking-info-less">
<view class="talking-text">
{{ dataInfo.talkingContent }}
</view>
</view>
</view>
<view v-else></view>
</template>
<script setup>
import { watch, unref, ref, toRefs, computed, nextTick } from 'vue'
import ImagePreview from '@/components/image-preview/image-preview.vue'
import VideoPreview from '@/components/image-preview/video-preview.vue'
import { downloadVoiceFile, uploadVoiceFile, } from "@/api/common.js"
import { studyParagraphOverApi } from "@/api/study.js"
import ChartFour from "./chartFour.vue"
import ChartThree from "./chartThree.vue"
import { getUserInfo } from '@/common/common';
const props = defineProps({
type:{
default: 0,// 1 userAnswer 0: AIAnswer
type:[Number,String]
},
dataInfo: {
default: () => ({}),
type: Object
},
activeVoiceId:{
default:'',
type: String
},
isLast:{
default:false,
type: Boolean
},
isPlaying:{
default:false,
type: Boolean
},
})
const { type, dataInfo, activeVoiceId, isPlaying, isLast} = toRefs(props)
const emit = defineEmits(['changePlay', 'nextQuestion'])
// 声音播放初始化
const talkVoiceObj = ref(uni.createInnerAudioContext())
const userInfo = computed(() => getUserInfo())
watch(() => props.isPlaying,(val) => {
if(!val){
talkVoiceObj.value?.stop()
}
},{
deep: true,
immediate: true
})
talkVoiceObj.value.onEnded(()=>{
emit('changePlay', '')
talkVoiceObj.value.src = ''
})
talkVoiceObj.value.onError(()=>{
emit('changePlay', '')
talkVoiceObj.value.src = ''
})
const pauseVoice = () => {
if(!talkVoiceObj.value) return
talkVoiceObj.value.pause()
emit('changePlay', '')
}
// 重播
const restartPlay = () => {
emit('changePlay', unref(dataInfo)?.id)
if(itemVoice.value){
setTimeout(()=>{
talkVoiceObj.value.src = itemVoice.value
talkVoiceObj.value.play()
},100)
}else{
playVoice()
}
}
const itemVoice = ref('')
const playVoice = (readType = 'pgth') =>{
// readType 阅读内容类型pgrh段落/qns问题
emit('changePlay', unref(dataInfo)?.id)
if(itemVoice.value){
console.log(1111111,itemVoice.value)
setTimeout(()=>{
talkVoiceObj.value.src = itemVoice.value
talkVoiceObj.value.play()
},100)
}else{
console.log(2,itemVoice.value)
let _params = {
crsId: unref(dataInfo)?.crsId || '',
pgrphId: unref(dataInfo)?.pgrphId || '',
qnsId: unref(dataInfo)?.qnsId || '',
readType: readType,
teachNo: unref(dataInfo)?.userInfo?.teacherNo, // 必填
content: unref(dataInfo)?.pgrphContent || unref(dataInfo)?.qnsContent || unref(dataInfo)?.talkingContent,
}
let _url = '/trastudy/traStdyInfo/readContent?' + Object.keys(_params).map(t=>{
return encodeURIComponent(t) + '=' + encodeURIComponent(_params[t])
}).join('&')
downloadVoiceFile(_url).then(res=>{
itemVoice.value = res.tempFilePath
talkVoiceObj.value.src = res.tempFilePath
talkVoiceObj.value.play()
})
}
}
const playQnsVoice = (readType = 'qns', item) =>{
if(type.value === 2){
// 文本转语音
playVoice(readType)
return
}
// readType 阅读内容类型pgrh段落/qns问题
emit('changePlay', unref(dataInfo)?.id)
setTimeout(()=>{
itemVoice.value = item.talkingVoice
talkVoiceObj.value.src = item.talkingVoice
talkVoiceObj.value.play()
},100)
}
const translateVoice = (item) =>{
if(!item.talkingVoice) return
uploadVoiceFile(item.talkingVoice,{}).then(res=>{
let _data = JSON.parse(res.data)
item.talkingContent = _data.body
})
}
const showNextBtn = ref(true)
const goOnGetQues = () => {
studyParagraphOverApi({
pgrphId:unref(dataInfo)?.pgrphId,
logId:unref(dataInfo)?.logId,
}).then(res=>{
showNextBtn.value = false
emit('nextQuestion', unref(dataInfo))
})
}
</script>
<style lang="scss">
.ai-answer{
width: 100%;
overflow: hidden;
.user-info{
overflow: hidden;
.avatar-box{
float: left;
width: 76rpx;
height: 76rpx;
border: 3rpx solid #fff;
background-color: #ccc;
overflow: hidden;
border-radius: 10rpx;
margin-right: 16rpx;
margin-bottom: 16rpx;
.img-box{
width: 100%;
height: 100%;
}
}
.ai-name{
float: left;
color: #666;
font-size: 25rpx;
line-height: 35rpx;
.ai-name-desc{
color: #999;
margin-left: 16rpx;
display: inline-block;
}
}
}
.talking-info{
padding: 20rpx;
border-radius: 15rpx;
background-color: #fff;
overflow: hidden;
&.talking-info-less{
float: left;
min-width: 400rpx;
}
.talking-gif{
width: 140rpx;
height: 38rpx;
background: url(@/static/images/course/voice-gray.png) no-repeat;
background-size: 100%;
margin-bottom: 16rpx;
&.is-play{
background: url(@/static/images/course/voice-gif.gif) no-repeat;
background-size: 100%;
}
}
.talking-cont{
.talking-text{
font-size: 28rpx;
line-height: 48rpx;
margin-bottom: 20rpx;
word-break: break-all;
}
.cont-img-box{
width: 433rpx;
height: 269rpx;
border-radius: 15rpx;
overflow: hidden;
background-color: #eee;
margin-bottom: 20rpx;
}
}
.btns-cont{
overflow: hidden;
border-top: 3rpx solid #eee;
.replay-btn{
float: left;
width: 46rpx;
height: 46rpx;
overflow: hidden;
margin-right: 16rpx;
margin-top: 25rpx;
}
.play-btn{
float: left;
width: 46rpx;
height: 46rpx;
overflow: hidden;
margin-right: 16rpx;
margin-top: 25rpx;
}
.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;
.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;
}
}
.replay-study-btn{
width: 162rpx;
height: 62rpx;
border-radius: 8rpx;
float: right;
margin-top: 16rpx;
.icon-iamge{
width: 100%;
height: 100%;
margin:0;
}
}
.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;
}
}
}
}
}
.user-answer{
overflow: hidden;
width: 100%;
.user-info{
overflow: hidden;
.avatar-box{
float: right;
width: 76rpx;
height: 76rpx;
border: 3rpx solid #fff;
background-color: #ccc;
overflow: hidden;
border-radius: 10rpx;
margin-left: 16rpx;
margin-bottom: 16rpx;
.img-box{
width: 100%;
height: 100%;
}
}
.ai-name{
float: right;
color: #666;
font-size: 25rpx;
line-height: 35rpx;
}
}
.talking-info-less{
padding: 20rpx;
float: right;
border-radius: 15rpx;
background-color: #06f;
color: #fff;
}
.talking-info{
padding: 20rpx;
border-radius: 15rpx;
background-color: #06f;
color: #fff;
.talking-gif{
width: 140rpx;
height: 38rpx;
background: url(@/static/images/course/voice-gray.png) no-repeat;
background-size: 100%;
margin-bottom: 16rpx;
&.is-play{
background: url(@/static/images/course/voice-white.gif) no-repeat;
background-size: 100%;
}
}
.talking-cont{
.talking-text{
font-size: 28rpx;
line-height: 48rpx;
margin-bottom: 20rpx;
word-break: break-all;
}
}
.btns-cont{
overflow: hidden;
border-top: 3rpx solid #eee;
.replay-btn{
float: left;
width: 46rpx;
height: 46rpx;
overflow: hidden;
margin-right: 16rpx;
margin-top: 25rpx;
}
.play-btn{
float: left;
width: 46rpx;
height: 46rpx;
overflow: hidden;
margin-right: 16rpx;
margin-top: 25rpx;
}
.text-btn{
float: left;
width: 46rpx;
height: 46rpx;
overflow: hidden;
line-height: 46rpx;
text-align: center;
color: #fff;
margin-right: 16rpx;
margin-top: 25rpx;
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;
}
}
}
}
}
</style>