Merge branch 'develop' into 'sit'

Develop

See merge request K17_AITS/tra-app!172
This commit is contained in:
田岩
2025-09-23 18:22:13 +08:00
22 changed files with 1925 additions and 47 deletions
+12 -3
View File
@@ -2,7 +2,10 @@
ENV = 'development'
# 'development'
VITE_APP_BASE_API_Url = 'https://aitstest.jlbank.com.cn:7001'
VITE_APP_BASE_API_Url = 'https://aitstest.jlbank.com.cn:7002'
# VITE_APP_BASE_API_Url = ' http://25.18.122.66:9786'
#sit
# VITE_APP_BASE_API_Url = 'http://25.18.122.91:9786'
# UAT
# VITE_APP_BASE_API_Url = 'http://25.18.122.78:9786'
@@ -12,12 +15,18 @@ VITE_APP_BASE_API_Url = 'https://aitstest.jlbank.com.cn:7001'
# VITE_APP_BASE_API_Url = 'http://25.18.122.65:7001'
# VITE_APP_BASE_API_Url = 'http://25.16.122.92:9786'
# VITE_APP_BASE_API_Url = 'http://25.18.122.66:9786'
# VITE_APP_BASE_API_Url = 'http://25.16.122.91:9786'
#VITE_APP_BASE_API_Url = 'http://25.18.122.66:9786'
# h5专用地址x2
#sit
# VITE_APP_BASE_H5_API_Url = 'http://25.18.122.91:9786'
# VITE_APP_BASE_H5_API_Url = 'http://25.18.122.65:7001'
# VITE_APP_BASE_H5_API_Url = 'http://25.18.122.91:9786'
# VITE_APP_BASE_H5_API_Url = 'http://25.18.122.78:9786'
# VITE_APP_BASE_H5_API_Url_TRAASK = 'http://25.64.16.144:9605'
+1 -1
View File
@@ -2,4 +2,4 @@
ENV = 'production'
# base api
VITE_APP_BASE_API_Url = 'https://aits.jlbank.com.cn:7001'
VITE_APP_BASE_API_Url = 'http://localhost:7000'
+3
View File
@@ -6,6 +6,7 @@ import common from '@/common/common.js'
const ENV = import.meta.env
export const get_base_url = (url = '') => {
if (ENV.MODE === 'development') { // 开发环境
// #ifdef H5
if (url) {
@@ -20,7 +21,9 @@ export const get_base_url = (url = '') => {
return ENV.VITE_APP_BASE_API_Url
// #endif
} else { // 其他环境,包括生产环境,sit环境,uat环境
return ENV.VITE_APP_BASE_API_Url
// return ENV.VITE_APP_BASE_API_Url
}
}
+2 -1
View File
@@ -176,12 +176,12 @@
};
const playVoice = () => {
// isPlaying.value = true;
console.log('点击播放');
innerAudio.playAudio();
};
let unsubscribe = () => {};
onMounted(() => {
console.log('question的onMounted');
// 2. 监听 Store 中 isPlaying 的变化,实时同步
// unsubscribe = innerAudio.$subscribe((mutation, state) => {
// isPlaying.value = state.isPlaying;
@@ -214,6 +214,7 @@
console.log('去下载oss内容333', props.dataInfo);
if (newVal === '' && props.dataInfo.ossKey) {
// 去下载oss内容
console.log('真下载oss内容');
downloadFile(props.dataInfo.ossKey).then((res) => {
voiceTime.value = '';
innerAudio.setAudioSrc(res.tempFilePath);
+19 -6
View File
@@ -73,7 +73,7 @@
</template>
<script setup>
import {ref, computed, onMounted, nextTick, watch, reactive } from 'vue';
import { ref, computed, onMounted, nextTick, watch, reactive } from 'vue';
import common from '@/common/common';
import permission from '@/common/permission.js';
import { getPhoneEnvBool } from '@/common/common.js';
@@ -133,7 +133,11 @@
const startTimer = ref(null); // 延迟启动的定时器ID
const startFlag = ref(1);
const startRecord = async (obj) => {
try {await initRecord()}catch(err) {return;}
try {
await initRecord();
} catch (err) {
return;
}
if (props.isDisabled) return;
// 开始录音
console.log('开始录音', recordingStatus.value);
@@ -163,9 +167,10 @@
const endTime = Date.now();
const times = Math.max(500 - endTime + startTime, 0);
setTimeout(() => {
popupRef.value.close();
console.log('停止录音');
recorderManager.stop();
recorderManager?.stop();
popupRef.value?.close();
}, times);
}
nextTick(() => (recordingStatus.value = 'not_started'));
@@ -184,7 +189,7 @@
return;
}
popupRef.value?.close();
recordingStatus.value = 'not_started'
recordingStatus.value = 'not_started';
// stopRecord(true); // 在去主动执行一次关闭
console.log('录音结束,时长:', recordDuration, '秒');
emit('submit', 'voice', res.tempFilePath);
@@ -215,6 +220,7 @@
const btnHeight = (windowInfo.screenWidth / 750) * 234;
return touchY.value < safeArea.bottom - btnHeight;
});
const activeNum = ref(1);
const handleMove = (obj) => {
touchY.value = obj.changedTouches[0].pageY;
@@ -224,7 +230,7 @@
inputText.value = text;
return old_text;
};
defineExpose({ clearinputText });
// 发送文本
const showKeyboard = () => {
if (keyboardIsShow.value) {
@@ -249,6 +255,13 @@
console.log('禁用时点击给的提示');
if (props.isDisabled) common.msg(props.disabledText);
};
// 外面调用放弃这次
const give_up = () => {
console.log('调用结束语音回答');
stopRecord(false)
};
defineExpose({ clearinputText, give_up });
</script>
<style lang="scss" scoped>
+10 -5
View File
@@ -12,7 +12,7 @@ const audioStopFun = ref(null)
export const useAudio = () => {
const audioInstance = uni.createInnerAudioContext();
const callbacks = reactive({
onEnded: null, // 播放完成回调
@@ -20,17 +20,23 @@ export const useAudio = () => {
onLoaded: null, // 音频加载完成回调(可获取时长)
onStop: null
})
let isInitialized = false;
const isPlaying = ref(false)
// 加载状态
const isLoading = ref(false)
// 当前播放的音频地址
const currentSrc = ref('')
const errorMsg = ref('')
// 初始化音频实例(确保全局唯一)
const initAudio = () => {
console.log('初始化音频实例(确保全局唯一)');
// if (isInitialized) return;
// isInitialized = true;
bindAudioEvents();
}
const bindAudioEvents = () => {
if (!audioInstance) return;
@@ -101,7 +107,7 @@ export const useAudio = () => {
const setAudioSrc = (src) => {
console.log('预设置音频地址并加载', src);
if (!src || src === currentSrc.value) return; // 地址不变则不重复加载
initAudio();
isLoading.value = true;
currentSrc.value = src;
errorMsg.value = '';
@@ -185,7 +191,6 @@ export const useAudio = () => {
callbacks.onEnded = callback;
}
}
// 注册播放错误回调
const onAudioError = (callback) => {
if (typeof callback === 'function') {
@@ -203,7 +208,7 @@ export const useAudio = () => {
callbacks.onStop = callback;
}
}
initAudio();
return {
isPlaying,
+14 -4
View File
@@ -1,8 +1,8 @@
<template>
<view>
<image :src="imgUrlShow" :mode="mode" @click="showDetail" style="width: 100%; height: 100%"></image>
<uni-popup ref="PopupRef" class="popup" mask-background-color="rgba(0,0,0,.2)">
<view class="overlay-box" @click="closePopup">
<uni-popup ref="PopupRef" class="popup" mask-background-color="rgba(0,0,0,.2)" background-color="rgba(0,0,0,.2)" @change="changeShow">
<view class="overlay-box" @click="closePopup" @touchmove.stop>
<image class="detail-img" :src="imgUrlShow" :mode="mode"></image>
</view>
</uni-popup>
@@ -88,18 +88,28 @@
},
immediate: true,
deep: true
},
showModel(val){
if(!val){
this.storageStore.setTouchBtnZIndex(12);
}
}
},
methods: {
changeShow(data){
if(!data.show){
this.storageStore.setTouchBtnZIndex(12);
}else{
this.storageStore.setTouchBtnZIndex(-1);
}
},
closePopup() {
this.showModel = false;
this.storageStore.setTouchBtnZIndex(9);
this.$refs.PopupRef.close();
},
showDetail() {
if (!!this.previewDetail) {
this.showModel = true;
this.storageStore.setTouchBtnZIndex(-1);
this.$refs.PopupRef.open();
}
},
@@ -10,7 +10,7 @@
></image>
</view>
<!-- <uv-overlay :show="showModel" opacity=".6" @click.stop="showModel = false"> -->
<uni-popup ref="PopupRef" class="popup" mask-background-color="rgba(0,0,0,.2)">
<uni-popup ref="PopupRef" class="popup" mask-background-color="rgba(0,0,0,.2)" @change="changeShow">
<view class="overlay-box" @click="playEnded">
<video class="video-cont-center"
:id="`video`+ fileId"
@@ -91,11 +91,17 @@
},
immediate: true,
deep: true,
}
},
},
methods: {
changeShow(data){
if(!data.show){
this.storageStore.setTouchBtnZIndex(12);
}else{
this.storageStore.setTouchBtnZIndex(-1);
}
},
showModelNow(){
this.storageStore.setTouchBtnZIndex(-1)
this.$refs.PopupRef.open()
this.showModel = true
},
@@ -103,7 +109,6 @@
console.log('err',info)
},
playEnded(){
this.storageStore.setTouchBtnZIndex(9)
this.$refs.PopupRef.close()
this.showModel = false
},
+10
View File
@@ -355,6 +355,16 @@
"titleNView": false
}
}
},
{
"path": "pages/example/dialog",
"style": {
"navigationBarTitleText": "AI问答演示",
"app-plus": {
"titleNView": false,
"animationType": "fade-in"
}
}
}
],
"tabBar": {
+1 -1
View File
@@ -711,7 +711,7 @@
<style lang="scss">
.ai-answer {
width: 100%;
overflow: hidden;
// overflow: hidden;
margin-bottom: 20rpx;
.user-info {
+1 -1
View File
@@ -121,7 +121,7 @@
const showTalkingModel = ref(false);
const keyboardIsShow = ref(false);
const talkModelRef = ref();
storageStore.setTouchBtnZIndex(9);
storageStore.setTouchBtnZIndex(12);
const btnZindex = computed(() => storageStore.getTouchBtnZIndex);
watch(
+3 -3
View File
@@ -766,7 +766,7 @@ const closeStudy = () => {
}
onHide(() => {
changePlayItemId('')
if (isPreview.value === 'S') {
if (!isPreview.value) {
closeStudy()
}
// socketStore.closeSocket()
@@ -785,7 +785,7 @@ onShow(() => {
if (watchFlag.value === 1) return
// getData('2');
// initsocketTask()
if (isPreview.value === 'S') {
if (!isPreview.value) {
studyStartApi({
crsId: crsId.value,
teacherNo: choiceTeacher.value,
@@ -806,7 +806,7 @@ onShow(() => {
const watchFlag = ref(1)
onUnload(() => {
console.log('onUnload')
if (isPreview.value === 'S') {
if (!isPreview.value) {
closeStudy()
}
// socketStore.closeSocket()
+12 -9
View File
@@ -19,7 +19,7 @@
<view class="question">
<view class="grey">剩余时间</view>
<uv-count-down
class="countdown_time"
class="countdown_time paperData limitTm"
ref="countDownRef"
:time="paperData.limitTm * 1000"
format="HH:mm:ss"
@@ -248,6 +248,7 @@
//
const finish = (item) => {
touchBtnRef.value?.give_up(); //
//
examinationAnswerCacheKey && common.setValue(examinationAnswerCacheKey, null);
//
@@ -386,7 +387,7 @@
paperData.flagQueryDetail = examination.flagQueryDetail || 'Y';
mode = paperData.examId ? 'PAPERS' : 'CRS'; // CRSPAPERS
//
examinationAnswerCacheKey = 'examinationAnswerCache' + paperData.execId;
answerCacheList.value = common.getValue(examinationAnswerCacheKey);
console.log('assaasas', answerCacheList.value);
@@ -424,19 +425,21 @@
}
})();
const my_answer = (() => {
console.log();
const default_return_data = res.qnsTyp==='ES'?{}:[];
try {
//
if (!paperData || typeof paperData !== 'object') {
return [];
return default_return_data;
}
//
if (paperData.isCacheExam === 'N') {
return [];
return default_return_data;
}
//
// if (!answerCacheList.value || typeof answerCacheList.value !== 'object') {
// return [];
// }
if (!answerCacheList.value || typeof answerCacheList.value !== 'object') {
return default_return_data;
}
// ID
// if (!res || typeof res.qnsId === 'undefined') {
// return [];
@@ -448,10 +451,10 @@
return cachedAnswer;
}
//
return [];
return default_return_data;
} catch (error) {
console.error('获取my_answer出错:', error);
return []; //
return default_return_data; //
}
})();
return {
+15 -6
View File
@@ -3,7 +3,7 @@
<nav-bar :is_seat="true"></nav-bar>
<view class="nav-div">
<view class="title ellipsis-text">
{{result.mode==='record'?'考试纪录':'考试结算'}}
{{ result.mode === 'record' ? '考试纪录' : '考试结算' }}
</view>
<view class="back_div" @click="goto_back()">
<view class="back-icon"></view>
@@ -68,7 +68,11 @@
时长{{ formatSecondsToText(result.examLenTm) }}
</view>
<view class="fl1"></view>
<view class="examination" @click="goExamRanking" v-if="result.mode === 'examination' && flagQuery === 'Y'">
<view
class="examination"
@click="goExamRanking"
v-if="result.mode === 'examination' && flagQuery === 'Y'"
>
<view>考试排行榜</view>
<view class="back-icon"></view>
</view>
@@ -106,7 +110,12 @@
<image src="@/static/images/examination/unable_img.png" mode="widthFix" class="img"></image>
<view class="tip">您暂无权限查看此试卷详情</view>
</view>
<view v-if="flagQueryDetail === 'Y'" v-show="index === sheet_index" :class="getStatusClass(index)" v-for="(item, index) in topicList">
<view
v-if="flagQueryDetail === 'Y'"
v-show="index === sheet_index"
:class="getStatusClass(index)"
v-for="(item, index) in topicList"
>
<Subject :item="item" mode="settlement" :clearResult="false" :isPreview="true"></Subject>
</view>
</view>
@@ -236,8 +245,8 @@
const get_result = () => {
//
if (requestCount.value >= 15) {
console.log('已达到最大请求次数(15次),停止请求');
if (requestCount.value >= 20) {
console.log('已达到最大请求次数(20次),停止请求');
requestCount.value = 0;
return;
}
@@ -303,7 +312,7 @@
flagQueryDetail.value = e.flagQueryDetail;
flagQuery.value = e.flagQuery;
// if (flagQueryDetail.value === 'Y' || flagQuery.value === 'Y') {
// }
get_result();
});
+408
View File
@@ -0,0 +1,408 @@
<template>
<view class="answer-content" v-if="props.isLoading">
<view class="talking-cont">
<CommonLoading color="#06f" />
</view>
</view>
<view class="answer-content" v-else>
<view :class="['talking-gif', !voiceLoading&&(props.activeVoiceId === dataInfo.id) ? 'is-play' : '']">
</view>
<view class="talking-cont">
<MarkdownPreview :mdString="mdText" />
</view>
<view class="talking-link">
<view :class="['link-cont', activeNames.indexOf(1) >= 0 ? 'is-active' : '']" v-if="fileList.length > 0">
<view class="link-info" @click="changeActive(1)">
<view class="info-text"> 引用知识库{{ fileList.length }}篇相关资料 </view>
</view>
<view class="link-items">
<view class="link-item" v-for="(item, index) in fileList" @click.stop="previewFile(item)">{{ index + 1 }}. {{ item.docName }}</view>
</view>
</view>
<view v-else-if="!props.isAnswering" class="link-cont-desc">本次回答参考资料来源于网络</view>
<view :class="['link-cont', activeNames.indexOf(2) >= 0 ? 'is-active' : '']" v-if="crsList.length > 0">
<view class="link-info" @click="changeActive(2)">
<view class="info-text"> 发现{{ crsList.length }}个关联课程 </view>
</view>
<view class="link-items">
<view class="link-item" v-for="(item, index) in crsList" @click.stop="previewCrs(item)">{{ index + 1 }}. {{ item.crsName }}</view>
</view>
</view>
</view>
<view class="btns-cont">
<view class="replay-btn" >
<image class="icon" src="@/static/images/course/play.png" style="width: 100%; height: 100%"
v-show="!(!voiceLoading&&(props.activeVoiceId === dataInfo.id))"
@click.stop="playVoice()" ></image>
<image class="icon"src="@/static/images/course/pause.png"
style="width: 100%; height: 100%" @click.stop="pauseVoice"></image>
</view>
</view>
</view>
</template>
<script setup>
import { onMounted, computed, ref, watch, toRefs } from 'vue'
import { onUnload } from '@dcloudio/uni-app';
import MarkdownPreview from '@/components/markdown-preview/markdown-preview.vue'
import common from '@/common/common'
import {
downloadVoiceFile,
} from '@/api/common.js';
const emit = defineEmits(['handleEvents'])
const props = defineProps({
dataInfo: {
default: () => ({}),
type: Object
},
isLoading: {
default: false,
type: Boolean
},
isAnswering: {
default: false,
type: Boolean
},
activeVoiceId:{
default:'',
type: String
},
isLast: {
default: false,
type: Boolean
}
})
const { activeVoiceId, dataInfo } = toRefs(props)
watch(
() => props.dataInfo,
() => {},
{
deep: true,
immediate: true
}
)
watch(
() => props.isLoading,
() => {},
{
deep: true,
immediate: true
}
)
const mdText = computed(() => {
let _text = (props.dataInfo?.talkingText|| '').replace(/\\n/g, `\n`);
return _text
})
const fileList = computed(() => props.dataInfo?.talkingFiles || [])
const crsList = computed(() => props.dataInfo?.talkingCrsList || [])
const activeNames = ref([1, 2])
const isPlaying = ref(false)
const talkVoiceObj = ref(null)
const voiceLoading = ref(false)
const voiceTime = ref('')
const itemVoice = ref('')
const initVoice = () => {
talkVoiceObj.value = uni.createInnerAudioContext()
talkVoiceObj.value.onEnded(()=>{
emit('changePlay', '')
talkVoiceObj.value.src = ''
})
talkVoiceObj.value.onError(()=>{
talkVoiceObj.value.src = ''
})
// itemVoice.value = unref(dataInfo).talkingVoice
// talkVoiceObj.value.src = itemVoice.value
// setTimeout(()=>{
// setVoiceTime(0)
// },100)
}
const setVoiceTime = (sum) => {
sum += 1
if(Math.ceil(talkVoiceObj.value.duration)>0){
voiceTime.value = (Math.ceil(talkVoiceObj.value.duration) || 1 ) + 's'
}else{
if(sum === 20) {
voiceTime.value = 0 + 's'
return
}
setTimeout(()=>{
setVoiceTime(sum)
},200)
}
}
const playVoice = (readType = 'pgth') => {
let _content = mdText.value;
if (!_content) {
common.msg('没有可以播放的文字信息!');
return;
}
isPlaying.value = true
voiceLoading.value = true;
setTimeout(() => {
let _params = {}
let _url =
'/trastudy/traStdyInfo/readContent?' +
Object.keys(_params)
.map((t) => {
return encodeURIComponent(t) + '=' + encodeURIComponent(_params[t]);
})
.join('&');
downloadVoiceFile(_url)
.then((res) => {
if (res?.data) {
common.msg(res.data.message);
return;
}
voiceLoading.value = false;
itemVoice.value = res.tempFilePath;
talkVoiceObj.value.src = res.tempFilePath;
talkVoiceObj.value.play();
storageStore.setStorage('audio', _url + _params.teachNo, itemVoice.value);
})
.catch((err) => {
voiceLoading.value = false;
});
}, 100);
};
const pauseVoice = () => {
if(!talkVoiceObj.value) return
talkVoiceObj.value.pause()
isPlaying.value = false
emit('changePlay', '')
}
onMounted(()=>{
initVoice()
})
onUnload(()=>{
pauseVoice()
})
watch(() => props.dataInfo.talkingVoice,(val)=>{
if(val){
initVoice()
}
},{
deep: true,
immediate: true
})
const changeActive = val => {
if (activeNames.value.indexOf(val) >= 0) {
activeNames.value = activeNames.value.filter(t => t !== val)
} else {
activeNames.value.push(val)
}
}
const reAnswer = () => {
emit('handleEvents', 'reAnswer')
}
const stopAnswer = () => {
emit('handleEvents', 'stop')
}
const zanAnswer = () => {
emit('handleEvents', 'great')
}
const feedbackAnswer = () => {
emit('handleEvents', 'feedback')
}
const previewFile = item => {
emit('handleEvents', 'preview', item)
}
const previewCrs = item => {
common.navigateTo('/pages/courseDetail/index?crsId=' + item.crsId)
}
</script>
<style scoped lang="scss">
.answer-content {
background-color: #f5f5f5;
padding: 20rpx;
border-radius: 15rpx;
padding-bottom: 10rpx;
position: relative;
overflow: hidden;
float: left;
width: 100%;
min-height: 80rpx;
.talking-gif{
width: 200rpx;
height: 38rpx;
margin-bottom: 20rpx;
background: url(@/static/images/course/voice-gray.png) no-repeat;
background-size: contain;
padding-left: 168rpx;
&.is-play{
background: url(@/static/images/course/voice-gif.gif) no-repeat;
background-size: contain;
}
}
.talking-cont {
padding-bottom: 20rpx;
}
.btns-cont {
overflow: hidden;
border-top: 3rpx solid #eee;
padding-bottom: 10rpx;
.replay-btn {
float: left;
width: 46rpx;
height: 46rpx;
overflow: hidden;
margin-right: 20rpx;
margin-top: 20rpx;
}
.play-btn {
float: left;
width: 46rpx;
height: 46rpx;
overflow: hidden;
margin-right: 20rpx;
margin-top: 20rpx;
}
.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;
}
}
}
.link-cont-desc {
margin-bottom: 12rpx;
font-size: 24rpx;
color: #999;
}
.link-cont {
margin-bottom: 12rpx;
font-size: 28rpx;
.link-info {
height: 50rpx;
line-height: 50rpx;
overflow: hidden;
margin-bottom: 12rpx;
.info-text {
float: left;
padding-right: 20rpx;
}
&::after {
transition: all 0.3s ease-in-out;
content: '';
display: block;
float: left;
position: relative;
top: 12rpx;
width: 12rpx;
height: 12rpx;
border-right: 4rpx solid #333;
border-bottom: 4rpx solid #333;
transform: rotate(135deg);
}
}
&.is-active {
.link-info {
&::after {
top: 18rpx;
transform: rotate(45deg);
}
}
.link-items {
max-height: 600rpx;
}
}
.link-items {
max-height: 0rpx;
overflow: hidden;
transition: all 0.3s ease-in-out;
.link-item {
line-height: 48rpx;
color: #06f;
}
}
}
}
</style>
+266
View File
@@ -0,0 +1,266 @@
<template>
<view class="talking-info" v-if="dataInfo.intrctWay === '02'">
<view :class="['talking-gif', !voiceLoading&&(props.activeVoiceId === dataInfo.id) ? 'is-play' : '']">
<CommonLoading color="#fff" v-show="voiceLoading"/>
</view>
<view class="right-time">{{voiceTime}}</view>
<view class="talking-cont">
<text :class="['talking-text translate-text loading']" v-show="!translateLoading">
{{ showContent }}
</text>
<view class="talking-text translate-text" v-if="translateLoading">
<CommonLoading color="#fff"/>
</view>
</view>
<view class="btns-cont">
<view class="play-btn">
<image class="icon"
v-show="!(!voiceLoading&&(props.activeVoiceId === dataInfo.id))"
@click="playVoice"
src="@/static/images/course/play-blue.png"
style="width: 100%;height: 100%;"
></image>
<image class="icon"
v-show="!voiceLoading&&(props.activeVoiceId === dataInfo.id)"
@click.stop="pauseVoice"
src="@/static/images/course/stop-blue.png"
style="width: 100%;height: 100%;"
></image>
<!-- <view class="icon text-btn" v-if="type === 1" @click.stop="translateVoice(dataInfo)"></view> -->
</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-loading" v-else>
<view :class="['talking-gif']">
<CommonLoading color="#fff"/>
</view>
</view>
</template>
<script setup>
import { computed, unref, ref, toRefs, watch, onMounted } from "vue"
import { onUnload } from '@dcloudio/uni-app';
const props = defineProps({
dataInfo: {
default: () => ({}),
type: Object
},
activeVoiceId:{
default:'',
type: String
},
})
const { activeVoiceId, dataInfo } = toRefs(props)
const emit = defineEmits(['changePlay'])
watch(() => props.dataInfo, () => {},{deep: true,immediate: true})
//
const talkVoiceObj = ref(null)
const isPlaying = ref(false)
const voiceLoading = ref(false)
const translateLoading = ref(false)
const voiceTime = ref('')
const itemVoice = ref('')
const showContent = computed(()=>{
return dataInfo.value.talkingText || ''
})
onMounted(()=>{
initVoice()
})
onUnload(()=>{
pauseVoice()
})
watch(() => props.dataInfo.talkingVoice,(val)=>{
if(val){
initVoice()
}
},{
deep: true,
immediate: true
})
const pauseVoice = () => {
if(!talkVoiceObj.value) return
talkVoiceObj.value.pause()
isPlaying.value = false
emit('changePlay', '')
}
const playVoice = () =>{
emit('changePlay', unref(dataInfo)?.id)
if(talkVoiceObj.value && talkVoiceObj.value.src) {
talkVoiceObj.value.play()
return
}else{
talkVoiceObj.value.src = ''
if(unref(dataInfo).talkingVoice){
setTimeout(()=>{
talkVoiceObj.value.src = unref(dataInfo).talkingVoice
talkVoiceObj.value.play()
isPlaying.value = true
},100)
}
}
}
const initVoice = () => {
talkVoiceObj.value = uni.createInnerAudioContext()
talkVoiceObj.value.onEnded(()=>{
emit('changePlay', '')
talkVoiceObj.value.src = ''
})
talkVoiceObj.value.onError(()=>{
talkVoiceObj.value.src = ''
})
if(unref(dataInfo).intrctWay === '02'){
itemVoice.value = unref(dataInfo).talkingVoice
talkVoiceObj.value.src = itemVoice.value
setTimeout(()=>{
setVoiceTime(0)
},100)
}
}
const setVoiceTime = (sum) => {
sum += 1
if(Math.ceil(talkVoiceObj.value.duration)>0){
voiceTime.value = (Math.ceil(talkVoiceObj.value.duration) || 1 ) + 's'
}else{
if(sum === 20) {
voiceTime.value = 0 + 's'
return
}
setTimeout(()=>{
setVoiceTime(sum)
},200)
}
}
</script>
<style scoped lang="scss">
.talking-info{
padding: 20rpx;
border-radius: 15rpx;
background-color: #06f;
padding-bottom: 30rpx;
position: relative;
color: #fff;
width: 100%;
float: right;
.talking-gif{
width: 200rpx;
height: 38rpx;
background: url(@/static/images/course/voice-gray.png) no-repeat;
background-size: contain;
padding-left: 168rpx;
&.is-play{
background: url(@/static/images/course/voice-white.gif) no-repeat;
background-size: contain;
}
}
.right-time{
position: absolute;
right: 20rpx;
top: 20rpx;
font-size: 28rpx;
height: 38rpx;
line-height: 38rpx;
}
&.talking-info-loading{
padding-bottom: 20rpx;
}
&.talking-info-text{
padding-bottom: 20rpx;
width: auto;
float: right;
.talking-cont{
margin-top: 0rpx;
}
}
.talking-cont{
margin-top: 16rpx;
overflow: hidden;
.talking-text{
font-size: 28rpx;
line-height: 48rpx;
margin-bottom: 20rpx;
word-break: break-all;
white-space: pre-line;
overflow: hidden;
&.translate-text{
min-height: 48rpx;
}
}
}
.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;
}
.text-btn{
float: left;
width: 46rpx;
height: 46rpx;
overflow: hidden;
line-height: 46rpx;
text-align: center;
color: #fff;
margin-right: 16rpx;
margin-top: 25rpx;
background-color: #67A4ff;
font-size: 24rpx;
font-weight: bold;
border-radius: 20rpx;
}
.text-right-btn{
float: right;
padding: 0 10rpx;
height: 46rpx;
overflow: hidden;
line-height: 46rpx;
text-align: center;
margin-left: 16rpx;
margin-top: 25rpx;
}
.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>
@@ -0,0 +1,81 @@
<template>
<view class="talking-item">
<questionVue v-if="talkingType === 'question'" @changePlay="changePlay" :dataInfo="talkingInfo" :isLast="isLast"
:activeVoiceId="activeVoiceId"></questionVue>
<answerVue v-else-if="talkingType === 'answer'" :dataInfo="props.talkingInfo" :isLast="isLast"
:isLoading="!!props.talkingInfo.isLoading" :isAnswering="props.isAnswering" @handleEvents="handleEvents">
</answerVue>
<view v-else></view>
</view>
</template>
<script setup>
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
},
activeVoiceId: {
default: '',
type: String
},
isAnswering: {
default: false,
type: Boolean
},
isLast: {
default: false,
type: Boolean
},
})
watch(() => props.activeVoiceId, () => {}, {
deep: true,
immediate: true
})
watch(() => props.talkingInfo, () => {}, {
deep: true,
immediate: true
})
const {
talkingType
} = toRefs(props)
const handleEvents = (type, info = {}) => {
emit('handleEvents', {
type,
data: props.talkingInfo,
id: "",
info
})
}
const changePlay = (id) => {
emit('handleEvents', {
type: 'changePlay',
data: props.talkingInfo,
id,
info: {}
})
}
</script>
<style scoped lang="scss">
.talking-item {
overflow: hidden;
margin-top: 0rpx;
&+.talking-item {
margin-top: 30rpx;
}
}
</style>
File diff suppressed because it is too large Load Diff
+1
View File
@@ -115,6 +115,7 @@
:border="false"
@click="common.navigateTo('/pages/preview/index')"
></uv-cell>
<!-- <uv-cell title="问答演示" :isLink="true" @click="common.navigateTo('/pages/example/dialog')"></uv-cell> -->
</uv-cell-group>
</view>
<view class="cell_div">
+3 -1
View File
@@ -73,6 +73,7 @@
import type_02 from '@/static/images/messageNotification/type_02.png';
import type_03 from '@/static/images/messageNotification/type_03.png';
import type_04 from '@/static/images/messageNotification/type_04.png';
import type_05 from '@/static/images/messageNotification/type_05.png';
import useZpaging from '@/composables/useZpaging.js';
const { pagingRef, queryList, data_list, reload } = useZpaging({
fetchFunction: queryTraPersonalMessageNoticePaging
@@ -81,7 +82,8 @@
'01': type_01,
'02': type_02,
'03': type_03,
'04': type_04
'04': type_04,
'05': type_05
};
const readLoading = ref(false);
const offset = ref(0);
+11 -1
View File
@@ -43,6 +43,16 @@
"titleNView": false
}
}
}
},
{
"path": "pages/example/dialog",
"style": {
"navigationBarTitleText": "AI问答演示",
"app-plus": {
"titleNView": false,
"animationType": "fade-in"
}
}
},
]
}
+1 -1
View File
@@ -23,7 +23,7 @@ export const useStorageStore = defineStore('storageStore',{
}
},
actions:{
setTouchBtnZIndex(index = 1){
setTouchBtnZIndex(index = 12){
this.touchBtnZIndex = index
},
setStorage(type, id, url) {