Merge branch 'develop' of http://25.13.9.101:9000/K17_AITS/tra-app into develop

This commit is contained in:
田岩
2025-07-01 21:05:47 +08:00
9 changed files with 208 additions and 220 deletions
+21 -2
View File
@@ -79,7 +79,17 @@ export const commonUploadVoiceFile = (file, url, data = {}, fileKey='voice') =>
name: fileKey,
formData: data,
success(result) {
resolve(result)
try {
const res = JSON.parse(result.data);
console.log(res)
if (res.rtnCode === '0000') {
resolve(result);
} else {
reject(new Error(res.message));
}
} catch (error) {
reject(new Error('Failed to parse response'));
}
},
fail(error) {
reject(new Error('Upload failed'));
@@ -104,7 +114,16 @@ export const uploadVoiceFile = (file) => {
filePath: file,
name: 'voice',
success(result) {
resolve(result)
try {
const res = JSON.parse(result.data);
if (res.rtnCode === '0000') {
resolve(result);
} else {
reject(new Error(res.message));
}
} catch (error) {
reject(new Error('Failed to parse response'));
}
},
fail(error) {
reject(new Error('Upload failed'));
+1 -1
View File
@@ -1,6 +1,6 @@
import App from './App'
import { createSSRApp } from 'vue'
import pinia from './store'
import pinia from './store.js'
export function createApp() {
const app = createSSRApp(App)
+72 -53
View File
@@ -43,12 +43,12 @@
class="icon"
src="@/static/images/course/play.png"
style="width: 100%;height: 100%;"
@click="playVoice"
v-show="props.activeVoiceId !== dataInfo.id"
@click="playVoice()"
v-show="!isPlaying"
></image>
<image
class="icon"
v-show="props.activeVoiceId === dataInfo.id"
v-show="isPlaying"
src="@/static/images/course/pause.png"
style="width: 100%;height: 100%;"
@click="pauseVoice"
@@ -65,7 +65,7 @@
<view class="user-info">
<view class="avatar-box">
<!-- <ImagePreview class="img-box" :imgId="''"></ImagePreview> -->
<img class="img-box" :src="userInfo.imageAddr" alt="" fit="contain" style="width: 100%; height: 100%;"/>
<img class="img-box" :src="userInfo.imagePath" alt="" fit="contain" style="width: 100%; height: 100%;"/>
</view>
<view class="ai-name">
{{ userInfo.userName }}
@@ -85,21 +85,21 @@
</view> -->
<view class="play-btn">
<image class="icon"
@click="playQnsVoice('qns', item)"
v-show="props.activeVoiceId !== dataInfo.id"
@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="props.activeVoiceId === dataInfo.id"
v-show="isPlaying"
@click="pauseVoice"
src="@/static/images/course/stop-blue.png"
style="width: 100%;height: 100%;"
></image>
</view>
<view class="icon text-btn" @click="translateVoice(item)"></view>
<view class="icon text-right-btn" @click="translateVoice(item)">完成</view>
<view class="icon text-right-btn" @click="translateVoice(item)">撤回</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>
@@ -107,7 +107,7 @@
<view class="user-info">
<view class="avatar-box">
<!-- <ImagePreview class="img-box" :imgId="''"></ImagePreview> -->
<img class="img-box" :src="userInfo.imageAddr" alt="" fit="contain" style="width: 100%; height: 100%;"/>
<img class="img-box" :src="userInfo.imagePath" alt="" fit="contain" style="width: 100%; height: 100%;"/>
</view>
<view class="ai-name">
{{ userInfo.userName }}
@@ -123,10 +123,10 @@
</template>
<script setup>
import { watch, unref, ref, toRefs, computed } from 'vue'
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 } from "@/api/common.js"
import { downloadVoiceFile, uploadVoiceFile, } from "@/api/common.js"
import { studyParagraphOverApi } from "@/api/study.js"
import ChartFour from "./chartFour.vue"
import ChartThree from "./chartThree.vue"
@@ -144,81 +144,101 @@ import { getUserInfo } from '@/common/common';
activeVoiceId:{
default:'',
type: String
}
},
isLast:{
default:false,
type: Boolean
},
isPlaying:{
default:false,
type: Boolean
},
})
const { type, dataInfo, activeVoiceId } = toRefs(props)
const { type, dataInfo, activeVoiceId, isPlaying, isLast} = toRefs(props)
const emit = defineEmits(['changePlay', 'nextQuestion'])
const talkVoiceObj = uni.createInnerAudioContext()
// 声音播放初始化
const talkVoiceObj = ref(uni.createInnerAudioContext())
const userInfo = computed(() => getUserInfo())
watch(() => props.activeVoiceId,(val) => {
if(val && (val !== unref(dataInfo).id)){
talkVoiceObj?.stop()
watch(() => props.isPlaying,(val) => {
if(!val){
talkVoiceObj.value?.stop()
}
},{
deep: true,
immediate: true
})
talkVoiceObj.onEnded(()=>{
// talkVoiceObj.src = ''
emit('changePlay',unref(dataInfo)?.id)
talkVoiceObj.value.onEnded(()=>{
emit('changePlay', '')
talkVoiceObj.value.src = ''
})
talkVoiceObj.onError(()=>{
emit('changePlay',unref(dataInfo)?.id)
talkVoiceObj.value.onError(()=>{
emit('changePlay', '')
talkVoiceObj.value.src = ''
})
const pauseVoice = () => {
if(!talkVoiceObj) return
talkVoiceObj.pause()
emit('changePlay',unref(dataInfo)?.id)
if(!talkVoiceObj.value) return
talkVoiceObj.value.pause()
emit('changePlay', '')
}
// 重播
const restartPlay = () => {
console.log(userInfo)
if(talkVoiceObj.src){
talkVoiceObj.seek(0)
talkVoiceObj.play()
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(talkVoiceObj.src){
talkVoiceObj.play()
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)?.talkingContent,
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=>{
talkVoiceObj.src = res.tempFilePath
talkVoiceObj.play()
itemVoice.value = res.tempFilePath
talkVoiceObj.value.src = res.tempFilePath
talkVoiceObj.value.play()
})
}
}
const playQnsVoice = (readType = 'qns', item) =>{
if(item.type === 2){
if(type.value === 2){
// 文本转语音
playVoice(readType)
return
}
// readType 阅读内容类型pgrh段落/qns问题
emit('changePlay', unref(dataInfo)?.id)
if(talkVoiceObj.src){
talkVoiceObj.play()
}else{
talkVoiceObj.src = item.talkingVoice
talkVoiceObj.play()
}
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
@@ -226,14 +246,13 @@ import { getUserInfo } from '@/common/common';
}
const showNextBtn = ref(true)
const goOnGetQues = () => {
// studyParagraphOverApi({
// pgrphId:unref(dataInfo)?.pgrphId
// }).then(res=>{
// showNextBtn.value = false
// emit('nextQuestion', unref(dataInfo))
// })
showNextBtn.value = false
emit('nextQuestion', unref(dataInfo))
studyParagraphOverApi({
pgrphId:unref(dataInfo)?.pgrphId,
logId:unref(dataInfo)?.logId,
}).then(res=>{
showNextBtn.value = false
emit('nextQuestion', unref(dataInfo))
})
}
</script>
@@ -449,7 +468,7 @@ import { getUserInfo } from '@/common/common';
background-size: 100%;
margin-bottom: 16rpx;
&.is-play{
background: url(@/static/images/course/voice-gif.gif) no-repeat;
background: url(@/static/images/course/voice-white.gif) no-repeat;
background-size: 100%;
}
}
+76 -39
View File
@@ -33,26 +33,31 @@
</view>
</view>
<view class="course-study-body" v-show="step === 2" @click="playRecord">
<view class="body-title">
<view class="body-title" @click="scrollToBottomNow">
<uv-icon class="arrow-icon" name="arrow-left" @click="common.navigateBack()"/>
课程学习
</view>
<view class="talking-list">
<scroll-view class="talking-list" :scroll-y="true">
<!-- <view class="talking-list-cont" id="scroll-box" ref="scrollBoxRef"> -->
<view class="study-num">
<view class="color-blue">{{ showOrder }}</view> / {{ pgrphNum }}
</view>
<TalkingItem
class="talking-item"
v-for="item in talkingList"
v-for="item,index in talkingList"
:key="'item' + item.id"
:type="item.type"
:dataInfo="item"
:activeVoiceId="activeVoiceTalkingItemId"
:isPlaying="activeVoiceTalkingItemId === item.id"
@changePlay="changePlayItemId"
@nextQuestion="nextQuestionSuccess"
:id="'item' + item.id"
:isLast="talkingList.length === (index+1)"
></TalkingItem>
<!-- <video v-if="videoUrlShow" :src="videoUrlShow" controls></video> -->
</view>
<!-- </view> -->
</scroll-view>
<view class="talk-btns">
<view
class="touch-btn"
@@ -95,7 +100,7 @@
</template>
<script setup>
import { reactive, ref, computed, unref, onMounted, onUnmounted, getCurrentInstance } from 'vue';
import { reactive, ref, computed, unref, onMounted, onUnmounted, getCurrentInstance, nextTick } from 'vue';
import { onLoad,onShow,onHide } from '@dcloudio/uni-app';
import {
previewFile,
@@ -135,28 +140,24 @@ const talkingList = ref([])
const activeVoiceTalkingItemId = ref('')
const answerValue = ref('')
const inputRef = ref('')
// 初始化数据
const getData = async () => {
try{
const { body } = await getCourseStudyInfoApi({crsId: crsId.value})
console.log(3)
initsocketTask()
teacherList.value = body.teacheList.map(t=>({
...t,
voicePath: '',
voiceObj: null
}))
if(body.stdyProgressFlag === 'N'){
step.value = 1
teacherList.value = body.teacheList.map(t=>({
...t,
voicePath: '',
voiceObj: null
}))
}else{
console.log(2)
common.show('提示信息', '此门课程已有学习历史,请选择是否继续学习?',true, '重新开始','继续学习').then((res) => {
console.log(res)
if(res) {
// 继续学习 获取学习历史接口
// 滚动到最下面
scrollToBottomNow()
choiceTeacher.value = body.choiceTeacher
sendMessage({
"commond" : 'STDY',
@@ -205,18 +206,19 @@ const changePlayItemId = (id) => {
activeVoiceTalkingItemId.value = id
}
}
// 选择的教师对象信息
const choiceTeacherInfo = computed(()=>{
return teacherList.value.find(t=> t.teacherNo === choiceTeacher.value)
})
const keyboardIsShow = ref(false)
// 发送文本
const showKeyboard = () => {
if(keyboardIsShow.value){
if(answerValue.value){
let _data = {
qnsId: unref(lastTalkingInfo).qnsId,// 问题ID 必输项:true 类型:String
pgrphId: unref(lastTalkingInfo).pgrphId,// 段落ID 必输项:true 类型:String
qstLogId: unref(qstLogId),// 段落ID 必输项:true 类型:String
crsId: crsId.value,// 课程ID 必输项:true 类型:String
stdyBatch: stdyBatch.value,// 学习批次 必输项:true 类型:String
ansterContent: answerValue.value // 回答文本内容 必输项:false 类型:String
@@ -227,9 +229,11 @@ const showKeyboard = () => {
talkingList.value.push({
crsId: crsId.value,
pgrphId: unref(lastTalkingInfo).pgrphId,
qnsId: unref(lastTalkingInfo).qnsId,
talkingContent: answerValue.value,
talkingVoice: '',
type: 2,
userInfo: {...choiceTeacherInfo.value},
id: _id
})
scrollToBottomNow()
@@ -292,13 +296,13 @@ const nextStep = () => {
const pgrphNum = ref(1)
const showOrder = ref(0)
// 获取段落
const getGraphInfoUnStudy = () => {
queryTraStdyBatchWaitParagraphInfoApi({
crsId: crsId.value,
stdyId: unref(stdyId),
stdyBatch: unref(stdyBatch)
}).then(res=>{
console.log(res)
let _id = new Date().getTime() + 'id'
lastTalkingInfo.value = {
...res.body,
@@ -310,19 +314,21 @@ const getGraphInfoUnStudy = () => {
pgrphNum.value = res.body.pgrphNum
showOrder.value = res.body.showOrder
logId.value = res.body.logId || ''
scrollToBottomNow()
})
}
const lastTalkingInfo = ref({})
const qstLogId = ref('')
// 获取问题
const getQuestionInfoUnStudy = (pgrphId) => {
queryTraStdyBatchWaitQuestionInfoApi({
crsId: crsId.value,
stdyId: unref(stdyId),
stdyBatch: unref(stdyBatch),
logId: unref(logId),
pgrphId
}).then(res=>{
console.log(res)
let _id = new Date().getTime() + 'id'
lastTalkingInfo.value = {
...res.body,
@@ -331,6 +337,7 @@ const getQuestionInfoUnStudy = (pgrphId) => {
id: _id
}
talkingList.value.push(lastTalkingInfo.value)
qstLogId.value = res.body.qstLogId
scrollToBottomNow()
})
}
@@ -349,15 +356,26 @@ const nextQuestionSuccess = (info) => {
// getGraphInfoUnStudy()
getQuestionInfoUnStudy(info.pgrphId)
}
const scrollBoxRef = ref()
const scrollToBottomNow = () => {
let _arr = talkingList.value || []
if(_arr.length && _arr.length > 0){
let _last = talkingList.value[talkingList.value.length - 1]
uni.pageScrollTo({
selector: '#item' + _last.id,
duration: 300
})
}
nextTick(()=>{
setTimeout(() => {
// let scrollElem = scrollBoxRef.value.$el
// // let scrollElem = document.getElementById('scroll-box')
// scrollElem.scrollTo({
// top: scrollElem.scrollHeight, behavior: 'smooth'
// })
let _arr = talkingList.value || []
if(_arr.length && _arr.length > 0){
let _last = talkingList.value[talkingList.value.length - 1]
console.log('#item' + _last.id)
uni.pageScrollTo({
selector: '#item' + _last.id,
duration: 300
})
}
},200)
})
}
const reconnectTimer = null // 重连timer
@@ -365,7 +383,8 @@ const isManualClose = ref(false) // 是否手动关闭
const stdyId = ref('')
const stdyBatch = ref('')
const logId = ref('')
// ws 初始化逻辑
const initsocketTask = () => {
socketStore.creatSocket()
unref(SocketObj).on('message', (res)=>{
@@ -374,6 +393,7 @@ const initsocketTask = () => {
if(commond === 'STDYBTH' && !stdyId.value){
stdyId.value = body.stdyId || ''
stdyBatch.value = body.stdyBatch || ''
logId.value = body.logId || ''
getGraphInfoUnStudy()
}
}else{
@@ -400,6 +420,7 @@ const closeWebSocket = () => {
uni.closeSocket({})
}
}
// ws 结束
onLoad((params) => {
crsId.value = params.crsId||'';
@@ -421,7 +442,7 @@ const startRecord = () => {
unref(recordObj).start({format:'mp3'})
}
const stopRecord = () => {
unref(recordObj).stop()
unref(recordObj)?.stop?.()
}
// #endif
//#ifndef APP-PLUS
@@ -429,20 +450,20 @@ const recordObj = {}
const initRecord = () => {}
// #endif
onMounted(()=>{
// getData();
getData();
initRecord?.()
})
onShow(()=>{
getData();
// getData();
})
onHide(()=>{
closeWebSocket()
teacherVoiceObj.stop()
// onHide(()=>{
// teacherVoiceObj.stop()
// teacherVoiceObj.src = ''
// })
onUnmounted(()=>{
teacherVoiceObj?.stop?.()
teacherVoiceObj.src = ''
})
// onUnmounted(()=>{
// closeWebSocket()
// })
const playRecord = () => {
audioObj.src = audioPath.value
unref(audioObj).play()
@@ -475,11 +496,11 @@ const hideOverlayTalking = (obj) => {
stopRecord()
// #endif
if(!isOut.value){
// audioPath.value /traStdyInfo/studyAnswerVoiceUp
setTimeout(()=>{
let _data = {
qnsId: unref(lastTalkingInfo).qnsId,// 问题ID 必输项:true 类型:String
pgrphId: unref(lastTalkingInfo).pgrphId,// 段落ID 必输项:true 类型:String
qstLogId: unref(qstLogId),// 段落ID 必输项:true 类型:String
crsId: crsId.value,// 课程ID 必输项:true 类型:String
stdyBatch: stdyBatch.value,// 学习批次 必输项:true 类型:String
ansterContent: '',// 回答文本内容 必输项:false 类型:String
@@ -490,28 +511,39 @@ const hideOverlayTalking = (obj) => {
talkingList.value.push({
crsId: crsId.value,
pgrphId: unref(lastTalkingInfo).pgrphId,
qnsId: unref(lastTalkingInfo).qnsId,
talkingContent: '',
talkingVoice: audioPath.value,
type: 1,
userInfo: {...choiceTeacherInfo.value},
id: _id
})
// #endif
//#ifdef APP-PLUS
console.log(222)
commonUploadVoiceFile(audioPath.value, '/trastudy/traStdyInfo/studyAnswerVoiceUp', _data).then(res=>{
console.log(JSON.parse(res.data))
console.log(res.data)
let _res = JSON.parse(res.data)
if(_res.rtnCode === '0000'){
let _id = new Date().getTime() + 'id'
talkingList.value.push({
crsId: crsId.value,
pgrphId: unref(lastTalkingInfo).pgrphId,
qnsId: unref(lastTalkingInfo).qnsId,
talkingContent: '',
talkingVoice: audioPath.value,
type: 1,
userInfo: {...choiceTeacherInfo.value},
id: _id
})
scrollToBottomNow()
}
}).catch(err=>{
uni.showToast({
title: err||'系统异常,请联系管理员',
icon: "none",
duration:1000
});
})
// #endif
},1000)
@@ -576,6 +608,11 @@ const handleMove = (obj) => {
background: url(@/static/images/course/study-bg.png) no-repeat;
background-size: cover;
border-radius: 30rpx 30rpx 0 0;
box-sizing: border-box;
width: 100%;
.talking-list-cont{
padding: 0;
}
.study-num{
height: 36rpx;
line-height: 36rpx;
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

+1 -1
View File
@@ -1,3 +1,3 @@
import { createPinia } from 'pinia';
const store = createPinia();;
const store = createPinia();
export default store;
+4 -4
View File
@@ -19,11 +19,11 @@ export const useSocketStore = defineStore('socketStore',{
if(this.SocketObj && this.SocketObj?.close) return
this.SocketObj = new SocketClass(_baseUrl + '/trastudy/trasoket/open?summary=' + getToken());
},
closeSocket: () => {
if(!this.SocketObj || !this.SocketObj?.close) return
this.SocketObj && this.SocketObj?.close?.()
closeSocket() {
if(!(this.SocketObj && this.SocketObj?.close)) return
this.SocketObj && this.SocketObj.close()
},
setOnmessageCb: (cb) => {
setOnmessageCb(cb){
if(!this.SocketObj || !this.SocketObj?.on) return
this.SocketObj.on('message', cb)
}