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

This commit is contained in:
田岩
2025-06-30 20:29:03 +08:00
5 changed files with 359 additions and 81 deletions
+25
View File
@@ -62,6 +62,31 @@ export function downloadFile(id) {
});
});
}
// 上传音频 通用
export const commonUploadVoiceFile = (file, url, data = {}, fileKey='voice') => {
const base_url = get_base_url(url);
const token = getToken();
const header = {
"Content-Type": "multipart/form-data; charset=UTF-8",
};
if (token)
header['summary'] = token
return new Promise((resolve, reject) => {
uni.uploadFile({
url: `${base_url}${url}`,
header: header,
filePath: file,
name: fileKey,
formData: data,
success(result) {
resolve(result)
},
fail(error) {
reject(new Error('Upload failed'));
}
});
});
};
// 上传音频 翻译文本
export const uploadVoiceFile = (file) => {
const url = '/trastudy/traStdyInfo/audioTranscriptions';
+25
View File
@@ -19,6 +19,15 @@ export const queryTraStdyBatchWaitParagraphInfoApi = (data) => {
data
});
};
// 重新开始 删除学习信息
export const deleteTraStdyInfoByIdApi = (data) => {
return request({
url: base_url + '/traStdyInfo/deleteTraStdyInfoById',
method: 'post',
data
});
};
// 学习段落完成
export const studyParagraphOverApi = (data) => {
return request({
@@ -27,6 +36,22 @@ export const studyParagraphOverApi = (data) => {
data
});
};
// 回答问题 /traStdyInfo/studyAnswerTextUp
export const studyAnswerTextUpApi = (data) => {
return request({
url: base_url + '/traStdyInfo/studyAnswerTextUp',
method: 'post',
data
});
};
// 获取问题信息/traStdyInfo/queryTraStdyBatchWaitQuestionInfo
export const queryTraStdyBatchWaitQuestionInfoApi = (data) => {
return request({
url: base_url + '/traStdyInfo/queryTraStdyBatchWaitQuestionInfo',
method: 'post',
data
});
};
// 上传语音翻译i文本 /traStdyInfo/audioTranscriptions
export const audioTranscriptionsApi = (data) => {
return request({
+104 -23
View File
@@ -1,5 +1,6 @@
<!-- type: 3, 段落信息 4 问题信息 5 统计信息 1语音回答 2 文本回答 0纯文本信息 不翻译 -->
<template>
<view class="ai-answer" v-if="type === 0">
<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>
@@ -9,18 +10,24 @@
<view class="ai-name-desc">{{dataInfo?.userInfo?.teacherDesc}}</view>
</view>
</view>
<view class="talking-info">
<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">
<view class="talking-text" v-if="[3, 5, 6].indexOf(type) >= 0">
{{ dataInfo.pgrphContent }}
</view>
<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>
<ChartFour/>
<ChartThree/>
<!-- <ChartFour/>
<ChartThree/> -->
<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">
@@ -49,12 +56,12 @@
</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">继续
<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="type === 1">
<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> -->
@@ -66,10 +73,10 @@
</view>
<view class="talking-info">
<view class="talking-gif"></view>
<view :class="['talking-gif', activeVoiceId === dataInfo.id?'is-play':'']"></view>
<view class="talking-cont">
<view class="talking-text">
{{ dataInfo.talkingContent }}
{{ dataInfo.talkingContent }}
</view>
</view>
<view class="btns-cont">
@@ -77,13 +84,26 @@
<image class="icon" src="@/static/images/course/replay.png" style="width: 100%;height: 100%;"></image>
</view> -->
<view class="play-btn">
<image class="icon" v-if="false" src="@/static/images/course/play-blue.png" style="width: 100%;height: 100%;"></image>
<image class="icon" src="@/static/images/course/stop-blue.png" style="width: 100%;height: 100%;"></image>
<image class="icon"
@click="playQnsVoice('qns', item)"
v-show="props.activeVoiceId !== dataInfo.id"
src="@/static/images/course/play-blue.png"
style="width: 100%;height: 100%;"
></image>
<image class="icon"
v-show="props.activeVoiceId === dataInfo.id"
@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>
</view>
</view>
<view class="user-answer" v-else-if="type===2">
<view class="user-answer" v-else-if="type === 0">
<view class="user-info">
<view class="avatar-box">
<!-- <ImagePreview class="img-box" :imgId="''"></ImagePreview> -->
@@ -143,6 +163,9 @@ import { getUserInfo } from '@/common/common';
// talkVoiceObj.src = ''
emit('changePlay',unref(dataInfo)?.id)
})
talkVoiceObj.onError(()=>{
emit('changePlay',unref(dataInfo)?.id)
})
const pauseVoice = () => {
if(!talkVoiceObj) return
talkVoiceObj.pause()
@@ -157,6 +180,7 @@ import { getUserInfo } from '@/common/common';
playVoice()
}
}
const playVoice = (readType = 'pgth') =>{
// readType 阅读内容类型pgrh段落/qns问题
emit('changePlay', unref(dataInfo)?.id)
@@ -169,7 +193,7 @@ import { getUserInfo } from '@/common/common';
qnsId: unref(dataInfo)?.qnsId || '',
readType: readType,
teachNo: unref(dataInfo)?.userInfo?.teacherNo, // 必填
content: unref(dataInfo)?.pgrphContent,
content: unref(dataInfo)?.pgrphContent || unref(dataInfo)?.talkingContent,
}
let _url = '/trastudy/traStdyInfo/readContent?' + Object.keys(_params).map(t=>{
return encodeURIComponent(t) + '=' + encodeURIComponent(_params[t])
@@ -180,19 +204,43 @@ import { getUserInfo } from '@/common/common';
})
}
}
const playQnsVoice = (readType = 'qns', item) =>{
if(item.type === 2){
playVoice(readType)
return
}
// readType 阅读内容类型pgrh段落/qns问题
emit('changePlay', unref(dataInfo)?.id)
if(talkVoiceObj.src){
talkVoiceObj.play()
}else{
talkVoiceObj.src = item.talkingVoice
talkVoiceObj.play()
}
}
const translateVoice = (item) =>{
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
}).then(res=>{
showNextBtn.value = false
emit('nextQuestion', unref(dataInfo))
})
// studyParagraphOverApi({
// pgrphId:unref(dataInfo)?.pgrphId
// }).then(res=>{
// showNextBtn.value = false
// emit('nextQuestion', unref(dataInfo))
// })
showNextBtn.value = false
emit('nextQuestion', unref(dataInfo))
}
</script>
<style lang="scss">
.ai-answer{
width: 100%;
overflow: hidden;
.user-info{
overflow: hidden;
.avatar-box{
@@ -226,6 +274,11 @@ import { getUserInfo } from '@/common/common';
padding: 20rpx;
border-radius: 15rpx;
background-color: #fff;
overflow: hidden;
&.talking-info-less{
float: left;
min-width: 400rpx;
}
.talking-gif{
width: 140rpx;
height: 38rpx;
@@ -242,6 +295,7 @@ import { getUserInfo } from '@/common/common';
font-size: 28rpx;
line-height: 48rpx;
margin-bottom: 20rpx;
word-break: break-all;
}
.cont-img-box{
width: 433rpx;
@@ -350,6 +404,7 @@ import { getUserInfo } from '@/common/common';
}
.user-answer{
overflow: hidden;
width: 100%;
.user-info{
overflow: hidden;
.avatar-box{
@@ -403,6 +458,7 @@ import { getUserInfo } from '@/common/common';
font-size: 28rpx;
line-height: 48rpx;
margin-bottom: 20rpx;
word-break: break-all;
}
}
.btns-cont{
@@ -424,6 +480,31 @@ import { getUserInfo } from '@/common/common';
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;
+204 -53
View File
@@ -39,7 +39,7 @@
</view>
<view class="talking-list">
<view class="study-num">
<view class="color-blue">1</view> / 20
<view class="color-blue">{{ showOrder }}</view> / {{ pgrphNum }}
</view>
<TalkingItem
class="talking-item"
@@ -59,7 +59,15 @@
@touchstart="showOverlayTalking"
@touchend="hideOverlayTalking"
@touchmove="handleMove"
v-if="!keyboardIsShow"
>按住说话
</view>
<view class="touch-btn" v-if="keyboardIsShow">
<uv-input class="input-box" ref="inputRef" v-model.trim="answerValue"
placeholderStyle='color:#999999;font-size:26rpx' placeholder="请输入答案">
</uv-input>
</view>
<view class="talk-btn-box" @click.stop="showKeyboard">
<image class="talk-btn-icon" src="/src/static/images/course/jianpan.png" mode=""></image>
</view>
</view>
@@ -87,12 +95,25 @@
</template>
<script setup>
import { reactive, ref, computed, unref, onMounted, onUnmounted } from 'vue';
import { reactive, ref, computed, unref, onMounted, onUnmounted, getCurrentInstance } from 'vue';
import { onLoad,onShow,onHide } from '@dcloudio/uni-app';
import { previewFile, downloadFile, downloadVoiceFile, uploadVoiceFile } from "@/api/common.js"
import {
previewFile,
downloadFile,
commonUploadVoiceFile,
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 { get_base_url, goto_login_fun } from '@/api/request'
import {
getCourseStudyInfoApi,
queryTraStdyBatchWaitParagraphInfoApi,
audioTranscriptionsApi,
deleteTraStdyInfoByIdApi,
queryTraStdyBatchWaitQuestionInfoApi,
studyAnswerTextUpApi
} 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'
@@ -103,6 +124,7 @@ import { storeToRefs } from 'pinia'
const socketStore = useSocketStore()
const { SocketObj } = storeToRefs(socketStore)
const { proxy } = getCurrentInstance()
const crsNum = ref('');
const crsId = ref('');
const step = ref(0)
@@ -111,11 +133,14 @@ const choiceTeacher = ref('')
const videoUrlShow = ref('')
const talkingList = ref([])
const activeVoiceTalkingItemId = ref('')
const answerValue = ref('')
const inputRef = ref('')
const getData = async () => {
try{
const { body } = await getCourseStudyInfoApi({crsId: crsId.value})
initsocketTask(1)
console.log(3)
initsocketTask()
if(body.stdyProgressFlag === 'N'){
step.value = 1
teacherList.value = body.teacheList.map(t=>({
@@ -124,6 +149,7 @@ const getData = async () => {
voiceObj: null
}))
}else{
console.log(2)
common.show('提示信息', '此门课程已有学习历史,请选择是否继续学习?',true, '重新开始','继续学习').then((res) => {
console.log(res)
if(res) {
@@ -133,22 +159,33 @@ const getData = async () => {
choiceTeacher.value = body.choiceTeacher
sendMessage({
"commond" : 'OPEN',
"commond" : 'STDY',
"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
}))
// 删除学习信息
deleteTraStdyInfoByIdApi({
id:''
}).then(res=>{
step.value = 1
teacherList.value = body.teacheList.map(t=>({
...t,
voicePath: '',
voiceObj: null
}))
}).catch(t=>{
step.value = 1
teacherList.value = body.teacheList.map(t=>({
...t,
voicePath: '',
voiceObj: null
}))
})
}
}).finally(() => {})
}
@@ -172,6 +209,42 @@ const changePlayItemId = (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
crsId: crsId.value,// 课程ID 必输项:true 类型:String
stdyBatch: stdyBatch.value,// 学习批次 必输项:true 类型:String
ansterContent: answerValue.value // 回答文本内容 必输项:false 类型:String
}
studyAnswerTextUpApi(_data).then(res=>{
if(res.rtnCode === '0000'){
let _id = new Date().getTime() + 'id'
talkingList.value.push({
crsId: crsId.value,
pgrphId: unref(lastTalkingInfo).pgrphId,
talkingContent: answerValue.value,
talkingVoice: '',
type: 2,
id: _id
})
scrollToBottomNow()
answerValue.value = ''
keyboardIsShow.value = !keyboardIsShow.value
}
})
}else{
keyboardIsShow.value = !keyboardIsShow.value
}
}else{
keyboardIsShow.value = !keyboardIsShow.value
}
// inputRef.value.focus?.()
}
const activeVoiceTeachNo = ref('')
const teacherVoiceObj = uni.createInnerAudioContext()
@@ -201,14 +274,13 @@ const nextStep = () => {
if(choiceTeacher.value){
teacherVoiceObj.destroy()
sendMessage({
"commond" : 'OPEN',
"commond" : 'STDY',
"body" : {
crsId: crsId.value,
teacherNo: choiceTeacher.value
}
})
step.value = 2
getGraphInfoUnStudy()
}else{
uni.showToast({
title: '请选择一位教师!',
@@ -217,19 +289,52 @@ const nextStep = () => {
});
}
}
const pgrphNum = ref(1)
const showOrder = ref(0)
const getGraphInfoUnStudy = () => {
queryTraStdyBatchWaitParagraphInfoApi({crsId: crsId.value}).then(res=>{
queryTraStdyBatchWaitParagraphInfoApi({
crsId: crsId.value,
stdyId: unref(stdyId),
stdyBatch: unref(stdyBatch)
}).then(res=>{
console.log(res)
let _id = new Date().getTime() + 'id'
talkingList.value.push({
lastTalkingInfo.value = {
...res.body,
type: 0,
type: 3,
userInfo: {...choiceTeacherInfo.value},
id: _id
})
}
talkingList.value.push(lastTalkingInfo.value)
pgrphNum.value = res.body.pgrphNum
showOrder.value = res.body.showOrder
scrollToBottomNow()
})
}
const lastTalkingInfo = ref({})
// 获取问题
const getQuestionInfoUnStudy = (pgrphId) => {
queryTraStdyBatchWaitQuestionInfoApi({
crsId: crsId.value,
stdyId: unref(stdyId),
stdyBatch: unref(stdyBatch),
pgrphId
}).then(res=>{
console.log(res)
let _id = new Date().getTime() + 'id'
lastTalkingInfo.value = {
...res.body,
type: 4,
userInfo: {...choiceTeacherInfo.value},
id: _id
}
talkingList.value.push(lastTalkingInfo.value)
scrollToBottomNow()
})
}
// 继续 执行方法
const nextQuestionSuccess = (info) => {
let _id = new Date().getTime() + 'id'
@@ -237,14 +342,16 @@ const nextQuestionSuccess = (info) => {
crsId: info.crsId,
pgrphId: info.pgrphId,
talkingContent: '继续',
type: 2,
type: 0,
id: _id
})
scrollToBottomNow()
getGraphInfoUnStudy()
// getGraphInfoUnStudy()
getQuestionInfoUnStudy(info.pgrphId)
}
const scrollToBottomNow = () => {
if(talkingList.value.length > 0){
let _arr = talkingList.value || []
if(_arr.length && _arr.length > 0){
let _last = talkingList.value[talkingList.value.length - 1]
uni.pageScrollTo({
selector: '#item' + _last.id,
@@ -256,31 +363,30 @@ const scrollToBottomNow = () => {
const reconnectTimer = null // 重连timer
const isManualClose = ref(false) // 是否手动关闭
const initsocketTask = ($step) => {
const stdyId = ref('')
const stdyBatch = ref('')
const initsocketTask = () => {
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()
// })
unref(SocketObj).on('message', (res)=>{
const { rtnCode, body, commond } = JSON.parse(res.data)
if(rtnCode === '0000'){
if(commond === 'STDYBTH' && !stdyId.value){
stdyId.value = body.stdyId || ''
stdyBatch.value = body.stdyBatch || ''
getGraphInfoUnStudy()
}
}else{
if(['0005', 'QQ0005'].includes(res.rtnCode)) {
goto_login_fun();
}
}
})
}
const reconnect = ()=> {
if(reconnectTimer || isManualClose.value) return
reconnectTimer = setTimeout(()=>{
initsocketTask(2)
initsocketTask()
},3000)
}
const sendMessage = (data) => {
@@ -307,7 +413,7 @@ const audioObj = uni.createInnerAudioContext()
// app端
const recordObj = uni.getRecorderManager()
const initRecord = () => {
unref(recordObj)?.onStop((res)=>{
unref(recordObj)?.onStop((res, duration)=>{
audioPath.value = res.tempFilePath
})
}
@@ -369,18 +475,47 @@ const hideOverlayTalking = (obj) => {
stopRecord()
// #endif
if(!isOut.value){
console.log('发送')
// audioPath.value /traStdyInfo/studyAnswerVoiceUp
setTimeout(()=>{
uploadVoiceFile(audioPath.value,{}).then(res=>{
let _data = {
qnsId: unref(lastTalkingInfo).qnsId,// 问题ID 必输项:true 类型:String
pgrphId: unref(lastTalkingInfo).pgrphId,// 段落ID 必输项:true 类型:String
crsId: crsId.value,// 课程ID 必输项:true 类型:String
stdyBatch: stdyBatch.value,// 学习批次 必输项:true 类型:String
ansterContent: '',// 回答文本内容 必输项:false 类型:String
}
//#ifndef APP-PLUS
// h5 假数据
let _id = new Date().getTime() + 'id'
talkingList.value.push({
crsId: crsId.value,
pgrphId: unref(lastTalkingInfo).pgrphId,
talkingContent: '',
talkingVoice: audioPath.value,
type: 1,
id: _id
})
// #endif
//#ifdef APP-PLUS
commonUploadVoiceFile(audioPath.value, '/trastudy/traStdyInfo/studyAnswerVoiceUp', _data).then(res=>{
console.log(JSON.parse(res.data))
}).catch(err=>{
console.log(err)
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,
talkingContent: '',
talkingVoice: audioPath.value,
type: 1,
id: _id
})
scrollToBottomNow()
}
})
// #endif
},1000)
}
// else{
// console.log('取消发送')
// }
}
const handleMove = (obj) => {
@@ -459,6 +594,7 @@ const handleMove = (obj) => {
height: 150rpx;
background-color: #fff;
padding: 25rpx 30rpx 0;
position: relative;
.touch-btn{
height: 92rpx;
box-shadow: 0 8rpx 20rpx 0 rgba(0,0,0,.06);
@@ -467,13 +603,28 @@ const handleMove = (obj) => {
color: #000;
font-weight: 400;
position: relative;
.input-box{
width: 100%;
height: 92rpx;
line-height: 92rpx;
text-align: left;
box-sizing: border-box;
padding:0 40rpx 0 40rpx;
}
}
.talk-btn-box{
position: absolute;
height: 100%;
width: 120rpx;
right: 0;
top:0;
.talk-btn-icon{
width: 40rpx;
height: 40rpx;
position: absolute;
right: 40rpx;
right: 60rpx;
top: 50%;
margin-top: -20rpx
margin-top: -20rpx;
}
}
}
+1 -5
View File
@@ -16,11 +16,7 @@ export const useSocketStore = defineStore('socketStore',{
actions:{
creatSocket(){
let _baseUrl = get_base_url().split('http').join('ws')
console.log(_baseUrl)
if(this.SocketObj && this.SocketObj?.close) {
this.closeSocket()
}
if(this.SocketObj && this.SocketObj?.close) return
this.SocketObj = new SocketClass(_baseUrl + '/trastudy/trasoket/open?summary=' + getToken());
},
closeSocket: () => {