Merge branch 'develop' of http://25.13.9.101:9000/K17_AITS/tra-app into develop
This commit is contained in:
+2
-2
@@ -3,13 +3,13 @@ ENV = 'development'
|
||||
# 'development'
|
||||
|
||||
# 苗扬
|
||||
# VITE_APP_BASE_API_Url = 'http://25.64.16.133:9786'
|
||||
VITE_APP_BASE_API_Url = 'http://25.64.16.133:9786'
|
||||
|
||||
# UAT
|
||||
# VITE_APP_BASE_API_Url = 'http://25.18.122.78:9786'
|
||||
|
||||
# DEV
|
||||
VITE_APP_BASE_API_Url = 'https://aitstest.jlbank.com.cn:7001'
|
||||
# VITE_APP_BASE_API_Url = 'https://aitstest.jlbank.com.cn:7001'
|
||||
|
||||
|
||||
# app入口
|
||||
|
||||
@@ -156,4 +156,14 @@ export const textChartApi = (data) => {
|
||||
toastErrors: true,
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
// 设置学习顾问音色/traStdyTeacher/setStudyTeacher
|
||||
export const setStudyTeacherApi = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traStdyTeacher/setStudyTeacher',
|
||||
method: 'post',
|
||||
toastErrors: true,
|
||||
data
|
||||
});
|
||||
};
|
||||
@@ -40,7 +40,7 @@
|
||||
},
|
||||
mode:{
|
||||
type: String,
|
||||
default: 'widthFix'
|
||||
default: 'aspectFit'
|
||||
},
|
||||
previewDetail: {
|
||||
default: '',
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
<view :class="['ai-answer', props.class]" :id="props.id" v-if="[3,4,5].indexOf(type) >= 0">
|
||||
<view class="user-info">
|
||||
<view class="avatar-box">
|
||||
<ImagePreview class="img-box" :imgId="dataInfo?.userInfo?.imgAddr"></ImagePreview>
|
||||
<ImagePreview class="img-box" :imgId="teacherInfo?.imgAddr"></ImagePreview>
|
||||
</view>
|
||||
<view class="ai-name">
|
||||
{{ dataInfo?.userInfo?.teacherName }}
|
||||
<view class="ai-name-desc">{{dataInfo?.userInfo?.teacherDesc}}</view>
|
||||
{{ teacherInfo?.teacherName }}
|
||||
<view class="ai-name-desc">{{teacherInfo?.teacherDesc}}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view :class="['talking-info', [4].indexOf(type) >= 0 ? 'talking-info-less':''] ">
|
||||
@@ -88,11 +88,11 @@
|
||||
<view :class="['ai-answer', props.class]" :id="props.id" v-if="[7].indexOf(type) >= 0">
|
||||
<view class="user-info">
|
||||
<view class="avatar-box">
|
||||
<ImagePreview class="img-box" :imgId="dataInfo?.userInfo?.imgAddr"></ImagePreview>
|
||||
<ImagePreview class="img-box" :imgId="teacherInfo?.imgAddr"></ImagePreview>
|
||||
</view>
|
||||
<view class="ai-name">
|
||||
{{ dataInfo?.userInfo?.teacherName }}
|
||||
<view class="ai-name-desc">{{dataInfo?.userInfo?.teacherDesc}}</view>
|
||||
{{ teacherInfo?.teacherName }}
|
||||
<view class="ai-name-desc">{{teacherInfo?.teacherDesc}}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view :class="['talking-info', [4,7].indexOf(type) >= 0 ? 'talking-info-less':''] ">
|
||||
@@ -205,6 +205,10 @@ const audioCacheObj = computed(() => storageStore.audioObj)
|
||||
default: () => ({}),
|
||||
type: Object
|
||||
},
|
||||
teacherInfo: {
|
||||
default: () => ({}),
|
||||
type: Object
|
||||
},
|
||||
activeVoiceId:{
|
||||
default:'',
|
||||
type: String
|
||||
@@ -239,12 +243,13 @@ const audioCacheObj = computed(() => storageStore.audioObj)
|
||||
}
|
||||
})
|
||||
|
||||
const { type, dataInfo, activeVoiceId, isPlaying, isLast, lastQuestionInfo} = toRefs(props)
|
||||
const { type, dataInfo, activeVoiceId, isPlaying, isLast, lastQuestionInfo, teacherInfo} = toRefs(props)
|
||||
const emit = defineEmits(['changePlay', 'nextQuestion', 'withdraw', 'finishQuestion'])
|
||||
// 声音播放初始化
|
||||
const talkVoiceObj = ref(uni.createInnerAudioContext())
|
||||
talkVoiceObj.value.playbackRate = Number(props.voiceRate)
|
||||
const userInfo = computed(() => getUserInfo())
|
||||
const itemVoice = ref('')
|
||||
|
||||
const showWithdraw = computed(()=>{
|
||||
let _isMe = unref(lastQuestionInfo).qnsLogId === unref(dataInfo).qnsLogId
|
||||
@@ -253,6 +258,13 @@ const audioCacheObj = computed(() => storageStore.audioObj)
|
||||
})
|
||||
|
||||
const voiceLoading = ref(false)
|
||||
watch(() => props.teacherInfo,(val) => {
|
||||
itemVoice.value = ''
|
||||
talkVoiceObj.value.src = ''
|
||||
},{
|
||||
deep: true,
|
||||
immediate: true
|
||||
})
|
||||
watch(() => props.isPlaying,(val) => {
|
||||
if(!val){
|
||||
talkVoiceObj.value?.pause()
|
||||
@@ -311,7 +323,6 @@ const audioCacheObj = computed(() => storageStore.audioObj)
|
||||
talkVoiceObj.value.src = ''
|
||||
})
|
||||
talkVoiceObj.value.onError(()=>{
|
||||
emit('changePlay', '')
|
||||
talkVoiceObj.value.src = ''
|
||||
})
|
||||
const pauseVoice = () => {
|
||||
@@ -333,19 +344,20 @@ const audioCacheObj = computed(() => storageStore.audioObj)
|
||||
playVoice()
|
||||
}
|
||||
}
|
||||
const itemVoice = ref('')
|
||||
const playVoice = (readType = 'pgth') =>{
|
||||
let _content = showContent.value
|
||||
if(!_content){
|
||||
common.msg('没有可以播放的文字信息!')
|
||||
return
|
||||
}
|
||||
emit('changePlay', unref(dataInfo)?.id)
|
||||
// readType 阅读内容类型pgrh段落/qns问题
|
||||
emit('changePlay', unref(dataInfo)?.id)
|
||||
if(talkVoiceObj.value.src && talkVoiceObj.value.src === itemVoice.value) {
|
||||
emit('changePlay', unref(dataInfo)?.id)
|
||||
talkVoiceObj.value.play()
|
||||
return
|
||||
}
|
||||
talkVoiceObj.value.src = ''
|
||||
if(itemVoice.value){
|
||||
setTimeout(()=>{
|
||||
talkVoiceObj.value.src = itemVoice.value
|
||||
@@ -358,14 +370,14 @@ const audioCacheObj = computed(() => storageStore.audioObj)
|
||||
pgrphId: unref(dataInfo)?.pgrphId || '',
|
||||
qnsId: unref(dataInfo)?.qnsId || '',
|
||||
readType: readType,
|
||||
teachNo: unref(dataInfo)?.userInfo?.teacherNo, // 必填
|
||||
teachNo: unref(teacherInfo)?.teacherNo, // 必填
|
||||
content: _content
|
||||
}
|
||||
let _url = '/trastudy/traStdyInfo/readContent?' + Object.keys(_params).map(t=>{
|
||||
return encodeURIComponent(t) + '=' + encodeURIComponent(_params[t])
|
||||
}).join('&')
|
||||
|
||||
storageStore.getStorageById('audio', _url, (url)=>{
|
||||
storageStore.getStorageById('audio', _url + _params.teachNo, (url)=>{
|
||||
if(url){
|
||||
voiceLoading.value = false
|
||||
itemVoice.value = url
|
||||
@@ -381,7 +393,7 @@ const audioCacheObj = computed(() => storageStore.audioObj)
|
||||
itemVoice.value = res.tempFilePath
|
||||
talkVoiceObj.value.src = res.tempFilePath
|
||||
talkVoiceObj.value.play()
|
||||
storageStore.setStorage('audio', _url, itemVoice.value)
|
||||
storageStore.setStorage('audio', _url + _params.teachNo, itemVoice.value)
|
||||
}).catch((err)=>{
|
||||
voiceLoading.value = false
|
||||
})
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
placeholderStyle='color:#999999;font-size:26rpx' placeholder="请输入答案">
|
||||
</uv-input>
|
||||
</view>
|
||||
<view class="talk-btn-box" @click.stop="showKeyboard">
|
||||
<view class="talk-btn-box" @click.stop="showKeyboard" v-show="!onlyVoice">
|
||||
<image class="talk-btn-icon" v-show="!keyboardIsShow" src="/src/static/images/course/jianpan.png" mode=""></image>
|
||||
<image class="talk-btn-icon" v-show="keyboardIsShow" src="/src/static/images/course/send.png" mode=""></image>
|
||||
</view>
|
||||
@@ -46,19 +46,26 @@ const proxy = getCurrentInstance()
|
||||
const emit = defineEmits(['uploadVoice', 'submitAnswer','startRecord'])
|
||||
const props = defineProps({
|
||||
isDisabled:{
|
||||
default: Boolean,
|
||||
type: true
|
||||
default: true,
|
||||
type: Boolean
|
||||
},
|
||||
isLoading:{
|
||||
default: false,
|
||||
type: Boolean
|
||||
},
|
||||
onlyVoice:{
|
||||
default: false,
|
||||
type: Boolean
|
||||
}
|
||||
},
|
||||
})
|
||||
const { isDisabled } = toRefs(props)
|
||||
const { isDisabled, onlyVoice } = toRefs(props)
|
||||
const isLoadingState = computed(() => props.isLoading)
|
||||
watch(()=>props.isLoading,()=>{},{
|
||||
deep:true,immediate:true
|
||||
})
|
||||
watch(()=>props.onlyVoice,()=>{},{
|
||||
deep:true,immediate:true
|
||||
})
|
||||
const showTalkingModel = ref(false)
|
||||
const keyboardIsShow = ref(false)
|
||||
const answerValue = ref('')
|
||||
@@ -181,6 +188,7 @@ const showOverlayTalking = (obj) => {
|
||||
hideOverlayTalking()
|
||||
},59 * 1010)
|
||||
}else{
|
||||
showTalkingModel.value = false
|
||||
uni.showToast({
|
||||
title: '暂无问题需要回答!',
|
||||
icon: "none",
|
||||
@@ -235,7 +243,7 @@ const showKeyboard = () => {
|
||||
}
|
||||
}else{
|
||||
uni.showToast({
|
||||
title: '请先获取问题信息!',
|
||||
title: '暂无问题需要回答!',
|
||||
icon: "none",
|
||||
duration:1000
|
||||
})
|
||||
|
||||
+113
-20
@@ -5,7 +5,7 @@
|
||||
<view class="course-study-body" v-show="step === 1">
|
||||
<view class="seek-setting">
|
||||
<view class="inline-block" @click="showChangeSeekModelNow">{{chooseRate}}倍</view>
|
||||
<image src="@/static/images/course/setting.png" alt="" class="image-icon" @click.stop="showChangeBgModelNow"></image>
|
||||
<!-- <image src="@/static/images/course/setting.png" alt="" class="image-icon" @click.stop="showChangeBgModelNow"></image> -->
|
||||
</view>
|
||||
<view class="body-title">
|
||||
<uv-icon class="arrow-icon" name="arrow-left" @click="backRouter()"/>
|
||||
@@ -67,6 +67,7 @@
|
||||
:isPlaying="activeVoiceTalkingItemId === item.id"
|
||||
:voiceRate="chooseRate"
|
||||
:lastQuestionInfo="lastTalkingInfo"
|
||||
:teacherInfo="choiceTeacherInfo"
|
||||
@changePlay="changePlayItemId"
|
||||
@nextQuestion="nextQuestionSuccess"
|
||||
@withdraw="withdrawNow"
|
||||
@@ -83,6 +84,7 @@
|
||||
@submitAnswer="submitAnswerNow"
|
||||
@startRecord="changePlayItemId('')"
|
||||
:isLoading="isUploadingVoice"
|
||||
:onlyVoice="answerOnlyVoice"
|
||||
:isDisabled="((lastTalkingInfo.type||0) !== 4)"
|
||||
/>
|
||||
</view>
|
||||
@@ -103,15 +105,22 @@
|
||||
</uv-overlay>
|
||||
<uv-overlay :show="showBgModel" opacity=".6" @click="showBgModel = false">
|
||||
<view class="overlay-box">
|
||||
<view class="white-bg-box">
|
||||
<view class="white-bg-box-setting white-bg-box">
|
||||
<view class="box-title">设置
|
||||
</view>
|
||||
<view class="box-btns">
|
||||
<view
|
||||
v-for="item in rateList"
|
||||
:class="{'is-active':item === chooseRate}"
|
||||
@click="chooseRateNow(item)"
|
||||
>{{item}}</view>
|
||||
<view class="box-items" @click.stop>
|
||||
<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-avatar-box">
|
||||
<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/>
|
||||
</view>
|
||||
</uv-radio-group>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -135,7 +144,8 @@ import {
|
||||
getCourseStudyInfoApi,
|
||||
afreshStudyCourseApi,
|
||||
queryQuestionEvalateApi,
|
||||
textChartApi
|
||||
textChartApi,
|
||||
setStudyTeacherApi
|
||||
} from "@/api/study.js"
|
||||
import common from '@/common/common';
|
||||
import TalkingItem from '@/pages/course/components/talkingItem.vue'
|
||||
@@ -244,20 +254,38 @@ const showChangeSeekModelNow = () => {
|
||||
}
|
||||
const showBgModel = ref(false)
|
||||
const showChangeBgModelNow = () => {
|
||||
changePlayItemId()
|
||||
showBgModel.value = true
|
||||
}
|
||||
// 关闭播放状态
|
||||
const changePlayItemId = (id = '') => {
|
||||
if(activeVoiceTalkingItemId.value === id){
|
||||
activeVoiceTalkingItemId.value = ''
|
||||
}else{
|
||||
activeVoiceTalkingItemId.value = id
|
||||
}
|
||||
activeVoiceTalkingItemId.value = id
|
||||
// if(activeVoiceTalkingItemId.value === id){
|
||||
// activeVoiceTalkingItemId.value = ''
|
||||
// }else{
|
||||
// activeVoiceTalkingItemId.value = id
|
||||
// }
|
||||
}
|
||||
// 选择的教师对象信息
|
||||
const choiceTeacherInfo = computed(()=>{
|
||||
return teacherList.value.find(t=> t.teacherNo === choiceTeacher.value)
|
||||
})
|
||||
|
||||
const changeTeacherVoiceNow = async (item) => {
|
||||
if(item.teacherNo === choiceTeacher.value) return
|
||||
try{
|
||||
const res = await setStudyTeacherApi({
|
||||
stdyId: stdyId.value,
|
||||
teacherNo: item.teacherNo
|
||||
})
|
||||
console.log(res)
|
||||
if(res.rtnCode === '0000'){
|
||||
choiceTeacher.value = item.teacherNo
|
||||
showBgModel.value = false
|
||||
}
|
||||
}catch{} finally{}
|
||||
}
|
||||
|
||||
// 选择教师语音对象
|
||||
const activeVoiceTeachNo = ref('')
|
||||
let teacherVoiceObj = uni.createInnerAudioContext()
|
||||
@@ -322,6 +350,11 @@ const isLastPgrph = ref(false)
|
||||
const backRouter = ()=>{
|
||||
common.redirectTo(`/pages/courseDetail/index?crsId=${crsId.value}&crsNum=${crsNum.value}&from=study`)
|
||||
}
|
||||
|
||||
const answerOnlyVoice = computed(()=>{
|
||||
return lastTalkingInfo.value.answerWay === 'V'
|
||||
})
|
||||
|
||||
// 获取段落
|
||||
const getGraphInfoUnStudy = (flag = 0) => {
|
||||
console.log(isLastPgrph.value)
|
||||
@@ -554,9 +587,9 @@ const initsocketTask = () => {
|
||||
getGraphInfoUnStudy(1)
|
||||
}
|
||||
}else{
|
||||
if(['0005', 'QQ0005'].includes(res.rtnCode)) {
|
||||
goto_login_fun();
|
||||
}
|
||||
// if(['0005', 'QQ0005'].includes(res.rtnCode)) {
|
||||
// goto_login_fun();
|
||||
// }
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -790,11 +823,11 @@ const finishQuestionNow = (data) => {
|
||||
}
|
||||
talkingList.value.push(lastTalkingInfo.value)
|
||||
scrollToBottomNow(0)
|
||||
getQuestionEvalateRequest(_id,data)
|
||||
getQuestionEvalateRequest(_id, data, 1)
|
||||
})
|
||||
}
|
||||
// 查询问题回答评分
|
||||
const getQuestionEvalateRequest = (id, data) => {
|
||||
const getQuestionEvalateRequest = (id, data, num) => {
|
||||
if(loadingQstLogId.value){
|
||||
textChartApi({
|
||||
processType: 'ANSER-RESULT',
|
||||
@@ -825,8 +858,15 @@ const getQuestionEvalateRequest = (id, data) => {
|
||||
})
|
||||
scrollToBottomNow()
|
||||
}else{
|
||||
// 1分钟之后取消获取报错
|
||||
if(num === 30){
|
||||
common.msg('获取评分数据失败,请重新提交获取!')
|
||||
talkingList.value.pop()//删除loading会话
|
||||
talkingList.value.pop()//删除完成会话
|
||||
return
|
||||
}
|
||||
setTimeout(()=>{
|
||||
getQuestionEvalateRequest(id, data)
|
||||
getQuestionEvalateRequest(id, data, num+1)
|
||||
}, 2 * 1000)
|
||||
}
|
||||
})
|
||||
@@ -1162,6 +1202,10 @@ const chooseRateNow = (item) => {
|
||||
overflow: hidden;
|
||||
border-radius: 32rpx 32rpx 0 0;
|
||||
padding: 58rpx 36rpx;
|
||||
&.white-bg-box-setting{
|
||||
height: auto;
|
||||
min-height: 400rpx;
|
||||
}
|
||||
.box-title{
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
@@ -1178,6 +1222,55 @@ const chooseRateNow = (item) => {
|
||||
color: #06f;
|
||||
}
|
||||
}
|
||||
.box-items{
|
||||
.box-item{
|
||||
margin-bottom: 20rpx;
|
||||
overflow: hidden;
|
||||
.box-item-title{
|
||||
width: 86rpx;
|
||||
height: 100%;
|
||||
float: left;
|
||||
overflow: hidden;
|
||||
color: #666;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
.box-item-cont{
|
||||
display: block;
|
||||
float: left;
|
||||
width: calc(100% - 86rpx);
|
||||
overflow: hidden;
|
||||
.overlay-voice-item{
|
||||
width: 123rpx;
|
||||
float: left;
|
||||
position: relative;
|
||||
margin-bottom: 30rpx;
|
||||
&:not(:nth-child(3n)){
|
||||
margin-right: 100rpx;
|
||||
}
|
||||
.overlay-avatar-box{
|
||||
width: 123rpx;
|
||||
height: 123rpx;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
box-shadow: 1rpx 0rpx 5rpx #ccc;
|
||||
}
|
||||
.overlay-teacher-name{
|
||||
width: 123rpx;
|
||||
text-align: center;
|
||||
height: 58rpx;
|
||||
line-height: 58rpx;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
.overlay-radio-box{
|
||||
position: absolute;
|
||||
left: 100rpx;
|
||||
top: 5rpx;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,55 +1,66 @@
|
||||
<template>
|
||||
<view class="answer-content">
|
||||
<MarkdownPreview :mdString="mdText"/>
|
||||
<view class="talking-cont">
|
||||
<MarkdownPreview :mdString="mdText"/>
|
||||
</view>
|
||||
<view class="btns-cont" v-if="false">
|
||||
<view class="replay-btn" @click="reAnswer">
|
||||
<image class="icon" src="@/static/images/dialog/answer-restart.png" style="width: 100%;height: 100%;"></image>
|
||||
</view>
|
||||
<view class="replay-btn" @click="stopAnswer">
|
||||
<image class="icon" src="@/static/images/dialog/answer-stop.png" style="width: 100%;height: 100%;"></image>
|
||||
</view>
|
||||
<!-- <view class="replay-btn" @click="zanAnswer">
|
||||
<image
|
||||
class="icon"
|
||||
src="@/static/images/dialog/answer-zan.png"
|
||||
style="width: 100%;height: 100%;"
|
||||
></image>
|
||||
<image
|
||||
class="icon"
|
||||
src="@/static/images/dialog/answer-yizan.png"
|
||||
style="width: 100%;height: 100%;"
|
||||
></image>
|
||||
</view>
|
||||
<view class="play-btn">
|
||||
<image
|
||||
class="icon"
|
||||
src="@/static/images/dialog/answer-fankui.png"
|
||||
style="width: 100%;height: 100%;"
|
||||
></image>
|
||||
<image
|
||||
class="icon"
|
||||
src="@/static/images/dialog/answer-yifankui.png"
|
||||
style="width: 100%;height: 100%;"
|
||||
></image>
|
||||
</view> -->
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { onMounted, computed, ref } from 'vue'
|
||||
import MarkdownPreview from '@/components/markdown-preview/markdown-preview.vue';
|
||||
|
||||
const mdText = ref('')
|
||||
const baseText = ref(`#### 在客服交流中使用标准术语是提高专业形象和服务质量的重要手段。以下是一些常见的客服标准用语及其适用场景:
|
||||
const emit = defineEmits(['handleEvents'])
|
||||
const props = defineProps({
|
||||
dataInfo: {
|
||||
default: () => ({}),
|
||||
type: Object
|
||||
},
|
||||
})
|
||||
|
||||
const mdText = computed(() => props.dataInfo?.talkingText)
|
||||
|
||||
1. **问候与介绍:**
|
||||
- “您好,欢迎来到[公司名称],我是您的客服代表[姓名],很高兴为您服务。”
|
||||
- “早上好/下午好/晚上好,[公司名称]客服中心,请问有什么可以帮到您的吗?”
|
||||
|
||||
2. **确认信息:**
|
||||
- “为了更好地为您提供服务,能否麻烦您提供一下[具体需要的信息,如订单号、会员账号等]?”
|
||||
- “我来确认一下,您的问题是关于[问题的具体描述],对吗?”
|
||||
|
||||
3. **表达理解与同情:**
|
||||
- “非常理解您的感受,遇到这种情况确实会让人感到不便/烦恼。”
|
||||
- “感谢您的耐心等待,我们正在积极处理您的问题。”
|
||||
|
||||
4. **解决问题:**
|
||||
- “针对您提到的问题,我们可以采取[解决方案]的方式解决,您看这样可以吗?”
|
||||
- “我已经记录了您的反馈,并将尽快转交给相关部门处理,预计[解决时间]内会有结果。”
|
||||
|
||||
5. **结束对话:**
|
||||
- “如果您没有其他问题的话,那我们就先这样吧,祝您生活愉快!”
|
||||
- “再次感谢您的来电,期待下次为您服务。再见!”
|
||||
|
||||
6. **特殊情况处理:**
|
||||
- “很抱歉给您带来了不愉快的体验,这并不是我们希望发生的事情。我们会立即调查此事,并确保类似情况不再发生。”
|
||||
- “对于您所反映的情况,我们深感歉意,但根据我们的规定,[解释原因]。不过,我可以尝试帮您寻找其他的解决方案。”
|
||||
|
||||
以上是客服交流中常用的标准化语言,实际应用时可根据具体情况适当调整,以保持沟通的真实性和亲和力。`)
|
||||
let start = 0
|
||||
let length = baseText.length
|
||||
const addText = () => {
|
||||
let _random = Math.ceil(Math.random()*5)
|
||||
let _str = baseText.value.substr(start,_random)
|
||||
start += _random
|
||||
mdText.value += _str
|
||||
setTimeout(()=>{
|
||||
addText()
|
||||
},200)
|
||||
}
|
||||
onMounted(()=>{
|
||||
addText()
|
||||
})
|
||||
const reAnswer = () => {
|
||||
emit('handleEvents', 'reAnswer')
|
||||
}
|
||||
const stopAnswer = () => {
|
||||
emit('handleEvents', 'stop')
|
||||
}
|
||||
const zanAnswer = () => {
|
||||
emit('handleEvents', 'great')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@@ -57,7 +68,100 @@ onMounted(()=>{
|
||||
background-color: #fff;
|
||||
padding: 20rpx;
|
||||
border-radius: 15rpx;
|
||||
padding-bottom: 30rpx;
|
||||
padding-bottom: 10rpx;
|
||||
position: relative;
|
||||
.talking-cont{
|
||||
padding-bottom: 20rpx;
|
||||
}
|
||||
.btns-cont{
|
||||
overflow: hidden;
|
||||
border-top: 3rpx solid #eee;
|
||||
.replay-btn{
|
||||
float: left;
|
||||
width: 46rpx;
|
||||
height: 46rpx;
|
||||
overflow: hidden;
|
||||
margin-right: 16rpx;
|
||||
margin-top: 25rpx;
|
||||
}
|
||||
.play-btn{
|
||||
float: left;
|
||||
width: 46rpx;
|
||||
height: 46rpx;
|
||||
overflow: hidden;
|
||||
margin-right: 16rpx;
|
||||
margin-top: 25rpx;
|
||||
}
|
||||
.next-learn-btn{
|
||||
float: left;
|
||||
padding: 0 60rpx 0 20rpx;
|
||||
height: 62rpx;
|
||||
line-height: 62rpx;
|
||||
font-size: 30rpx;
|
||||
background-color: #06f;
|
||||
color: #fff;
|
||||
border-radius: 8rpx;
|
||||
margin-top: 16rpx;
|
||||
margin-right: 16rpx;
|
||||
position: relative;
|
||||
&.replay-study-btn{
|
||||
float: right;
|
||||
}
|
||||
&.restart{
|
||||
background-color: #eaf2ff;
|
||||
color: #06f;
|
||||
}
|
||||
.icon-iamge{
|
||||
width: 25rpx;
|
||||
height: 25rpx;
|
||||
position: absolute;
|
||||
right: 20rpx;
|
||||
top: 50%;
|
||||
margin-top: -12rpx;
|
||||
}
|
||||
}
|
||||
.replay-learn-btn{
|
||||
float: left;
|
||||
padding: 0 60rpx 0 20rpx;
|
||||
height: 62rpx;
|
||||
line-height: 62rpx;
|
||||
font-size: 30rpx;
|
||||
color: #06f;
|
||||
background-color: #eaf2ff;
|
||||
border-radius: 8rpx;
|
||||
margin-top: 16rpx;
|
||||
margin-right: 16rpx;
|
||||
position: relative;
|
||||
.icon-iamge{
|
||||
width: 25rpx;
|
||||
height: 25rpx;
|
||||
position: absolute;
|
||||
right: 20rpx;
|
||||
top: 50%;
|
||||
margin-top: -12rpx;
|
||||
}
|
||||
}
|
||||
.next-btn{
|
||||
float: right;
|
||||
padding: 0 60rpx 0 20rpx;
|
||||
height: 62rpx;
|
||||
line-height: 62rpx;
|
||||
font-size: 30rpx;
|
||||
background-color: #06f;
|
||||
color: #fff;
|
||||
border-radius: 8rpx;
|
||||
margin-top: 16rpx;
|
||||
position: relative;
|
||||
.icon-iamge{
|
||||
width: 25rpx;
|
||||
height: 25rpx;
|
||||
position: absolute;
|
||||
right: 20rpx;
|
||||
top: 50%;
|
||||
margin-top: -12rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
@@ -25,7 +25,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref, toRefs } from "vue"
|
||||
import { computed, ref, toRefs, watch } from "vue"
|
||||
const props = defineProps({
|
||||
dataInfo: {
|
||||
default: () => ({}),
|
||||
@@ -37,12 +37,13 @@
|
||||
},
|
||||
})
|
||||
const { activeVoiceId, dataInfo } = toRefs(props)
|
||||
watch(() => props.dataInfo, () => {},{deep: true,immediate: true})
|
||||
const voiceLoading = ref(false)
|
||||
const translateLoading =ref(false)
|
||||
const translateLoading = ref(false)
|
||||
|
||||
const voiceTime = ref('')
|
||||
const showContent = computed(()=>{
|
||||
return '问题'
|
||||
return dataInfo.value.talkingText || ''
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,22 +1,36 @@
|
||||
<template>
|
||||
<view class="talking-item">
|
||||
<questionVue v-if="talkingType === 'question'"></questionVue>
|
||||
<answerVue v-else-if="talkingType === 'answer'"></answerVue>
|
||||
<questionVue v-if="talkingType === 'question'" :dataInfo="talkingInfo"></questionVue>
|
||||
<answerVue
|
||||
v-else-if="talkingType === 'answer'"
|
||||
:dataInfo="props.talkingInfo"
|
||||
@handleEvents="handleEvents"
|
||||
></answerVue>
|
||||
<view v-else></view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { toRefs } from 'vue'
|
||||
import { toRefs, watch } from 'vue'
|
||||
import questionVue from './question.vue';
|
||||
import answerVue from './answer.vue';
|
||||
|
||||
const emit = defineEmits(['handleEvents'])
|
||||
const props = defineProps({
|
||||
talkingType:{
|
||||
default:'question',
|
||||
type: String
|
||||
},
|
||||
talkingInfo:{
|
||||
default: () => ({}),
|
||||
type: Object
|
||||
}
|
||||
})
|
||||
const { talkingType } = toRefs(props)
|
||||
watch(() => props.talkingInfo, () => {},{deep: true,immediate: true})
|
||||
const { talkingType } = toRefs(props)
|
||||
const handleEvents = (type) => {
|
||||
emit('handleEvents', type, props.talkingInfo)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style >
|
||||
|
||||
+248
-11
@@ -5,8 +5,8 @@
|
||||
<view class="bot-bg"></view>
|
||||
<scroll-view class="index-dialog-body" :scroll-y="true" upper-threshold="0" refresher-enabled="true" refresher-default-style="none" @refresherrefresh="scrolltolower">
|
||||
<view class="seek-setting">
|
||||
设置
|
||||
<image src="@/static/images/course/setting.png" alt="" class="image-icon"></image>
|
||||
<image src="@/static/images/dialog/new-dialog.png" alt="" class="image-icon-new"></image>
|
||||
<image src="@/static/images/dialog/history.png" alt="" class="image-icon"></image>
|
||||
</view>
|
||||
<view class="body-title">
|
||||
<uv-icon class="arrow-icon" name="arrow-left" @click="common.navigateBack()"/>
|
||||
@@ -23,10 +23,18 @@
|
||||
<view class="questions-view">
|
||||
<scroll-view :scroll-x="true" :show-scrollbar="false">
|
||||
<view class="question-row">
|
||||
<view class="question-item" v-for="item in Math.ceil(questionList.length/2)">{{questionList[item-1]}}</view>
|
||||
<view
|
||||
class="question-item"
|
||||
v-for="item in Math.ceil(questionList.length/2)"
|
||||
@click="sendText(questionList[item-1])"
|
||||
>{{questionList[item-1]}}</view>
|
||||
</view>
|
||||
<view class="question-row">
|
||||
<view class="question-item" v-for="item in questionList.length - Math.ceil(questionList.length/2)">{{questionList[item + Math.ceil(questionList.length/2)-1]}}</view>
|
||||
<view
|
||||
class="question-item"
|
||||
v-for="item in questionList.length - Math.ceil(questionList.length/2)"
|
||||
@click="sendText(questionList[item + Math.ceil(questionList.length/2)-1])"
|
||||
>{{questionList[item + Math.ceil(questionList.length/2)-1]}}</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
@@ -39,8 +47,10 @@
|
||||
:scroll-with-animation="true"
|
||||
>
|
||||
<TalkingItem
|
||||
v-for="item in 2"
|
||||
:talkingType="item%2===1?'question':'answer'"
|
||||
v-for="item in talkingList"
|
||||
:talkingType="item.type"
|
||||
:talkingInfo="item"
|
||||
@handleEvents="talkItemEvents"
|
||||
>
|
||||
</TalkingItem>
|
||||
</scroll-view>
|
||||
@@ -89,10 +99,19 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { ref, unref, onMounted, watch, nextTick } from 'vue'
|
||||
import common from '@/common/common';
|
||||
import TouchBtn from '@/pages/course/components/touchBtn.vue'
|
||||
import TalkingItem from './components/talking-item.vue';
|
||||
import { useSocketStore } from "@/store/socket.js"
|
||||
import { useStorageStore } from "@/store/storage.js"
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { onLoad, onShow, onUnload, onHide } from '@dcloudio/uni-app';
|
||||
import { get_base_url, goto_login_fun } from '@/api/request'
|
||||
|
||||
const socketStore = useSocketStore()
|
||||
const { SocketObj } = storeToRefs(socketStore)
|
||||
|
||||
const questionList = ref([
|
||||
'对公小程序开户流程?',
|
||||
'厅堂服务经理2025年管理办法?',
|
||||
@@ -107,7 +126,7 @@
|
||||
])
|
||||
const scrollBoxRef = ref()
|
||||
const scrollTop = ref(99999)
|
||||
const scrollToBottomNow = (time = 500) => {
|
||||
const scrollToBottomNow = (time = 100) => {
|
||||
nextTick(()=>{
|
||||
setTimeout(() => {
|
||||
scrollTop.value += 1
|
||||
@@ -122,6 +141,175 @@
|
||||
},time)
|
||||
})
|
||||
}
|
||||
|
||||
// ws 初始化逻辑
|
||||
const reconnectTimer = null // 重连timer
|
||||
const isManualClose = ref(false) // 是否手动关闭
|
||||
const reqBatch = ref('')
|
||||
const seqNo = ref('')
|
||||
const mascotId = ref('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('message', (res)=>{
|
||||
const { rtnCode, body, commond } = JSON.parse(res.data)
|
||||
console.log(rtnCode, body, commond)
|
||||
if(rtnCode === '0000'){
|
||||
if(commond === 'ASK-OPEN'){
|
||||
console.log('链接成功!')
|
||||
sendMessage({
|
||||
"commond" : 'ASK-START',
|
||||
"body" : {
|
||||
mascotId: mascotId.value,
|
||||
deviceImei: ''
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
if(commond === 'ASK-START'){
|
||||
console.log('开启对话!')
|
||||
reqBatch.value = body.reqBatch
|
||||
return
|
||||
}
|
||||
if(commond === 'ASK'){
|
||||
console.log('body')
|
||||
seqNo.value = body.seqNo
|
||||
touchBtnCallBack.value && touchBtnCallBack.value()
|
||||
touchBtnCallBack.value = null
|
||||
talkingList.value.push({
|
||||
type: 'question',
|
||||
talkingText: body.intrctContent,
|
||||
...body
|
||||
})
|
||||
return
|
||||
}
|
||||
if(commond === 'ASK-STOP'){
|
||||
console.log('停止回答标记!')
|
||||
stopAnswerCallBack.value && stopAnswerCallBack.value(askData.value)
|
||||
setTimeout(()=>{
|
||||
stopAnswerCallBack.value = null
|
||||
},10)
|
||||
return
|
||||
}
|
||||
if(commond === 'ASK-ANSWER'){
|
||||
console.log('开始回答!')
|
||||
if(body.event === 'start'){
|
||||
answerIsOver.value = false
|
||||
answerText.value = ''
|
||||
talkingList.value.push({
|
||||
type: 'answer',
|
||||
talkingText: answerText.value,
|
||||
...body
|
||||
})
|
||||
}else if(body.event === 'token'){
|
||||
answerIsOver.value = false
|
||||
answerText.value += body.data
|
||||
}else if(body.event === 'end'){
|
||||
answerIsOver.value = true
|
||||
}
|
||||
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('操作异常!')
|
||||
console.log(body)
|
||||
return
|
||||
}
|
||||
}else{
|
||||
if(['0005', 'QQ0005'].includes(res.rtnCode)) {
|
||||
goto_login_fun();
|
||||
}
|
||||
}
|
||||
})
|
||||
watchFlag.value += 1
|
||||
}
|
||||
|
||||
const reconnect = ()=> {
|
||||
if(reconnectTimer || isManualClose.value) return
|
||||
reconnectTimer = setTimeout(()=>{
|
||||
initsocketTask()
|
||||
},3000)
|
||||
}
|
||||
const sendMessage = (data) => {
|
||||
unref(SocketObj).send(data)
|
||||
}
|
||||
watch(() => answerText.value,(val) => {
|
||||
if(val){
|
||||
changeText(val)
|
||||
scrollToBottomNow()
|
||||
}
|
||||
})
|
||||
|
||||
const changeText = () => {
|
||||
if(talkingList.value.length === 0) return
|
||||
talkingList.value[talkingList.value.length-1].talkingText = answerText.value
|
||||
}
|
||||
|
||||
const stopAnswerCallBack = ref(null)
|
||||
const talkItemEvents = (type, data) => {
|
||||
switch (type){
|
||||
case 'reAnswer':
|
||||
console.log('重新获取数据')
|
||||
// 设置停止回调
|
||||
stopAnswerCallBack.value = ($data) => {
|
||||
sendMessage({
|
||||
"commond" : 'ASK',
|
||||
"body" : $data
|
||||
})
|
||||
}
|
||||
// 停止获取
|
||||
sendMessage({
|
||||
"commond" : 'ASK-STOP',
|
||||
"body" : {
|
||||
reqBatch: reqBatch.value,
|
||||
seqNo: seqNo.value,
|
||||
}
|
||||
})
|
||||
break;
|
||||
case 'stop':
|
||||
console.log('停止获取数据')
|
||||
stopAnswerCallBack.value = null
|
||||
// ASK-STOP
|
||||
sendMessage({
|
||||
"commond" : 'ASK-STOP',
|
||||
"body" : {
|
||||
reqBatch: reqBatch.value,
|
||||
seqNo: seqNo.value,
|
||||
}
|
||||
})
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
const routerTo = (type) => {
|
||||
switch(type){
|
||||
case 'AI学':
|
||||
@@ -136,15 +324,57 @@
|
||||
common.switchTab('/pages/index/index')
|
||||
}
|
||||
const uploadRecordNow = (voicePath) => {
|
||||
// /traask/traAskchat/voiceTrans
|
||||
}
|
||||
const touchBtnCallBack = ref(null)
|
||||
const askData = ref({})
|
||||
const sendText = (item) => {
|
||||
submitAnswerNow(item)
|
||||
}
|
||||
|
||||
// 发送文本
|
||||
const submitAnswerNow = (val, cb) => {
|
||||
|
||||
const submitAnswerNow = (val, cb = null) => {
|
||||
if(!answerIsOver.value){
|
||||
// 问题回答中
|
||||
return
|
||||
}
|
||||
touchBtnCallBack.value = cb
|
||||
askData.value = {
|
||||
reqBatch: reqBatch.value,
|
||||
intrctWay: '01',//01-文本;02-语音;03-图片
|
||||
intrctContent: val,
|
||||
bucketKey:''
|
||||
}
|
||||
sendMessage({
|
||||
"commond" : 'ASK',
|
||||
"body" : askData.value
|
||||
})
|
||||
}
|
||||
const startRecordCallback = () => {
|
||||
// 开始录音回调
|
||||
}
|
||||
const watchFlag = ref(1)
|
||||
onMounted(()=>{
|
||||
initsocketTask()
|
||||
})
|
||||
onHide(()=>{
|
||||
if(watchFlag.value === 1) return
|
||||
// 停止获取
|
||||
sendMessage({
|
||||
"commond" : 'ASK-STOP',
|
||||
"body" : {
|
||||
reqBatch: reqBatch.value,
|
||||
seqNo: seqNo.value,
|
||||
}
|
||||
})
|
||||
socketStore?.closeSocket()
|
||||
})
|
||||
onShow(()=>{
|
||||
if(watchFlag.value === 1) return
|
||||
socketStore?.createSocket()
|
||||
})
|
||||
onUnload(()=>{
|
||||
socketStore?.closeSocket()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@@ -215,6 +445,13 @@
|
||||
right: 0;
|
||||
top: 10rpx;
|
||||
}
|
||||
.image-icon-new{
|
||||
right: 50rpx;
|
||||
width: 30rpx;
|
||||
height: 32rpx;
|
||||
position: absolute;
|
||||
top: 10rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.ai-info{
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 475 B |
Binary file not shown.
|
After Width: | Height: | Size: 795 B |
Reference in New Issue
Block a user