Merge branch 'dev-recording' into develop

This commit is contained in:
田岩
2026-03-11 08:39:59 +08:00
8 changed files with 3032 additions and 2708 deletions
+1 -1
View File
@@ -5,7 +5,7 @@ ENV = 'development'
#VITE_APP_BASE_API_Url = 'https://aits.jlbank.com.cn:7001'
VITE_APP_BASE_API_Url = 'https://aitstest.jlbank.com.cn:7003'
VITE_APP_BASE_API_Url = 'https://aitstest.jlbank.com.cn:7001'
# DEV
+11
View File
@@ -46,4 +46,15 @@
height: 50rpx;
}
}
&.blue{
background-color: #06f;
&.inputMode {
border: 2rpx solid #dedede00;
}
&.action {
background-color: #0066ff99;
}
}
}
+7 -1
View File
@@ -1,6 +1,6 @@
<template>
<view>
<view class="talk-btns-components" :class="{ action: isRecording, inputMode: !isInputVisible }">
<view :class="['talk-btns-components', props.theme, { action: isRecording, inputMode: !isInputVisible }]">
<view class="touch-btn" @click="disabled_click">
<button
v-if="isInputVisible"
@@ -11,7 +11,9 @@
@touchcancel="stopRecord()"
:plain="true"
>
<slot>
<text class="voice-text">{{ isRecording ? '松开发送' : '按住说话' }}</text>
</slot>
</button>
<input
v-if="!isVoiceOnlyMode && !isInputVisible"
@@ -180,6 +182,10 @@
/* '01': '不限制回答方式', '02': '仅文字回答', '03': '仅语音回答'*/
return ['01', '02', '03'].includes(value);
}
},
theme: {
type: String,
default: ''
}
});
// 兼容以前的按钮参数
+60 -22
View File
@@ -623,7 +623,8 @@
};
const downloadFnc = ref(null)
const playVoice = (readType = 'pgth') => {
console.log('playVoice11111111')
console.log(unref(dataInfo))
if (voiceIsPlaying.value) return
let _content = showContent.value;
if (!_content) {
@@ -765,6 +766,37 @@
setTimeout(() => {
setVoiceTime(0);
}, 100);
}else{
console.log(type.value)
if(type.value === 1){
downloadFile(unref(dataInfo).bucketKey)
.then((res) => {
if (res?.data) {
common.msg(res.data.message);
return;
}
voiceLoading.value = false;
itemVoice.value = res.tempFilePath;
talkVoiceObj.value.src = res.tempFilePath;
console.log('talkVoiceObj.value.src', talkVoiceObj.value.src)
if (voiceIsPlaying.value) return
if(unref(dataInfo)?.id !== props.activeVoiceId) return
talkVoiceObj.value.play();
console.log('播放')
storageStore.setStorage('audio', unref(dataInfo)?.id, itemVoice.value);
setTimeout(()=>{
setVoiceTime(0)
},100)
})
.catch((err) => {
console.log('err',err)
voiceLoading.value = false;
emit('changePlay', '');
});
}
}
// else{
// setTimeout(()=>{
@@ -783,33 +815,39 @@
}
})
}
// const setVoiceTime = (sum) => {
// sum += 1;
// if (talkVoiceObj.value.duration) {
// voiceTime.value = (Math.ceil(talkVoiceObj.value.duration) || 0) + 's';
// } else {
// if (sum === 100) {
// voiceTime.value = (unref(dataInfo).voiceTime || 0) + 's';
// return;
// }
// setTimeout(() => {
// setVoiceTime(sum);
// }, 300);
// }
// };
const playQnsVoice = (readType = 'qns', item) => {
console.log(type.value)
if (type.value === 2) {
// 文本转语音
playVoice(readType);
return;
}
// readType 阅读内容类型pgrh段落/qns问题
emit('changePlay', unref(dataInfo)?.id);
}else{
emit('changePlay', unref(dataInfo)?.id)
if(talkVoiceObj.value && talkVoiceObj.value.src) {
console.log(2)
console.log('talkVoiceObj.value.src', talkVoiceObj.value.src)
talkVoiceObj.value.play()
return
}else{
console.log(3)
talkVoiceObj.value.src = ''
if(unref(dataInfo).talkingVoice){
setTimeout(()=>{
itemVoice.value = item.talkingVoice;
talkVoiceObj.value.src = item.talkingVoice;
talkVoiceObj.value.play();
}, 100);
talkVoiceObj.value.src = unref(dataInfo).talkingVoice
talkVoiceObj.value.play()
isPlaying.value = true
},100)
}
}
}
// // readType 阅读内容类型pgrh段落/qns问题
// emit('changePlay', unref(dataInfo)?.id);
// setTimeout(() => {
// itemVoice.value = item.talkingVoice;
// talkVoiceObj.value.src = item.talkingVoice;
// talkVoiceObj.value.play();
// }, 100);
};
const translateLoading = ref(false);
// 翻译
+165 -74
View File
@@ -12,19 +12,26 @@
</view>
<view class="voice-list">
<uv-radio-group v-model="choiceTeacher">
<view class="voice-item" v-for="item in teacherList" :key="item.teacherNo" @click="choiceTeacher = item.teacherNo">
<view class="voice-item" v-for="item in teacherList" :key="item.teacherNo"
@click="choiceTeacher = item.teacherNo">
<view class="avatar-box">
<ImagePreview class="img-box" :src="item.imgAddr" :isCache="true" :width="120" :height="160"> </ImagePreview>
<ImagePreview class="img-box" :src="item.imgAddr" :isCache="true" :width="120" :height="160">
</ImagePreview>
</view>
<view class="right-cont">
<view class="right-name">{{ item.teacherName }}</view>
<view class="right-desc">描述{{ item.voiceDesc || '--' }}</view>
<view class="audio-box">
<view :class="['talking-gif', activeVoiceTeachNo === item.teacherNo && !loadingTeacherVoice ? 'is-play' : '']"> </view>
<CommonLoading color="#06f" v-show="activeVoiceTeachNo === item.teacherNo && loadingTeacherVoice" style="float: left" />
<view
:class="['talking-gif', activeVoiceTeachNo === item.teacherNo && !loadingTeacherVoice ? 'is-play' : '']">
</view>
<CommonLoading color="#06f" v-show="activeVoiceTeachNo === item.teacherNo && loadingTeacherVoice"
style="float: left" />
<view class="play-btn" @click="playTeacherVoice(item)">
<image class="icon" v-if="activeVoiceTeachNo !== item.teacherNo" src="@/static/images/course/play-blue.png" style="width: 100%; height: 100%"></image>
<image class="icon" v-else src="@/static/images/course/stop-blue.png" style="width: 100%; height: 100%"></image>
<image class="icon" v-if="activeVoiceTeachNo !== item.teacherNo"
src="@/static/images/course/play-blue.png" style="width: 100%; height: 100%"></image>
<image class="icon" v-else src="@/static/images/course/stop-blue.png"
style="width: 100%; height: 100%"></image>
</view>
</view>
</view>
@@ -46,7 +53,8 @@
课程学习
</view>
<view class="study-num">
<view class="color-black ml-40">学习中<text class="color-gray"><text class="color-black">{{ showOrder }}</text>/{{ pgrphNum }}</text></view>
<view class="color-black ml-40">学习中<text class="color-gray"><text
class="color-black">{{ showOrder }}</text>/{{ pgrphNum }}</text></view>
<view class="bg-block">
<view class="bg-gray">
<view class="bg-blue" :style="`width: ${showOrder/pgrphNum * 100}%;`"></view>
@@ -55,31 +63,19 @@
</view>
<scroll-view class="talking-list" :scroll-y="true" :scroll-top="scrollTop" :show-scrollbar="false" :scroll-with-animation="true">
<scroll-view class="talking-list" :scroll-y="true" :scroll-top="scrollTop" :show-scrollbar="false"
:scroll-with-animation="true">
<!-- <view class="talking-list-cont" id="scroll-box" ref="scrollBoxRef"> -->
<TalkingItem
class="talking-item"
v-for="(item, index) in talkingList"
:key="'item' + item.id"
:type="item.type"
:dataInfo="item"
:activeVoiceId="activeVoiceTalkingItemId"
:isPlaying="activeVoiceTalkingItemId === item.id"
:voiceRate="chooseRate"
:lastQuestionInfo="lastTalkingInfo"
:teacherInfo="choiceTeacherInfo"
:answerMethod="answerMethod"
@changePlay="changePlayItemId"
@nextQuestion="nextQuestionSuccess"
@withdraw="withdrawNow"
@answerQuestion="answerQuestionNow"
@finishQuestion="finishQuestionNow"
:id="'item' + item.id"
:isLast="talkingList.length === index + 1"></TalkingItem>
<TalkingItem class="talking-item" v-for="(item, index) in talkingList" :key="'item' + item.id" :type="item.type"
:dataInfo="item" :activeVoiceId="activeVoiceTalkingItemId" :isPlaying="activeVoiceTalkingItemId === item.id"
:voiceRate="chooseRate" :lastQuestionInfo="lastTalkingInfo" :teacherInfo="choiceTeacherInfo"
:answerMethod="answerMethod" @changePlay="changePlayItemId" @nextQuestion="nextQuestionSuccess"
@withdraw="withdrawNow" @answerQuestion="answerQuestionNow" @finishQuestion="finishQuestionNow"
:id="'item' + item.id" :isLast="talkingList.length === index + 1"></TalkingItem>
<!-- <video v-if="videoUrlShow" :src="videoUrlShow" controls></video> -->
<!-- </view> -->
</scroll-view>
<TouchBtn
<!-- <TouchBtn
v-if="answerMethod !== '03'"
class="talk-btns"
@uploadVoice="uploadRecordNow"
@@ -104,24 +100,14 @@
:adjustPosition="true"
:canAnswerText="canAnswerText"
:answerMethod="answerMethod"
:isDisabled="(lastTalkingInfo.type || 0) !== 4 || ['SN', 'MU', 'JD'].includes(lastTalkingInfo.qnsTyp)" />
:isDisabled="(lastTalkingInfo.type || 0) !== 4 || ['SN', 'MU', 'JD'].includes(lastTalkingInfo.qnsTyp)" /> -->
<touch-btn
class="talk-btns"
ref="touchBtnRef"
@sendLoading="sendLoading"
@submitVoice="submitVoice"
@submitAnswer="submitAnswerNow"
@startRecord="changePlayItemId('')"
:isLoading="isUploadingVoice"
:adjustPosition="true"
:canAnswer="testAnswer"
:canAnswerText="canAnswerText"
<touch-btn class="talk-btns" ref="touchBtnRef" @sendLoading="sendLoading" @submitVoice="submitVoice"
@submitAnswer="submitAnswerNow" @startRecord="changePlayItemId('')" :isLoading="isUploadingVoice"
:adjustPosition="true" :canAnswer="!testAnswer" :canAnswerText="canAnswerText"
:isDisabled="(lastTalkingInfo.type || 0) !== 4 || ['SN', 'MU', 'JD'].includes(lastTalkingInfo.qnsTyp)"
loadingText="只能进行一条回答"
:answerMethod="answerMethod"
/>
loadingText="只能进行一条回答" :answerMethod="answerMethod" />
</view>
<uv-overlay :show="showSeekModel" opacity=".6" @click="showSeekModel = false">
<view class="overlay-box">
@@ -143,9 +129,11 @@
<view class="box-item">
<view class="box-item-title">音色</view>
<uv-radio-group v-model="choiceTeacher" class="box-item-cont">
<view class="overlay-voice-item" v-for="item in teacherList" :key="item.teacherNo" @click="changeTeacherVoiceNow(item)">
<view class="overlay-voice-item" v-for="item in teacherList" :key="item.teacherNo"
@click="changeTeacherVoiceNow(item)">
<view class="overlay-avatar-box">
<ImagePreview class="overlay-img-box" :src="item.imgAddr" :isCache="true" :width="120" :height="160"></ImagePreview>
<ImagePreview class="overlay-img-box" :src="item.imgAddr" :isCache="true" :width="120"
:height="160"></ImagePreview>
</view>
<view class="overlay-teacher-name">{{ item.teacherName }}</view>
<uv-radio :name="item.teacherNo" class="overlay-radio-box" @click.stop />
@@ -163,12 +151,39 @@
</template>
<script setup>
import { reactive, ref, computed, unref, onMounted, onUnmounted, getCurrentInstance, nextTick } from 'vue'
import { onLoad, onShow, onUnload, onHide } from '@dcloudio/uni-app'
import { previewFile, downloadFile, commonUploadVoiceFile, downloadVoiceFile, uploadVoiceFile } from '@/api/common.js'
import { getToken, getPhoneEnvBool } from '@/common/common.js'
import { get_base_url, goto_login_fun } from '@/api/request'
import { getCourseStudyInfoApi,
import {
reactive,
ref,
computed,
unref,
onMounted,
onUnmounted,
getCurrentInstance,
nextTick
} from 'vue'
import {
onLoad,
onShow,
onUnload,
onHide
} from '@dcloudio/uni-app'
import {
previewFile,
downloadFile,
commonUploadVoiceFile,
downloadVoiceFile,
uploadVoiceFile
} from '@/api/common.js'
import {
getToken,
getPhoneEnvBool
} from '@/common/common.js'
import {
get_base_url,
goto_login_fun
} from '@/api/request'
import {
getCourseStudyInfoApi,
afreshStudyCourseApi,
queryQuestionEvalateApi,
textChartApi,
@@ -177,7 +192,8 @@ setPreviewTeacherApi,
textChartPreviewApi,
previewAfreshStudyCourse,
studyEndApi,
studyStartApi } from '@/api/study.js'
studyStartApi
} 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'
@@ -187,18 +203,32 @@ import PreviewDetail from '@/components/image-preview/preview-detail.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 {
pointAndBadgesRun,
badgeRef,
pointsRef
} = usePointsAndBadge();
import { useSocketStore } from '@/store/socket.js'
import { useStorageStore } from '@/store/storage.js'
import { storeToRefs } from 'pinia'
import {
useSocketStore
} from '@/store/socket.js'
import {
useStorageStore
} from '@/store/storage.js'
import {
storeToRefs
} from 'pinia'
const socketStore = useSocketStore()
const storageStore = useStorageStore()
const { SocketObj } = storeToRefs(socketStore)
const {
SocketObj
} = storeToRefs(socketStore)
const audioCacheObj = computed(() => storageStore.audioObj)
const { proxy } = getCurrentInstance()
const {
proxy
} = getCurrentInstance()
const crsNum = ref('')
const crsId = ref('')
const step = ref(0)
@@ -218,7 +248,9 @@ const IsAndEnv = ref(getPhoneEnvBool('AND'))
const ajaxApi = computed(() => (isPreview.value ? textChartPreviewApi : textChartApi))
const { statusBarHeight } = uni.getWindowInfo()
const {
statusBarHeight
} = uni.getWindowInfo()
const pageTopPadding = computed(() => statusBarHeight + 'px')
@@ -226,7 +258,9 @@ const isContinue = ref('N')
// 初始化数据
const getData = async flag => {
try {
const { body } = await getCourseStudyInfoApi({
const {
body
} = await getCourseStudyInfoApi({
crsId: crsId.value
})
teacherList.value = body.teacheList.map(t => ({
@@ -284,8 +318,7 @@ const getData = async flag => {
.finally(() => {})
}
}
} catch {
} finally {
} catch {} finally {
watchFlag.value += 1
}
}
@@ -293,7 +326,9 @@ const answerMethod = ref('01')
const startStudy = data => {
studyStartApi(data)
.then(res => {
const { body } = res
const {
body
} = res
stdyId.value = body.stdyId || ''
stdyBatch.value = body.stdyBatch || ''
oldStdyId.value = body.oldStdyId || ''
@@ -363,9 +398,7 @@ const changeTeacherVoiceNow = async item => {
choiceTeacher.value = item.teacherNo
showBgModel.value = false
}
} catch {
} finally {
}
} catch {} finally {}
}
// 选择教师语音对象
@@ -487,7 +520,9 @@ const getGraphInfoUnStudy = (flag = 0, lastPgrphId = '') => {
.then(res => {
if (res) {
// 去评论
common.redirectTo(`/pages/courseDetail/index?tabAct=2&crsId=${crsId.value}&crsNum=${crsNum.value}&from=study&isPreview=true`)
common.redirectTo(
`/pages/courseDetail/index?tabAct=2&crsId=${crsId.value}&crsNum=${crsNum.value}&from=study&isPreview=true`
)
} else {
// 取消
common.navigateBack()
@@ -766,7 +801,11 @@ const logId = ref('')
const initsocketTask = () => {
socketStore.creatSocket()
unref(SocketObj).on('message', res => {
const { rtnCode, body, commond } = JSON.parse(res.data)
const {
rtnCode,
body,
commond
} = JSON.parse(res.data)
if (rtnCode === '0000') {
if (commond === 'STDYBTH' && !stdyId.value) {
stdyId.value = body.stdyId || ''
@@ -942,17 +981,60 @@ const sendLoading = () => {
}
// 新录音组件语音提交
const submitVoice = ({ossKey,text}) => {
const submitVoice = ({
ossKey,
text
}) => {
if (!ossKey) {
common.msg('系统异常,请联系管理员');
} else if (text === '') {
common.msg('未识别到文字');
} else {
let _data = {
stdyId: unref(stdyId),
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
asrStartTm: unref(lastTalkingInfo).asrStartTm,
ossKey,
text
}
let _id = new Date().getTime() + 'id'
unref(ajaxApi)({
processType: 'ANSER-TEXT',
processType: 'ANSER-VOICE',
requestBody: JSON.stringify(_data)
})
.then(res => {
let _obj = {
crsId: crsId.value,
pgrphId: _data.pgrphId,
qstLogId: _data.qstLogId,
qnsId: _data.qnsId,
talkingContent: text,
bucketKey: ossKey,
text,
talkingVoice: '',
recordId: res.body.chatBody.recordId,
asrStartTm: unref(lastTalkingInfo).asrStartTm,
stdyPgrphId: res.body.chatBody.stdyPgrphId,
type: 1,
isLoading: false,
userInfo: {
...choiceTeacherInfo.value
},
id: _id,
voiceTime: 0
}
talkingList.value.push(_obj)
scrollToBottomNow()
setIsUploadingVoice()
})
.catch((err) => {
setIsUploadingVoice()
})
}
}
const uploadRecordNow = (voicePath, voiceTime) => {
@@ -1388,6 +1470,7 @@ const chooseRateNow = item => {
left: 50%;
transform: translateX(-50%);
}
// margin-bottom: 23rpx;
.arrow-icon {
position: absolute;
@@ -1411,15 +1494,18 @@ const chooseRateNow = item => {
background: url(@/static/images/course/study-bg.png) no-repeat;
border-radius: 30rpx 30rpx 0 0;
font-size: 29rpx;
.color-blue {
color: #06f;
display: inline;
}
.color-black {
color: #333;
position: relative;
height: 44rpx;
line-height: 44rpx;
&.ml-40 {
padding-left: 24rpx;
@@ -1438,9 +1524,11 @@ const chooseRateNow = item => {
}
}
}
.color-gray {
color: #999;
}
.bg-block {
display: block;
width: 100%;
@@ -1449,6 +1537,7 @@ const chooseRateNow = item => {
position: relative;
// margin: 0 30rpx;
}
.bg-gray {
width: 100%;
background-color: #ededed;
@@ -1458,6 +1547,7 @@ const chooseRateNow = item => {
top: 13rpx;
left: 0;
height: 16rpx;
.bg-blue {
height: 100%;
float: left;
@@ -1488,7 +1578,8 @@ const chooseRateNow = item => {
.talk-btns {
height: 150rpx;
background-color: #fff;
padding: 25rpx 30rpx 0;
padding: 25rpx 10rpx 0;
width: 100%;
position: relative;
.touch-btn {
+2 -2
View File
@@ -163,12 +163,12 @@
const sendReply = (data) => {
console.log(data,'111111111111')
commentRef.value?.setReplyData(data, ()=>{
commentInputRef.value.hideAll(true)
commentInputRef.value?.hideAll(true)
});
}
const hideKeyboradNow = () => {
if (isShowing.value) return
commentInputRef.value.hideAll()
commentInputRef.value?.hideAll()
}
const getData = async () => {
common.loading();
+39 -7
View File
@@ -51,6 +51,15 @@
import {
getPhoneEnvBool
} from '@/common/common.js';
import {
downloadFile,
downloadVoiceFile
} from '@/api/common.js'
import {
useStorageStore
} from '@/store/storage.js';
const storageStore = useStorageStore();
const props = defineProps({
dataInfo: {
default: () => ({}),
@@ -120,14 +129,37 @@
}, 300)
return
} else {
talkUserVoiceObj.value.src = ''
if (unref(dataInfo).talkingVoice) {
setTimeout(() => {
talkUserVoiceObj.value.src = itemVoice.value
talkUserVoiceObj.value.play()
isPlaying.value = true
}, 100)
voiceLoading.value = true;
let _id = unref(dataInfo)?.fileId
storageStore.getStorageById('audio', _id, (url) => {
if (url) {
voiceLoading.value = false;
itemVoice.value = url;
talkVoiceObj.value.src = url;
if (voiceIsPlaying.value) return
if (unref(dataInfo)?.id !== props.activeVoiceId) return
talkVoiceObj.value.play();
} else {
downloadFile(_id)
.then((res) => {
if (res?.data) {
common.msg(res.data.message);
return;
}
voiceLoading.value = false;
itemVoice.value = res.tempFilePath;
talkVoiceObj.value.src = res.tempFilePath;
if (voiceIsPlaying.value) return
if (unref(dataInfo)?.id !== props.activeVoiceId) return
talkVoiceObj.value.play();
storageStore.setStorage('audio', _id, itemVoice.value);
})
.catch((err) => {
voiceLoading.value = false;
emit('changePlay', '');
});
}
});
}
}
const IsAndEnv = getPhoneEnvBool('AND');
+185 -39
View File
@@ -11,7 +11,9 @@
</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">
<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>
@@ -21,8 +23,10 @@
<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>
<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>
@@ -58,29 +62,40 @@
<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>
<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('')" formatType="wav" :isLoading="!answerIsOver" :isDisabled="false" v-if="detailInfo.traInterTyp === '01'" />
<!-- <TouchBtn
class="body-bottom"
@uploadVoice="uploadRecordNow"
@startRecord="changePlayItemId('')"
formatType="wav"
:isLoading="!answerIsOver"
:isDisabled="false"
v-if="detailInfo.traInterTyp === '01'" /> -->
<touch-btn class="body-bottom" @submitVoice="submitVoice" @sendLoading="sendLoading" @submitAnswer="submitAnswer"
@startRecord="changePlayItemId('')" :isLoading="!answerIsOver" :isDisabled="false" theme="blue"
answerMethod="03" v-if="detailInfo.traInterTyp === '01'" >
<view class="center-text">
<image class="type-icon" src="@/static/images/sparring/talk-icon.png"></image>
{{'语音对话'}}
</view>
</touch-btn>
<view class="body-bottom" v-else>
<view class="body-bottom-btn" @click="beforeToTalking">
<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 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>
@@ -93,27 +108,62 @@
</template>
<script setup>
import { ref, unref, onMounted, computed, nextTick } from 'vue'
import { onShow, onHide, onLoad, onUnload, onLaunch, onBackPress } from '@dcloudio/uni-app'
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 {
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, checkTraPartnerInfoById } from '@/api/sparring.js'
import { useSocketStore } from '@/store/socket.js'
import { useStorageStore } from '@/store/storage.js'
import { storeToRefs } from 'pinia'
import {
commonUploadVoiceFile
} from '@/api/common.js'
import {
queryTraPartnerCharacterInfoById,
partnerChatReportTrigger,
checkTraPartnerInfoById
} 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 {
initRecord
} from '@/composables/useExamSubject.js'
import permissionApi from '@/common/permission'
const { pointAndBadgesRun, badgeRef, pointsRef } = usePointsAndBadge()
const {
pointAndBadgesRun,
badgeRef,
pointsRef
} = usePointsAndBadge()
const socketStore = useSocketStore()
const { SocketObj } = storeToRefs(socketStore)
const {
SocketObj
} = storeToRefs(socketStore)
const traId = ref('')
const chaId = ref('')
@@ -162,12 +212,69 @@ const scrollToBottomNow = (time = 100) => {
}, time)
})
}
// 新发送语音
const submitVoice = ({
ossKey,
text
}) => {
console.log('新发送语音', ossKey, text);
if (!ossKey) {
uni.showToast({
title: '系统异常,请联系管理员',
icon: 'none',
duration: 1000
});
} else if (text) {
if (!execId.value) {
common.show('提示', '网络连接失败,请重新加载', false).then(res => {
if (res) {
common.redirectTo('/pages/sparring/tip?traId=' + traId.value + '&chaId=' + chaId.value + '&type=' +
talkingType.value + '&isPreview=' + (isPreview.value ? '1' : ''))
}
})
return
}
askData.value = {
execId: execId.value,
intrctWay: '02', //01-文本;02-语音;03-图片
intrctContent: text,
fileId: ossKey,
ossFileAddr: `/traoss/tras3/show/${ossKey}`,
talkingVoice: '',
voiceTime: 0
}
sendMessage({
commond: 'ASK',
body: askData.value
})
} else {
talkingList.value.pop();
uni.showToast({
title: '未识别到文字',
icon: 'none',
duration: 1000
});
}
}
// 新发送语音前loading动画
const sendLoading = () => {
talkingList.value.push({
type: 'question',
talkingText: '',
isLoading: true
});
scrollToBottomNow();
}
const uploadRecordNow = (voicePath, voiceTime) => {
console.log(voicePath, voiceTime)
if (!execId.value) {
common.show('提示', '网络连接失败,请重新加载', false).then(res => {
if (res) {
common.redirectTo('/pages/sparring/tip?traId=' + traId.value + '&chaId=' + chaId.value + '&type=' + talkingType.value + '&isPreview=' + (isPreview.value ? '1' : ''))
common.redirectTo('/pages/sparring/tip?traId=' + traId.value + '&chaId=' + chaId.value + '&type=' +
talkingType.value + '&isPreview=' + (isPreview.value ? '1' : ''))
}
})
return
@@ -262,7 +369,8 @@ const toTalking = async () => {
} else if (detailInfo.value.traInterTyp === '03') {
_gotUrl = '/pages/sparring/talk' // 没改好,暂时跳到一个页
}
common.redirectTo(_gotUrl + '?traId=' + traId.value + '&chaId=' + chaId.value + '&type=' + talkingType.value + '&traInterTyp=' + detailInfo.value.traInterTyp + '&isPreview=' + (isPreview.value ? '1' : '0'))
common.redirectTo(_gotUrl + '?traId=' + traId.value + '&chaId=' + chaId.value + '&type=' + talkingType.value +
'&traInterTyp=' + detailInfo.value.traInterTyp + '&isPreview=' + (isPreview.value ? '1' : '0'))
} catch (err) {
console.error('申请权限失败')
}
@@ -273,7 +381,12 @@ const changePlayItemId = (id = '') => {
activeVoiceTalkingItemId.value = id
}
const talkItemEvents = ({ type, data, id, info = {} }) => {
const talkItemEvents = ({
type,
data,
id,
info = {}
}) => {
switch (type) {
case 'changePlay':
activeVoiceTalkingItemId.value = id
@@ -324,7 +437,8 @@ const initsocketTask = () => {
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' : ''))
common.redirectTo('/pages/sparring/result?isOver=1&traId=' + traId.value + '&execId=' + execId
.value + '&type=' + talkingType.value + '&isPreview=' + (isPreview.value ? '1' : ''))
})
})
.finally(() => {
@@ -332,7 +446,11 @@ const initsocketTask = () => {
})
})
unref(SocketObj).on('message', res => {
const { rtnCode, body, commond } = JSON.parse(res.data)
const {
rtnCode,
body,
commond
} = JSON.parse(res.data)
let _id = new Date().getTime() + 'id'
if (rtnCode === '0000') {
if (commond === 'ASK-OPEN') {
@@ -451,7 +569,8 @@ const initsocketTask = () => {
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' : ''))
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') {
@@ -486,7 +605,8 @@ const overTalking = () => {
if (!execId.value) {
common.show('提示', '网络连接失败,请重新加载', false).then(res => {
if (res) {
common.redirectTo('/pages/sparring/tip?traId=' + traId.value + '&chaId=' + chaId.value + '&type=' + talkingType.value + '&isPreview=' + (isPreview.value ? '1' : ''))
common.redirectTo('/pages/sparring/tip?traId=' + traId.value + '&chaId=' + chaId.value + '&type=' +
talkingType.value + '&isPreview=' + (isPreview.value ? '1' : ''))
}
})
return
@@ -507,7 +627,8 @@ const overNow = (cb = () => {}) => {
})
socketStore?.closeSocket()
changePlayItemId('')
common.redirectTo('/pages/sparring/result?isOver=1&traId=' + traId.value + '&execId=' + execId.value + '&type=' + talkingType.value + '&isPreview=' + (isPreview.value ? '1' : ''))
common.redirectTo('/pages/sparring/result?isOver=1&traId=' + traId.value + '&execId=' + execId.value + '&type=' +
talkingType.value + '&isPreview=' + (isPreview.value ? '1' : ''))
// partnerChatReportTrigger({
// execId: execId.value,
@@ -764,6 +885,7 @@ onBackPress(res => {
display: flex;
flex-direction: column;
justify-content: space-evenly;
.role-name {
width: 100%;
@@ -887,6 +1009,7 @@ onBackPress(res => {
text-align: center;
}
}
.talking-desc {
// padding-top: 24rpx;
padding: 24rpx 20rpx 0;
@@ -894,6 +1017,7 @@ onBackPress(res => {
font-size: 24rpx;
// text-align: center;
color: #999;
&.second {
padding-top: 0;
}
@@ -933,7 +1057,7 @@ onBackPress(res => {
.body-bottom {
height: 180rpx;
background-color: #fff;
padding: 36rpx 23rpx 0;
padding: 36rpx 10rpx 0;
position: fixed;
width: 100vw;
bottom: 0;
@@ -970,4 +1094,26 @@ onBackPress(res => {
}
}
}
.body-bottom{
.center-text {
height: 35rpx;
line-height: 35rpx;
width: 100%;
position: absolute;
top: 50%;
transform: translateY(-50%);
display: flex;
justify-content: center;
color:#fff;
}
.type-icon {
width: 35rpx;
height: 35rpx;
margin-right: 10rpx;
}
}
</style>