Files
tra-app/src/pages/sparring/tip.vue
T

980 lines
24 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="goBack()"></view>
{{ detailInfo.traName }}
</view>
<view class="top-desc">内容由AI生成</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' || detailInfo.gender === '01'"
src="@/static/images/me/man.png"
></image>
<image
class="card-title-icon"
v-if="detailInfo.gender === 'f' || detailInfo.gender === '02'"
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-desc">陪练角色不具备主动终止场景陪练的功能当用户完成预设陪练目标时需由用户手动发起结束陪练操作</view>
<view class="talking-box" v-if="detailInfo.traInterTyp !== '01'">
<view class="tip-text">-请点击下方按钮开始{{ detailInfo.traInterTypDesc }}</view>
</view>
<view class="talking-box" v-else>
<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, onHide, onLoad, onUnload, onLaunch, onBackPress } from '@dcloudio/uni-app';
import common from '@/common/common';
import { getPhoneEnvBool } 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';
import { initRecord } from '@/composables/useExamSubject.js';
import permissionApi from '@/common/permission'
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, voiceTime) => {
//#ifndef APP-PLUS
// h5 假数据
// #endif
//#ifdef APP-PLUS
talkingList.value.push({
type: 'question',
talkingText: '',
isLoading: true
});
scrollToBottomNow();
// 设为提问中状态
answerIsOver.value = false;
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,
fileId: _res.body.fileId,
ossFileAddr: _res.body.ossFileAddr,
talkingVoice: voicePath,
voiceTime: voiceTime
};
sendMessage({
commond: 'ASK',
body: askData.value
});
} else {
answerIsOver.value = true;
talkingList.value.pop();
uni.showToast({
title: '未识别到文字',
icon: 'none',
duration: 1000
});
}
})
.catch((err) => {
answerIsOver.value = true;
talkingList.value.pop();
uni.showToast({
title: '系统异常,请联系管理员',
icon: 'none',
duration: 1000
});
});
// #endif
};
const toTalking = async () => {
try {
//#ifdef APP-PLUS
console.log('申请权限');
try {
// await initRecord();
await permissionApi.judgeIosPermission('record')
await permissionApi.judgeIosPermission('camera')
} catch(e) {
console.error('摄像头或者麦克风权限申请失败:', e);
return;
}
//#endif
common.setPageCache('tipMsgsceneDesc', {
ossAddr: detailInfo.value.ossAddr,
chaName: detailInfo.value.chaName,
traInterTyp: detailInfo.value.traInterTyp,
traInterTypDesc: detailInfo.value.traInterTypDesc,
});
common.redirectTo(
'/pages/sparring/talk?traId=' +
traId.value +
'&chaId=' +
chaId.value +
'&type=' +
talkingType.value +
'&traInterTyp=' +
detailInfo.value.traInterTyp +
'&isPreview=' +
(isPreview.value ? '1' : '0')
);
} catch (err) {
console.error('申请权限失败');
}
};
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);
let isLeave = false;
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('系统网络异常,请稍后再试!');
common.redirectTo(`/pages/index/dialog`);
return;
}
showModelComfirm.value = true;
reconcatNum.value += 1;
common
.show('提示信息', '网络环境不稳定,请重试!', false, '', '确认')
.then((res) => {
overNow(() => {
reconcatNum.value += 1;
// common.redirectTo(`/pages/index/dialog`);
socketStore?.closeSocket();
common.redirectTo(
'/pages/sparring/result?isOver=1&traId=' +
traId.value +
'&execId=' +
execId.value +
'&type=' +
talkingType.value +
'&isPreview=' +
(isPreview.value ? '1' : '')
);
});
})
.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') {
console.log('ASK-OPEN', body);
// 新连接
sendMessage({
commond: 'ASK-START',
body: {
traId: traId.value,
chaId: chaId.value,
traTyp: talkingType.value,
execId: execId.value || ''
}
});
sendMessage({
commond: 'ASK-PING',
body: {
execId: execId.value || ''
}
});
return;
}
if (commond === 'ASK-START') {
console.log('开启对话!', body);
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 = talkingList.value.filter((t) => {
return !(t.type === 'question' && t.isLoading);
});
}
// 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 + 'qns',
...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') {
if (body.event === 'start') {
answerIsOver.value = false;
answerText.value = '';
// talkingList.value.pop();
talkingList.value = talkingList.value.filter((t) => {
return !(t.type === 'answer' && t.isLoading);
});
talkingList.value.push({
type: 'answer',
talkingText: answerText.value,
isLoading: false,
execLogId: body.execLogId || '',
id: _id + 'ans',
...body,
askData: {
...askData.value
}
});
} else if (body.event === 'token') {
answerIsOver.value = false;
answerText.value += body.data;
talkingList.value[talkingList.value.length - 1].talkingText = answerText.value;
} else if (body.event === 'end') {
answerIsOver.value = true;
} else if (body.event === 'recordAi') {
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;
}
if (commond === 'ASK-OVER') {
console.log(body);
console.log('陪练已结束,去结果页面!');
socketStore?.closeSocket();
common.redirectTo(
'/pages/sparring/result?isOver=1&traId=' +
traId.value +
'&execId=' +
execId.value +
'&type=' +
talkingType.value +
'&isPreview=' +
(isPreview.value ? '1' : '')
);
return;
}
if (commond === 'ASK-PONG') {
console.log('PONG');
if (isLeave) return;
setTimeout(() => {
sendMessage({
commond: 'ASK-PING',
body: {
execId: execId.value || ''
}
});
}, 30000);
}
} else {
if (['0005', 'QQ0005'].includes(res.rtnCode)) {
goto_login_fun();
}
}
});
watchFlag.value += 1;
};
const goBack = () => {
if (detailInfo.value.traInterTyp === '01') {
overTalking();
} else {
common.navigateBack();
}
};
const overTalking = () => {
common.show('提示', '确定结束陪练吗?').then((res) => {
if (res) {
overNow();
}
});
};
const overNow = (cb = () => {}) => {
partnerChatReportTrigger({
execId: execId.value,
traId: traId.value,
talkingType: isPreview.value ? 'preview' : 'practice'
})
.then((res) => {
watchFlag.value = 1;
sendMessage({
commond: 'ASK-STOP',
body: {
execId: execId.value || ''
}
});
socketStore?.closeSocket();
changePlayItemId('');
common.redirectTo(
'/pages/sparring/result?isOver=1&traId=' +
traId.value +
'&execId=' +
execId.value +
'&type=' +
talkingType.value +
'&isPreview=' +
(isPreview.value ? '1' : '')
);
// pointAndBadgesRun(talkingType.value === '01'? '07': '08', '', ()=>{
// common.redirectTo('/pages/sparring/result?isOver=1&traId=' + traId.value + '&execId=' + execId.value + '&type=' + talkingType.value + '&isPreview=' + (isPreview.value ?'1':''))
// })
})
.catch(() => {
cb();
});
};
const sendMessage = (data) => {
unref(SocketObj).send(data);
if (data.commond == 'ASK-PING') {
console.log('PING');
}
};
// let timer = null
//#ifdef APP-PLUS
onShow(() => {
isLeave = false;
// timer = setInterval(() => {
// sendMessage({
// commond: 'ASK-PING',
// body: {
// execId: execId.value || ''
// }
// }, 1000)
// })
});
onHide(() => {
isLeave = true;
// clearInterval(timer)
// timer = null
});
// #endif
onLoad((params) => {
traId.value = params.traId;
chaId.value = params.chaId;
talkingType.value = params.type;
isPreview.value = params.isPreview === '1';
getDetailInfo();
console.log('onLoad');
//#ifdef APP-PLUS
plus.screen.lockOrientation('portrait-primary');
// #endif
});
onUnload(() => {
console.log('onUnload');
watchFlag.value = 1;
sendMessage({
commond: 'ASK-STOP',
body: {
execId: execId.value || ''
}
});
if (detailInfo.value.traInterTyp === '01') {
socketStore?.closeSocket();
}
execId.value = '';
});
onLaunch(() => {
console.log('onLaunch');
});
onMounted(() => {
// getDetailInfo()
});
onBackPress((res) => res.from === 'backbutton');
</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-desc {
position: absolute;
bottom: 0;
font-size: 18rpx;
color: #999;
left: 50%;
transform: translateX(-50%);
}
.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, 0.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-desc{
// padding-top: 24rpx;
padding: 24rpx 20rpx 0;
font-size: 24rpx;
// text-align: center;
color: #999;
}
.talking-box {
padding-top: 24rpx;
.tip-text {
color: #7295cb;
text-align: center;
font-size: 29rpx;
}
}
}
.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>