659 lines
16 KiB
Vue
659 lines
16 KiB
Vue
<template>
|
||
<view class="course-study max_page">
|
||
<view class="top-bg"></view>
|
||
<view class="bot-bg"></view>
|
||
<view class="course-study-body" v-show="step === 1">
|
||
<view class="body-title">
|
||
<uv-icon class="arrow-icon" name="arrow-left" @click="common.navigateBack()"/>
|
||
语言选择,开启学习之旅
|
||
</view>
|
||
<view class="voice-list">
|
||
<uv-radio-group v-model="choiceTeacher">
|
||
<view class="voice-item" v-for="item in teacherList" :key="item.teacherNo" @click="choiceTeacher = item.teacherNo">
|
||
<view class="avatar-box">
|
||
<ImagePreview class="img-box" :imgId="item.imgAddr" :isCache="true" :width="120" :height="160"></ImagePreview>
|
||
</view>
|
||
<view class="right-cont">
|
||
<view class="right-name">{{item.teacherName}}</view>
|
||
<view class="right-desc">描述:{{item.voiceDesc || '--'}}</view>
|
||
<view class="audio-box">
|
||
<view :class="['talking-gif', activeVoiceTeachNo === item.teacherNo ? 'is-play':'']"></view>
|
||
<view class="play-btn" @click="playTeacherVoice(item)">
|
||
<image class="icon" v-if="activeVoiceTeachNo !== item.teacherNo" src="@/static/images/course/play-blue.png" style="width: 100%;height: 100%;"></image>
|
||
<image class="icon" v-else src="@/static/images/course/stop-blue.png" style="width: 100%;height: 100%;"></image>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<uv-radio :name="item.teacherNo" class="radio-box" @click.stop/>
|
||
</view>
|
||
</uv-radio-group>
|
||
<view class="start-btns" @click="nextStep">
|
||
开启学习之旅
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view class="course-study-body" v-show="step === 2" @click="playRecord">
|
||
<view class="body-title">
|
||
<uv-icon class="arrow-icon" name="arrow-left" @click="common.navigateBack()"/>
|
||
课程学习
|
||
</view>
|
||
<view class="talking-list">
|
||
<view class="study-num">
|
||
<view class="color-blue">1</view> / 20
|
||
</view>
|
||
<TalkingItem
|
||
class="talking-item"
|
||
v-for="item in talkingList"
|
||
:type="item.type"
|
||
:dataInfo="item"
|
||
:activeVoiceId="activeVoiceTalkingItemId"
|
||
@changePlay="changePlayItemId"
|
||
@nextQuestion="nextQuestionSuccess"
|
||
:id="'item' + item.id"
|
||
></TalkingItem>
|
||
<!-- <video v-if="videoUrlShow" :src="videoUrlShow" controls></video> -->
|
||
</view>
|
||
<view class="talk-btns">
|
||
<view
|
||
class="touch-btn"
|
||
@touchstart="showOverlayTalking"
|
||
@touchend="hideOverlayTalking"
|
||
@touchmove="handleMove"
|
||
>按住说话
|
||
<image class="talk-btn-icon" src="/src/static/images/course/jianpan.png" mode=""></image>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<uv-overlay :show="showTalkingModel" opacity=".9" @click="showTalkingModel = false">
|
||
<view class="overlay-box">
|
||
<view class="circle-bg-box">
|
||
<view class="icon-box">
|
||
<image src="/src/static/images/course/talk-icon.png" mode=""></image>
|
||
</view>
|
||
</view>
|
||
<view class="tips-box">松开发送</view>
|
||
<view class="btns-box">
|
||
<view :class="['close-btn',isOut?'is-out':'']" >取消
|
||
<image class="btn-box" v-if="!isOut" src="/src/static/images/course/close.png" mode=""></image>
|
||
<image class="btn-box" v-else src="/src/static/images/course/close-active.png" mode=""></image>
|
||
</view>
|
||
</view>
|
||
<!-- <view class="talk-dialog">
|
||
<view class="voice-gif"></view>
|
||
</view> -->
|
||
</view>
|
||
</uv-overlay>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { reactive, ref, computed, unref, onMounted, onUnmounted } from 'vue';
|
||
import { onLoad,onShow,onHide } from '@dcloudio/uni-app';
|
||
import { previewFile, downloadFile, downloadVoiceFile, uploadVoiceFile } from "@/api/common.js"
|
||
import { getToken } from '@/common/common.js'
|
||
import { get_base_url } from '@/api/request'
|
||
import { getCourseStudyInfoApi, queryTraStdyBatchWaitParagraphInfoApi,audioTranscriptionsApi } from "@/api/study.js"
|
||
import common from '@/common/common';
|
||
import TalkingItem from '@/pages/course/components/talkingItem.vue'
|
||
import ImagePreview from '@/components/image-preview/image-preview.vue'
|
||
|
||
import { useSocketStore } from "@/store/socket.js"
|
||
import { storeToRefs } from 'pinia'
|
||
|
||
const socketStore = useSocketStore()
|
||
const { SocketObj } = storeToRefs(socketStore)
|
||
|
||
const crsNum = ref('');
|
||
const crsId = ref('');
|
||
const step = ref(0)
|
||
const teacherList = ref([])
|
||
const choiceTeacher = ref('')
|
||
const videoUrlShow = ref('')
|
||
const talkingList = ref([])
|
||
const activeVoiceTalkingItemId = ref('')
|
||
|
||
const getData = async () => {
|
||
try{
|
||
const { body } = await getCourseStudyInfoApi({crsId: crsId.value})
|
||
initsocketTask(1)
|
||
if(body.stdyProgressFlag === 'N'){
|
||
step.value = 1
|
||
teacherList.value = body.teacheList.map(t=>({
|
||
...t,
|
||
voicePath: '',
|
||
voiceObj: null
|
||
}))
|
||
}else{
|
||
common.show('提示信息', '此门课程已有学习历史,请选择是否继续学习?',true, '重新开始','继续学习').then((res) => {
|
||
console.log(res)
|
||
if(res) {
|
||
// 继续学习 , 获取学习历史接口
|
||
// 滚动到最下面
|
||
scrollToBottomNow()
|
||
|
||
choiceTeacher.value = body.choiceTeacher
|
||
sendMessage({
|
||
"commond" : 'OPEN',
|
||
"body" : {
|
||
crsId: crsId.value,
|
||
teacherNo: choiceTeacher.value
|
||
}
|
||
})
|
||
step.value = 2
|
||
getGraphInfoUnStudy()
|
||
}else{
|
||
// 重新开始
|
||
step.value = 1
|
||
teacherList.value = body.teacheList.map(t=>({
|
||
...t,
|
||
voicePath: '',
|
||
voiceObj: null
|
||
}))
|
||
}
|
||
}).finally(() => {})
|
||
}
|
||
}catch{
|
||
|
||
}
|
||
// downloadFile('S3F0250640161332025061618052000000267276').then(res=>{
|
||
// videoUrlShow.value = res.tempFilePath
|
||
// })
|
||
};
|
||
|
||
// 关闭播放状态
|
||
const changePlayItemId = (id) => {
|
||
if(activeVoiceTalkingItemId.value === id){
|
||
activeVoiceTalkingItemId.value = ''
|
||
}else{
|
||
activeVoiceTalkingItemId.value = id
|
||
}
|
||
}
|
||
|
||
const choiceTeacherInfo = computed(()=>{
|
||
return teacherList.value.find(t=> t.teacherNo === choiceTeacher.value)
|
||
})
|
||
|
||
const activeVoiceTeachNo = ref('')
|
||
const teacherVoiceObj = uni.createInnerAudioContext()
|
||
teacherVoiceObj.onEnded(()=>{
|
||
activeVoiceTeachNo.value = ''
|
||
teacherVoiceObj.src = ''
|
||
})
|
||
const playTeacherVoice = (item) =>{
|
||
teacherVoiceObj.stop()
|
||
teacherVoiceObj.src = ''
|
||
let _isStop = item.teacherNo === activeVoiceTeachNo.value
|
||
activeVoiceTeachNo.value = _isStop ? '' : item.teacherNo
|
||
if(_isStop) return
|
||
if(item.voicePath){
|
||
teacherVoiceObj.src = item.voicePath
|
||
teacherVoiceObj.play()
|
||
}else{
|
||
downloadVoiceFile('/trastudy/traStdyInfo/trialListening?teachNo='+item.teacherNo).then(res=>{
|
||
item.voicePath = res.tempFilePath
|
||
teacherVoiceObj.src = item.voicePath
|
||
teacherVoiceObj.play()
|
||
})
|
||
}
|
||
}
|
||
var socketTask = null
|
||
const nextStep = () => {
|
||
if(choiceTeacher.value){
|
||
teacherVoiceObj.destroy()
|
||
sendMessage({
|
||
"commond" : 'OPEN',
|
||
"body" : {
|
||
crsId: crsId.value,
|
||
teacherNo: choiceTeacher.value
|
||
}
|
||
})
|
||
step.value = 2
|
||
getGraphInfoUnStudy()
|
||
}else{
|
||
uni.showToast({
|
||
title: '请选择一位教师!',
|
||
icon: "none",
|
||
duration:1000
|
||
});
|
||
}
|
||
}
|
||
const getGraphInfoUnStudy = () => {
|
||
queryTraStdyBatchWaitParagraphInfoApi({crsId: crsId.value}).then(res=>{
|
||
console.log(res)
|
||
let _id = new Date().getTime() + 'id'
|
||
talkingList.value.push({
|
||
...res.body,
|
||
type: 0,
|
||
userInfo: {...choiceTeacherInfo.value},
|
||
id: _id
|
||
})
|
||
scrollToBottomNow()
|
||
})
|
||
}
|
||
// 继续 执行方法
|
||
const nextQuestionSuccess = (info) => {
|
||
let _id = new Date().getTime() + 'id'
|
||
talkingList.value.push({
|
||
crsId: info.crsId,
|
||
pgrphId: info.pgrphId,
|
||
talkingContent: '继续',
|
||
type: 2,
|
||
id: _id
|
||
})
|
||
scrollToBottomNow()
|
||
getGraphInfoUnStudy()
|
||
}
|
||
const scrollToBottomNow = () => {
|
||
if(talkingList.value.length > 0){
|
||
let _last = talkingList.value[talkingList.value.length - 1]
|
||
uni.pageScrollTo({
|
||
selector: '#item' + _last.id,
|
||
duration: 300
|
||
})
|
||
}
|
||
}
|
||
|
||
const reconnectTimer = null // 重连timer
|
||
const isManualClose = ref(false) // 是否手动关闭
|
||
|
||
const initsocketTask = ($step) => {
|
||
socketStore.creatSocket()
|
||
// socketTask = uni.connectSocket({
|
||
// url: get_base_url() + '/trastudy/trasoket/open?summary=' + getToken(),
|
||
// complete: function(){}
|
||
// })
|
||
// uni.onSocketMessage((res)=>{
|
||
// let _data = JSON.parse(res.data)
|
||
// console.log(_data)
|
||
// })
|
||
// uni.onSocketOpen((e)=>{
|
||
// console.log('链接成功!')
|
||
// })
|
||
// uni.onSocketError((e)=>{
|
||
// console.log(e,111)
|
||
// })
|
||
// uni.onSocketClose((e)=>{
|
||
// // 重连
|
||
// reconnect()
|
||
// })
|
||
}
|
||
const reconnect = ()=> {
|
||
if(reconnectTimer || isManualClose.value) return
|
||
reconnectTimer = setTimeout(()=>{
|
||
initsocketTask(2)
|
||
},3000)
|
||
}
|
||
const sendMessage = (data) => {
|
||
unref(SocketObj).send(data)
|
||
}
|
||
const closeWebSocket = () => {
|
||
isManualClose.value = true
|
||
if(socketTask){
|
||
// socketTask.close()
|
||
socketTask = null
|
||
uni.closeSocket({})
|
||
}
|
||
}
|
||
|
||
onLoad((params) => {
|
||
crsId.value = params.crsId||'';
|
||
crsNum.value = params.crsNum||'';
|
||
});
|
||
const showTalkingModel = ref(false)
|
||
// 录音
|
||
const audioPath = ref('')
|
||
const audioObj = uni.createInnerAudioContext()
|
||
//#ifdef APP-PLUS
|
||
// app端
|
||
const recordObj = uni.getRecorderManager()
|
||
const initRecord = () => {
|
||
unref(recordObj)?.onStop((res)=>{
|
||
audioPath.value = res.tempFilePath
|
||
})
|
||
}
|
||
const startRecord = () => {
|
||
unref(recordObj).start({format:'mp3'})
|
||
}
|
||
const stopRecord = () => {
|
||
unref(recordObj).stop()
|
||
}
|
||
// #endif
|
||
//#ifndef APP-PLUS
|
||
const recordObj = {}
|
||
const initRecord = () => {}
|
||
// #endif
|
||
onMounted(()=>{
|
||
// getData();
|
||
initRecord?.()
|
||
})
|
||
onShow(()=>{
|
||
getData();
|
||
})
|
||
onHide(()=>{
|
||
closeWebSocket()
|
||
teacherVoiceObj.stop()
|
||
teacherVoiceObj.src = ''
|
||
})
|
||
// onUnmounted(()=>{
|
||
// closeWebSocket()
|
||
// })
|
||
const playRecord = () => {
|
||
audioObj.src = audioPath.value
|
||
unref(audioObj).play()
|
||
}
|
||
// 录音结束
|
||
|
||
// 记录 按下移动位置
|
||
const touchX = ref(0)
|
||
const touchY = ref(0)
|
||
const baseY = ref(0)
|
||
const systemInfo = uni.getSystemInfoSync()
|
||
const widowHeight = systemInfo.windowHeight
|
||
// 计算是否超出按下盒子位置
|
||
const isOut = computed(() => {
|
||
let moveVal = (widowHeight - baseY.value)/140 * 234
|
||
return (widowHeight - moveVal) > touchY.value
|
||
})
|
||
const showOverlayTalking = (obj) => {
|
||
showTalkingModel.value = true
|
||
touchX.value = obj.changedTouches[0].pageX
|
||
touchY.value = obj.changedTouches[0].pageY
|
||
baseY.value = obj.currentTarget.offsetTop
|
||
//#ifdef APP-PLUS
|
||
startRecord()
|
||
// #endif
|
||
}
|
||
const hideOverlayTalking = (obj) => {
|
||
showTalkingModel.value = false
|
||
//#ifdef APP-PLUS
|
||
stopRecord()
|
||
// #endif
|
||
if(!isOut.value){
|
||
console.log('发送')
|
||
setTimeout(()=>{
|
||
uploadVoiceFile(audioPath.value,{}).then(res=>{
|
||
console.log(JSON.parse(res.data))
|
||
}).catch(err=>{
|
||
console.log(err)
|
||
})
|
||
},1000)
|
||
}
|
||
// else{
|
||
// console.log('取消发送')
|
||
// }
|
||
}
|
||
|
||
const handleMove = (obj) => {
|
||
touchX.value = obj.changedTouches[0].pageX
|
||
touchY.value = obj.changedTouches[0].pageY
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss">
|
||
.course-study{
|
||
position: relative;
|
||
display: flex;
|
||
flex-direction: column;
|
||
.top-bg{
|
||
background: linear-gradient(16deg,rgba(234,246,255,0) 0%,#c3e6ff 100%);
|
||
height: 480rpx;
|
||
width: 750rpx;
|
||
}
|
||
.bot-bg{
|
||
// background-color: #F6F8FF;
|
||
background: linear-gradient(136deg,rgba(234,246,255,0) 0%,#c3e6ff 100%);
|
||
width: 750rpx;
|
||
flex: 1;
|
||
}
|
||
.course-study-body{
|
||
position: absolute;
|
||
overflow: hidden;
|
||
left:0;
|
||
top: 0;
|
||
height: 100%;
|
||
width: 100%;
|
||
padding:0 0rpx;
|
||
box-sizing: border-box;
|
||
display: flex;
|
||
flex-direction: column;
|
||
.body-title{
|
||
height: 150rpx;
|
||
text-align: center;
|
||
font-size: 33rpx;
|
||
padding-top: 70rpx;
|
||
line-height: 46rpx;
|
||
box-sizing: border-box;
|
||
font-weight: 500;
|
||
color:#000;
|
||
position: relative;
|
||
// margin-bottom: 23rpx;
|
||
.arrow-icon{
|
||
position: absolute;
|
||
left:36rpx;
|
||
top:80rpx;
|
||
color: #000;
|
||
}
|
||
}
|
||
.talking-list{
|
||
flex:1;
|
||
overflow-y: auto;
|
||
padding: 30rpx;
|
||
background: url(@/static/images/course/study-bg.png) no-repeat;
|
||
background-size: cover;
|
||
border-radius: 30rpx 30rpx 0 0;
|
||
.study-num{
|
||
height: 36rpx;
|
||
line-height: 36rpx;
|
||
text-align: center;
|
||
margin-bottom: 15rpx;
|
||
.color-blue{
|
||
color: #06f;
|
||
display: inline;
|
||
}
|
||
}
|
||
.talking-item{
|
||
margin-bottom: 46rpx;
|
||
}
|
||
}
|
||
.talk-btns{
|
||
height: 150rpx;
|
||
background-color: #fff;
|
||
padding: 25rpx 30rpx 0;
|
||
.touch-btn{
|
||
height: 92rpx;
|
||
box-shadow: 0 8rpx 20rpx 0 rgba(0,0,0,.06);
|
||
text-align: center;
|
||
line-height: 92rpx;
|
||
color: #000;
|
||
font-weight: 400;
|
||
position: relative;
|
||
.talk-btn-icon{
|
||
width: 40rpx;
|
||
height: 40rpx;
|
||
position: absolute;
|
||
right: 40rpx;
|
||
top: 50%;
|
||
margin-top: -20rpx
|
||
}
|
||
}
|
||
}
|
||
.voice-list{
|
||
flex:1;
|
||
overflow-y: auto;
|
||
padding: 10rpx 40rpx 30rpx;
|
||
.voice-item{
|
||
min-height: 246rpx;
|
||
width: 100%;
|
||
overflow: hidden;
|
||
background-color: #fff;
|
||
border-radius: 16rpx;
|
||
display: flex;
|
||
padding: 25rpx;
|
||
position: relative;
|
||
&:not(:last-child){
|
||
margin-bottom: 30rpx;
|
||
}
|
||
.avatar-box{
|
||
width: 160rpx;
|
||
height: 207rpx;
|
||
margin-right: 20rpx;
|
||
border-radius: 16rpx;
|
||
overflow: hidden;
|
||
background-color: #ccc;
|
||
.img-box{
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
}
|
||
.right-cont{
|
||
flex:1;
|
||
.right-name{
|
||
width: 380rpx;
|
||
line-height: 40rpx;
|
||
font-size: 28rpx;
|
||
font-weight: 500;
|
||
margin-bottom: 25rpx;
|
||
word-break: break-all;
|
||
white-space: nowrap;
|
||
text-overflow: ellipsis;
|
||
overflow: hidden;
|
||
}
|
||
.right-desc{
|
||
width: 380rpx;
|
||
line-height: 38rpx;
|
||
font-size: 25rpx;
|
||
color: #999;
|
||
margin-bottom: 12rpx;
|
||
word-break: break-all;
|
||
white-space: nowrap;
|
||
text-overflow: ellipsis;
|
||
overflow: hidden;
|
||
}
|
||
.audio-box{
|
||
width: 90%;
|
||
height: 70rpx;
|
||
line-height: 70rpx;
|
||
background-color: #eaf2ff;
|
||
padding: 15rpx 25rpx;
|
||
overflow: hidden;
|
||
.talking-gif{
|
||
width: 140rpx;
|
||
height: 40rpx;
|
||
// background-color: #ccc;
|
||
background: url(@/static/images/course/voice-gray.png) no-repeat;
|
||
background-size: 100%;
|
||
box-sizing: border-box;
|
||
float: left;
|
||
&.is-play{
|
||
background: url(@/static/images/course/voice-gif.gif) no-repeat;
|
||
background-size: 100%;
|
||
}
|
||
}
|
||
.talking-time{
|
||
float: right;
|
||
text-align: right;
|
||
line-height: 40rpx;
|
||
font-size: 23rpx;
|
||
color: #b1bbd3;
|
||
}
|
||
.play-btn{
|
||
float:right;
|
||
width: 35rpx;
|
||
height: 40rpx;
|
||
padding-top:2rpx;
|
||
line-height: 35rpx;
|
||
margin-left: 20rpx;
|
||
}
|
||
}
|
||
}
|
||
.radio-box{
|
||
position: absolute;
|
||
right: 20rpx;
|
||
top: 20rpx;
|
||
}
|
||
}
|
||
.start-btns{
|
||
margin-top:20rpx;
|
||
height: 80rpx;
|
||
width: 100%;
|
||
background-color: #06f;
|
||
color:#fff;
|
||
text-align: center;
|
||
line-height: 80rpx;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
.overlay-box{
|
||
display: flex;
|
||
flex-direction: column-reverse;
|
||
// justify-content: flex-end;
|
||
width: 100%;
|
||
height: 100%;
|
||
.circle-bg-box{
|
||
height: 234rpx;
|
||
background: url(@/static/images/course/talking.png) no-repeat;
|
||
background-size: cover;
|
||
position: relative;
|
||
.icon-box{
|
||
position: absolute;
|
||
left:50%;
|
||
top: 50%;
|
||
margin-left: -17rpx;
|
||
margin-top: -24rpx;
|
||
width: 34rpx;
|
||
height: 48rpx;
|
||
image{
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
}
|
||
}
|
||
.tips-box{
|
||
height: 40rpx;
|
||
line-height: 40rpx;
|
||
color:#c0c0c0;
|
||
text-align: center;
|
||
margin: 12rpx 0 24rpx;
|
||
}
|
||
.btns-box{
|
||
height: 270rpx;
|
||
// padding: 0 120rpx;
|
||
width: 154rpx;
|
||
margin: 0 auto 100rpx;
|
||
overflow: hidden;
|
||
font-size: 28rpx;
|
||
text-align: center;
|
||
line-height: 116rpx;
|
||
.close-btn{
|
||
float: left;
|
||
height: 100%;
|
||
width: 154rpx;
|
||
overflow: hidden;
|
||
color:#999;
|
||
&.is-out{
|
||
color:#fff;
|
||
}
|
||
}
|
||
.translate-btn{
|
||
float: right;
|
||
height: 100%;
|
||
width: 154rpx;
|
||
overflow: hidden;
|
||
color:#999;
|
||
}
|
||
.btn-box{
|
||
width: 100%;
|
||
height: 154rpx;
|
||
}
|
||
}
|
||
.talk-dialog{
|
||
width: 506rpx;
|
||
height: 234rpx;
|
||
margin: 0 auto;
|
||
background-color: #06f;
|
||
}
|
||
}
|
||
|
||
</style>
|