Merge branch 'sit' into 'uat'

Sit

See merge request K17_AITS/tra-app!33
This commit is contained in:
李泉德
2025-07-16 18:13:36 +08:00
24 changed files with 978 additions and 163 deletions
+1 -4
View File
@@ -2,10 +2,6 @@
ENV = 'development'
# 'development'
VITE_APP_ENV = 'AND'
# base api
# 苗扬
# VITE_APP_BASE_API_Url = 'http://25.64.16.133:9786'
@@ -16,6 +12,7 @@ VITE_APP_ENV = 'AND'
VITE_APP_BASE_API_Url = 'https://aitstest.jlbank.com.cn:7001'
# app入口
#VITE_APP_BASE_API_Url = 'http://25.16.122.65:7001'
#VITE_APP_BASE_API_Url = 'http://25.16.122.91:9786'
-3
View File
@@ -1,8 +1,5 @@
# 生产环境
ENV = 'production'
VITE_APP_ENV = 'AND'
# base api
VITE_APP_BASE_API_Url = 'https://aitstest.jlbank.com.cn:7001'
-2
View File
@@ -1,8 +1,6 @@
# SIT环境
ENV = 'sit'
VITE_APP_ENV = 'AND'
# base api
VITE_APP_BASE_API_Url = 'https://aitstest.jlbank.com.cn:7002'
-8
View File
@@ -1,8 +0,0 @@
# SIT环境
ENV = 'sit'
VITE_APP_ENV = 'IOS'
# base api
VITE_APP_BASE_API_Url = 'https://aitstest.jlbank.com.cn:7002'
-3
View File
@@ -1,8 +1,5 @@
# UAT环境
ENV = 'uat'
VITE_APP_ENV = 'AND'
# base api
VITE_APP_BASE_API_Url = 'https://aitstest.jlbank.com.cn:7003'
-8
View File
@@ -1,8 +0,0 @@
# UAT环境
ENV = 'uat'
VITE_APP_ENV = 'IOS'
# base api
VITE_APP_BASE_API_Url = 'https://aitstest.jlbank.com.cn:7003'
+2 -1
View File
@@ -2,4 +2,5 @@ npm install --registry=http://25.12.10.69:8081/repository/aliyun-npm/
npm 淘宝源下载 npm config set registry https://registry.npmmirror.com
npm config set registry http://25.12.10.69:8081/repository/aliyun-npm/
npm config set registry http://25.12.10.69:8081/repository/aliyun-npm/
+1 -1
View File
@@ -144,7 +144,7 @@ export function downloadVoiceFile(url) {
let baseUrl = get_base_url();
return new Promise((resolve, reject) => {
uni.downloadFile({
url: `${baseUrl}${url}`,
url: `${baseUrl}${url}`,
header,
success(result) {
console.log(result)
+1 -1
View File
@@ -63,7 +63,7 @@ export default class WebSocketUtil {
this.socketTask.onMessage((res) => {
if(res.data){
const { rtnCode } = JSON.parse(res.data)
console.log('收到WebSocket消息', JSON.parse(res.data) );
// console.log('收到WebSocket消息', JSON.parse(res.data) );
if(['0005', 'QQ0005'].includes(rtnCode)) {
clearInterval(this.reconnectTimer);
this.reconnectTimer = null
+10
View File
@@ -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
});
};
+19 -3
View File
@@ -1,6 +1,4 @@
const _ENV = import.meta.env
function msg(title, duration = 1000) {
uni.showToast({
title: title,
@@ -116,7 +114,25 @@ export const getUserInfo = (key = '') => {
const userInfo = uni.getStorageSync('userInfo')
return '' === key ? userInfo : userInfo[key];
}
export const getPhoneEnvBool = (flag) => _ENV.VITE_APP_ENV === flag
/**
* 判断当前运行平台是否匹配指定标识
* @param {string} flag - 平台标识:'IOS' 表示苹果iOS平台,'AND' 表示安卓平台
* @returns {boolean} 如果当前平台匹配指定标识返回true,否则返回false
*/
export const getPhoneEnvBool = (flag) => {
// 获取当前设备的平台信息
const { platform } = uni.getSystemInfoSync();
// 平台与标识的映射关系
const platformMap = {
ios: 'IOS',
android: 'AND',
};
// 根据当前平台返回匹配结果
return platformMap[platform] === flag;
};
// 将对象和字符串相互转换
+223
View File
@@ -0,0 +1,223 @@
export default function showDialog(options) {
const view = new plus.nativeObj.View('customModal', {
top: '0',
left: '0',
height: '100%',
width: '100%',
backgroundColor: 'rgba(0,0,0,0.5)',
position: 'fixed'
});
const screenWidth = plus.screen.resolutionWidth;
const screenHeight = plus.screen.resolutionHeight;
const borderRadius = 10;
const contentPadding = 20; // 内容左右内边距
const lineHeight = 24; // 单行文本行高
const maxLines = 2; // 最大行数
const fontSize = 16; // 字体大小
// 判断内容和标题是否存在
const hasContent = options.content && options.content.trim() !== '';
const hasTitle = options.title && options.title.trim() !== '';
// 弹窗宽度计算
const modalWidth = Math.min(0.8 * screenWidth, 500);
const modalLeft = (screenWidth - modalWidth) / 2;
// 内容区域可用宽度
const contentAvailableWidth = modalWidth - 2 * contentPadding - 10;
// 动态计算各部分高度
const titleHeight = hasTitle ? 30 : 0;
const titleToContentSpacing = (hasTitle && hasContent) ? 10 : 0;
const contentHeight = hasContent ? (lineHeight * maxLines) : 0;
const contentToBtnSpacing = 10;
const btnHeight = 50;
// 弹窗总高度
let modalHeight = contentPadding + titleHeight + titleToContentSpacing + contentHeight + contentToBtnSpacing + btnHeight;
modalHeight = options.showCancel ? Math.min(modalHeight, 300) : Math.min(modalHeight, 250);
// 位置计算
const modalTop = (screenHeight - modalHeight) / 2;
const contentAreaTop = modalTop + contentPadding;
const titleTop = hasTitle ? contentAreaTop : 0;
// Fix: Calculate contentTop properly when there's no content
let contentTop;
if (hasContent) {
contentTop = hasTitle ? (titleTop + titleHeight + titleToContentSpacing) : contentAreaTop;
} else {
contentTop = hasTitle ? (titleTop + titleHeight) : contentAreaTop;
}
const btnTop = hasContent
? (contentTop + contentHeight + contentToBtnSpacing)
: (hasTitle
? (titleTop + titleHeight + contentToBtnSpacing)
: (contentAreaTop + (modalHeight - btnHeight - contentPadding))); // 既无标题也无内容时
// 绘制弹窗背景
view.draw([{
tag: 'rect',
id: 'bg',
position: { top: modalTop + 'px', left: modalLeft + 'px', width: modalWidth + 'px', height: modalHeight + 'px' },
rectStyles: { color: '#fff', radius: borderRadius + 'px' }
}]);
// 绘制标题和内容
const elements = [];
// 标题(仅当存在时)
if (hasTitle) {
elements.push({
tag: 'font',
id: 'title',
text: options.title,
position: { top: titleTop + 'px', left: modalLeft + 'px', width: modalWidth + 'px', height: titleHeight + 'px' },
textStyles: {
color: '#000',
size: '20px',
align: 'center',
fontWeight: 'bold',
verticalAlign: 'middle'
}
});
}
if (hasContent) {
const text = options.content;
const maxLines = 2; // 最大显示行数
const lineHeightPx = lineHeight;
const leftPos = modalLeft + contentPadding;
const textWidth = contentAvailableWidth;
const fontSizePx = fontSize;
// 按 \n 分割段落
const paragraphs = text.split('\n');
let lines = [];
// 遍历每个段落,计算换行
for (let para of paragraphs) {
if (lines.length >= maxLines) break;
// 估算每行能容纳的字符数(中文按 1.2 倍宽度计算)
const avgCharWidth = fontSizePx * (isChinese(para) ? 1.2 : 0.6);
const maxCharsPerLine = Math.floor(textWidth / avgCharWidth);
let currentLine = '';
for (let i = 0; i < para.length; i++) {
const char = para[i];
currentLine += char;
// 如果当前行字符数超过限制,换行
if (currentLine.length >= maxCharsPerLine) {
lines.push(currentLine);
currentLine = '';
if (lines.length >= maxLines) break;
}
}
// 添加剩余部分
if (currentLine && lines.length < maxLines) {
lines.push(currentLine);
}
}
// 如果超出最大行数,最后一行加 "..."
if (lines.length > maxLines) {
lines = lines.slice(0, maxLines);
const lastLine = lines[maxLines - 1];
lines[maxLines - 1] = lastLine.substring(0, lastLine.length - 3) + '...';
}
// 绘制每一行文本(确保行高正确)
lines.forEach((line, index) => {
const topPos = contentTop + index * lineHeightPx;
elements.push({
tag: 'font',
id: 'content_line_' + index,
text: line,
position: {
top: topPos + 'px',
left: leftPos + 'px',
width: textWidth + 'px',
height: lineHeightPx + 'px'
},
textStyles: {
color: '#666',
size: fontSizePx + 'px',
align: 'center',
verticalAlign: 'top',
lineHeight: lineHeightPx + 'px'
}
});
});
}
// 判断是否主要是中文
function isChinese(text) {
return /[\u4e00-\u9fa5]/.test(text);
}
// 分割线
elements.push({
tag: 'rect',
id: 'divider',
position: { top: btnTop + 'px', left: modalLeft + 'px', width: modalWidth + 'px', height: '1px' },
rectStyles: { color: '#b3b3b3' }
});
view.draw(elements);
// 绘制按钮(不变)
if (options.showCancel) {
const btnWidth = Math.floor((modalWidth - 2 * borderRadius) / 2);
view.draw([
{ tag: 'rect', id: 'cancelBtn', position: { top: (btnTop + 2) + 'px', left: (modalLeft + borderRadius) + 'px', width: btnWidth + 'px', height: (btnHeight - 3) + 'px' }, rectStyles: { color: '#fff' } },
{ tag: 'font', id: 'cancelText', text: options.cancelText || '取消', position: { top: btnTop + 'px', left: (modalLeft + borderRadius) + 'px', width: btnWidth + 'px', height: btnHeight + 'px' }, textStyles: { color: '#007AFF', size: '16px', align: 'center', verticalAlign: 'middle' } },
{ tag: 'rect', id: 'btnDivider', position: { top: btnTop + 'px', left: (modalLeft + borderRadius + btnWidth) + 'px', width: '1px', height: btnHeight + 'px' }, rectStyles: { color: '#b3b3b3' } },
{ tag: 'rect', id: 'confirmBtn', position: { top: (btnTop + 2) + 'px', left: (modalLeft + borderRadius + btnWidth + 1) + 'px', width: btnWidth + 'px', height: (btnHeight - 3) + 'px' }, rectStyles: { color: '#fff' } },
{ tag: 'font', id: 'confirmText', text: options.confirmText || '确定', position: { top: btnTop + 'px', left: (modalLeft + borderRadius + btnWidth + 1) + 'px', width: btnWidth + 'px', height: btnHeight + 'px' }, textStyles: { color: '#007AFF', size: '16px', align: 'center', verticalAlign: 'middle' } }
]);
} else {
view.draw([
{ tag: 'rect', id: 'confirmBtn', position: { top: (btnTop + 2) + 'px', left: (modalLeft + borderRadius) + 'px', width: (modalWidth - 2 * borderRadius) + 'px', height: (btnHeight - 3) + 'px' }, rectStyles: { color: '#fff' } },
{ tag: 'font', id: 'confirmText', text: options.confirmText || '确定', position: { top: btnTop + 'px', left: (modalLeft + borderRadius) + 'px', width: (modalWidth - 2 * borderRadius) + 'px', height: btnHeight + 'px' }, textStyles: { color: '#007AFF', size: '16px', align: 'center', verticalAlign: 'middle' } }
]);
}
// 点击事件(不变)
view.addEventListener("click", function(e) {
const x = e.clientX;
const y = e.clientY;
if (options.showCancel) {
const btnWidth = Math.floor((modalWidth - 2 * borderRadius) / 2);
const cancelBtnLeft = modalLeft + borderRadius;
if (y > btnTop && y < btnTop + btnHeight) {
if (x >= cancelBtnLeft && x < cancelBtnLeft + btnWidth) {
options.cancel?.();
view.close();
} else if (x >= cancelBtnLeft + btnWidth + 1 && x < cancelBtnLeft + 2 * btnWidth + 1) {
options.success?.({ confirm: true });
view.close();
}
}
} else {
const btnLeft = modalLeft + borderRadius;
if (y > btnTop && y < btnTop + btnHeight && x >= btnLeft && x < btnLeft + (modalWidth - 2 * borderRadius)) {
options.success?.({ confirm: true });
view.close();
}
}
}, false);
view.show();
return {
close: () => {
view.close();
}
};
}
@@ -40,7 +40,7 @@
},
mode:{
type: String,
default: 'widthFix'
default: 'aspectFit'
},
previewDetail: {
default: '',
+25 -13
View File
@@ -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
})
+27 -7
View File
@@ -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,30 @@ 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
},
loadingText:{
default: '发送回答中,请稍后再试!',
type: String
},
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('')
@@ -161,7 +172,7 @@ const getRecordPermission = async (obj) => {
const showOverlayTalking = (obj) => {
if(isLoadingState.value){
uni.showToast({
title: '发送回答中,请稍后再试!',
title: props.loadingText,
icon: "none",
duration:1000
})
@@ -181,6 +192,7 @@ const showOverlayTalking = (obj) => {
hideOverlayTalking()
},59 * 1010)
}else{
showTalkingModel.value = false
uni.showToast({
title: '暂无问题需要回答!',
icon: "none",
@@ -216,6 +228,14 @@ const handleMove = (obj) => {
}
// 发送文本
const showKeyboard = () => {
if(isLoadingState.value){
uni.showToast({
title: props.loadingText,
icon: "none",
duration:1000
})
return
}
if(!isDisabled.value){
if(keyboardIsShow.value){
if(answerValue.value){
@@ -235,7 +255,7 @@ const showKeyboard = () => {
}
}else{
uni.showToast({
title: '请先获取问题信息',
title: '暂无问题需要回答',
icon: "none",
duration:1000
})
+130 -25
View File
@@ -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'
@@ -172,8 +182,8 @@ const getData = async (flag) => {
// return
// }
try{
await initsocketTask()
const { body } = await getCourseStudyInfoApi({crsId: crsId.value})
initsocketTask()
teacherList.value = body.teacheList.map(t=>({
...t,
voicePath: '',
@@ -183,7 +193,6 @@ const getData = async (flag) => {
step.value = 1
}else{
if(startPgrphId.value){
scrollToBottomNow()
choiceTeacher.value = body.choiceTeacher
sendMessage({
"commond" : 'STDY',
@@ -244,20 +253,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 +349,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)
@@ -342,6 +374,7 @@ const getGraphInfoUnStudy = (flag = 0) => {
if(!_body.chatBody) {
return
}
console.log('prgh', _body)
if(Number(_body.bodyTyp) === 7){
common.show('恭喜你已学完全部知识点', '有什么心得跟我们分享吗?',true, '取消','去评论').then((res) => {
if(res) {
@@ -392,6 +425,18 @@ const getQuestionInfoUnStudy = (pgrphId) => {
let _id = new Date().getTime() + 'id'
let _body = res.body || {}
console.log(_body)
if(Number(_body.bodyTyp) === 7){
common.show('恭喜你已学完全部知识点', '有什么心得跟我们分享吗?',true, '取消','去评论').then((res) => {
if(res) {
// 去评论
common.redirectTo(`/pages/courseDetail/submit?crsId=${crsId.value}&crsNum=${crsNum.value}&from=study&imgId=${imageId.value}`)
}else{
// 取消
common.redirectTo(`/pages/courseDetail/index?crsId=${crsId.value}&crsNum=${crsNum.value}&from=study`)
}
}).finally(() => {})
return
}
if(_body.chatBody && _body.chatBody?.stdyStat !== 'N'){
let _id = new Date().getTime() + 'id'
lastTalkingInfo.value = {
@@ -452,7 +497,7 @@ const nextQuestionSuccess = (info, type='next') => {
talkingList.value.push({
crsId: info.crsId,
pgrphId: info.pgrphId,
talkingContent: '重新',
talkingContent: '重新',
type: 0,
id: _id
})
@@ -542,8 +587,8 @@ const stdyId = ref('')
const stdyBatch = ref('')
const logId = ref('')
// ws 初始化逻辑
const initsocketTask = () => {
socketStore.creatSocket()
const initsocketTask = async () => {
await socketStore.creatSocket()
unref(SocketObj).on('message', (res)=>{
const { rtnCode, body, commond } = JSON.parse(res.data)
if(rtnCode === '0000'){
@@ -554,9 +599,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 +835,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 +870,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 +1214,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 +1234,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;
}
}
}
}
}
}
}
+149 -44
View File
@@ -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,101 @@ onMounted(()=>{
background-color: #fff;
padding: 20rpx;
border-radius: 15rpx;
padding-bottom: 30rpx;
padding-bottom: 10rpx;
position: relative;
float: left;
.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>
+30 -4
View File
@@ -1,5 +1,5 @@
<template>
<view class="talking-info">
<view class="talking-info" v-if="dataInfo.intrctWay === '02'">
<view :class="['talking-gif', !voiceLoading ? 'is-play' : '']">
<CommonLoading color="#fff" v-show="voiceLoading"/>
</view>
@@ -22,10 +22,24 @@
</view>
</view>
</view>
<view class="talking-info talking-info-text" v-else-if="dataInfo.intrctWay === '01'">
<view class="talking-cont">
<text :class="['talking-text translate-text loading']">
{{ showContent }}
</text>
</view>
</view>
<view class="talking-info talking-info-text" v-else>
<view class="talking-cont">
<view class="talking-text translate-text" v-if="translateLoading">
<CommonLoading color="#fff"/>
</view>
</view>
</view>
</template>
<script setup>
import { computed, ref, toRefs } from "vue"
import { computed, ref, toRefs, watch } from "vue"
const props = defineProps({
dataInfo: {
default: () => ({}),
@@ -37,12 +51,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>
@@ -54,6 +69,8 @@
padding-bottom: 30rpx;
position: relative;
color: #fff;
width: 100%;
float: right;
.talking-gif{
width: 200rpx;
height: 38rpx;
@@ -73,6 +90,15 @@
height: 38rpx;
line-height: 38rpx;
}
&.talking-info-text{
padding-bottom: 20rpx;
width: auto;
float: right;
.talking-cont{
margin-top: 0rpx;
}
}
.talking-cont{
margin-top: 16rpx;
overflow: hidden;
+19 -4
View File
@@ -1,26 +1,41 @@
<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 >
.talking-item{
overflow: hidden;
&+.talking-item{
margin-top: 30rpx;
}
+290 -22
View File
@@ -5,11 +5,11 @@
<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()"/>
<!-- <uv-icon class="arrow-icon" name="arrow-left" @click="common.navigateBack()"/> -->
AI问答
</view>
<view class="ai-info">
@@ -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>
@@ -36,11 +44,13 @@
:scroll-y="true"
:scroll-top="scrollTop"
:show-scrollbar="false"
:scroll-with-animation="true"
:scroll-with-animation="false"
>
<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>
@@ -82,6 +92,8 @@
@uploadVoice="uploadRecordNow"
@submitAnswer="submitAnswerNow"
@startRecord="startRecordCallback"
:loadingText="sendLoadingText"
:isLoading="!answerIsOver"
:isDisabled="false"
/>
</scroll-view>
@@ -89,10 +101,22 @@
</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'
import {
commonUploadVoiceFile
} from "@/api/common.js"
const socketStore = useSocketStore()
const { SocketObj } = storeToRefs(socketStore)
const questionList = ref([
'对公小程序开户流程?',
'厅堂服务经理2025年管理办法?',
@@ -107,21 +131,181 @@
])
const scrollBoxRef = ref()
const scrollTop = ref(99999)
const scrollToBottomNow = (time = 500) => {
const scrollToBottomNow = (time = 100) => {
nextTick(()=>{
setTimeout(() => {
scrollTop.value += 1
// let _arr = talkingList.value || []
// if(_arr.length && _arr.length > 0){
// let _last = talkingList.value[talkingList.value.length - 1]
// uni.pageScrollTo({
// selector: '#item' + _last.id,
// duration: 300
// })
// }
scrollTop.value += 100
scrollTop.value += 100
},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)
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('发送问题成功!')
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('操作异常!')
return
}
}else{
if(['0005', 'QQ0005'].includes(res.rtnCode)) {
goto_login_fun();
}
}
})
watchFlag.value += 1
}
const sendLoadingText = ref('问题回答中,请在问题回答结束后重试!')
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学':
@@ -135,16 +319,93 @@
const scrolltolower = () => {
common.switchTab('/pages/index/index')
}
const touchBtnCallBack = ref(null)
const askData = ref({})
const sendText = (item) => {
submitAnswerNow(item)
}
// 发送语音
const uploadRecordNow = (voicePath) => {
// /traask/traAskchat/voiceTrans
//#ifndef APP-PLUS
// h5 假数据
// #endif
//#ifdef APP-PLUS
talkingList.value.push({
type: 'question',
talkingText: ''
})
scrollToBottomNow()
commonUploadVoiceFile(voicePath, '/traask/traAskchat/voiceTrans', {}).then(res=>{
let _res = JSON.parse(res.data)
if(_res.rtnCode === '0000'){
askData.value = {
reqBatch: reqBatch.value,
intrctWay: '02',//01-文本;02-语音;03-图片
intrctContent: _res.body.transContent,
bucketKey: _res.body.fileId,
ossFileAddr: _res.body.ossFileAddr,
voicePath: voicePath
}
sendMessage({
"commond" : 'ASK',
"body": askData.value
})
}else{}
}).catch(err=>{
talkingList.value.pop()
uni.showToast({
title: '系统异常,请联系管理员',
icon: "none",
duration:1000
});
})
// #endif
}
// 发送文本
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 +476,13 @@
right: 0;
top: 10rpx;
}
.image-icon-new{
right: 50rpx;
width: 30rpx;
height: 32rpx;
position: absolute;
top: 10rpx;
}
}
.ai-info{
+1 -1
View File
@@ -60,7 +60,7 @@ import { encryptByAES } from '@/api/encryptAndDecryptData.js';
import { useSocketStore } from '@/store/socket.js';
import { storeToRefs } from 'pinia';
import {getPhoneEnvBool} from '@/common/common.js'
const socketStore = useSocketStore();
const { taskSocket } = storeToRefs(socketStore);
+49 -8
View File
@@ -9,22 +9,63 @@
</view>
<view class="detail-body">
<uv-cell-group :border="false">
<uv-cell title="根域名" :value="url" ></uv-cell>
<uv-cell title="根域名" :value="url"></uv-cell>
<uv-cell title="当前环境:" :value="ENV.MODE"></uv-cell>
<uv-cell title="版本名称" :value="appBaseInfo.appVersion"></uv-cell>
<uv-cell title="版本号" :value="appBaseInfo.appVersionCode"></uv-cell>
<uv-cell title="当前环境:" :value="ENV.MODE" :border="false"></uv-cell>
<uv-cell title="点击1:" @click="aaaa"></uv-cell>
<uv-cell title="点击2:" :border="false" @click="bbbb"></uv-cell>
</uv-cell-group>
</view>
</view>
</template>
<script setup>
import common from '@/common/common';
import { get_base_url } from '../../api/request';
const url = get_base_url();
const ENV = import.meta.env
const appBaseInfo = uni.getAppBaseInfo()
console.log('appBaseInfo', appBaseInfo);
import common from '@/common/common';
import {
get_base_url
} from '../../api/request';
const url = get_base_url();
const ENV = import.meta.env
const appBaseInfo = uni.getAppBaseInfo()
console.log('appBaseInfo', appBaseInfo);
import showDialog from '@/common/dialogMessage'
const bbbb = () => {
showDialog({
title: '提示信息',
content: '检测到您已离开,即将退出学习页面。',
confirmText: '确认',
showCancel: false,
cancelText: '再想想',
success: (res) => {
if (res.confirm) {
console.log('用户点击确定');
}
},
cancel: () => {
console.log('用户点击取消');
}
});
}
const aaaa = () => {
showDialog({
title: '提示',
content: '检测到您已存在学习记录,要继续学习吗?',
confirmText: '重新学习',
showCancel: true,
cancelText: '继续学习',
success: (res) => {
if (res.confirm) {
console.log('用户点击确定');
}
},
cancel: () => {
console.log('用户点击取消');
}
});
}
</script>
<style lang="scss" scoped>
Binary file not shown.

After

Width:  |  Height:  |  Size: 475 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 795 B