Merge branch 'DEV-20251115' into develop

This commit is contained in:
杨航
2025-11-17 08:45:33 +08:00
11 changed files with 1779 additions and 1718 deletions
+3 -2
View File
@@ -19,13 +19,14 @@ VITE_APP_BASE_API_Url = 'https://aitstest.jlbank.com.cn:7001'
# DEV
# app入口
#VITE_APP_BASE_API_Url = 'http://25.18.122.65:7001'
# VITE_APP_BASE_API_Url = 'http://25.16.122.91:9786'
# VITE_APP_BASE_API_Url = 'http://25.18.122.66:9786'
# h5专用地址x2
# 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.91:9786'
# VITE_APP_BASE_H5_API_Url = 'http://25.18.122.78:9786'
# VITE_APP_BASE_H5_API_Url_TRAAPP = 'http://192.168.247.200'
# VITE_APP_BASE_H5_API_Url_TRASTUDY = 'http://25.64.32.154:9602'
+15
View File
@@ -169,6 +169,21 @@ export function downloadVoiceFile(url) {
});
});
}
// 获取音频 通用参数
export function downloadVoiceParams(url) {
const token = getToken();
const header = {
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
};
if (token)
header['summary'] = token
let baseUrl = get_base_url(url);
return {
url: `${baseUrl}${url}`,
header,
timeout: 180000,
}
}
export function getAudioByText(data) {
+73 -13
View File
@@ -220,6 +220,7 @@
import VideoPreview from '@/components/image-preview/video-preview.vue';
import {
downloadVoiceFile,
downloadVoiceParams,
uploadVoiceFile,
getAudioByText
} from '@/api/common.js';
@@ -238,7 +239,8 @@
useStorageStore
} from '@/store/storage.js';
import {
onUnload
onUnload,
onHide
} from '@dcloudio/uni-app';
import Subject from '@/components/examination/subject.vue';
@@ -246,6 +248,7 @@
const storageStore = useStorageStore();
const audioCacheObj = computed(() => storageStore.audioObj);
const isGettingId = computed(() => storageStore.getGettingVoiceId);
const props = defineProps({
type: {
default: 0, // 1 userAnswer 0: AIAnswer
@@ -434,12 +437,25 @@
initVoice();
});
onUnload(() => {
pauseVoice();
// pauseVoice();
console.log('onUnload')
talkVoiceObj.value?.destroy()
if(downloadFnc.value && downloadFnc.value.abord) downloadFnc.value?.abord()
downloadFnc.value = null
});
onHide(() => {
console.log('onHide')
voiceLoading.value = false;
if(downloadFnc.value && downloadFnc.value.abord) downloadFnc.value?.abord()
downloadFnc.value = null
});
let prevTime = -1;
let pauseTime = -1;
const startPlay = ref(false)
const voiceIsPlaying = ref(false)
talkVoiceObj.value.onEnded(() => {
console.log('onEnded');
voiceIsPlaying.value = false
if(props.activeVoiceId){
emit('changePlay', '');
}
@@ -449,6 +465,8 @@
});
talkVoiceObj.value.onStop(() => {
console.log('onStop');
voiceLoading.value = false;
voiceIsPlaying.value = false
if(props.activeVoiceId){
emit('changePlay', '');
}
@@ -457,13 +475,22 @@
talkVoiceObj.value.src = '';
});
talkVoiceObj.value.onPause(() => {
voiceIsPlaying.value = false
voiceLoading.value = false;
prevTime = talkVoiceObj.value.currentTime;
pauseTime = prevTime
if(downloadFnc.value && downloadFnc.value.abord) downloadFnc.value?.abord()
downloadFnc.value = null
});
talkVoiceObj.value.onPlay(() => {
console.log('onPlay');
prevTime = talkVoiceObj.value.currentTime;
pauseTime = prevTime
startPlay.value = true
voiceIsPlaying.value = true
setTimeout(()=> {
startPlay.value= false
},300)
emit('changePlay', unref(dataInfo)?.id);
});
@@ -475,7 +502,7 @@
prevTime = talkVoiceObj.value.currentTime;
}
} else {
if (props.isPlaying && (talkVoiceObj.value.currentTime > 0) && props.activeVoiceId) {
if (props.isPlaying && (talkVoiceObj.value.currentTime > 0) && props.activeVoiceId && !startPlay.value) {
console.log(pauseTime , prevTime)
emit('changePlay', '');
}
@@ -491,7 +518,7 @@
// emit('changePlay', '');
});
const pauseVoice = () => {
console.log('手动')
voiceLoading.value = false;
if (!talkVoiceObj.value) return;
talkVoiceObj.value.pause();
emit('changePlay', '');
@@ -510,7 +537,10 @@
playVoice();
}
};
const downloadFnc = ref(null)
const playVoice = (readType = 'pgth') => {
console.log('playVoice11111111')
if(voiceIsPlaying.value) return
let _content = showContent.value;
if (!_content) {
common.msg('没有可以播放的文字信息!');
@@ -518,15 +548,16 @@
}
setTimeout(() => {
emit('changePlay', unref(dataInfo)?.id);
console.log(1)
// readType 阅读内容类型pgrh段落/qns问题
if (talkVoiceObj.value.src && talkVoiceObj.value.src === itemVoice.value) {
if(voiceIsPlaying.value) return
talkVoiceObj.value.play();
return;
}
talkVoiceObj.value.src = '';
if (itemVoice.value) {
setTimeout(() => {
if(voiceIsPlaying.value) return
talkVoiceObj.value.src = itemVoice.value;
talkVoiceObj.value.play();
}, 100);
@@ -578,24 +609,53 @@
if (url) {
voiceLoading.value = false;
itemVoice.value = url;
if(voiceIsPlaying.value) return
talkVoiceObj.value.src = url;
talkVoiceObj.value.play();
} else {
downloadVoiceFile(_url)
.then((res) => {
storageStore.setGettingVoiceId(unref(dataInfo)?.id);
let _paramsObj = downloadVoiceParams(_url)
downloadFnc.value = uni.downloadFile({
..._paramsObj,
success(res) {
if (res?.data) {
common.msg(res.data.message);
return;
}
voiceLoading.value = false;
itemVoice.value = res.tempFilePath;
if(voiceIsPlaying.value) return
talkVoiceObj.value.src = res.tempFilePath;
talkVoiceObj.value.play();
storageStore.setStorage('audio', _url + _params.teachNo, itemVoice.value);
})
.catch((err) => {
voiceLoading.value = false;
});
// 判斷是否在當前頁面
if(unref(isGettingId) === unref(dataInfo)?.id){
talkVoiceObj.value.play();
storageStore.setStorage('audio', _url + _params.teachNo, itemVoice.value);
}
},
complete() {
voiceLoading.value = false;
downloadFnc.value = null
},
});
// downloadVoiceFile(_url)
// .then((res) => {
// if (res?.data) {
// common.msg(res.data.message);
// return;
// }
// voiceLoading.value = false;
// if()
// itemVoice.value = res.tempFilePath;
// talkVoiceObj.value.src = res.tempFilePath;
// // 判斷是否在當前頁面
// if(unref(isGettingId) === unref(dataInfo)?.id){
// talkVoiceObj.value.play();
// storageStore.setStorage('audio', _url + _params.teachNo, itemVoice.value);
// }
// })
// .catch((err) => {
// voiceLoading.value = false;
// });
}
});
+99 -93
View File
@@ -1,54 +1,24 @@
<template>
<view class="talk-btns" ref="talkBtnRef">
<view
class="touch-btn"
@touchstart="getRecordPermission"
@touchend="hideOverlayTalking"
@touchmove="handleMove"
v-if="!keyboardIsShow"
>
<view class="touch-btn" @touchstart="getRecordPermission" @touchend="hideOverlayTalking" @touchmove="handleMove"
v-if="!keyboardIsShow">
按住说话
</view>
<view class="touch-btn" v-if="keyboardIsShow">
<uv-input
class="input-box"
ref="inputRef"
v-model="answerValue"
:maxlength="props.textValueLength"
:adjustPosition="adjustPosition"
placeholderStyle="color:#999999;font-size:26rpx"
placeholder="请输入文字"
></uv-input>
<uv-input class="input-box" ref="inputRef" v-model="answerValue" :maxlength="props.textValueLength"
:adjustPosition="adjustPosition" placeholderStyle="color:#999999;font-size:26rpx"
placeholder="请输入文字"></uv-input>
</view>
<view class="talk-btn-box" v-show="!onlyVoice">
<image
class="talk-btn-icon"
v-show="!keyboardIsShow"
src="/src/static/images/course/jianpan.png"
mode=""
@click="changeBtn"
></image>
<image
class="talk-btn-icon"
v-show="keyboardIsShow && !answerValue"
src="/src/static/images/course/record.png"
mode=""
@click="changeBtn"
></image>
<image
class="talk-btn-icon"
v-show="keyboardIsShow && !!answerValue"
src="/src/static/images/course/send.png"
mode=""
@click.stop="showKeyboard"
></image>
<image class="talk-btn-icon" v-show="!keyboardIsShow" src="/src/static/images/course/jianpan.png" mode=""
@click="changeBtn"></image>
<image class="talk-btn-icon" v-show="keyboardIsShow && !answerValue" src="/src/static/images/course/record.png"
mode="" @click="changeBtn"></image>
<image class="talk-btn-icon" v-show="keyboardIsShow && !!answerValue" src="/src/static/images/course/send.png"
mode="" @click.stop="showKeyboard"></image>
</view>
<uni-popup
ref="talkModelRef"
type="bottom"
@click="showTalkingModel = false"
mask-background-color="rgba(0,0,0,0.9)"
>
<uni-popup ref="talkModelRef" type="bottom" @click="showTalkingModel = false"
mask-background-color="rgba(0,0,0,0.9)">
<view class="overlay-box">
<view class="circle-bg-box">
<view class="icon-box">
@@ -72,11 +42,25 @@
</template>
<script setup>
import { toRefs, ref, computed, unref, onMounted, onUnmounted, nextTick, watch, getCurrentInstance } from 'vue';
import {
toRefs,
ref,
computed,
unref,
onMounted,
onUnmounted,
nextTick,
watch,
getCurrentInstance
} from 'vue';
import common from '../../../common/common';
import permission from '@/common/permission.js';
import { getPhoneEnvBool } from '@/common/common.js';
import { useStorageStore } from '@/store/storage.js';
import {
getPhoneEnvBool
} from '@/common/common.js';
import {
useStorageStore
} from '@/store/storage.js';
const storageStore = useStorageStore();
const proxy = getCurrentInstance();
@@ -115,11 +99,16 @@
type: Boolean
},
formatType: {
default: 'mp3',// 生成语音文件类型
default: 'mp3', // 生成语音文件类型
type: String
},
});
const { isDisabled, onlyVoice, adjustPosition, formatType } = toRefs(props);
const {
isDisabled,
onlyVoice,
adjustPosition,
formatType
} = toRefs(props);
const isLoadingState = computed(() => props.isLoading);
const showTalkingModel = ref(false);
@@ -134,24 +123,21 @@
);
watch(
() => props.isLoading,
() => {},
{
() => {}, {
deep: true,
immediate: true
}
);
watch(
() => props.canAnswer,
() => {},
{
() => {}, {
deep: true,
immediate: true
}
);
watch(
() => props.onlyVoice,
() => {},
{
() => {}, {
deep: true,
immediate: true
}
@@ -162,8 +148,7 @@
if (val) {
keyboardIsShow.value = false;
}
},
{
}, {
deep: true,
immediate: true
}
@@ -265,39 +250,7 @@
// #endif
onMounted(async () => {
initRecord?.();
//#ifdef APP-PLUS
// 安卓端
if (IsAndEnv) {
let _recordPerm = await permission.requestAndroidPermission('android.permission.RECORD_AUDIO');
if (_recordPerm !== 1) {
common.show('提示信息', '当前页面需要使用的录音权限,是否前往开启?', true, '取消', '确定').then((res) => {
if (res) {
// 去开启权限
permission.gotoAppPermissionSetting();
} else {
// 取消
common.navigateBack();
}
});
}
} else {
let _recordPerm = uni.getAppAuthorizeSetting();
let _state = _recordPerm.microphoneAuthorized;
if (_state === 'denied') {
common.show('提示信息', '当前页面需要使用的录音权限,是否前往开启?', true, '取消', '确定').then((res) => {
if (res) {
// 去开启权限
uni.openAppAuthorizeSetting();
} else {
// 取消
common.navigateBack();
}
});
} else if (_state === 'not determined') {
// startRecord()
}
}
// #endif
});
// 录音结束
@@ -317,6 +270,7 @@
});
const recNum = ref(1);
const activeNum = ref(1);
const hasPermission = ref(false)
const getRecordPermission = (obj) => {
if (isDisabled.value) return;
@@ -327,8 +281,59 @@
}, 500);
return;
}
showOverlayTalking(obj);
if(!hasPermission.value){
let _res = getPermission()
if (_res) {
hasPermission.value = true
showOverlayTalking(obj);
}
}else{
showOverlayTalking(obj);
}
};
const getPermission = async () => {
//#ifdef APP-PLUS
// 安卓端
if (IsAndEnv) {
let _recordPerm = await permission.requestAndroidPermission('android.permission.RECORD_AUDIO');
if (_recordPerm !== 1) {
common.show('提示信息', '当前页面需要使用的录音权限,是否前往开启?', true, '取消', '确定').then((res) => {
if (res) {
// 去开启权限
permission.gotoAppPermissionSetting();
} else {
// 取消
common.navigateBack();
}
});
return false
} else {
return true
}
} else {
let _recordPerm = uni.getAppAuthorizeSetting();
let _state = _recordPerm.microphoneAuthorized;
if (_state === 'denied') {
common.show('提示信息', '当前页面需要使用的录音权限,是否前往开启?', true, '取消', '确定').then((res) => {
if (res) {
// 去开启权限
uni.openAppAuthorizeSetting();
} else {
// 取消
common.navigateBack();
}
});
return false
} else if (_state === 'not determined') {
// startRecord()
return true
}
}
// #endif
//#ifndef APP-PLUS
return true
// #endif
}
const isMoving = ref(false);
const isTalking = ref(false);
const showOverlayTalking = (obj) => {
@@ -366,8 +371,8 @@
startRecord();
// #endif
setTimeout(() => {
console.log(_recnum,recNum.value)
if(_recnum === recNum.value) {
console.log(_recnum, recNum.value)
if (_recnum === recNum.value) {
hideOverlayTalking(recNum.value);
}
}, 59 * 1010);
@@ -440,6 +445,7 @@
padding: 25rpx 30rpx 0;
position: relative;
z-index: v-bind(btnZindex);
.touch-btn {
height: 92rpx;
box-shadow: 0 8rpx 20rpx 0 rgba(0, 0, 0, 0.06);
@@ -621,4 +627,4 @@
::v-deep .uv-input__content__field-wrapper__field {
padding-right: 60rpx;
}
</style>
</style>
+1556 -1585
View File
File diff suppressed because it is too large Load Diff
@@ -40,7 +40,7 @@
</view>
<view class="list_small_item">
<image src="@/static/images/courseRecord/duration.png" class="icon_img"></image>
<view class="prompt">练习时长{{ formatDuration(item_list.exrStartTm, item_list.exrEndTm) }}</view>
<view class="prompt">练习时长{{ item_list.stdtTmLenS || '--' }}</view>
</view>
</view>
<view class="fl1"></view>
@@ -61,7 +61,7 @@
import { computed, ref, reactive } from 'vue';
import common from '@/common/common';
import { queryPracticeHis } from '@/api/courseRecord.js';
import { useItemList, formatDuration } from '../useItemList.js';
import { useItemList } from '../useItemList.js';
const props = defineProps({
item: {
@@ -40,7 +40,7 @@
</view>
<view class="list_small_item">
<image src="@/static/images/courseRecord/duration.png" class="icon_img"></image>
<view class="prompt">学习时长{{ formatDuration(item_list.startTm, item_list.endTm) }}</view>
<view class="prompt">学习时长{{ item_list.stdtTmLenS || '--' }}</view>
</view>
</view>
<view class="fl1"></view>
@@ -61,7 +61,7 @@
import { computed } from 'vue';
import common from '@/common/common';
import { queryStdyBatchByCrsId } from '@/api/courseRecord.js';
import { useItemList, formatDuration, secondsToHours } from '../useItemList.js';
import { useItemList, secondsToHours } from '../useItemList.js';
const props = defineProps({
item: {
type: Object,
+19 -19
View File
@@ -7,25 +7,25 @@ import {
// 计算两个标准时间的时间差值
export const formatDuration = (startTm, endTm) => {
if (endTm === null || startTm === null) return "00:00:00";
// 解析时间字符串为时间戳(毫秒)
const startTime = new Date(startTm).getTime();
const endTime = new Date(endTm).getTime();
// 计算时间差(秒),确保为正数
const seconds = Math.abs(Math.floor((endTime - startTime) / 1000));
// 计算时、分、秒
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const remainingSeconds = seconds % 60;
// 补零格式化函数(统一处理所有单位)
const formatNumber = (num) => num.toString().padStart(2, '0');
// 小时、分钟、秒均保持两位数格式
return `${formatNumber(hours)}:${formatNumber(minutes)}:${formatNumber(remainingSeconds)}`;
};
// export const formatDuration = (startTm, endTm) => {
// if (endTm === null || startTm === null) return "00:00:00";
// // 解析时间字符串为时间戳(毫秒)
// const startTime = new Date(startTm).getTime();
// const endTime = new Date(endTm).getTime();
//
// // 计算时间差(秒),确保为正数
// const seconds = Math.abs(Math.floor((endTime - startTime) / 1000));
// // 计算时、分、秒
// const hours = Math.floor(seconds / 3600);
// const minutes = Math.floor((seconds % 3600) / 60);
// const remainingSeconds = seconds % 60;
//
// // 补零格式化函数(统一处理所有单位)
// const formatNumber = (num) => num.toString().padStart(2, '0');
//
// // 小时、分钟、秒均保持两位数格式
// return `${formatNumber(hours)}:${formatNumber(minutes)}:${formatNumber(remainingSeconds)}`;
// };
// 秒数转换成小时,带小数点的小时
export const secondsToHours = (seconds) => {
+1
View File
@@ -1114,6 +1114,7 @@
.talking-list {
height: calc(100% - 772rpx);
min-height: 300rpx;
padding: 12rpx 24rpx;
box-sizing: border-box;
min-height: 300rpx;
+1 -1
View File
@@ -354,7 +354,7 @@
.main_div {
display: flex;
flex-direction: column;
overflow: hidden;
// overflow: hidden;
max-height: 100vh;
}
</style>
+8 -1
View File
@@ -10,7 +10,8 @@ export const useStorageStore = defineStore('storageStore',{
showPreviewDetail: false,
fileType: 'iamge',
fileId: '',
fileSrc: ''
fileSrc: '',
gettingVoiceId: ''
}),
getters:{
imgObj(state){
@@ -37,8 +38,14 @@ export const useStorageStore = defineStore('storageStore',{
getFileSrc(state){
return state.fileSrc
},
getGettingVoiceId(state){
return state.gettingVoiceId
},
},
actions:{
setGettingVoiceId(id){
return this.gettingVoiceId = id
},
setShowPreviewDetail({ status = false, fileType = 'image', fileId = '', fileSrc = '' }){
this.showPreviewDetail = status
this.fileId = fileId