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

This commit is contained in:
李泉德
2025-07-08 14:01:29 +08:00
27 changed files with 995 additions and 456 deletions
+1 -1
View File
@@ -11,7 +11,7 @@ VITE_APP_BASE_API_Url = 'https://aitstest.jlbank.com.cn:7001'
# http://25.18.122.65:7001/download/dev-jax.apk
# app入口
#VITE_APP_BASE_API_Url = 'http://25.18.122.65:7001'
#VITE_APP_BASE_API_Url = 'http://25.16.122.65:7001'
#VITE_APP_BASE_API_Url = 'http://25.18.122.91:9786'
#VITE_APP_BASE_API_Url = 'http://25.18.122.66:9786'
+1 -1
View File
@@ -3,4 +3,4 @@ ENV = 'production'
# base api
VITE_APP_BASE_API_Url = 'https://aitstest.jlbank.com.cn:7002'
VITE_APP_BASE_API_Url = 'https://aitstest.jlbank.com.cn:7001'
Binary file not shown.
Binary file not shown.
Binary file not shown.
+2 -2
View File
@@ -6,5 +6,5 @@
# header note.
#Thu Jul 03 09:46:08 CST 2025
#sdk.dir=C\:\\Users\\ty\\AppData\\Local\\Android\\Sdk
sdk.dir=/data/jenkins/agent/workspace/tra-app-build-test/android-sdk
sdk.dir=C\:\\Users\\ty\\AppData\\Local\\Android\\Sdk
#sdk.dir=/data/jenkins/agent/workspace/tra-app-build-test/android-sdk
File diff suppressed because one or more lines are too long
@@ -7,8 +7,8 @@
"id": "__UNI__EDE0E52",
"name": "tra-app",
"version": {
"name": "1.0.0",
"code": "100"
"name": "0.0.3",
"code": 3
},
"description": "",
"developer": {
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
@charset "UTF-8";.main_div[data-v-e9416fd9]{margin:20% auto 0;display:flex;justify-content:center;font-weight:600}
@charset "UTF-8";.main_div[data-v-e5b805df]{margin:20% auto 0;display:flex;justify-content:center;font-weight:600}
File diff suppressed because one or more lines are too long
+88
View File
@@ -0,0 +1,88 @@
import { getToken } from '@/common/common.js'
import { get_base_url } from '@/api/request'
const base_url = get_base_url();
const token = getToken();
// 基础配置
const baseConfig = {
enableChunked: true, // 必须开启分块传输
responseType: 'arraybuffer', // 微信小程序需用arraybuffer
timeout: 30000 // 超时时间延长
}
// 流式请求核心实现
function streamRequest(options) {
const {
url,
method = 'GET',
data,
headers = {},
onChunk,
onComplete,
onError
} = options
headers['Accept'] = 'text/event-stream'
headers['Content-Type'] = 'application/json;charset=UTF-8'
headers['summary'] = token
// 创建请求任务
const requestTask = uni.request({
url,
method,
data,
header: headers,
...baseConfig,
success: (res) => {
onComplete?.(res)
},
fail: (err) => {
onError?.(err)
}
})
console.log(requestTask)
// 流式数据处理器
let bufferCache = ''
requestTask.onChunkReceived((res) => {
try {
// ArrayBuffer转字符串(兼容多平台)
const uint8Array = new Uint8Array(res.data)
const chunkText = bufferCache +
String.fromCharCode.apply(null, uint8Array)
// 处理SSE格式数据(data:开头)
const events = chunkText.split('\n\n')
bufferCache = events.pop() || '' // 缓存不完整数据
events.forEach(event => {
if (event.startsWith('data:')) {
onChunk?.(event.substring(5).trim())
}
})
} catch (e) {
onError?.(e)
}
})
return {
abort: () => requestTask.abort()
}
}
// 使用示例
export const demoUsage = (data) => {
const controller = streamRequest({
url: `${base_url}/trastudy/intgask/askAbout`,
data: data,
onChunk: (chunk) => {
console.log('收到数据块:', chunk)
// 实时更新UI逻辑
},
onComplete: () => console.log('传输完成'),
onError: (err) => console.error('错误:', err)
})
// 需要中断时调用
// controller.abort()
}
+1
View File
@@ -93,6 +93,7 @@ export const commonUploadVoiceFile = (file, url, data = {}, fileKey='voice') =>
}
},
fail(error) {
console.log(error)
reject(new Error('Upload failed'));
}
});
+6 -1
View File
@@ -63,11 +63,16 @@ export default class WebSocketUtil {
if(res.data){
const { rtnCode } = JSON.parse(res.data)
console.log('收到WebSocket消息', JSON.parse(res.data) );
if(['0005', 'QQ0005'].includes(res.rtnCode)) {
if(['0005', 'QQ0005'].includes(rtnCode)) {
clearInterval(this.reconnectTimer);
this.reconnectTimer = null
this.close()
goto_login_fun();
return
}
}else{
clearInterval(this.reconnectTimer); // 清除重连计时器
this.reconnectTimer = null
goto_login_fun();
return
}
+35 -21
View File
@@ -1,5 +1,6 @@
import request from '@/api/request'
import { getToken } from '@/common/common.js'
import { get_base_url } from '@/api/request'
const base_url = '/trastudy'
// 获取课程学习信息接口
@@ -31,6 +32,15 @@ export const deleteTraStdyInfoByIdApi = (data) => {
});
};
// 重新学习
export const afreshStudyCourseApi = (data) => {
return request({
url: base_url + '/traStdyInfo/afreshStudyCourse',
method: 'post',
toastErrors: true,
data
});
};
// 学习段落完成
export const studyParagraphOverApi = (data) => {
return request({
@@ -94,6 +104,17 @@ export const waitMakeEvaluateApi = (data) => {
data
});
};
// 查询问题评价
// /traStdyInfo/queryQuestionEvalate
export const queryQuestionEvalateApi = (data) => {
return request({
url: base_url + '/traStdyInfo/queryQuestionEvalate',
method: 'post',
toastErrors: true,
data
});
};
// /trastudy/intgask/askAbout
// 流式获取文本
@@ -111,35 +132,28 @@ export const getAskAboutRawApi = (data) => {
// toastErrors: false,
// data
// });
return new Promise((resolve, reject) => {
request({
return request({
url: base_url + '/intgask/askAbout',
header: {
'Accept':'text/event-stream',
'content-type': 'application/json; charset=UTF-8',
'content-type': 'text/event-stream; charset=UTF-8',
},
responseType: 'arrayBuffer',
method: 'get',
isStream: true,
timeout: 30000,
toastErrors: false,
data
}).then(res=>{
console.log(res.data)
let _str = `[${ res.data.split('}{').join('},{')}]`
console.log(JSON.parse(_str))
})
};
// 学习 对话
export const textChartApi = (data) => {
return request({
url: base_url + '/traStdychat/textChart',
method: 'post',
toastErrors: true,
data
});
// return request({
// url: base_url + '/intgask/askAbout',
// header: {
// 'Accept':'text/event-stream',
// 'content-type': 'application/json; charset=UTF-8',
// },
// method: 'get',
// isStream: true,
// timeout: 30000,
// toastErrors: false,
// data
// });
};
+32 -21
View File
@@ -9,16 +9,16 @@
</view>
</view>
<view class="title">
<img class="title-icon" src="@/static/images/course/edit.png" alt="" />
<image class="title-icon" src="@/static/images/course/edit.png" alt=""> </image>
改进建议
</view>
<view class="total-score">
<view class="score-num">57</view>
<view class="score-num">{{props.score}}</view>
<img class="total-score-icon" src="@/static/images/course/score.png" alt="" />
</view>
<view v-for="item,index in 4" :class="['score-item', 'score-item'+(index + 1)]">
<view class="score-item-name">词命中率</view>
<view class="score-item-score">98</view>
<view v-for="item,index in props.dataList" :class="['score-item', 'score-item'+(index+1)]">
<view class="score-item-name">{{ item.gradeDimensName }}</view>
<view class="score-item-score">{{ item.anlyGrade }}</view>
</view>
</view>
</template>
@@ -26,15 +26,22 @@
<script setup>
import { ref, computed} from "vue"
const props = defineProps({
data:{
default:() => [60,70,80,90],
dataList:{
default:() => [],
type: Array
},
score:{
default: 0,
type: [Number,String]
},
size:{
default: 262,
type: [Number,String]
}
})
const dataValue = computed(() => {
return props.dataList.map(t=>t.anlyGrade)
})
const chartWidth = computed(() => props.size + 'rpx')
const chartHeight = computed(() => props.size + 'rpx')
const scoreMarginX = computed(() => {
@@ -51,16 +58,20 @@
})
const styleFour = ref('polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%)')
const styleFourResult = computed(()=>{
let score = props.data
let _tx = 50
let _ty = 50 - Math.floor((score[0]/100)*50)
let _rx = 50 + Math.floor((score[1]/100)*50)
let _ry = 50
let _bx = 50
let _by = 50 + Math.floor((score[2]/100)*50)
let _lx = 50 - Math.floor((score[3]/100)*50)
let _ly = 50
return `polygon(${_tx}% ${_ty}%, ${_rx}% ${_ry}%, ${_bx}% ${_by}%, ${_lx}% ${_ly}%)`
let score = dataValue.value
if(score.length>=4){
let _tx = 50
let _ty = 50 - Math.floor((score[0]/100)*50)
let _rx = 50 + Math.floor((score[1]/100)*50)
let _ry = 50
let _bx = 50
let _by = 50 + Math.floor((score[2]/100)*50)
let _lx = 50 - Math.floor((score[3]/100)*50)
let _ly = 50
return `polygon(${_tx}% ${_ty}%, ${_rx}% ${_ry}%, ${_bx}% ${_by}%, ${_lx}% ${_ly}%)`
}else{
return `polygon(${0}% ${50}%, ${50}% ${100}%, ${100}% ${50}%, ${50}% ${0}%)`
}
})
</script>
@@ -225,9 +236,9 @@
transform: translateY(v-bind(scoreMarginAbsY));
}
&.score-item2{
left: 0%;
right: 0%;
top: 50%;
transform: translate(calc(v-bind(scoreMarginX) - 60rpx), calc(-50% + 25rpx));
transform: translate(calc(v-bind(scoreMarginAbsX) + 60rpx), calc(-50% + 25rpx));
}
&.score-item3{
left: 50%;
@@ -236,9 +247,9 @@
transform: translateY(v-bind(scoreMarginY));
}
&.score-item4{
right: 0%;
left: 0%;
top: 50%;
transform: translate(calc(v-bind(scoreMarginAbsX) + 60rpx), calc(-50% + 25rpx));
transform: translate(calc(v-bind(scoreMarginX) - 60rpx), calc(-50% + 25rpx));
}
}
}
+29 -17
View File
@@ -12,16 +12,16 @@
</view>
</view>
<view class="title">
<img class="title-icon" src="@/static/images/course/edit.png" alt="" />
<image class="title-icon" src="@/static/images/course/edit.png" alt=""> </image>
改进建议
</view>
<view class="total-score">
<view class="score-num">57</view>
<view class="score-num">{{props.score}}</view>
<img class="total-score-icon" src="@/static/images/course/score.png" alt="" />
</view>
<view v-for="item,index in 3" :class="['score-item', 'score-item'+(index + 1)]">
<view class="score-item-name">词命中率</view>
<view class="score-item-score">98</view>
<view v-for="item,index in props.dataList" :class="['score-item', 'score-item'+(index+1)]">
<view class="score-item-name">{{ item.gradeDimensName }}</view>
<view class="score-item-score">{{ item.anlyGrade }}</view>
</view>
</view>
</template>
@@ -29,15 +29,22 @@
<script setup>
import { ref, computed} from "vue"
const props = defineProps({
data:{
default:() => [60,70,80,90],
dataList:{
default:() => [],
type: Array
},
score:{
default: 0,
type: [Number,String]
},
size:{
default: 262,
type: [Number,String]
}
})
const dataValue = computed(() => {
return props.dataList.map(t=>t.anlyGrade)
})
let _scale = .9
const chartWidth = computed(() => props.size / _scale + 'rpx')
const chartHeight = computed(() => props.size / _scale + 'rpx')
@@ -49,8 +56,8 @@
return '-' + (props.size/2 - 40) + 'rpx'
})
const scoreMarginPercentY = computed(() => {
let score = props.data
return setPercent(score[2], Math.round(100 * 1.732 / 6 * _scale * 100)/100) + '%'
let score = dataValue.value
return setPercent(80, Math.round(100 * 1.732 / 6 * _scale * 100)/100) + '%'
})
@@ -66,14 +73,19 @@
return `polygon(${_tx}% ${_ty}%, ${_rx}% ${_ry}%, ${_bx}% ${_by}%)`
})
const styleFourResult = computed(()=>{
let score = props.data
let _tx = 50
let _ty = 50 - setPercent(score[0], Math.round(100 * 1.732 / 3 * _scale * 100)/100)
let _rx = 50 + setPercent(score[1], Math.round(50 *_scale * 100)/100)
let _ry = 50 + setPercent(score[1], Math.round(100 * 1.732 / 6 * _scale * 100)/100)
let _bx = 50 - setPercent(score[2], Math.round(50 *_scale * 100)/100)
let _by = 50 + setPercent(score[2], Math.round(100 * 1.732 / 6 * _scale * 100)/100)
return `polygon(${_tx}% ${_ty}%, ${_rx}% ${_ry}%, ${_bx}% ${_by}%)`
let score = dataValue.value
if(score.length >= 3){
let _tx = 50
let _ty = 50 - setPercent(score[0], Math.round(100 * 1.732 / 3 * _scale * 100)/100)
let _rx = 50 + setPercent(score[1], Math.round(50 *_scale * 100)/100)
let _ry = 50 + setPercent(score[1], Math.round(100 * 1.732 / 6 * _scale * 100)/100)
let _bx = 50 - setPercent(score[2], Math.round(50 *_scale * 100)/100)
let _by = 50 + setPercent(score[2], Math.round(100 * 1.732 / 6 * _scale * 100)/100)
return `polygon(${_tx}% ${_ty}%, ${_rx}% ${_ry}%, ${_bx}% ${_by}%)`
}else{
return styleFour.value
}
})
const setPercent = (precent, show) => {
return Math.floor((precent/100) * show)
+147 -83
View File
@@ -1,4 +1,4 @@
<!-- type: 3, 段落信息 4 问题信息 5 统计信息 1语音回答 2 文本回答 0纯文本信息 不翻译 6: 发送loading状态-->
<!-- type: 0纯文本信息不翻译 1语音回答 2 文本回答 3, 段落信息 4 问题信息 5 统计信息 6: 发送loading状态 -->
<template>
<view class="ai-answer" v-if="[3,4,5].indexOf(type) >= 0">
<view class="user-info">
@@ -11,24 +11,25 @@
</view>
</view>
<view :class="['talking-info', [4].indexOf(type) >= 0 ? 'talking-info-less':''] ">
<view :class="['talking-gif', !voiceLoading && (activeVoiceId === dataInfo.id)?'is-play':'']">
<view :class="['talking-gif', !voiceLoading && (activeVoiceId === dataInfo.id)?'is-play':'']" v-if="type !== 5">
<CommonLoading color="#06f" v-show="voiceLoading"/>
</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">
<CommonLoading color="#06f" v-show="dataInfo.isLoading"/>
<text class="talking-text" v-if="[3, 5, 6].indexOf(type) >= 0 && !dataInfo.isLoading">
{{ showContent }}
</text>
<text class="talking-text" v-if="[4].indexOf(type) >= 0">
<!-- 请您思考并作答:<br> -->
{{ showContent }}
</text>
<view v-if="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 v-if="type === 5 && !dataInfo.isLoading">
<ChartFour v-if="dataInfo.evalDimens.length === 4" :dataList="dataInfo.evalDimens" :score="dataInfo.intrctGrade"/>
<ChartThree v-if="dataInfo.evalDimens.length === 3" :dataList="dataInfo.evalDimens" :score="dataInfo.intrctGrade"/>
</view>
</view>
<!-- <view class="talking-cont">
@@ -36,16 +37,16 @@
阿列克山大是快乐的你艾德卡省的mask安东拉森扣篮大赛了快递那里是啊克里斯蒂娜老师的阿萨是看
</view>
</view> -->
<view class="btns-cont">
<view class="replay-btn" @click="restartPlay">
<view class="btns-cont" v-show="!dataInfo.isLoading">
<view class="replay-btn" @click.stop="restartPlay" v-if="!([5].indexOf(type) >= 0)">
<image class="icon" src="@/static/images/course/replay.png" style="width: 100%;height: 100%;"></image>
</view>
<view class="play-btn">
<view class="play-btn" v-if="!([5].indexOf(type) >= 0)">
<image
class="icon"
src="@/static/images/course/play.png"
style="width: 100%;height: 100%;"
@click="playVoice()"
@click.stop="playVoice()"
v-show="!isPlaying"
></image>
<image
@@ -53,12 +54,27 @@
v-show="isPlaying"
src="@/static/images/course/pause.png"
style="width: 100%;height: 100%;"
@click="pauseVoice"
@click.stop="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 && isLast">继续
<view
class="next-learn-btn"
v-if="showNextBtn && [5].indexOf(type) >= 0 && isLast"
@click.stop="goOnGetQues"
>
继续学
<image class="icon icon-iamge" src="@/static/images/course/goon.png" ></image>
</view>
<view
class="next-learn-btn restart"
v-if="showNextBtn && [5].indexOf(type) >= 0 && isLast"
@click.stop="goOnRestart"
>
重新学
<image class="icon icon-iamge" src="@/static/images/course/restart.png" ></image>
</view>
<!-- <view class="replay-study-btn" v-if="showNextBtn && [5].indexOf(type) >= 0 && isLast"><image class="icon icon-iamge" src="@/static/images/course/restudy.png" ></image></view> -->
<view class="next-btn" @click.stop="goOnGetQues" v-if="showNextBtn && [3, 6].indexOf(type) >= 0 && isLast">继续
<image class="icon icon-iamge" src="@/static/images/course/goon.png" ></image></view>
</view>
</view>
@@ -66,8 +82,9 @@
<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%;"/>
<image v-if="userInfo.imagePath" class="img-box" :src="userInfo.imagePath" style="width: 100%; height: 100%;"></image>
<ImagePreview v-else-if="userInfo.imageAddr" class="img-box" :src="userInfo.imageAddr" style="width: 100%; height: 100%;"></ImagePreview>
<image v-else class="img-box" src="/static/images/me/default_user_avatar.png" style="width: 100%; height: 100%;"></image>
</view>
<view class="ai-name">
{{ userInfo.userName }}
@@ -75,54 +92,58 @@
</view>
<view class="talking-info">
<view :class="['talking-gif', !voiceLoading && (activeVoiceId === dataInfo.id) ? 'is-play' : '']">
<CommonLoading color="#fff" v-show="voiceLoading"/>
<view :class="['talking-gif', !voiceLoading && (activeVoiceId === dataInfo.id) ? 'is-play' : '']">
<CommonLoading color="#fff" v-show="voiceLoading || dataInfo.isLoading"/>
</view>
<view class="talking-cont">
<view class="talking-text">
<CommonLoading color="#fff" v-show="translateLoading"/>
{{ dataInfo.talkingContent }}
<view class="talking-cont" v-show="!dataInfo.isLoading">
<text :class="['talking-text translate-text loading']" v-show="!translateLoading">
{{ showContent }}
</text>
<view class="talking-text translate-text" v-if="translateLoading">
<CommonLoading color="#fff"/>
</view>
</view>
<view class="btns-cont">
<view class="btns-cont" v-show="!dataInfo.isLoading">
<!-- <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)"
@click.stop="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"
@click.stop="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="finishQuestion(dataInfo)" v-if="isLast">完成</view>
<view class="icon text-right-btn" @click="withdraw(dataInfo)">撤回</view>
<view class="icon text-btn" v-if="type === 1" @click.stop="translateVoice(dataInfo)"></view>
<view class="icon text-right-btn" @click.stop="finishQuestion(dataInfo)" v-if="isLast">完成</view>
<view class="icon text-right-btn" @click.stop="withdraw(dataInfo)" v-show="showWithdraw">撤回
</view>
</view>
</view>
</view>
<view class="user-answer" v-else-if="[0, 6].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%;"/>
<image v-if="userInfo.imagePath" class="img-box" :src="userInfo.imagePath" style="width: 100%; height: 100%;"></image>
<ImagePreview v-else-if="userInfo.imageAddr" class="img-box" :src="userInfo.imageAddr" style="width: 100%; height: 100%;"></ImagePreview>
<image v-else class="img-box" src="/static/images/me/default_user_avatar.png" style="width: 100%; height: 100%;"></image>
</view>
<view class="ai-name">
{{ userInfo.userName }}
</view>
</view>
<view class="talking-info-less">
<view class="talking-text" v-if="type === 0">
{{ dataInfo.talkingContent }}
</view>
<view class="talking-text" v-if="type === 6">
<text class="talking-text" v-if="type === 0">
{{ showContent }}
</text>
<view class="talking-text loading" v-if="type === 6">
<CommonLoading color="#fff"/>
</view>
</view>
@@ -141,10 +162,11 @@ import ChartThree from "./chartThree.vue"
import common from '@/common/common';
import { getUserInfo } from '@/common/common';
import { useStorageStore } from "@/store/storage.js"
import { demoUsage } from "@/api/arraybuffer.js"
import { onUnload } from '@dcloudio/uni-app'
const storageStore = useStorageStore()
const audioCacheObj = computed(() => storageStore.audioObj)
const props = defineProps({
type:{
default: 0,// 1 userAnswer 0: AIAnswer
@@ -174,18 +196,28 @@ const audioCacheObj = computed(() => storageStore.audioObj)
default: false,
type: Boolean
},
lastQuestionInfo: {
default: () => ({}),
type: Object
},
})
const { type, dataInfo, activeVoiceId, isPlaying, isLast} = toRefs(props)
const { type, dataInfo, activeVoiceId, isPlaying, isLast, lastQuestionInfo} = toRefs(props)
const emit = defineEmits(['changePlay', 'nextQuestion', 'withdraw', 'finishQuestion'])
// 声音播放初始化
const talkVoiceObj = ref(uni.createInnerAudioContext())
talkVoiceObj.value.playbackRate = Number(props.voiceRate)
const userInfo = computed(() => getUserInfo())
const showWithdraw = computed(()=>{
let _isMe = unref(lastQuestionInfo).qnsLogId === unref(dataInfo).qnsLogId
let _isQuestion = unref(lastQuestionInfo).isQuestion
return _isMe && _isQuestion
})
const voiceLoading = ref(false)
watch(() => props.isPlaying,(val) => {
if(!val){
talkVoiceObj.value?.stop()
talkVoiceObj.value?.pause()
}
},{
deep: true,
@@ -197,24 +229,43 @@ const audioCacheObj = computed(() => storageStore.audioObj)
deep: true,
immediate: true
})
watch(()=>props.lastTalkingInfo,()=>{},{
deep: true,
immediate: true
})
watch(() => props.dataInfo,()=>{},{
deep: true,
immediate: true
})
const transformContent = ref('')
const showContentBase = computed(() => {
if(props.isTransform){
return transformContent.value
}else{
if(props.type === 1) return unref(dataInfo)?.chatContent || unref(dataInfo)?.talkingContent
else if(props.type === 2) return unref(dataInfo)?.chatContent || unref(dataInfo)?.talkingContent
else if(props.type === 3) return unref(dataInfo)?.chatContent || unref(dataInfo)?.pgrphContent
else if(props.type === 4) return unref(dataInfo)?.chatContent || unref(dataInfo)?.qnsContent
else if(props.type === 5) return unref(dataInfo)?.chatContent || unref(dataInfo)?.evalContent
else if(props.type === 0) return unref(dataInfo)?.chatContent || unref(dataInfo)?.talkingContent
}
})
const showContent = computed(() => {
return props.isTransform
? transformContent.value
: unref(dataInfo)?.pgrphContent || unref(dataInfo)?.qnsContent || unref(dataInfo)?.talkingContent || ''
let _str = showContentBase.value || ''
return _str.replace(/\/n/g,"\n")
})
onMounted(()=>{
// getAskAboutRawApi({
// question: unref(dataInfo)?.pgrphContent,
// streaming: true
// }).then(res=>{
// console.log(res)
// })
if(props.isTransform){}
// demoUsage({
// question: unref(dataInfo)?.pgrphContent,
// streaming: true
// })
// if(props.isTransform){}
})
onUnload(()=>{
pauseVoice()
})
talkVoiceObj.value.onEnded(()=>{
emit('changePlay', '')
talkVoiceObj.value.src = ''
@@ -242,13 +293,17 @@ const audioCacheObj = computed(() => storageStore.audioObj)
}
const itemVoice = ref('')
const playVoice = (readType = 'pgth') =>{
let _content = unref(dataInfo)?.pgrphContent || unref(dataInfo)?.qnsContent || unref(dataInfo)?.talkingContent || ''
let _content = showContent.value
if(!_content){
common.msg('没有可以播放的文字信息!')
return
}
// readType 阅读内容类型pgrh段落/qns问题
emit('changePlay', unref(dataInfo)?.id)
if(talkVoiceObj.value.src === itemVoice.value) {
talkVoiceObj.value.play()
return
}
if(itemVoice.value){
setTimeout(()=>{
talkVoiceObj.value.src = itemVoice.value
@@ -276,7 +331,6 @@ const audioCacheObj = computed(() => storageStore.audioObj)
talkVoiceObj.value.play()
}else{
downloadVoiceFile(_url).then(res=>{
console.log(res)
if(res?.data){
common.msg(res.data.message)
return
@@ -291,23 +345,6 @@ const audioCacheObj = computed(() => storageStore.audioObj)
})
}
})
// let _cache = audioCacheObj.value[_url]
// if(_cache){
// voiceLoading.value = false
// itemVoice.value = _cache
// talkVoiceObj.value.src = _cache
// talkVoiceObj.value.play()
// return
// }
// downloadVoiceFile(_url).then(res=>{
// voiceLoading.value = false
// itemVoice.value = res.tempFilePath
// talkVoiceObj.value.src = res.tempFilePath
// talkVoiceObj.value.play()
// storageStore.setStorage('audio', _url, itemVoice.value)
// }).catch((err)=>{
// voiceLoading.value = false
// })
}
}
const playQnsVoice = (readType = 'qns', item) =>{
@@ -331,7 +368,7 @@ const audioCacheObj = computed(() => storageStore.audioObj)
translateLoading.value = true
uploadVoiceFile(item.talkingVoice,{}).then(res=>{
let _data = JSON.parse(res.data)
item.talkingContent = _data.body
item.talkingContent = _data.body.text
translateLoading.value = false
}).catch(()=>{
translateLoading.value = false
@@ -339,13 +376,21 @@ const audioCacheObj = computed(() => storageStore.audioObj)
}
const showNextBtn = ref(true)
const goOnGetQues = () => {
studyParagraphOverApi({
pgrphId:unref(dataInfo)?.pgrphId,
logId:unref(dataInfo)?.logId,
}).then(res=>{
showNextBtn.value = false
if(props.type === 5){
emit('nextQuestion', unref(dataInfo))
})
}else if(props.type === 3){
// studyParagraphOverApi({
// pgrphId:unref(dataInfo)?.pgrphId,
// logId:unref(dataInfo)?.logId,
// }).then(res=>{
// showNextBtn.value = false
// })
emit('nextQuestion', unref(dataInfo), 'overGraph')
}
}
const goOnRestart = () => {
emit('nextQuestion', unref(dataInfo), 'reStart')
}
// 撤回
const withdraw = (data) => {
@@ -395,16 +440,17 @@ const audioCacheObj = computed(() => storageStore.audioObj)
border-radius: 15rpx;
background-color: #fff;
overflow: hidden;
padding-bottom: 40rpx;
&.talking-info-less{
float: left;
min-width: 400rpx;
padding-bottom: 20rpx;
}
.talking-gif{
width: 200rpx;
height: 38rpx;
background: url(@/static/images/course/voice-gray.png) no-repeat;
background-size: contain;
margin-bottom: 16rpx;
padding-left: 168rpx;
&.is-play{
background: url(@/static/images/course/voice-gif.gif) no-repeat;
@@ -412,11 +458,19 @@ const audioCacheObj = computed(() => storageStore.audioObj)
}
}
.talking-cont{
margin-top: 16rpx;
overflow: hidden;
min-height: 48rpx;
.talking-text{
font-size: 28rpx;
line-height: 48rpx;
min-height: 48rpx;
margin-bottom: 20rpx;
word-break: break-all;
white-space: pre-line;
&.loading{
// min-height: 36px;
}
}
.cont-img-box{
width: 433rpx;
@@ -458,6 +512,10 @@ const audioCacheObj = computed(() => storageStore.audioObj)
margin-top: 16rpx;
margin-right: 16rpx;
position: relative;
&.restart{
background-color: #eaf2ff;
color: #06f;
}
.icon-iamge{
width: 25rpx;
height: 25rpx;
@@ -568,7 +626,6 @@ const audioCacheObj = computed(() => storageStore.audioObj)
height: 38rpx;
background: url(@/static/images/course/voice-gray.png) no-repeat;
background-size: contain;
margin-bottom: 16rpx;
padding-left: 168rpx;
&.is-play{
background: url(@/static/images/course/voice-white.gif) no-repeat;
@@ -576,11 +633,18 @@ const audioCacheObj = computed(() => storageStore.audioObj)
}
}
.talking-cont{
margin-top: 16rpx;
overflow: hidden;
.talking-text{
font-size: 28rpx;
line-height: 48rpx;
margin-bottom: 20rpx;
word-break: break-all;
white-space: pre-line;
overflow: hidden;
&.translate-text{
min-height: 48rpx;
}
}
}
.btns-cont{
+293
View File
@@ -0,0 +1,293 @@
<template>
<view class="talk-btns" ref="talkBtnRef">
<view
class="touch-btn"
@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" maxlength="1000"
placeholderStyle='color:#999999;font-size:26rpx' placeholder="请输入答案">
</uv-input>
</view>
<view class="talk-btn-box" @click.stop="showKeyboard">
<image class="talk-btn-icon" v-show="!keyboardIsShow" src="/src/static/images/course/jianpan.png" mode=""></image>
<image class="talk-btn-icon" v-show="keyboardIsShow" src="/src/static/images/course/send.png" mode=""></image>
</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>
</uv-overlay>
</view>
</template>
<script setup>
import { toRefs, ref, computed, unref, onMounted, onUnmounted, defineEmits, nextTick } from 'vue';
const emit = defineEmits(['uploadVoice', 'submitAnswer','startRecord'])
const props = defineProps({
answerType:{
default: 0,
type: Number
}
})
const { answerType } = toRefs(props)
const showTalkingModel = ref(false)
const keyboardIsShow = ref(false)
const answerValue = ref('')
const talkBtnRef = ref()
// 录音
const audioPath = ref('')
const audioObj = uni.createInnerAudioContext()
//#ifdef APP-PLUS
// app端
const recordObjCom = uni.getRecorderManager()
const initRecord = () => {
unref(recordObjCom)?.onStop((res, duration)=>{
audioPath.value = res.tempFilePath
})
}
const startRecord = () => {
unref(recordObjCom).start({format:'mp3'})
}
const stopRecord = () => {
unref(recordObjCom)?.stop?.()
}
// #endif
//#ifndef APP-PLUS
const recordObjCom = {}
const initRecord = () => {}
// #endif
onMounted(()=>{
initRecord?.()
})
// const playRecord = () => {
// audioObj.src = audioPath.value
// unref(audioObj).play()
// }
// 录音结束
// 记录 按下移动位置
const touchX = ref(0)
const touchY = ref(0)
const baseY = ref(0)
const windowInfo = uni.getWindowInfo()
// 计算是否超出按下盒子位置
const isOut = computed(() => {
let _btnHeight = windowInfo.screenWidth/750 * 234
let moveVal = (windowInfo.screenHeight - touchY.value) > _btnHeight
return moveVal
})
const showOverlayTalking = (obj) => {
emit('startRecord')
if(answerType.value === 4){
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
}else{
uni.showToast({
title: '请先获取问题信息!',
icon: "none",
duration:1000
})
}
}
const hideOverlayTalking = (obj) => {
showTalkingModel.value = false
//#ifdef APP-PLUS
stopRecord()
// #endif
if(!isOut.value){
setTimeout(()=>{
emit('uploadVoice', audioPath.value)
},1000)
}
}
const handleMove = (obj) => {
touchX.value = obj.changedTouches[0].pageX
touchY.value = obj.changedTouches[0].pageY
}
// 发送文本
const showKeyboard = () => {
if(answerType.value === 4){
if(keyboardIsShow.value){
if(answerValue.value){
emit(
'submitAnswer',
answerValue.value,
() => {
keyboardIsShow.value = !keyboardIsShow.value
answerValue.value = ''
}
)
}else{
keyboardIsShow.value = !keyboardIsShow.value
}
}else{
keyboardIsShow.value = !keyboardIsShow.value
}
}else{
uni.showToast({
title: '请先获取问题信息!',
icon: "none",
duration:1000
})
}
}
</script>
<style lang="scss" scoped>
.talk-btns{
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);
text-align: center;
line-height: 92rpx;
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: 60rpx;
top: 50%;
margin-top: -20rpx;
}
}
}
.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;
}
.white-bg-box{
height: 365rpx;
background-color: #fff;
overflow: hidden;
border-radius: 32rpx 32rpx 0 0;
padding: 58rpx 36rpx;
.box-title{
font-size: 32rpx;
font-weight: bold;
line-height: 46rpx;
margin-bottom: 46rpx;
}
.box-btns{
height: 46rpx;
line-height: 46rpx;
display: flex;
justify-content: space-between;
color: #999;
.is-active{
color: #06f;
}
}
}
}
</style>
+9 -7
View File
@@ -351,7 +351,7 @@ import { onMounted, ref, computed } from 'vue'
.list-view{
flex:1;
overflow: hidden;
margin-bottom: 80rpx;
margin-bottom: 15rpx;
background: #ffffff;
border-radius: 23rpx;
display: flex;
@@ -469,14 +469,16 @@ import { onMounted, ref, computed } from 'vue'
border-right: 2rpx solid #eee;
overflow-y: auto;
.type-item{
height: 90rpx;
line-height: 90rpx;
font-size: 28rpx;
min-height: 90rpx;
line-height: 40rpx;
font-size: 22rpx;
box-sizing: border-box;
padding: 0 18rpx;
padding: 25rpx 18rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
// text-overflow: ellipsis;
word-break: break-all;
text-align: center;
white-space: pre-wrap;
&.is-active{
background-color: rgba(44,142,242,.1);
color: #06f;
+339 -290
View File
@@ -3,9 +3,9 @@
<view class="top-bg"></view>
<view class="bot-bg"></view>
<view class="course-study-body" v-show="step === 1">
<view class="seek-setting" @click="showSeekModel = true">
<view class="seek-setting" @click="showChangeSeekModelNow">
{{chooseRate}}
<image src="@/static/images/course/setting.png" alt=""/>
<image src="@/static/images/course/setting.png" alt="" class="image-icon"></image>
</view>
<view class="body-title">
<uv-icon class="arrow-icon" name="arrow-left" @click="common.navigateBack()"/>
@@ -36,14 +36,14 @@
</view>
</view>
</view>
<view class="course-study-body" v-show="step === 2" @click="playRecord">
<view class="seek-setting" @click="showSeekModel = true">
<view class="course-study-body" v-show="step === 2">
<view class="seek-setting" @click="showChangeSeekModelNow">
{{chooseRate}}
<img src="@/static/images/course/setting.png" alt="">
<image src="@/static/images/course/setting.png" alt="" class="image-icon"></image>
</view>
<view class="body-title" @click="scrollToBottomNow">
<uv-icon class="arrow-icon" name="arrow-left" @click="common.navigateBack()"/>
课程学习{{scrollTop}}
课程学习
</view>
<scroll-view
class="talking-list"
@@ -65,6 +65,7 @@
:activeVoiceId="activeVoiceTalkingItemId"
:isPlaying="activeVoiceTalkingItemId === item.id"
:voiceRate="chooseRate"
:lastQuestionInfo="lastTalkingInfo"
@changePlay="changePlayItemId"
@nextQuestion="nextQuestionSuccess"
@withdraw="withdrawNow"
@@ -75,45 +76,14 @@
<!-- <video v-if="videoUrlShow" :src="videoUrlShow" controls></video> -->
<!-- </view> -->
</scroll-view>
<view class="talk-btns">
<view
class="touch-btn"
@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" maxlength="1000"
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>
<TouchBtn
class="talk-btns"
@uploadVoice="uploadRecordNow"
@submitAnswer="submitAnswerNow"
@startRecord="changePlayItemId('')"
:answerType="lastTalkingInfo.type||0"
/>
</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>
<uv-overlay :show="showSeekModel" opacity=".6" @click="showSeekModel = false">
<view class="overlay-box">
<view class="white-bg-box">
@@ -134,7 +104,7 @@
<script setup>
import { reactive, ref, computed, unref, onMounted, onUnmounted, getCurrentInstance, nextTick } from 'vue';
import { onLoad,onShow,onHide } from '@dcloudio/uni-app';
import { onLoad,onShow, onUnload } from '@dcloudio/uni-app';
import {
previewFile,
downloadFile,
@@ -146,17 +116,14 @@ import { getToken } from '@/common/common.js'
import { get_base_url, goto_login_fun } from '@/api/request'
import {
getCourseStudyInfoApi,
queryTraStdyBatchWaitParagraphInfoApi,
audioTranscriptionsApi,
deleteTraStdyInfoByIdApi,
queryTraStdyBatchWaitQuestionInfoApi,
studyAnswerTextUpApi,
withdrawalAnswerApi,
waitMakeEvaluateApi
afreshStudyCourseApi,
queryQuestionEvalateApi,
textChartApi
} 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 TouchBtn from '@/pages/course/components/touchBtn.vue'
import { useSocketStore } from "@/store/socket.js"
import { useStorageStore } from "@/store/storage.js"
@@ -178,8 +145,14 @@ const talkingList = ref([])
const activeVoiceTalkingItemId = ref('')
const answerValue = ref('')
const inputRef = ref('')
const flagVal = ref('1')
// 初始化数据
const getData = async () => {
const getData = async (flag) => {
if(flag === flagVal.value){
flagVal.value = '2'
}else{
return
}
try{
const { body } = await getCourseStudyInfoApi({crsId: crsId.value})
initsocketTask()
@@ -206,10 +179,9 @@ const getData = async () => {
})
step.value = 2
}else{
// 重新开始
// 删除学习信息
deleteTraStdyInfoByIdApi({
id:''
// 重新学习
afreshStudyCourseApi({
crsId: crsId.value,
}).then(res=>{
step.value = 1
teacherList.value = body.teacheList.map(t=>({
@@ -231,13 +203,14 @@ const getData = async () => {
}catch{
}
// downloadFile('S3F0250640161332025061618052000000267276').then(res=>{
// videoUrlShow.value = res.tempFilePath
// })
};
const showChangeSeekModelNow = () => {
// changePlayItemId()
showSeekModel.value = true
}
// 关闭播放状态
const changePlayItemId = (id) => {
const changePlayItemId = (id = '') => {
if(activeVoiceTalkingItemId.value === id){
activeVoiceTalkingItemId.value = ''
}else{
@@ -248,9 +221,9 @@ const changePlayItemId = (id) => {
const choiceTeacherInfo = computed(()=>{
return teacherList.value.find(t=> t.teacherNo === choiceTeacher.value)
})
// 选择教师语音对象
const activeVoiceTeachNo = ref('')
const teacherVoiceObj = uni.createInnerAudioContext()
let teacherVoiceObj = uni.createInnerAudioContext()
teacherVoiceObj.onEnded(()=>{
activeVoiceTeachNo.value = ''
@@ -306,67 +279,143 @@ const pgrphNum = ref(1)
const showOrder = ref(0)
// 获取段落
const getGraphInfoUnStudy = () => {
queryTraStdyBatchWaitParagraphInfoApi({
crsId: crsId.value,
stdyId: unref(stdyId),
stdyBatch: unref(stdyBatch)
textChartApi({
processType: 'PRGH-STDY',
requestBody: JSON.stringify({
crsId: crsId.value,
stdyId: unref(stdyId),
stdyBatch: unref(stdyBatch)
})
}).then(res=>{
let _id = new Date().getTime() + 'id'
lastTalkingInfo.value = {
...res.body,
type: 3,
let _body = res.body|| {}
if(!_body.chatBody) {
return
}
lastTalkingInfo.value = {
crsId: crsId.value,
qstLogId: _body.chatBody,
pgrphId: _body.pgrphId,
logId:_body.chatBody.logId || '',
chatContent: _body.chatContent,
userInfo: {...choiceTeacherInfo.value},
..._body.chatBody,
type: Number(_body.bodyTyp) || 0,
id: _id
}
talkingList.value.push(lastTalkingInfo.value)
pgrphNum.value = res.body.pgrphNum
showOrder.value = res.body.showOrder
logId.value = res.body.logId || ''
pgrphNum.value = _body.chatBody.pgrphNum
showOrder.value = _body.chatBody.showOrder
logId.value = _body.chatBody.logId || ''
scrollToBottomNow()
})
// common.msg('您已学完当前课程的全部内容!')
}
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=>{
textChartApi({
processType: 'QSN-ANSER',
requestBody: JSON.stringify({
crsId: crsId.value,
stdyId: unref(stdyId),
stdyBatch: unref(stdyBatch),
logId: unref(logId),
pgrphId
})
})
.then(res=>{
let _id = new Date().getTime() + 'id'
lastTalkingInfo.value = {
...res.body,
type: 4,
userInfo: {...choiceTeacherInfo.value},
id: _id
let _body = res.body || {}
console.log(_body.chatBody)
if(_body.chatBody && _body.chatBody?.stdyStat !== 'N'){
let _id = new Date().getTime() + 'id'
lastTalkingInfo.value = {
crsId: crsId.value,
stdyId: unref(stdyId),
stdyBatch: unref(stdyBatch),
logId: unref(logId),
pgrphId,
chatContent: _body.chatContent||'',
userInfo: {...choiceTeacherInfo.value},
...(_body.chatBody||{}),
type: 4,
userInfo: {...choiceTeacherInfo.value},
id: _id,
isQuestion: true
}
talkingList.value.push(lastTalkingInfo.value)
qstLogId.value = _body.chatBody?.qstLogId
scrollToBottomNow()
}else{
getGraphInfoUnStudy()
}
talkingList.value.push(lastTalkingInfo.value)
qstLogId.value = res.body.qstLogId
scrollToBottomNow()
})
}
// 继续 执行方法
const nextQuestionSuccess = (info) => {
const nextQuestionSuccess = (info, type='next') => {
let _id = new Date().getTime() + 'id'
talkingList.value.push({
crsId: info.crsId,
pgrphId: info.pgrphId,
talkingContent: '继续',
type: 0,
id: _id
})
scrollToBottomNow()
// getGraphInfoUnStudy()
getQuestionInfoUnStudy(info.pgrphId)
if(type === 'next'){
talkingList.value.push({
crsId: info.crsId,
pgrphId: info.pgrphId,
talkingContent: '继续',
type: 0,
id: _id
})
scrollToBottomNow(0)
getQuestionInfoUnStudy(info.pgrphId)
}else if( type === 'overGraph'){
textChartApi({
processType: 'PRGH-OVER',
requestBody: JSON.stringify({
pgrphId: info?.pgrphId,
logId: info?.logId,
})
}).then(res=>{
talkingList.value.push({
crsId: info.crsId,
pgrphId: info.pgrphId,
talkingContent: '继续',
type: 0,
id: _id
})
scrollToBottomNow(0)
getQuestionInfoUnStudy(info.pgrphId)
})
}else if( type === 'reStart'){
// 重新回答
textChartApi({
processType: 'NEW-ANSER',
requestBody: info.qstLogId
}).then(res=>{
let _body = res.body|| {}
if(!_body.chatBody) {
return
}
lastTalkingInfo.value = {
crsId: info.crsId,
qstLogId: _body.chatBody,
pgrphId: info.pgrphId,
chatContent: _body.chatContent,
userInfo: {...choiceTeacherInfo.value},
..._body.chatBody,
type: Number(_body.bodyTyp) || 0,
id: _id,
isQuestion: true
}
talkingList.value.push(lastTalkingInfo.value)
qstLogId.value = _body.chatBody.qstLogId
scrollToBottomNow()
})
}
}
const scrollBoxRef = ref()
const scrollTop = ref(99999)
const scrollToBottomNow = () => {
const scrollToBottomNow = (time = 500) => {
nextTick(()=>{
setTimeout(() => {
scrollTop.value += 1
@@ -379,7 +428,7 @@ const scrollToBottomNow = () => {
duration: 300
})
}
},200)
},time)
})
}
@@ -417,14 +466,6 @@ const reconnect = ()=> {
const sendMessage = (data) => {
unref(SocketObj).send(data)
}
const closeWebSocket = () => {
isManualClose.value = true
if(socketTask){
// socketTask.close()
socketTask = null
uni.closeSocket({})
}
}
// ws 结束
onLoad((params) => {
@@ -433,211 +474,219 @@ onLoad((params) => {
});
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, duration)=>{
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?.()
getData('1');
})
onShow(()=>{
// getData();
getData('2');
})
// onHide(()=>{
// teacherVoiceObj.stop()
// teacherVoiceObj.src = ''
// })
onUnmounted(()=>{
onUnload(()=>{
changePlayItemId()
teacherVoiceObj?.stop?.()
teacherVoiceObj.src = ''
})
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) => {
if(lastTalkingInfo.value.type === 4){
//#ifdef APP-PLUS
showTalkingModel.value = true
touchX.value = obj.changedTouches[0].pageX
touchY.value = obj.changedTouches[0].pageY
baseY.value = obj.currentTarget.offsetTop
startRecord()
// #endif
}else{
uni.showToast({
title: '请先获取问题信息!',
icon: "none",
duration:1000
})
}
}
const hideOverlayTalking = (obj) => {
showTalkingModel.value = false
//#ifdef APP-PLUS
stopRecord()
// #endif
if(!isOut.value){
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
}
//#ifndef APP-PLUS
// h5 假数据
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()
// #endif
//#ifdef APP-PLUS
commonUploadVoiceFile(audioPath.value, '/trastudy/traStdyInfo/studyAnswerVoiceUp', _data).then(res=>{
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,
intrctId: res.body||'',
type: 1,
userInfo: {...choiceTeacherInfo.value},
id: _id
})
scrollToBottomNow()
}
}).catch(err=>{
uni.showToast({
title: err||'系统异常,请联系管理员',
icon: "none",
duration:1000
});
const uploadRecordNow = (voicePath) => {
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
}
let _id = new Date().getTime() + 'id'
//#ifndef APP-PLUS
// h5 假数据
talkingList.value.push({
crsId: crsId.value,
pgrphId: unref(lastTalkingInfo).pgrphId,
qnsId: unref(lastTalkingInfo).qnsId,
talkingContent: '',
talkingVoice: voicePath,
type: 1,
userInfo: {...choiceTeacherInfo.value},
id: _id
})
// #endif
},1000)
}
}
const handleMove = (obj) => {
touchX.value = obj.changedTouches[0].pageX
touchY.value = obj.changedTouches[0].pageY
}
const keyboardIsShow = ref(false)
// 发送文本
const showKeyboard = () => {
if(lastTalkingInfo.value.type === 4){
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
}
studyAnswerTextUpApi(_data).then(res=>{
if(res.rtnCode === '0000'){
let _id = new Date().getTime() + 'id'
talkingList.value.push({
scrollToBottomNow()
// #endif
//#ifdef APP-PLUS
talkingList.value.push({
crsId: crsId.value,
pgrphId: unref(lastTalkingInfo).pgrphId,
qnsId: unref(lastTalkingInfo).qnsId,
talkingContent: '',
talkingVoice: voicePath,
intrctId: '',
isLoading: true,
type: 1,
userInfo: {...choiceTeacherInfo.value},
id: _id
})
scrollToBottomNow()
commonUploadVoiceFile(voicePath, '/trastudy/traStdychat/voiceChart', {
processType: 'ANSER-VOICE',
requestBody: JSON.stringify(_data)
}).then(res=>{
let _res = JSON.parse(res.data)
console.log(_res)
if(_res.rtnCode === '0000'){
talkingList.value = talkingList.value.map(t=>{
if(t.id === _id){
return {
crsId: crsId.value,
pgrphId: unref(lastTalkingInfo).pgrphId,
qstLogId: unref(lastTalkingInfo).qstLogId,
qnsId: unref(lastTalkingInfo).qnsId,
talkingContent: answerValue.value,
talkingVoice: '',
intrctId: res.body||'',
type: 2,
pgrphId: _data.pgrphId,
qstLogId: _data.qstLogId,
qnsId: _data.qnsId,
talkingContent: '',
talkingVoice: voicePath,
intrctId: _res.chatBody,
type: 1,
userInfo: {...choiceTeacherInfo.value},
id: _id
})
scrollToBottomNow()
answerValue.value = ''
keyboardIsShow.value = !keyboardIsShow.value
}
}else{
return t
}
})
}else{
keyboardIsShow.value = !keyboardIsShow.value
}
}else{
keyboardIsShow.value = !keyboardIsShow.value
}
}else{
uni.showToast({
title: '请先获取问题信息!',
icon: "none",
duration:1000
scrollToBottomNow()
}else{}
}).catch(err=>{
talkingList.value = talkingList.value.filter(t=>t.id !== _id)
uni.showToast({
title: '系统异常,请联系管理员',
icon: "none",
duration:1000
});
})
// #endif
}
// 发送文本
const submitAnswerNow = (val, cb) => {
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: val // 回答文本内容 必输项:false 类型:String
}
textChartApi({
processType: 'ANSER-TEXT',
requestBody: JSON.stringify(_data)
})
.then(res=>{
let _id = new Date().getTime() + 'id'
let _body = res.body|| {}
if(!_body.chatBody) {
return
}
talkingList.value.push({
..._data,
chatContent: _body.chatContent,
userInfo: {...choiceTeacherInfo.value},
intrctId: _body.chatBody ||'',
talkingContent: val,
talkingVoice: '',
type: 2,
userInfo: {...choiceTeacherInfo.value},
id: _id
})
scrollToBottomNow()
cb && cb()
})
}
// 撤回
const withdrawNow = (data) => {
withdrawalAnswerApi({
intrctId: data.intrctId
}).then(res=> {
textChartApi({
processType: 'ANSER-WITHDRAW',
requestBody: JSON.stringify({
intrctId: data.intrctId
})
})
.then(res=> {
// 删除撤回的数据item
common.msg('撤回成功!')
talkingList.value = talkingList.value.filter(t => t.intrctId !== data.intrctId)
})
}
const loadingQstLogId = ref('')
// 完成
const finishQuestionNow = (data) => {
waitMakeEvaluateApi({
qstLogId: data.qstLogId
}).then(res=> {
console.log(res.body.processStat)
common.msg('完成成功,开始获取评分维度数据!')
// qstLogId 问题日志ID String
// qnsId 问题ID String
// processStat 状态 String
// message 处理消息 String
let _id1 = new Date().getTime() + 'id'
talkingList.value.push({
id: _id1,
talkingContent: '完成',
userInfo: {...choiceTeacherInfo.value},
type: 0
})
// waitMakeEvaluateApi({
// qstLogId: data.qstLogId
// })
textChartApi({
processType: 'ANSER-OVER',
requestBody: JSON.stringify({
qstLogId: data.qstLogId
})
})
.then(res=> {
// common.msg('完成成功,开始获取评分维度数据!')
loadingQstLogId.value = res.body.chatBody.qstLogId
qstLogId.value = loadingQstLogId.value
let _id = new Date().getTime() + 'id'
lastTalkingInfo.value = {
isLoading: true,
id: _id,
userInfo: {...choiceTeacherInfo.value},
type: 5
}
talkingList.value.push(lastTalkingInfo.value)
scrollToBottomNow(0)
getQuestionEvalateRequest(_id,data)
})
}
// 查询问题回答评分
const getQuestionEvalateRequest = (id, data) => {
if(loadingQstLogId.value){
// queryQuestionEvalateApi({
// qstLogId:loadingQstLogId.value
// })
textChartApi({
processType: 'ANSER-RESULT',
requestBody: JSON.stringify({
qstLogId:loadingQstLogId.value
})
})
.then(res=>{
let _body = res.body|| {}
if(!_body.chatBody) {
return
}
if(_body.chatBody && _body.chatBody.evalOver === 'Y'){
talkingList.value = talkingList.value.map(t=>{
if(t.id === id){
lastTalkingInfo.value = {
..._body.chatBody,
pgrphId: data.pgrphId,
qstLogId: loadingQstLogId.value,
id,
userInfo: {...choiceTeacherInfo.value},
type: 5
}
return lastTalkingInfo.value
}else{
return t
}
})
scrollToBottomNow()
}else{
setTimeout(()=>{
getQuestionEvalateRequest(id, data)
}, 2 * 1000)
}
})
}
}
const showSeekModel = ref(false)
@@ -680,7 +729,7 @@ const chooseRateNow = (item) => {
text-align: right;
padding-right: 48rpx;
img{
.image-icon{
width: 30rpx;
height: 32rpx;
position: absolute;