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

This commit is contained in:
田岩
2025-07-18 18:27:18 +08:00
20 changed files with 623 additions and 149 deletions
+1
View File
@@ -72,6 +72,7 @@ export const commonUploadVoiceFile = (file, url, data = {}, fileKey='voice') =>
};
if (token)
header['summary'] = token
console.log(`${base_url}${url}`)
return new Promise((resolve, reject) => {
uni.uploadFile({
url: `${base_url}${url}`,
+1
View File
@@ -35,6 +35,7 @@ export default class WebSocketUtil {
*/
init(flag = false) {
if(flag) this.reconnectCount = 0
this.isClose = false; // 手动关闭
// 创建WebSocket连接
this.socketTask = uni.connectSocket({
url: this.url,
@@ -156,7 +156,7 @@
position: absolute;
left: 50%;
top: 50%;
max-width: 100%;
width: 100vw;
max-height: 100%;
transform: translate(-50%, -50%);
}
@@ -104,8 +104,8 @@
transform: translate(-50%, -50%);
}
.overlay-box .video-cont-center{
max-width: 100%;
max-height: 80%;
width: 100vw;
max-height: 100%;
position: absolute;
top: 50%;
left: 50%;
+329
View File
@@ -0,0 +1,329 @@
<template>
<view class="chart-box" :style="allStyle">
<view class="cont-box">
<view v-for="width in 5" :class="'total-line total-line' + width"></view>
<view v-for="width in 3" :class="`percent-${width * 25}`"></view>
<view class="data-box">
<view class="line-box">
<view v-for="width in 3" :class="`percent-${width * 25}`"></view>
</view>
</view>
</view>
<view class="title">
<image class="title-icon" src="@/static/images/course/edit.png" alt=""> </image>
改进建议
</view>
<view class="total-score">
<view class="score-num">{{ score }}</view>
<image class="total-score-icon" src="@/static/images/course/score.png" alt=""></image>
</view>
<view v-for="(item,index) in 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>
<script>
export default{
props:{
dataList:{
default:() => [],
type: Array
},
score:{
default: 0,
type: [Number,String]
},
size:{
default: 262,
type: [Number,String]
}
},
data(){
return {
}
},
computed:{
dataValue(){
return this.dataList.map(t=>t.anlyGrade)
},
chartWidth(){
return this.size + 'rpx'
},
chartHeight(){
return this.size + 'rpx'
},
scoreMargin1Y(){
return '-' + (this.size/2 - 25) + 'rpx'
},
scoreMargin2X(){
return `calc(-50% + ${this.size/2 + 60}rpx)`
},
scoreMargin2Y(){
return `calc(-50% - ${this.size * .077}rpx)`
},
scoreMargin3X(){
return `calc(-50% + ${this.size * .38}rpx)`
},
scoreMargin3Y(){
return (this.size/2 + 20) + 'rpx'
},
scoreMargin4X(){
return `calc(-50% - ${this.size * .38}rpx)`
},
scoreMargin4Y(){
return this.scoreMargin3Y
},
scoreMargin5X(){
return `calc(-50% - ${this.size /2 + 60}rpx)`
},
scoreMargin5Y(){
return this.scoreMargin2Y
},
styleFive(){
let base = 50
let _tx = 50
let _ty = 0
let _rx = 50 + base*Math.cos(1/10 * Math.PI)
let _ry = 50 - base*Math.sin(1/10 * Math.PI)
let _bx = 50 + base*Math.sin(1/5 * Math.PI)
let _by = 50 + base*Math.cos(1/5 * Math.PI)
let _lx = 50 - base*Math.sin(1/5 * Math.PI)
let _ly = 50 + base*Math.cos(1/5 * Math.PI)
let _lxl = 50 - base*Math.cos(1/10 * Math.PI)
let _lyl = 50 - base*Math.sin(1/10 * Math.PI)
return `polygon(${_tx}% ${_ty}%, ${_rx}% ${_ry}%, ${_bx}% ${_by}%, ${_lx}% ${_ly}%, ${_lxl}% ${_lyl}%)`
},
styleFiveResult(){
let score = this.dataList
if(score.length >= 5){
let base = 50
let _tx = 50
let _ty = 50 - score[0] * 50 / 100
let _rx = 50 + base*Math.cos(1/10 * Math.PI) / 100 * score[1]
let _ry = 50 - base*Math.sin(1/10 * Math.PI) / 100 * score[1]
let _bx = 50 + base*Math.sin(1/5 * Math.PI) / 100 * score[2]
let _by = 50 + base*Math.cos(1/5 * Math.PI) / 100 * score[2]
let _lx = 50 - base*Math.sin(1/5 * Math.PI) / 100 * score[3]
let _ly = 50 + base*Math.cos(1/5 * Math.PI) / 100 * score[3]
let _lxl = 50 - base*Math.cos(1/10 * Math.PI) / 100 * score[4]
let _lyl = 50 - base*Math.sin(1/10 * Math.PI) / 100 * score[4]
return `polygon(${_tx}% ${_ty}%, ${_rx}% ${_ry}%, ${_bx}% ${_by}%, ${_lx}% ${_ly}%, ${_lxl}% ${_lyl}%)`
}else{
return this.styleFive
}
},
allStyle(){
return {
'--styleFive': this.styleFive,
'--styleFiveResult': this.styleFiveResult,
'--chartWidth': this.chartWidth,
'--chartHeight': this.chartHeight,
'--scoreMargin1Y': this.scoreMargin1Y,
'--scoreMargin2X': this.scoreMargin2X,
'--scoreMargin2Y': this.scoreMargin2Y,
'--scoreMargin3X': this.scoreMargin3X,
'--scoreMargin3Y': this.scoreMargin3Y,
'--scoreMargin4X': this.scoreMargin4X,
'--scoreMargin4Y': this.scoreMargin4Y,
'--scoreMargin5X': this.scoreMargin5X,
'--scoreMargin5Y': this.scoreMargin5Y
}
}
}
}
</script>
<style lang="scss" scoped>
.chart-box{
width: 100%;
height: 580rpx;
background:linear-gradient( 270deg, #4a74ff 0%, #6fa7fe 100%);
overflow: hidden;
position: relative;
border-radius: 16rpx;
margin-bottom: 20rpx;
.cont-box{
background-color: #87abfe;
width: var(--chartWidth);
height: var(--chartHeight);
clip-path: var(--styleFive);
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
margin-top: 25rpx;
.total-line{
width: 57.7%;
height: 3rpx;
background-color: rgba(255, 255, 255, .3);
position: absolute;
overflow: hidden;
z-index: 20;
&.total-line1{
top: 40.5%;
left: 49.5%;
transform: rotateZ(162deg);
}
&.total-line2{
top: 40.5%;
left:-6.5%;
transform: rotateZ(18deg);
}
&.total-line3{
top: 21.5%;
left: 21%;
transform: rotateZ(-90deg);
}
&.total-line4{
top: 72.5%;
left: 4.5%;
transform: rotateZ(-54deg);
}
&.total-line5{
top: 72.5%;
left: 37.5%;
transform: rotateZ(-126deg);
}
}
@for $i from 1 through 3 {
.percent-#{$i*25}{
width: $i * 25%;
height: $i * 25%;
clip-path: var(--styleFive);
background-color: rgba(255, 255, 255, .3);
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: (4 - $i) * 2;
&::before{
content: '';
display: block;
width: calc(100% - 10rpx);
height: calc(100% - 10rpx);
background-color: #87abfe;
clip-path: var(--styleFive);
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
}
}
.data-box{
width: 100%;
height: 100%;
clip-path: var(--styleFiveResult);
position: absolute;
background-color: #fff;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 12;
overflow: hidden;
.line-box{
display: block;
width: calc(100% - 24rpx);
height: calc(100% - 24rpx);
// background-color: #6691fe;
background-color: #6691fe;
clip-path: var(--styleFiveResult);
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 14;
}
@for $i from 1 through 3 {
.percent-#{$i*25}{
background-color: rgba(255, 255, 255, .1);
z-index: (4 - $i) * 2 + 10;
&::before{
background-color: #6692ff;
}
}
}
}
}
.title{
padding: 24rpx;
line-height: 46rpx;
font-size: 32rpx;
color: #fff;
.title-icon{
width: 27rpx;
height: 27rpx;
}
}
.total-score{
position: absolute;
right: 30rpx;
top:30rpx;
width: 100rpx;
height: 80rpx;
line-height: 70rpx;
font-size: 30rpx;
color: #fff;
white-space: nowrap;
text-shadow: 0 4px 12px rgba(0, 0, 0, .33);
.score-num{
display: inline-block;
font-size: 60rpx;
line-height: 70rpx;
font-weight: bold;
}
.total-score-icon{
width: 100%;
height: 16rpx;
position: absolute;
bottom:0;
left:0;
}
}
.score-item{
position: absolute;
color: #fff;
width: 120rpx;
text-align: center;
.score-item-name{
font-size: 28rpx;
line-height: 36rpx;
margin-bottom: 4rpx;
}
.score-item-score{
font-size: 38rpx;
line-height: 46rpx;
font-weight: bold;
}
&.score-item1{
left: 50%;
margin-left: -60rpx;
bottom: 50%;
transform: translateY(var(--scoreMargin1Y));
}
&.score-item2{
left: 50%;
top: 50%;
transform: translate(var(--scoreMargin2X), var(--scoreMargin2Y));
}
&.score-item3{
left: 50%;
top: 50%;
transform: translate(var(--scoreMargin3X), var(--scoreMargin3Y));
}
&.score-item4{
left: 50%;
top: 50%;
transform: translate(var(--scoreMargin4X), var(--scoreMargin3Y));
}
&.score-item5{
left: 50%;
top: 50%;
transform: translate(var(--scoreMargin5X), var(--scoreMargin2Y));
}
}
}
</style>
+12 -19
View File
@@ -30,6 +30,7 @@
<view v-if="type === 5 && !dataInfo.isLoading" style="padding-top: 16rpx;">
<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"/>
<ChartFive v-if="dataInfo.evalDimens.length === 5" :dataList="dataInfo.evalDimens" :score="dataInfo.intrctGrade"/>
</view>
</view>
<view class="btns-cont" v-show="!dataInfo.isLoading">
@@ -151,7 +152,7 @@
<view
class="icon text-right-btn"
@click.stop="withdraw"
v-show="isLast"
v-show="showWithdraw"
>撤回
<!-- v-show="showWithdraw" -->
</view>
@@ -189,6 +190,7 @@ import { downloadVoiceFile, uploadVoiceFile, getAudioByText } from "@/api/common
import { studyParagraphOverApi, getAskAboutRawApi } from "@/api/study.js"
import ChartFour from "./chartFour.vue"
import ChartThree from "./chartThree.vue"
import ChartFive from './chartFive.vue'
import common from '@/common/common';
import { getUserInfo } from '@/common/common';
import { useStorageStore } from "@/store/storage.js"
@@ -252,7 +254,7 @@ const audioCacheObj = computed(() => storageStore.audioObj)
const itemVoice = ref('')
const showWithdraw = computed(()=>{
let _isMe = unref(lastQuestionInfo).qnsLogId === unref(dataInfo).qnsLogId
let _isMe = unref(lastQuestionInfo).qstLogId === unref(dataInfo).qstLogId
let _isQuestion = unref(lastQuestionInfo).isQuestion
return _isMe && _isQuestion
})
@@ -365,13 +367,17 @@ const audioCacheObj = computed(() => storageStore.audioObj)
},100)
}else{
voiceLoading.value = true
let _type = props.type == 2 ? 'ans' : props.type == 3 ? 'pgrh' : props.type == 4 ? 'qns' : ''
let _readId = props.type == 2 ? unref(dataInfo)?.intrctId : props.type == 3 ? unref(dataInfo)?.pgrphId : props.type == 4 ? unref(dataInfo)?.qnsId : ''
console.log(unref(dataInfo))
let _params = {
crsId: unref(dataInfo)?.crsId || '',
pgrphId: unref(dataInfo)?.pgrphId || '',
qnsId: unref(dataInfo)?.qnsId || '',
readType: readType,
readType: _type,// 2 文本回答 3, 段落信息 4 问题信息
teachNo: unref(teacherInfo)?.teacherNo, // 必填
content: _content
readId: _readId
// content: _content
}
@@ -386,19 +392,6 @@ const audioCacheObj = computed(() => storageStore.audioObj)
talkVoiceObj.value.src = url
talkVoiceObj.value.play()
}else{
// getAudioByText(_params).then(res=>{
// // uni.env.USER_DATA_PATH ||
// let filePath = `${'/'}/${dataInfo.value.id}.mp3`
// saveFile(res, filePath, () => {
// voiceLoading.value = false
// itemVoice.value = filePath
// talkVoiceObj.value.src = filePath
// talkVoiceObj.value.play()
// storageStore.setStorage('audio', _url + _params.teachNo, itemVoice.value)
// })
// }).catch(err=>{
// console.log(err)
// })
downloadVoiceFile(_url).then(res=>{
if(res?.data){
common.msg(res.data.message)
@@ -522,7 +515,7 @@ const audioCacheObj = computed(() => storageStore.audioObj)
width: 76rpx;
height: 76rpx;
border: 3rpx solid #fff;
background-color: #ccc;
background-color: #f1f1f1;
overflow: hidden;
border-radius: 10rpx;
margin-right: 16rpx;
@@ -698,7 +691,7 @@ const audioCacheObj = computed(() => storageStore.audioObj)
width: 76rpx;
height: 76rpx;
border: 3rpx solid #fff;
background-color: #ccc;
background-color: #f1f1f1;
overflow: hidden;
border-radius: 10rpx;
margin-left: 16rpx;
+60 -37
View File
@@ -61,12 +61,23 @@ const props = defineProps({
default: false,
type: Boolean
},
canAnswer:{
default: true,
type: Boolean
},
canAnswerText:{
default: '当前状态不能发送内容!',
type: String
},
})
const { isDisabled, onlyVoice } = toRefs(props)
const isLoadingState = computed(() => props.isLoading)
watch(()=>props.isLoading,()=>{},{
deep:true,immediate:true
})
watch(()=>props.canAnswer,()=>{},{
deep:true,immediate:true
})
watch(()=>props.onlyVoice,()=>{},{
deep:true,immediate:true
})
@@ -87,11 +98,32 @@ const initRecord = () => {
// stopRecord()
unref(recordObjCom)?.onStop((res, duration)=>{
audioPath.value = res.tempFilePath
showTalkingModel.value = false
if(startFlag.value !== 2){
common.msg('说话时间太短,没有听清!')
return
}
if(!isOut.value){
emit('uploadVoice', audioPath.value)
}
})
unref(recordObjCom).onStart(() => {
startFlag.value = 1
setTimeout(()=>{
startFlag.value = 2
},1000)
let _recordPerm = uni.getAppAuthorizeSetting()
let _state = _recordPerm.microphoneAuthorized
if(_state === 'authorized'){
showTalkingModel.value = true
}
})
}
const startFlag = ref(1)
const startRecord = () => {
unref(recordObjCom).start({format:'mp3'})
}
const stopRecord = () => {
if(unref(recordObjCom).stop){
unref(recordObjCom)?.stop?.()
@@ -120,17 +152,20 @@ onMounted(async ()=>{
})
}
}else{
let _recordPerm = permission.judgeIosPermission('record')
if(!_recordPerm){
let _recordPerm = uni.getAppAuthorizeSetting()
let _state = _recordPerm.microphoneAuthorized
if(_state === 'denied'){
common.show('提示信息', '当前页面需要使用的录音权限,是否前往开启?',true, '取消','确定').then((res) => {
if(res) {
// 去开启权限
permission.gotoAppPermissionSetting()
uni.openAppAuthorizeSetting()
}else{
// 取消
common.navigateBack()
}
})
}else if(_state === 'not determined'){
// startRecord()
}
}
// #endif
@@ -154,21 +189,10 @@ const outScreen = computed(()=>{
const recNum = ref(1)
const activeNum = ref(1)
const getRecordPermission = async (obj) => {
//#ifdef APP-PLUS
// 安卓端
// let _recordPerm = await permission.requestAndroidPermission('android.permission.RECORD_AUDIO')
// if(_recordPerm !== 1){
// permission.gotoAppPermissionSetting()
// return
// }
showOverlayTalking(obj)
// #endif
//#ifndef APP-PLUS
// pc端
showOverlayTalking(obj)
// #endif
const getRecordPermission = (obj) => {
showOverlayTalking(obj)
}
const isMoving = ref(false)
const showOverlayTalking = (obj) => {
if(isLoadingState.value){
uni.showToast({
@@ -178,10 +202,18 @@ const showOverlayTalking = (obj) => {
})
return
}
if(!props.canAnswer){
uni.showToast({
title: props.canAnswerText,
icon: "none",
duration:1000
})
return
}
isMoving.value = true
emit('startRecord')
recNum.value += 1
if(!isDisabled.value){
showTalkingModel.value = true
touchX.value = obj.changedTouches[0].pageX
touchY.value = obj.changedTouches[0].pageY
baseY.value = obj.currentTarget.offsetTop
@@ -201,29 +233,20 @@ const showOverlayTalking = (obj) => {
}
}
const hideOverlayTalking = (obj) => {
isMoving.value = false
if(!showTalkingModel.value) return
if(activeNum.value + 1 === recNum.value){
showTalkingModel.value = false
//#ifdef APP-PLUS
stopRecord()
// #endif
if(!isOut.value){
setTimeout(()=>{
emit('uploadVoice', audioPath.value)
},1000)
}
}else{
showTalkingModel.value = false
common.msg('录音失败,请重试!')
}
activeNum.value = recNum.value
//#ifdef APP-PLUS
stopRecord()
// #endif
}
const handleMove = (obj) => {
touchX.value = obj.changedTouches[0].pageX
touchY.value = obj.changedTouches[0].pageY
if(outScreen.value){
hideOverlayTalking(obj)
if(isMoving.value){
touchX.value = obj.changedTouches[0].pageX
touchY.value = obj.changedTouches[0].pageY
if(outScreen.value){
hideOverlayTalking(obj)
}
}
}
// 发送文本
+26 -25
View File
@@ -85,6 +85,8 @@
@startRecord="changePlayItemId('')"
:isLoading="isUploadingVoice"
:onlyVoice="answerOnlyVoice"
:canAnswer="testAnswer"
:canAnswerText="'每个问题最多连续发送五次答案!'"
:isDisabled="((lastTalkingInfo.type||0) !== 4)"
/>
</view>
@@ -177,7 +179,6 @@ const isUploadingVoice = ref(false)
// 初始化数据
const getData = async (flag) => {
try{
initsocketTask()
const { body } = await getCourseStudyInfoApi({crsId: crsId.value})
teacherList.value = body.teacheList.map(t=>({
...t,
@@ -445,7 +446,7 @@ const getQuestionInfoUnStudy = (pgrphId) => {
id: _id,
isQuestion: true
}
talkingList.value.push(lastTalkingInfo.value)
talkingList.value.push(JSON.parse(JSON.stringify(lastTalkingInfo.value)))
qstLogId.value = _body.chatBody?.qstLogId
scrollToBottomNow()
}else{
@@ -548,7 +549,8 @@ const nextQuestionSuccess = (info, type='next') => {
isQuestion: true
}
talkingList.value.push(lastTalkingInfo.value)
qstLogId.value = _body.chatBody.qstLogId
logId.value = _body.chatBody.logId
qstLogId.value = _body.chatBody.qstLogId || qstLogId.value
scrollToBottomNow()
})
}
@@ -581,18 +583,18 @@ const logId = ref('')
// ws 初始化逻辑
const initsocketTask = () => {
socketStore.creatSocket()
unref(SocketObj).on('open', (res)=>{
if(startPgrphId.value){
sendMessage({
"commond" : 'STDY',
"body" : {
crsId: crsId.value,
teacherNo: choiceTeacher.value
}
})
step.value = 2
}
})
// unref(SocketObj).on('open', (res)=>{
// if(startPgrphId.value){
// sendMessage({
// "commond" : 'STDY',
// "body" : {
// crsId: crsId.value,
// teacherNo: choiceTeacher.value
// }
// })
// step.value = 2
// }
// })
unref(SocketObj).on('message', (res)=>{
const { rtnCode, body, commond } = JSON.parse(res.data)
if(rtnCode === '0000'){
@@ -611,10 +613,7 @@ const initsocketTask = () => {
})
}
const reconnect = ()=> {
if(reconnectTimer || isManualClose.value) return
reconnectTimer = setTimeout(()=>{
initsocketTask()
},3000)
initsocketTask()
}
const sendMessage = (data) => {
unref(SocketObj).send(data)
@@ -623,6 +622,7 @@ const sendMessage = (data) => {
const startPgrphId = ref('')
const imageId = ref('')
onLoad((params) => {
watchFlag.value = 1
crsId.value = params.crsId||'';
crsNum.value = params.crsNum||'';
imageId.value = params.imgId || ''
@@ -632,21 +632,21 @@ onLoad((params) => {
const showTalkingModel = ref(false)
// 录音
onMounted(()=>{
initsocketTask()
getData('1');
console.log('onMounted')
})
onHide(()=>{
if(watchFlag.value === 1) return
changePlayItemId('')
socketStore.closeSocket()
common.show('提示信息', '检测到您已离开,即将退出学习页面。',false, '','确认').then((res) => {
common.redirectTo(`/pages/courseDetail/index?crsId=${crsId.value}&crsNum=${crsNum.value}&from=study`)
}).finally(() => {})
// common.show('提示信息', '检测到您已离开,即将退出学习页面。',false, '','确认').then((res) => {
// common.redirectTo(`/pages/courseDetail/index?crsId=${crsId.value}&crsNum=${crsNum.value}&from=study`)
// }).finally(() => {})
})
onShow(()=>{
console.log('onShow')
if(watchFlag.value === 1) return
getData('1');
// getData('2');
initsocketTask()
})
const watchFlag = ref(1)
onUnload(()=>{
@@ -786,6 +786,7 @@ const submitAnswerNow = (val, cb) => {
talkingVoice: '',
type: 2,
userInfo: {...choiceTeacherInfo.value},
qstLogId: qstLogId.value,
id: _id
})
scrollToBottomNow()
+36 -21
View File
@@ -1,6 +1,6 @@
<template>
<view class="talking-info" v-if="dataInfo.intrctWay === '02'">
<view :class="['talking-gif', !voiceLoading ? 'is-play' : '']">
<view :class="['talking-gif', !voiceLoading&&(props.activeVoiceId === dataInfo.id) ? 'is-play' : '']">
<CommonLoading color="#fff" v-show="voiceLoading"/>
</view>
<view class="right-time">{{voiceTime}}</view>
@@ -15,13 +15,13 @@
<view class="btns-cont">
<view class="play-btn">
<image class="icon"
v-show="!isPlaying"
v-show="!(!voiceLoading&&(props.activeVoiceId === dataInfo.id))"
@click="playVoice"
src="@/static/images/course/play-blue.png"
style="width: 100%;height: 100%;"
></image>
<image class="icon"
v-show="isPlaying"
v-show="!voiceLoading&&(props.activeVoiceId === dataInfo.id)"
@click.stop="pauseVoice"
src="@/static/images/course/stop-blue.png"
style="width: 100%;height: 100%;"
@@ -45,7 +45,7 @@
</template>
<script setup>
import { computed, unref, ref, toRefs, watch, onMounted } from "vue"
import { computed, unref, ref, toRefs, watch, onMounted } from "vue"
import { onUnload } from '@dcloudio/uni-app';
const props = defineProps({
dataInfo: {
@@ -63,6 +63,7 @@ import { onUnload } from '@dcloudio/uni-app';
// 声音播放初始化
const talkVoiceObj = ref(null)
const isPlaying = ref(false)
const voiceLoading = ref(false)
const translateLoading = ref(false)
@@ -77,35 +78,46 @@ import { onUnload } from '@dcloudio/uni-app';
onUnload(()=>{
pauseVoice()
})
watch(() => props.dataInfo.talkingVoice,(val)=>{
if(val){
initVoice()
}
},{
deep: true,
immediate: true
})
const pauseVoice = () => {
if(!talkVoiceObj.value) return
talkVoiceObj.value.pause()
isPlaying.value = false
emit('changePlay', '')
}
const playVoice = () =>{
emit('changePlay', unref(dataInfo)?.id)
if(talkVoiceObj.value.src && talkVoiceObj.value.src === itemVoice.value) {
if(talkVoiceObj.value && talkVoiceObj.value.src) {
talkVoiceObj.value.play()
return
}
talkVoiceObj.value.src = ''
if(itemVoice.value){
setTimeout(()=>{
talkVoiceObj.value.src = itemVoice.value
talkVoiceObj.value.play()
},100)
}else{
talkVoiceObj.value.src = ''
if(unref(dataInfo).talkingVoice){
setTimeout(()=>{
talkVoiceObj.value.src = unref(dataInfo).talkingVoice
talkVoiceObj.value.play()
isPlaying.value = true
},100)
}
}
}
const initVoice = () => {
if(unref(dataInfo).talkingVoice){
talkVoiceObj.value = uni.createInnerAudioContext()
talkVoiceObj.value.onEnded(()=>{
emit('changePlay', '')
talkVoiceObj.value.src = ''
})
talkVoiceObj.value.onError(()=>{
talkVoiceObj.value.src = ''
})
talkVoiceObj.value = uni.createInnerAudioContext()
talkVoiceObj.value.onEnded(()=>{
emit('changePlay', '')
talkVoiceObj.value.src = ''
})
talkVoiceObj.value.onError(()=>{
talkVoiceObj.value.src = ''
})
if(unref(dataInfo).intrctWay === '02'){
itemVoice.value = unref(dataInfo).talkingVoice
talkVoiceObj.value.src = itemVoice.value
setTimeout(()=>{
@@ -158,6 +170,9 @@ import { onUnload } from '@dcloudio/uni-app';
height: 38rpx;
line-height: 38rpx;
}
&.talking-info-loading{
padding-bottom: 20rpx;
}
&.talking-info-text{
padding-bottom: 20rpx;
width: auto;
@@ -4,6 +4,7 @@
v-if="talkingType === 'question'"
@changePlay="changePlay"
:dataInfo="talkingInfo"
:activeVoiceId="activeVoiceId"
></questionVue>
<answerVue
v-else-if="talkingType === 'answer'"
@@ -28,8 +29,13 @@
talkingInfo:{
default: () => ({}),
type: Object
},
activeVoiceId:{
default:'',
type: String
}
})
watch(() => props.activeVoiceId, () => {},{deep: true,immediate: true})
watch(() => props.talkingInfo, () => {},{deep: true,immediate: true})
const { talkingType } = toRefs(props)
const handleEvents = (type) => {
+83 -44
View File
@@ -13,9 +13,11 @@
AI问答
</view>
<view class="ai-info">
<image src="@/static/images/dialog/pet-3.png" alt="" class="image-pet-bg"></image>
<view class="ai-info-bg-box">
<view class="hello-text">Hi我是{{'李想'}}</view>
<!-- <image src="@/static/images/dialog/pet-3.png" alt="" class="image-pet-bg"></image> -->
<image src="@/static/images/dialog/pet-bg-base.png" alt="" class="image-pet-bg"></image>
<image :src="userInfo.mascotInfo?.imgAddr ||''" alt="" class="image-pet"></image>
<view class="ai-info-bg-box">
<view class="hello-text">Hi我是{{ userInfo.mascotInfo?.mascotName || ''}}</view>
<view class="info-text">工作学习我都能帮你</view>
<view class="info-btn">试试他的能力</view>
</view>
@@ -50,6 +52,7 @@
v-for="item in talkingList"
:talkingType="item.type"
:talkingInfo="item"
:activeVoiceId="activeVoiceTalkingItemId"
@handleEvents="talkItemEvents"
>
</TalkingItem>
@@ -101,7 +104,7 @@
</template>
<script setup>
import { ref, unref, onMounted, watch, nextTick } from 'vue'
import { ref, unref, onMounted, watch, nextTick, computed } from 'vue'
import common from '@/common/common';
import TouchBtn from '@/pages/course/components/touchBtn.vue'
import TalkingItem from './components/talking-item.vue';
@@ -110,12 +113,14 @@
import { storeToRefs } from 'pinia'
import { onLoad, onShow, onUnload, onHide } from '@dcloudio/uni-app';
import { get_base_url, goto_login_fun } from '@/api/request'
import {
commonUploadVoiceFile
} from "@/api/common.js"
import { getUserInfo } from '@/common/common';
import {
commonUploadVoiceFile
} from "@/api/common.js"
const socketStore = useSocketStore()
const { SocketObj } = storeToRefs(socketStore)
const userInfo = computed(() => getUserInfo())
const questionList = ref([
'对公小程序开户流程?',
@@ -139,31 +144,33 @@ import {
},time)
})
}
const activeVoiceTalkingItemId = ref('')
// ws 初始化逻辑
const reconnectTimer = null // 重连timer
const isManualClose = ref(false) // 是否手动关闭
const reqBatch = ref('')
const seqNo = ref('')
const mascotId = ref('Tst00001')
const mascotId = computed(() => {
return unref(userInfo).mascotInfo?.mascotId || 'Tst00001'
})
const answerText = ref('')
const answerIsOver = ref(true)
const talkingList = ref([])
const initsocketTask = () => {
socketStore.creatSocket('/traask/asksocket/open?summary=')
unref(SocketObj).on('open', (res)=>{
if(!answerIsOver.value){
sendMessage({
"commond" : 'ASK-RE-START',
"body" : {
reqBatch: reqBatch.value,
seqNo: seqNo.value,
answerContent: answerText.value
}
})
}
})
// unref(SocketObj).on('open', (res)=>{
// if(!answerIsOver.value){
// sendMessage({
// "commond" : 'ASK-RE-START',
// "body" : {
// reqBatch: reqBatch.value,
// seqNo: seqNo.value,
// answerContent: answerText.value
// }
// })
// }
// })
unref(SocketObj).on('message', (res)=>{
const { rtnCode, body, commond } = JSON.parse(res.data)
let _id = new Date().getTime() + 'id'
@@ -197,7 +204,8 @@ import {
talkingText: body.intrctContent,
isLoading: false,
id: _id,
...body
...body,
talkingVoice: askData.value.talkingVoice
})
return
}
@@ -246,6 +254,7 @@ import {
if(commond === 'ASK-ERR'){
console.log('提问失败,系统异常!')
console.log('body')
return
}
}else{
@@ -313,6 +322,7 @@ import {
break;
case 'changePlay':
console.log('播放/暂停')
activeVoiceTalkingItemId.value = id
break;
default:
break;
@@ -349,21 +359,31 @@ import {
isLoading: true
})
scrollToBottomNow()
console.log(voicePath,111111111)
commonUploadVoiceFile(voicePath, '/traask/traAskchat/voiceTrans', {}).then(res=>{
let _res = JSON.parse(res.data)
if(_res.rtnCode === '0000'){
askData.value = {
reqBatch: reqBatch.value,
intrctWay: '02',//01-文本;02-语音;03-图片
intrctContent: _res.body.transContent,
bucketKey: _res.body.fileId,
ossFileAddr: _res.body.ossFileAddr,
talkingVoice: voicePath
if(!!(_res.body.transContent || '').trim()){
askData.value = {
reqBatch: reqBatch.value,
intrctWay: '02',//01-文本;02-语音;03-图片
intrctContent: _res.body.transContent,
bucketKey: _res.body.fileId,
ossFileAddr: _res.body.ossFileAddr,
talkingVoice: voicePath
}
sendMessage({
"commond" : 'ASK',
"body": askData.value
})
}else{
talkingList.value.pop()
uni.showToast({
title: '语音内容不能为空!',
icon: "none",
duration:1000
});
}
sendMessage({
"commond" : 'ASK',
"body": askData.value
})
}else{}
}).catch(err=>{
talkingList.value.pop()
@@ -414,14 +434,25 @@ import {
})
onShow(()=>{
if(watchFlag.value === 1) return
socketStore?.createSocket?.()
sendMessage({
"commond" : 'ASK-START',
"body" : {
mascotId: mascotId.value,
deviceImei: ''
}
})
socketStore?.createSocket()
if(!answerIsOver.value){
sendMessage({
"commond" : 'ASK-RE-START',
"body" : {
reqBatch: reqBatch.value,
seqNo: seqNo.value,
answerContent: answerText.value
}
})
}else{
sendMessage({
"commond" : 'ASK-START',
"body" : {
mascotId: mascotId.value,
deviceImei: ''
}
})
}
})
onUnload(()=>{
watchFlag.value = 1
@@ -514,14 +545,22 @@ import {
margin-bottom: 24rpx;
position: relative;
box-sizing: border-box;
.image-pet{
width: 230rpx;
height: 235rpx;
position: absolute;
right: 24rpx;
top: 15rpx;
z-index: 2;
}
.image-pet-bg{
position: absolute;
padding: 0 24rpx;
box-sizing: border-box;
width: 100%;
height: 100%;
height: 230rpx;
left: 0;
top: 0;
bottom: 0;
z-index: 2;
}
.ai-info-bg-box{
@@ -556,7 +595,7 @@ import {
text-align: center;
border-radius: 15rpx 15rpx 0 0;
position: absolute;
left: 64rpx;
left: 68rpx;
bottom: 0;
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 344 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB