合并冲突

This commit is contained in:
杨航
2025-08-28 09:53:24 +08:00
13 changed files with 219 additions and 146 deletions
+1
View File
@@ -38,3 +38,4 @@ VITE_APP_BASE_H5_API_Url_TRASTUDY = 'http://25.64.16.130:9602'
# VITE_APP_BASE_H5_API_Url_TRAEXAM = 'http://25.64.16.143:9604'
# 孙宇
# VITE_APP_BASE_H5_API_Url_TRAEXAM = 'http://25.64.16.139:9604'
+12
View File
@@ -0,0 +1,12 @@
import request from '@/api/request'
const base_url = '/trastudy'
export const queryTraStdyInfoPreviewPaging = (data) => {
return request({
url: base_url + '/traStdyInfoPreview/queryTraStdyInfoPreviewPaging',
method: 'post',
toastErrors: true,
data
});
};
+21 -4
View File
@@ -102,6 +102,7 @@
type: String
}
});
const audioStore = useAudioStore();
const { dataInfo } = toRefs(props);
const status = computed(() => props.status);
@@ -146,11 +147,15 @@
};
const playVoice = () => {
console.log('播放音频', props.dataInfo.voicePath);
isPlaying.value = true;
audioStore.playAudio(props.dataInfo.voicePath);
// isPlaying.value = true;
console.log('点击播放');
audioStore.playAudio();
};
onMounted(() => {
// 2. 监听 Store 中 isPlaying 的变化,实时同步
const unsubscribe = audioStore.$subscribe((mutation, state) => {
isPlaying.value = state.isPlaying;
});
// 注册播放完成回调
audioStore.onAudioEnded(() => {
console.log('播放完成(通过回调)');
@@ -158,17 +163,29 @@
isPlaying.value = false;
}, 60);
});
// 注册播放错误回调
audioStore.onAudioError((error) => {
console.error('播放错误(通过回调):', error);
});
// 声音加载完毕
audioStore.onAudioLoaded((e) => {
voiceTime.value = (Math.ceil(e.duration) || 1) + 's';
});
watch(
() => props.dataInfo.voicePath,
(newVal) => {
audioStore.setAudioSrc(newVal);
},
{ deep: true, immediate: true }
);
});
onUnload(() => {
// 1. 停止当前播放(优先处理,避免音频继续播放)
audioStore.stopAudio();
// 2. 移除当前组件注册的回调(避免内存泄漏)
audioStore.removeCallbacks();
unsubscribe();
});
</script>
+3 -3
View File
@@ -25,7 +25,7 @@
{{ subject_data.qnsContent }}
</view>
<view :style="props.mode==='mistake'?animationStyle:{}" class="collapse-wrapper" ref="wrapperRef">
<view :style="props.mode === 'mistake' ? animationStyle : {}" class="collapse-wrapper" ref="wrapperRef">
<view class="collapse-content" ref="contentRef">
<view class="option_div">
<template v-if="qnsTyp === 'ES'">
@@ -96,7 +96,7 @@
const emit = defineEmits(['subject_click', 'update:modelValue']);
const props = defineProps({
modelValue: {
type: Array,
type: [Array, Object],
default: () => []
},
item: {
@@ -147,7 +147,7 @@
let answerText = props.item?.my_answer || props.item.anserResult || [];
// item_answer 是答题结果,可以是对象 数组 字符串类型
const item_answer = ref(typeof answerText === 'string' ? answerText.split(',') : answerText);
const feedback_click = () => {
emit('subject_click', 'feedback', props.item);
};
+49 -33
View File
@@ -48,10 +48,7 @@
:clearResult="true"
v-model="item.my_answer"
></Subject>
<!-- <CharactersSubject :item="item" v-if="item.qnsTyp === 'ES'" :mode="mode"
:activeVoiceId="activeVoiceId" :voicePathValue="voicePathValue"
@subject_click="subject_click">
</CharactersSubject> -->
{{ item.my_answer }}
</scroll-view>
</view>
</swiper-item>
@@ -89,13 +86,12 @@
<view class="touch-btn-div" :class="{ show: showTouchBtn }">
<TouchBtn
class="talk-btns"
ref="touchBtnRef"
@submit="touchBtnSubmit"
loadingText="问题回答中,请在问题回答结束后重试!"
:isLoading="!answerIsOver"
:isDisabled="touchIsDisabled"
/>
</view>
</view>
@@ -114,7 +110,7 @@
:class="{
current: index === questionNumber,
mark: markList.includes(item.qnsId),
completed: completedList.includes(item.qnsId)
completed: completedListStatus[index]
}"
@click="clikc_sheet_item(index)"
>
@@ -161,10 +157,12 @@
const feedbackRef = ref(null);
const dialogRef = ref(null);
const popupRef = ref(null);
const touchBtnRef = ref(null);
let examinationStartTime = 0; // 考试开始时间
const mode = ref('examination'); // 考试 examination 练习practice
const paperData = reactive({
crsId: '', // 课程ID,通过课程id是否为空字符串判断这个试卷是课程考试还是考试中心考试
execId: '', // 执行ID
examId: '', // 考试ID
papersId: '', // 试卷ID
@@ -175,7 +173,7 @@
const popup_input_bottom = ref('0px');
const questionNumber = ref(0); // 题号
const systemInfo = uni.getSystemInfoSync();
const topic = computed({
get: () => topicList[questionNumber.value],
set: (val) => (topicList[questionNumber.value] = val)
@@ -183,9 +181,21 @@
const showTouchBtn = computed(() => {
return topicList[questionNumber.value]['qnsTyp'] === 'ES';
});
const completedList = computed(() => {
return [];
// 答题卡判断每道题的状态
const completedListStatus = computed(() => {
return topicList.map((res) => {
// 问答题
if (res.qnsTyp === 'ES') {
return !!res.my_answer.anserResult;
} else if (typeof res.my_answer === 'string') {
return res.my_answer !== '';
} else if (Array.isArray(res.my_answer)) {
return res.my_answer.length !== 0;
}
});
});
// 下面语音组件的禁用状态判断
const touchIsDisabled = computed(() => {
return topic.value?.es_status !== '' && topic.value?.es_status !== undefined;
@@ -244,32 +254,36 @@
// 录音按钮提交
const touchBtnSubmit = (type, submitObj) => {
const old_text = touchBtnRef.value?.clearinputText(); // 清除发送框数据
if (type === 'voice') {
uploadRecordNow(submitObj);
topic.value.es_status = '00'; // 00转圈
const voicePath = submitObj;
commonUploadVoiceFile(voicePath, '/traask/traAskchat/voiceTrans', {})
.then((res) => {
let _res = JSON.parse(res.data);
if (_res.rtnCode === '0000') {
topic.value.es_status = '02';
topic.value.my_answer = {
anserResult: _res.body.transContent,
ossKey: _res.body.fileId,
ossAddr: _res.body.ossFileAddr,
voicePath: voicePath
};
}
})
.catch((err) => {
topic.value.es_status = ''; // 取消转圈
// 失败了再把发送框数据还原回去
touchBtnRef.value?.clearinputText(old_text); // 清除发送框数据
common.msg('系统异常,请联系管理员');
});
} else if (type === 'text') {
topic.value.es_status = '01';
topic.value.anserResult = submitObj; // 正确答案文字内容
// submitProblemEssayQuestion({ anserResult: submitObj });
}
};
// 发送语音
const uploadRecordNow = (voicePath) => {
commonUploadVoiceFile(voicePath, '/traask/traAskchat/voiceTrans', {})
.then((res) => {
let _res = JSON.parse(res.data);
voicePathValue.value = voicePath;
console.log('_res', _res, voicePath);
if (_res.rtnCode === '0000') {
}
})
.catch((err) => {
uni.showToast({
title: '系统异常,请联系管理员',
icon: 'none',
duration: 1000
});
});
};
// 发送文本
const submitAnswerNow = (val, cb = null) => {};
// 初始化时检查
onLoad(() => {
const examination = getPageCache('examination');
@@ -279,7 +293,8 @@
}
// 记录起始时间
examinationStartTime = new Date().getTime();
paperData.crsId = examination.crsId;
paperData.execId = examination.execId;
paperData.examId = examination.examId;
paperData.papersId = examination.papersId;
@@ -433,8 +448,9 @@
if (res.body.flagQuery === 'Y') {
// 需要查分
const examLenTm = 678;
common.redirectTo(
`/pages/examination/result?execId=${res.body.execId}&examLenTm=${examLenTm}&papersName=${paperData.papersName}`
`/pages/examination/result?execId=${res.body.execId}&examLenTm=${examLenTm}&papersName=${paperData.papersName}&papersId=${paperData.papersId}&mode=${mode.value}&crsId=${paperData.crsId}`
);
} else {
// todo 此处应该有个弹窗交代下结果
+73 -17
View File
@@ -81,7 +81,7 @@
时长{{ result.examLenTm }}
</view>
<view class="fl1"></view>
<view class="examination">
<view class="examination" @click="goExamRanking">
<view>考试排行榜</view>
<view class="back-icon"></view>
</view>
@@ -125,7 +125,7 @@
</template>
<script setup>
import { onLoad } from '@dcloudio/uni-app';
import { onLoad, onUnload } from '@dcloudio/uni-app';
import { ref, reactive, computed, onMounted, nextTick, getCurrentInstance } from 'vue';
import { queryPaperExamScore } from '@/api/examination.js';
import Subject from '@/components/examination/subject.vue';
@@ -133,12 +133,15 @@
const pass_status = ref(0);
const sheet_swiper_index = ref(0);
const result = reactive({
crsId: '',
execId: '',
papersId: '',
mode: '',
papersName: '',
examLenTm: '',
examGrade: ''
});
const sheet_swiper_change = (event) => {
sheet_swiper_index.value = event.detail.current;
};
@@ -167,6 +170,16 @@
const scroll = (event) => {
};
// 跳转到去考试页面
const goExamRanking = () => {
// 首先判断是课程跳入还是考试中心跳入
if(result.crsId !== '') {
common.navigateTo('/pages/examRanking/index?crsId=' + result.crsId)
} else {
common.navigateTo('/pages/examRanking/index?papersId=' + result.papersId)
}
}
const getStatusClass = (index) => {
const remainder = index % 3;
@@ -177,30 +190,73 @@
const animationfinish = (event) => {
console.log('event', event);
};
// 定义请求计数器和定时器引用
const requestCount = ref(0);
const timerId = ref(null);
const get_result = () => {
setTimeout(() => {
queryPaperExamScore({
execId: result.execId
}).then(({ body }) => {
console.log('分数返回', body);
if (body.isWait === 'Y') {
get_result();
} else if (body.isWait === 'N') {
pass_status.value = body.examResult === 'P' ? 1 : -1; // p通过
result.examGrade = body.examGrade;
topicList.push(...body.qnsInfoVo);
}
});
}, 1000);
// 重置计数器(首次调用时)
if (requestCount.value === 0) {
requestCount.value = 0;
}
// 检查是否已达到最大请求次数
if (requestCount.value >= 15) {
console.log('已达到最大请求次数(15次),停止请求');
return;
}
timerId.value = setTimeout(() => {
// 每次请求前计数器+1
requestCount.value++;
queryPaperExamScore({
execId: result.execId
}).then(({ body }) => {
console.log(`${requestCount.value}次请求返回`, body);
if (body.isWait === 'Y') {
// 未完成且未达上限,继续请求
if (requestCount.value < 15) {
get_result();
} else {
console.log('已达到最大请求次数,停止等待');
}
} else if (body.isWait === 'N') {
// 完成请求,处理结果
pass_status.value = body.examResult === 'P' ? 1 : -1;
result.examGrade = body.examGrade;
topicList.push(...body.qnsInfoVo);
}
}).catch(err => {
console.error('请求失败', err);
// 请求失败也计入次数,避免无限重试
if (requestCount.value < 15) {
get_result();
}
});
}, 2000);
};
// 初始化时检查
onLoad((e) => {
result.crsId = e.crsId;
result.execId = e.execId;
result.papersName = e.papersName;
result.examLenTm = e.examLenTm;
result.mode = e.mode;
result.papersId = e.papersId;
get_result();
});
// 页面卸载时清除定时器,终止请求
onUnload(() => {
if (timerId.value) {
clearTimeout(timerId.value);
timerId.value = null;
}
});
</script>
<style scoped lang="scss">
+1
View File
@@ -113,6 +113,7 @@
get: () => topicList[questionNumber.value],
set: (val) => (topicList[questionNumber.value] = val)
});
const showTouchBtn = computed(() => {
return topic.value?.qnsTyp === 'ES';
});
@@ -3,108 +3,79 @@
<scroll-view scroll-y="true" class="scroll-Y" @scrolltolower="scrolltolower" :show-scrollbar="false">
<view class="item" v-for="item in data_list">
<view class="img_div">
<image class="img" src="@/static/images/test/4.png" mode="scaleToFill"></image>
<image-preview class="img" :imgId="item.imgId" mode="scaleToFill"></image-preview>
</view>
<view class="right_div">
<view class="title ellipsis-text">
对公小程序开户操作流对公小程序开户操作流程
</view>
<view class="title ellipsis-text">{{ item.crsName }}</view>
<view class="tag_div">
<view class="tag">
公金业务
</view>
<view class="tag">
中级
</view>
<view class="tag">
中级
</view>
<view class="tag">
中级
</view>
<view class="tag">
中级
</view>
<view class="tag">
中级
</view>
<view class="tag" v-for="tag in item.tagCataList">{{ tag.tagName }}</view>
</view>
<view class="time_and_num"></view>
</view>
</view>
<list-no-data v-if="total == 0 && status == 'nomore'"></list-no-data>
<uv-load-more v-if="total != 0" :status="status" loadmore-text="轻轻上拉加载" :height="30"></uv-load-more>
</scroll-view>
</view>
</template>
<script setup>
const status = ref('loading') // loadmore - loading - nomore -
const limit = 15
const total = ref(-1)
const page = ref(0)
const data_list = reactive([])
import {
ref,
reactive,
onMounted,
nextTick
} from 'vue'
import {
queryTraCrsBbsInfoByCrsId
} from '@/api/courseDetail.js';
const status = ref('loading'); // loadmore - loading - nomore -
const limit = 15;
const total = ref(-1);
const page = ref(0);
const data_list = reactive([]);
import { ref, reactive, onMounted, nextTick } from 'vue';
import { queryTraStdyInfoPreviewPaging } from '@/api/preview.js';
const getData = (from = '') => {
if (from == 'first') {
if (total.value !== -1) {
return
return;
}
status.value = 'loading'
total.value = -1
page.value = 1
data_list.length = 0
status.value = 'loading';
total.value = -1;
page.value = 1;
data_list.length = 0;
} else {
if (status.value !== 'loadmore' && status.value !== '') return
status.value = 'loading'
page.value++
if (status.value !== 'loadmore' && status.value !== '') return;
status.value = 'loading';
page.value++;
}
console.log(status.value, '阿斯顿撒');
setTimeout(() => {
queryTraCrsBbsInfoByCrsId({
crsId: 'CRS0250181220722025070408313900000018729',
page: page.value,
limit: limit
}).then(res => {
total.value = res.total
data_list.push(...res.body)
}).finally(() => {
if (data_list.length >= total.value) {
status.value = 'nomore'
} else {
status.value = 'loadmore'
}
queryTraStdyInfoPreviewPaging({
page: page.value,
limit: limit
})
.then((res) => {
total.value = res.total;
data_list.push(...res.body);
})
}, 200)
}
.finally(() => {
if (data_list.length >= total.value) {
status.value = 'nomore';
} else {
status.value = 'loadmore';
}
});
};
const scrolltolower = (item) => {
getData()
getData();
};
defineExpose({
getData
})
});
</script>
<style scoped lang="scss">
.scroll_div {
background-color: #F1F5FA;
background-color: #f1f5fa;
padding: 22rpx;
}
.scroll-Y {
height: calc(100vh - var(--status-bar-height) - 54rpx - 88rpx - 20rpx - 44rpx);
background-color: #fff;
border-radius: 16rpx;
}
.scroll-Y .item {
height: 170rpx;
margin: 32rpx 22rpx;
@@ -119,7 +90,6 @@
.img {
width: 224rpx;
height: 168rpx;
}
.subscript {
@@ -135,8 +105,8 @@
display: flex;
align-items: center;
justify-content: center;
background-color: #EF5705;
color: #FFFFFF;
background-color: #ef5705;
color: #ffffff;
}
}
@@ -166,16 +136,15 @@
padding: 0 10rpx;
margin-right: 14rpx;
border-radius: 8rpx;
border: 2rpx solid #FFA150;
border: 2rpx solid #ffa150;
font-family: PingFangSC;
font-weight: 500;
font-size: 23rpx;
color: #FFA150;
color: #ffa150;
display: flex;
justify-content: center;
align-items: center;
}
}
.time_and_num {
@@ -192,4 +161,4 @@
}
}
}
</style>
</style>
+2 -2
View File
@@ -31,8 +31,8 @@
<script setup>
// 我的预览
import PreviewAiSparring from './components/preview-ai-sparring.vue'
import PreviewCourse from './components/preview-course.vue'
import PreviewAiSparring from './components/previewAiSparring.vue'
import PreviewCourse from './components/previewCourse.vue'
import {
reactive,
ref,
+4 -4
View File
@@ -4,11 +4,12 @@
scroll-y="true"
class="scroll-Y"
:refresher-enabled="false"
:show-scrollbar="false"
@scrolltolower="scrolltolower"
>
<!-- 主体 -->
<view class="content">
<itemVue :item="item" v-for="item in data_list"></itemVue>
<itemVue :item="item" v-for="item in data_list"></itemVue>
<list-no-data v-if="total == 0 && status == 'nomore'"></list-no-data>
<uv-load-more v-if="total != 0" :status="status" loadmore-text="轻轻上拉加载" :height="30"></uv-load-more>
</view>
@@ -20,13 +21,12 @@
import { queryTraExamPapersPage } from '@/api/testingHall.js';
import { queryTraCrsBbsInfoByCrsId } from '@/api/courseDetail.js';
import itemVue from './content-item.vue';
const status = ref('loadmore'); // loadmore - 加载前,loading - 加载中,nomore - 没有数据
const limit = 15;
const total = ref(-1);
const page = ref(0);
const data_list = reactive([]);
const triggered = ref(false);
const _freshing = ref(false);
const offset = ref(0);
@@ -38,7 +38,7 @@
default: 0
}
});
const onRefresh = () => {
if (_freshing.value) return;
_freshing.value = true;
+1 -1
View File
@@ -159,7 +159,7 @@
examId: detailData.examId
})
.then((res) => {
setPageCache('examination', { ...res.body, examId: detailData.examId });
setPageCache('examination', { ...res.body, examId: detailData.examId});
common.navigateTo('/pages/examination/index');
})
.finally(() => {
+8 -7
View File
@@ -68,8 +68,10 @@ export const useAudioStore = defineStore('audioStore', {
// 音频可以播放时触发(此时通常能获取到时长)
this.audioInstance.onCanplay(() => {
// 尝试获取时长(部分环境需要延迟一点)
console.log('尝试获取时长(部分环境需要延迟一点)');
setTimeout(() => {
const duration = this.audioInstance.duration || 0;
console.log('尝试获duration', duration);
if (duration > 0) {
this.duration = duration;
this.isLoaded = true;
@@ -114,12 +116,11 @@ export const useAudioStore = defineStore('audioStore', {
try {
// 先停止当前播放并清空旧地址
this.audioInstance.stop();
this.audioInstance.src = '';
// this.audioInstance.stop();
// this.audioInstance.src = '';
// 设置新地址并加载(不自动播放)
this.audioInstance.src = src;
this.audioInstance.load(); // 手动触发加载
// this.audioInstance.load(); // 手动触发加载
} catch (err) {
this.isLoading = false;
this.errorMsg = `设置音频失败: ${err.message}`;
@@ -134,11 +135,12 @@ export const useAudioStore = defineStore('audioStore', {
}
// 已在播放则暂停
console.log('已在播放则暂停', this.isPlaying);
if (this.isPlaying) {
this.pauseAudio();
return;
}
console.log('未加载完成则等待加载', this.isLoaded);
// 未加载完成则等待加载
if (!this.isLoaded) {
this.isLoading = true;
@@ -175,7 +177,7 @@ export const useAudioStore = defineStore('audioStore', {
resetAudioState() {
this.isPlaying = false;
this.isLoading = false;
this.currentSrc = '';
// this.currentSrc = '';
// 清空地址避免残留
if (this.audioInstance) {
this.audioInstance.src = '';
@@ -225,7 +227,6 @@ export const useAudioStore = defineStore('audioStore', {
this.callbacks.onLoaded = callback;
}
},
}
});