Files
tra-app/src/pages/sparring/tip.vue
T
2025-11-21 17:32:36 +08:00

807 lines
20 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="sparring-detail max_page">
<view class="sparring-detail-loading" v-show="showLoading">
<view class="loading-img">
</view>
</view>
<view class="sparring-detail-body">
<view class="top-box">
<view class="top-title">
<view class="back-icon" @click="common.navigateBack()"></view>
{{ detailInfo.traName }}
</view>
</view>
<scroll-view :class="[' body-content index-dialog-body', detailInfo.traInterTyp === '01'?'talking-type1':'']" :scroll-y="true" refresher-default-style="none"
:scroll-top="scrollTop" :show-scrollbar="false" :scroll-with-animation="false">
<view :class="['body-content-role', showRoleInfo?'':'hide-info']">
<view :class="['role-title']">
<ImagePreview class="role-title-avatar" :src="detailInfo.ossAddr" :isCache="true">
</ImagePreview>
<view class="role-title-info" @click="showRoleInfo = !showRoleInfo">
<view class="arrow-r-icon"></view>
<view class="role-name card-title">
<text class="card-title-name">{{ detailInfo.chaName }}</text>
<text class="card-title-age">{{ detailInfo.chaAge }}</text>
<text class="card-title-img">
<image class="card-title-icon" v-if="detailInfo.gender === 'm'" src="@/static/images/me/man.png">
</image>
<image class="card-title-icon" v-if="detailInfo.gender === 'f'" src="@/static/images/me/wuman.png">
</image>
</text>
</view>
<view class="role-desc">预计时间 {{detailInfo.traStartLimit}} ~ {{detailInfo.traEndLimit}} 分钟</view>
</view>
</view>
<view class="role-content">
<view class="role-content-item">
<!-- 身份情况 -->
{{ detailInfo.occupation }}
</view>
<view class="role-content-item">
<!-- 性格特点 -->
{{ detailInfo.personality }}
</view>
<view class="role-content-item">
<!-- 提问思路 -->
{{ detailInfo.thoughtProcess }}
</view>
<view class="role-content-item">
<!-- 人物描述 -->
{{ detailInfo.chaDesc }}
</view>
</view>
</view>
<view class="body-scene">
<view class="body-scene-title">场景描述</view>
<!-- sceneDesc -->
{{ detailInfo.sceneDesc }}
</view>
<view class="talking-box">
<TalkingItem v-for="(item, index) in talkingList" :talkingType="item.type" :talkingInfo="item" :roleInfo="detailInfo"
:traId="traId" :execId="execId" :isExam="talkingType === '02'"
:isLast="index + 1 === talkingList.length" :isAnswering="!answerIsOver"
:activeVoiceId="activeVoiceTalkingItemId" @handleEvents="talkItemEvents"></TalkingItem>
</view>
</scroll-view>
<view class="over-btn-box" v-if="detailInfo.traInterTyp === '01'">
<view class="over-btn" @click="overTalking">
结束陪练
</view>
</view>
<TouchBtn class="body-bottom" @uploadVoice="uploadRecordNow" @startRecord="changePlayItemId('')"
:isLoading="!answerIsOver" :isDisabled="false" v-if="detailInfo.traInterTyp === '01'"/>
<view class="body-bottom" v-else>
<view class="body-bottom-btn" @click="toTalking">
<view class="center-text">
<image class="type-icon" src="@/static/images/sparring/video-icon.png"
v-if="detailInfo.traInterTyp === '03'"></image>
<image class="type-icon" src="@/static/images/sparring/phone-icon.png"
v-else-if="detailInfo.traInterTyp === '02'"></image>
<image v-else class="type-icon" src="@/static/images/sparring/talk-icon.png"></image>
{{detailInfo.traInterTypDesc}}
</view>
</view>
</view>
</view>
<badge ref="badgeRef"></badge>
<points ref="pointsRef"></points>
</view>
</template>
<script setup>
import {
ref,
unref,
onMounted,
computed,
nextTick
} from 'vue'
import {
onShow,
onLoad,
onUnload
} from '@dcloudio/uni-app';
import common from '@/common/common';
import TouchBtn from './components/touchBtn.vue';
import ImagePreview from '@/components/image-preview/image-preview.vue';
import {
commonUploadVoiceFile
} from '@/api/common.js';
import {
queryTraPartnerCharacterInfoById,
partnerChatReportTrigger
} from "@/api/sparring.js"
import {
useSocketStore
} from '@/store/socket.js';
import {
useStorageStore
} from '@/store/storage.js';
import {
storeToRefs
} from 'pinia';
import TalkingItem from './components/talking-item.vue'
import badge from '@/components/points-and-badge/badge';
import points from '@/components/points-and-badge/points';
import usePointsAndBadge from '@/components/points-and-badge/usePointsAndBadge';
const { pointAndBadgesRun, badgeRef, pointsRef } = usePointsAndBadge();
const socketStore = useSocketStore();
const {
SocketObj
} = storeToRefs(socketStore);
const traId = ref('')
const chaId = ref('')
const talkingType = ref('')
// const badgeRef = ref(null);
// const pointsRef = ref(null);
const isPreview = ref(false)
const detailInfo = ref({})
const currentIndex = ref(0)
const showRoleInfo = ref(false)
const showLoading = ref(true)
const talkingList = ref([])
// {
// type: 'answer',
// talkingText: 'asad我是回答内容',
// isLoading: false,
// id: new Date().getTime(),
// },
const askData = ref({})
const execId = ref('')
const answerIsOver = ref(true);
const getDetailInfo = () => {
queryTraPartnerCharacterInfoById({
traId: traId.value,
chaId: chaId.value
}).then(res => {
console.log(res)
detailInfo.value = {
...res.body
}
if (detailInfo.value.traInterTyp === '01') {
initsocketTask()
}
setTimeout(() => {
showLoading.value = false
}, Math.random() * 1000 + 500)
})
}
const scrollTop = ref(99999);
const scrollToBottomNow = (time = 100) => {
nextTick(() => {
setTimeout(() => {
scrollTop.value += 100;
scrollTop.value += 100;
}, time);
});
};
const uploadRecordNow = (voicePath) => {
//#ifndef APP-PLUS
// h5 假数据
// #endif
//#ifdef APP-PLUS
talkingList.value.push({
type: 'question',
talkingText: '',
isLoading: true
});
scrollToBottomNow();
commonUploadVoiceFile(voicePath, '/trapractice/traPartnerChat/voiceTrans', {})
.then((_res) => {
if (!!(_res.body.transContent || '').trim()) {
askData.value = {
execId: execId.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
});
}
})
.catch((err) => {
talkingList.value.pop();
uni.showToast({
title: '系统异常,请联系管理员',
icon: 'none',
duration: 1000
});
});
// #endif
}
const toTalking = () => {
common.navigateTo('/pages/sparring/chooseRole?traId=' + traId.value + '&chaId=' + chaId)
}
const activeVoiceTalkingItemId = ref('')
// 关闭播放状态
const changePlayItemId = (id = '') => {
activeVoiceTalkingItemId.value = id
}
const talkItemEvents = ({
type,
data,
id,
info = {}
}) => {
switch (type) {
case 'changePlay':
activeVoiceTalkingItemId.value = id;
break;
case 'scrollToBottom':
scrollToBottomNow();
break;
default:
break;
}
};
const watchFlag = ref(1);
const showModelComfirm = ref(false)
const reconcatNum = ref(0)
const answerText = ref('')
const touchBtnCallBack = ref(null)
const initsocketTask = () => {
let _src = isPreview.value ?'/trapractice/previewsocket/open':'/trapractice/partnersocket/open'
socketStore.creatSocket(_src + '?summary=');
// unref(SocketObj).on('open', (res)=>{
// if(!answerIsOver.value){
// sendMessage({
// "commond" : 'ASK-RE-START',
// "body" : {
// execId: execId.value,
// seqNo: seqNo.value,
// answerContent: answerText.value
// }
// })
// }
// })
unref(SocketObj).on('error', () => {
if (watchFlag.value === 1 || showModelComfirm.value) return;
if (reconcatNum.value === 5) {
common.msg('系统网络异常,请稍后再试!');
return;
}
showModelComfirm.value = true;
common
.show('提示信息', '网络环境不稳定,请重试!', false, '', '确认')
.then((res) => {
reconcatNum.value += 1;
common.redirectTo(`/pages/index/dialog`);
})
.finally(() => {
showModelComfirm.value = false;
});
});
unref(SocketObj).on('message', (res) => {
const {
rtnCode,
body,
commond
} = JSON.parse(res.data);
let _id = new Date().getTime() + 'id';
if (rtnCode === '0000') {
if (commond === 'ASK-OPEN') {
// 新连接
sendMessage({
commond: 'ASK-START',
body: {
traId: traId.value,
chaId: chaId.value,
traTyp: talkingType.value
}
});
return;
}
if (commond === 'ASK-START') {
// console.log('开启对话!')
execId.value = body.execId;
return;
}
if (commond === 'ASK') {
console.log('发送问题成功!');
console.log(body);
console.log(talkingList.value[talkingList.value.length - 1].isLoading);
if (talkingList.value.length > 0 && talkingList.value[talkingList.value.length - 1].isLoading) {
talkingList.value.pop();
}
// seqNo.value = body.seqNo;
// touchBtnCallBack.value && touchBtnCallBack.value();
// touchBtnCallBack.value = null;
talkingList.value.push({
type: 'question',
talkingText: body.content,
isLoading: false,
intrctWay: '02',
id: _id,
...body,
talkingVoice: askData.value.talkingVoice
});
talkingList.value.push({
type: 'answer',
isLoading: true,
id: _id + 'loading'
});
scrollToBottomNow();
return;
}
if (commond === 'ASK-STOP') {
console.log('停止回答标记!');
stopAnswerCallBack.value && stopAnswerCallBack.value(askData.value);
setTimeout(() => {
stopAnswerCallBack.value = null;
answerIsOver.value = true;
}, 10);
return;
}
if (commond === 'ASK-ANSWER') {
console.log('ASK-ANSWER',body);
if (body.event === 'start') {
console.log('开始回答!');
answerIsOver.value = false;
answerText.value = '';
talkingList.value.pop();
talkingList.value.push({
type: 'answer',
talkingText: answerText.value,
isLoading: false,
execLogId: body.execLogId||'',
id: _id,
...body,
askData: {
...askData.value
},
});
} else if (body.event === 'token') {
answerIsOver.value = false;
answerText.value += body.data;
console.log(answerText.value)
talkingList.value[talkingList.value.length - 1].talkingText = answerText.value;
} else if (body.event === 'end') {
console.log('完成回答!');
answerIsOver.value = true;
} else if (body.event === 'recordAi') {
console.log('推送声音id');
talkingList.value[talkingList.value.length - 1].execLogId = body.execLogId
}
scrollToBottomNow();
return;
}
if (commond === 'ASK-RE-ANSWER') {
// console.log('开始继续回答!')
if (body.event === 'start') {
answerIsOver.value = false;
answerText.value = '';
} else if (body.event === 'token') {
answerIsOver.value = false;
answerText.value = body.data;
} else if (body.event === 'end') {
answerIsOver.value = true;
}
return;
}
if (commond === 'ASK-ERR') {
console.log('提问失败,系统异常!');
return;
}
} else {
if (['0005', 'QQ0005'].includes(res.rtnCode)) {
goto_login_fun();
}
}
});
watchFlag.value += 1;
};
const overTalking = () => {
partnerChatReportTrigger({
execId: execId.value,
traId: traId.value,
talkingType: isPreview.value? 'preview' : 'practice'
}).then(res=>{
watchFlag.value = 1;
socketStore?.closeSocket();
changePlayItemId('')
pointAndBadgesRun(talkingType.value === '01'? '07': '08', null, ()=>{
common.navigateTo('/pages/sparring/result?traId=' + traId.value + '&execId=' + execId.value + '&type=' + talkingType.value + '&isPreview=' + (isPreview.value ?'1':''))
})
})
}
const getPointsList = (pointsList = [], badgesList = []) => {
pointsRef.value.open(pointsList, badgesList);
}
const badgeConfirm = (status, pointsList = [], badgesList = []) => {
badgeRef.value.close();
overTalking()
};
// 积分确认
const pointsConfirm = (status, pointsList = [], badgesList = []) => {
pointsRef.value.close();
if (badgesList.length > 0) {
nextTick(() => {
setTimeout(() => {
badgeRef.value.open(pointsList, badgesList);
}, 500);
});
}
};
const sendMessage = (data) => {
unref(SocketObj).send(data);
};
//#ifdef APP-PLUS
onShow(() => {});
// #endif
onLoad((params) => {
traId.value = params.traId;
chaId.value = params.chaId;
talkingType.value = params.type;
isPreview.value = params.isPreview === '1'
getDetailInfo()
});
onUnload(() => {
watchFlag.value = 1;
socketStore?.closeSocket();
});
onMounted(() => {
// getDetailInfo()
})
</script>
<style lang="scss" scoped>
.sparring-detail {
position: relative;
overflow-x: hidden;
overflow-y: auto;
flex-direction: column;
background-image: url(@/static/images/sparring/bg.png);
background-size: cover;
// background-color: #f2f6fd;
.sparring-detail-loading {
position: fixed;
z-index: 20;
height: 100vh;
width: 100vw;
background-image: url(@/static/images/sparring/bg.png);
.loading-img {
position: absolute;
width: 380rpx;
height: 590rpx;
top: 40%;
transform: translateY(-50%) translateX(-50%);
left: 50%;
padding-top: 540rpx;
line-height: 50rpx;
box-sizing: border-box;
text-align: center;
background-image: url(@/static/images/sparring/waiting.png);
background-repeat: no-repeat;
background-size: cover;
}
}
.sparring-detail-body {
position: absolute;
overflow-x: hidden;
overflow-y: hidden;
left: 0;
top: 0;
height: 100vh;
width: 100%;
box-sizing: border-box;
// background-color: #f6f8ff;
z-index: 1;
padding: calc(var(--status-bar-height) + 90rpx) 0 180rpx;
.top-box {
height: 90rpx;
width: 100%;
position: fixed;
top: var(--status-bar-height);
left: 0;
z-index: 2;
.top-title {
height: 90rpx;
// background-color: red;
text-align: center;
font-size: 33rpx;
padding: 10rpx 100rpx 0;
line-height: 46rpx;
box-sizing: border-box;
font-weight: 500;
color: #000;
position: relative;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
// margin-bottom: 23rpx;
.arrow-icon {
position: absolute;
left: 36rpx;
top: 20rpx;
color: #fff !important;
}
.back-icon {
position: absolute;
width: 40rpx;
height: 40rpx;
cursor: pointer;
left: 50rpx;
top: 33rpx;
transform: translateY(-50%);
}
.back-icon::before,
.back-icon::after {
content: '';
position: absolute;
transition: all 0.3s ease;
}
.back-icon::after {
top: 25%;
left: 25%;
width: 40%;
height: 40%;
border-top: 4rpx solid #000;
border-left: 4rpx solid #000;
transform: rotate(-45deg);
}
}
}
.body-content {
height: 100%;
padding: 10rpx 24rpx 20rpx;
box-sizing: border-box;
overflow-y: auto;
&.talking-type1{
padding: 10rpx 24rpx 140rpx;
}
.body-content-role {
background-color: #fff;
border-radius: 16rpx;
overflow: hidden;
margin-bottom: 30rpx;
&.hide-info {
.arrow-r-icon::after {
transform: rotate(225deg) !important;
}
.role-content {
max-height: 0rpx !important;
padding-bottom: 0rpx !important;
}
}
.role-title {
height: 154rpx;
position: relative;
padding-left: 180rpx;
&-avatar {
width: 92rpx;
height: 92rpx;
position: absolute;
top: 50%;
left: 42rpx;
transform: translateY(-50%);
border-radius: 50%;
overflow: hidden;
}
&-info {
position: relative;
height: 100%;
box-sizing: border-box;
padding-top: 34rpx;
.role-name {
width: 100%;
&.card-title {
display: flex;
font-size: 29rpx;
line-height: 44rpx;
font-weight: 500;
color: #000;
overflow: hidden;
text-align: left;
justify-content: start;
margin-bottom: 8rpx;
.card-title-name {
margin-right: 20rpx;
line-height: 44rpx;
}
.card-title-age {
margin-right: 20rpx;
line-height: 44rpx;
}
.card-title-img {
height: 44rpx;
width: 30rpx;
padding: 5rpx 0;
box-sizing: border-box;
}
.card-title-icon {
height: 30rpx;
width: 30rpx;
}
}
}
.role-desc {
width: 100%;
font-size: 25rpx;
color: #666;
}
.arrow-r-icon {
position: absolute;
width: 36rpx;
height: 36rpx;
cursor: pointer;
right: 50rpx;
top: 50%;
transform: translateY(-50%);
}
.arrow-r-icon::before,
.arrow-r-icon::after {
content: '';
position: absolute;
transition: all 0.3s ease;
}
.arrow-r-icon::after {
top: 25%;
left: 25%;
width: 40%;
height: 40%;
border-top: 2rpx solid #000;
border-left: 2rpx solid #000;
transform: rotate(135deg);
}
}
}
.role-content {
max-height: 800rpx;
transition: all 0.3s ease;
padding-bottom: 12rpx;
&-item {
position: relative;
padding: 8rpx 46rpx 8rpx 76rpx;
line-height: 42rpx;
font-size: 29rpx;
&::before {
content: '';
display: block;
background-color: #000;
width: 12rpx;
height: 12rpx;
border-radius: 50%;
position: absolute;
left: 46rpx;
top: 22rpx;
}
}
}
}
.body-scene {
background: linear-gradient(209deg, #fefeff 0%, #d7dfff 100%);
border: 2rpx solid rgba(255, 255, 255, .65);
position: relative;
line-height: 42rpx;
font-size: 29rpx;
border-radius: 16rpx;
overflow: hidden;
padding: 108rpx 46rpx 30rpx;
.body-scene-title {
position: absolute;
left: 0;
top: 0;
background-color: #06f;
border-radius: 0 0 36rpx 0;
width: 170rpx;
height: 78rpx;
line-height: 78rpx;
color: #fff;
text-align: center;
}
}
.talking-box{
padding-top: 24rpx;
}
}
.over-btn-box{
position: absolute;
bottom: 180rpx;
height: 140rpx;
padding: 0 23rpx;
width: 100%;
.over-btn{
float: right;
width: 194rpx;
height: 76rpx;
line-height: 72rpx;
border: 2rpx solid #06f;
border-radius: 15rpx;
text-align: center;
color:#06f;
margin-top: 34rpx;
}
}
.body-bottom {
height: 180rpx;
background-color: #fff;
padding: 36rpx 23rpx 0;
position: fixed;
width: 100vw;
bottom: 0;
left: 0;
&-btn {
width: 100%;
height: 92rpx;
line-height: 92rpx;
background-color: #06f;
text-align: center;
color: #fff;
font-size: 29rpx;
border-radius: 23rpx;
position: relative;
.center-text {
height: 35rpx;
line-height: 35rpx;
width: 100%;
position: absolute;
top: 50%;
transform: translateY(-50%);
display: flex;
justify-content: center;
}
.type-icon {
width: 35rpx;
height: 35rpx;
margin-right: 10rpx;
}
}
}
}
}
</style>