Merge branch 'dev-20250930' of http://25.13.9.101:9000/K17_AITS/tra-app into dev-20250930

This commit is contained in:
杨航
2025-08-28 17:08:54 +08:00
27 changed files with 556 additions and 623 deletions
+12 -1
View File
@@ -72,4 +72,15 @@ export const queryPaperExamRankingList = (data) => {
data data
}); });
}; };
// 反馈问题
export const saveTraQnsFeedback = (data) => {
return request({
url: base_url + '/traCrsPapers/saveTraQnsFeedback',
method: 'post',
toastErrors: true,
data
});
};
+66 -32
View File
@@ -21,7 +21,6 @@ function show(title, msg, showCancel = true, cancelText = '取消', confirmText
confirmText, confirmText,
success: (res) => { success: (res) => {
// 统一处理成功回调,解析为布尔值 // 统一处理成功回调,解析为布尔值
console.log('res.confirm', res.confirm);
resolve(!!res.confirm); resolve(!!res.confirm);
} }
}; };
@@ -65,23 +64,23 @@ const hideLoading = () => uni.hideLoading()
// 防抖工具函数 // 防抖工具函数
function debounce(fn, wait = 300) { function debounce(fn, wait = 300) {
let isLocked = false; // 锁定状态标记 let isLocked = false; // 锁定状态标记
return function(...args) { return function(...args) {
// 如果处于锁定状态,直接返回不执行 // 如果处于锁定状态,直接返回不执行
if (isLocked) { if (isLocked) {
return; return;
} }
// 执行函数 // 执行函数
fn.apply(this, args); fn.apply(this, args);
// 锁定,防止再次执行 // 锁定,防止再次执行
isLocked = true; isLocked = true;
// 等待指定时间后解锁 // 等待指定时间后解锁
setTimeout(() => { setTimeout(() => {
isLocked = false; isLocked = false;
}, wait); }, wait);
}; };
} }
// 保留当前页面,跳转到应用内的某个页面,使用uni.navigateBack可以返回到原页面。 // 保留当前页面,跳转到应用内的某个页面,使用uni.navigateBack可以返回到原页面。
@@ -174,23 +173,23 @@ export const getUserInfo = (key = '') => {
} }
// 数字转换,把传入的秒转换为HH:mm:ss格式 // 数字转换,把传入的秒转换为HH:mm:ss格式
export const formatSeconds = (seconds) => { export const formatSeconds = (seconds) => {
// 处理非数字或负数情况 // 处理非数字或负数情况
if (typeof seconds !== 'number' || isNaN(seconds) || seconds < 0) { if (typeof seconds !== 'number' || isNaN(seconds) || seconds < 0) {
return '00:00:00'; return '00:00:00';
} }
// 计算小时、分钟和剩余秒数 // 计算小时、分钟和剩余秒数
const hours = Math.floor(seconds / 3600); const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60); const minutes = Math.floor((seconds % 3600) / 60);
const remainingSeconds = Math.floor(seconds % 60); const remainingSeconds = Math.floor(seconds % 60);
// 补零函数:将数字转为两位数字符串 // 补零函数:将数字转为两位数字符串
const padZero = (num) => num.toString().padStart(2, '0'); const padZero = (num) => num.toString().padStart(2, '0');
// 拼接成00:00:00格式
return `${padZero(hours)}:${padZero(minutes)}:${padZero(remainingSeconds)}`;
};
// 拼接成00:00:00格式
return `${padZero(hours)}:${padZero(minutes)}:${padZero(remainingSeconds)}`;
};
/** /**
* 判断当前运行平台是否匹配指定标识 * 判断当前运行平台是否匹配指定标识
* @param {string} flag - 平台标识:'IOS' 表示苹果iOS平台,'AND' 表示安卓平台 * @param {string} flag - 平台标识:'IOS' 表示苹果iOS平台,'AND' 表示安卓平台
@@ -242,6 +241,41 @@ function isLogin() {
return !!getToken(); return !!getToken();
} }
// 封装键盘高度变化处理
export function setupKeyboardHeightListener(updateCallback) {
// #ifndef APP-PLUS
return () => {};
// #endif
// #ifdef APP-PLUS
// 获取系统信息
const systemInfo = uni.getSystemInfoSync();
// 计算安全区域底部高度
const safeAreaBottomHeight = systemInfo.screenHeight - systemInfo.safeArea.bottom;
// 定义处理函数
const handleKeyboardHeightChange = (res) => {
let keyboardHeight = res.height;
// 仅在iOS端补偿安全区域高度
if (systemInfo.platform === 'ios') {
keyboardHeight = Math.max(0, keyboardHeight - safeAreaBottomHeight); // 避免负数
}
// 通过回调函数更新外部的响应式变量
updateCallback(keyboardHeight);
};
// 监听键盘高度变化
uni.onKeyboardHeightChange(handleKeyboardHeightChange);
// 返回取消监听的函数,方便组件卸载时清理
return () => {
uni.offKeyboardHeightChange(handleKeyboardHeightChange);
};
// #endif
}
export default { export default {
getPageCache, getPageCache,
setPageCache, setPageCache,
+8 -18
View File
@@ -29,12 +29,10 @@
style="width: 100%; height: 100%" style="width: 100%; height: 100%"
></image> ></image>
</view> </view>
<view class="text-btn" @click.stop="translateVoice()"></view> <view class="text-btn" v-if="!textShow" @click.stop="translateVoice()"></view>
<view class="fl1"></view> <view class="fl1"></view>
<view class="btns-cont-button-text" @click.stop="buutton_click('again')">重答</view> <view class="btns-cont-button-text" @click.stop="buutton_click('again')">重答</view>
<view class="btns-cont-button-text" @click.stop="buutton_click('complete')">完成</view> <view class="btns-cont-button-text" @click.stop="buutton_click('complete')">完成</view>
<!-- <view class="icon text-btn" v-if="type === 1" @click.stop="translateVoice(dataInfo)"></view> -->
</view> </view>
</view> </view>
<!-- 01纯文字 --> <!-- 01纯文字 -->
@@ -45,20 +43,6 @@
</text> </text>
</view> </view>
<view class="btns-cont"> <view class="btns-cont">
<!-- <view class="play-btn">
<image
class="icon"
@click="playVoice"
src="@/static/images/course/play-blue.png"
style="width: 100%; height: 100%"
></image>
<image
class="icon"
@click.stop="pauseVoice"
src="@/static/images/course/stop-blue.png"
style="width: 100%; height: 100%"
></image>
</view> -->
<view class="fl1"></view> <view class="fl1"></view>
<view class="btns-cont-button-text" @click.stop="buutton_click('again')">重答</view> <view class="btns-cont-button-text" @click.stop="buutton_click('again')">重答</view>
<view class="btns-cont-button-text" @click.stop="buutton_click('complete')">完成</view> <view class="btns-cont-button-text" @click.stop="buutton_click('complete')">完成</view>
@@ -100,6 +84,10 @@
status: { status: {
default: '', default: '',
type: String type: String
},
isPreview: { // 是否是预览语音
default: false,
type: Boolean
} }
}); });
@@ -151,9 +139,10 @@
console.log('点击播放'); console.log('点击播放');
audioStore.playAudio(); audioStore.playAudio();
}; };
let unsubscribe = () =>{};
onMounted(() => { onMounted(() => {
// 2. 监听 Store 中 isPlaying 的变化,实时同步 // 2. 监听 Store 中 isPlaying 的变化,实时同步
const unsubscribe = audioStore.$subscribe((mutation, state) => { unsubscribe = audioStore.$subscribe((mutation, state) => {
isPlaying.value = state.isPlaying; isPlaying.value = state.isPlaying;
}); });
// 注册播放完成回调 // 注册播放完成回调
@@ -185,6 +174,7 @@
audioStore.stopAudio(); audioStore.stopAudio();
// 2. 移除当前组件注册的回调(避免内存泄漏) // 2. 移除当前组件注册的回调(避免内存泄漏)
audioStore.removeCallbacks(); audioStore.removeCallbacks();
unsubscribe(); unsubscribe();
}); });
</script> </script>
+6 -5
View File
@@ -9,7 +9,7 @@
<view class="mark_and_feedback_div" v-if="['examination', 'practice'].includes(props.mode)"> <view class="mark_and_feedback_div" v-if="['examination', 'practice'].includes(props.mode)">
<image <image
v-if="props.mode === 'examination'" v-if="props.mode === 'examination'"
:src="markIcon(props.item.mark ? '#0066FF' : '#ABABAB')" :src="markIcon(props.item.mark ? '#e8b531' : '#ABABAB')"
class="img" class="img"
@click="mark_click()" @click="mark_click()"
></image> ></image>
@@ -32,8 +32,9 @@
<questionVue <questionVue
:status="subject_data.es_status" :status="subject_data.es_status"
:dataInfo="props.item?.my_answer" :dataInfo="props.item?.my_answer"
:isPreview="props.isPreview"
:activeVoiceId="subject_data.qnsId" :activeVoiceId="subject_data.qnsId"
@buutton_click="questionBuuttonClick" @buutton_click="questionButtonClick"
></questionVue> ></questionVue>
</template> </template>
<template v-else> <template v-else>
@@ -212,8 +213,8 @@
set: (val) => {} set: (val) => {}
}); });
// 按钮 // 语音播放组件小按钮
const questionBuuttonClick = (type, submitObj) => { const questionButtonClick = (type, submitObj) => {
console.log('type', type, submitObj); console.log('type', type, submitObj);
// 判断点击的是什么 // 判断点击的是什么
if (type === 'complete') { if (type === 'complete') {
@@ -224,7 +225,7 @@
emit('subject_click', type, { emit('subject_click', type, {
qnsTyp: props.item.qnsTyp, qnsTyp: props.item.qnsTyp,
qnsId: props.item.qnsId, qnsId: props.item.qnsId,
answer: item_answer.value, answer: submitObj,
answerItems: [] answerItems: []
}); });
}; };
+18 -4
View File
@@ -1,12 +1,26 @@
<template> <template>
<view style="width: 100%;" class="flex-c-c"> <view style="width: 100%;" class="no-data">
<image src="@/static/images/common/default_img.png" mode="aspectFit" style="width:400rpx"></image> <view class="flex-c-c">
<image src="@/static/images/common/default_img.png" mode="aspectFit" style="width:400rpx"></image>
</view>
<view class="text">
<slot ></slot>
</view>
</view> </view>
</template> </template>
<script> <script>
</script> </script>
<style> <style scoped lang="scss">
.no-data{
display: flex;
flex-direction: column;
align-items: center;
}
.text{
color: #666666;
font-size: 30rpx;
}
</style> </style>
+139 -142
View File
@@ -1,24 +1,33 @@
<template> <template>
<view class="comment_div"> <view class="comment_div">
<view class="message_div" v-for="(item,index) in message_list"> <view class="message_div" v-for="(item, index) in message_list">
<view class="head_div"> <view class="head_div">
<image-preview v-if="item.userIconImgId" class="img-box img" :src="item.userIconImgId"></image-preview> <image-preview v-if="item.userIconImgId" class="img-box img" :src="item.userIconImgId"></image-preview>
<image v-else src="/static/images/me/default_user_avatar.png" class="img"></image> <image v-else src="/static/images/me/default_user_avatar.png" class="img"></image>
</view> </view>
<view class="msg_content"> <view class="msg_content">
<view class="title_div"> <view class="title_div">
<view class="name">{{item.userName}}</view> <view class="name">{{ item.userName }}</view>
<view class="title_tip">{{item.bbsTagDesc}}</view> <view class="title_tip">
<image class="img" v-if="item.bbsTag === 'ASK'" src="@/static/images/courseDetail/ASK.png" mode="widthFix"></image>
<image class="img" v-if="item.bbsTag === 'SUG'" src="@/static/images/courseDetail/SUG.png" mode="widthFix"></image>
</view>
<!-- <view class="title_tip">{{item.bbsTagDesc}}</view> -->
</view> </view>
<view class="title"> <view class="title">
{{item.bbsContent}} {{ item.bbsContent }}
</view> </view>
<view class="img_div pic-cell"> <view class="img_div pic-cell">
<image-preview v-for="img in item.imgIdList" @click="showPicture(item.imgIdList, img)" :src="img" <image-preview
class="pic-item" mode="scaleToFill"></image-preview> v-for="img in item.imgIdList"
@click="showPicture(item.imgIdList, img)"
:src="img"
class="pic-item"
mode="scaleToFill"
></image-preview>
</view> </view>
<view class="time_and_reply"> <view class="time_and_reply">
<view class="time">{{item.bbsTm.substr(0,16)}}</view> <view class="time">{{ item.bbsTm.substr(0, 16) }}</view>
<view class="reply" @click="click_reply(item, item.bbsId, index)">回复</view> <view class="reply" @click="click_reply(item, item.bbsId, index)">回复</view>
</view> </view>
<view class="show_close_div" v-if="item.children?.length" @click="item.show = !item.show"> <view class="show_close_div" v-if="item.children?.length" @click="item.show = !item.show">
@@ -29,7 +38,7 @@
</view> </view>
</template> </template>
<template v-else> <template v-else>
<view>展开{{item.children?.length}}条回复</view> <view>展开{{ item.children?.length }}条回复</view>
<view class="back_div"> <view class="back_div">
<view class="back-icon"></view> <view class="back-icon"></view>
</view> </view>
@@ -38,25 +47,27 @@
<view class="children" v-if="item.show"> <view class="children" v-if="item.show">
<view class="message_div" v-for="childrenItem in item.children"> <view class="message_div" v-for="childrenItem in item.children">
<view class="head_div"> <view class="head_div">
<image-preview v-if="childrenItem.userIconImgId" class="img-box img" <image-preview
:src="childrenItem.userIconImgId"></image-preview> v-if="childrenItem.userIconImgId"
class="img-box img"
:src="childrenItem.userIconImgId"
></image-preview>
<image v-else src="/static/images/me/default_user_avatar.png" class="img"></image> <image v-else src="/static/images/me/default_user_avatar.png" class="img"></image>
</view> </view>
<view class="msg_content"> <view class="msg_content">
<view class="title_div"> <view class="title_div">
<view class="name">{{childrenItem.userName}}</view> <view class="name">{{ childrenItem.userName }}</view>
<view class="arrow"></view> <view class="arrow"></view>
<view class="name">{{childrenItem.rtnUName}}</view> <view class="name">{{ childrenItem.rtnUName }}</view>
</view> </view>
<view class="title"> <view class="title">
{{childrenItem.bbsContent}} {{ childrenItem.bbsContent }}
</view>
<view class="img_div">
</view> </view>
<view class="img_div"></view>
<view class="time_and_reply"> <view class="time_and_reply">
<view class="time">{{childrenItem.bbsTm.substr(0,16)}}</view> <view class="time">{{ childrenItem.bbsTm.substr(0, 16) }}</view>
<view class="reply" @click="click_reply(childrenItem, childrenItem.parentId, index)">回复 <view class="reply" @click="click_reply(childrenItem, childrenItem.parentId, index)">
回复
</view> </view>
</view> </view>
</view> </view>
@@ -64,23 +75,26 @@
</view> </view>
</view> </view>
</view> </view>
<view class="empty_message_div" v-if="status=='nomore' && total == 0"> <view class="empty_message_div" v-if="status == 'nomore' && total == 0">
<image class="img" src="@/static/images/courseDetail/empty_message.png" mode="widthFix"></image> <image class="img" src="@/static/images/courseDetail/empty_message.png" mode="widthFix"></image>
<view class="text"> <view class="text">这里什么都没有</view>
这里什么都没有
</view>
</view> </view>
<uv-load-more v-if="total != 0" :status="status" loadmore-text="轻轻上拉加载"></uv-load-more> <uv-load-more v-if="total != 0" :status="status" loadmore-text="轻轻上拉加载"></uv-load-more>
<uni-popup ref="popup" class="popup"> <uni-popup ref="popup" class="popup">
<view class="bottom_reply_input_div" :style="{marginBottom:popup_input_bottom}"> <view class="bottom_reply_input_div" :style="{ marginBottom: popup_input_bottom }">
<uv-input v-model="reply_data.bbsContent" :adjustPosition="false" <uv-input
placeholderStyle='color:#999999;font-size:26rpx' :placeholder="reply_data.placeholder" :focus="true" v-model="reply_data.bbsContent"
:maxlength="100"> :adjustPosition="false"
</uv-input> placeholderStyle="color:#999999;font-size:26rpx"
<view class="" style="display: flex;flex-direction: column; margin:10rpx 20rpx;"> :placeholder="reply_data.placeholder"
:focus="true"
:maxlength="100"
></uv-input>
<view class="" style="display: flex; flex-direction: column; margin: 10rpx 20rpx">
<view class="fl1"></view> <view class="fl1"></view>
<uv-button class="bottom_reply_button" type="primary" :throttleTime="500" <uv-button class="bottom_reply_button" type="primary" :throttleTime="500" @click="reply_send">
@click="reply_send">回复</uv-button> 回复
</uv-button>
<view class="fl1"></view> <view class="fl1"></view>
</view> </view>
</view> </view>
@@ -89,129 +103,120 @@
</template> </template>
<script setup> <script setup>
import { import { ref, reactive, onMounted, nextTick } from 'vue';
ref, import { queryTraCrsBbsInfoByCrsId, replyTraCrsBbsInfo, qryChildTraCrsBbsInfo } from '@/api/courseDetail.js';
reactive, import common from '@/common/common.js';
onMounted, import { get_base_url } from '@/api/request.js';
nextTick
} from 'vue'
import {
queryTraCrsBbsInfoByCrsId,
replyTraCrsBbsInfo,
qryChildTraCrsBbsInfo
} from '@/api/courseDetail.js';
import common from '@/common/common.js'
import {
get_base_url
} from '@/api/request.js'
const popup = ref(null) const popup = ref(null);
const popup_input_bottom = ref('0px') const popup_input_bottom = ref('0px');
const inputValue = ref('') const inputValue = ref('');
const placeholder = ref('') const placeholder = ref('');
const message_list = reactive([]) const message_list = reactive([]);
const status = ref('') // loadmore - 加载前,loading - 加载中,nomore - 没有数据 const status = ref(''); // loadmore - 加载前,loading - 加载中,nomore - 没有数据
const limit = 15 const limit = 20;
const total = ref(-1) const total = ref(-1);
const page = ref(0) const page = ref(0);
const reply_data_init = { const reply_data_init = {
"index": null, index: null,
"parentBbsId": null, parentBbsId: null,
"bbsId": null, bbsId: null,
"bbsContent": '', bbsContent: '',
"placeholder": '', placeholder: ''
} };
const reply_data = reactive({ const reply_data = reactive({
...reply_data_init ...reply_data_init
}) });
const props = defineProps({ const props = defineProps({
crsId: String, crsId: String,
imgId: String imgId: String
}) });
const click_reply = (item, parentBbsId, index) => { const click_reply = (item, parentBbsId, index) => {
popup.value.open('bottom'); popup.value.open('bottom');
reply_data.placeholder = '回复 ' + item.userName + "" reply_data.placeholder = '回复 ' + item.userName + '';
reply_data.parentBbsId = parentBbsId reply_data.parentBbsId = parentBbsId;
reply_data.bbsId = item.bbsId reply_data.bbsId = item.bbsId;
reply_data.index = index reply_data.index = index;
const systemInfo = uni.getSystemInfoSync(); const systemInfo = uni.getSystemInfoSync();
// 安全区域底部到屏幕底部的高度(即底部安全区域高度) // 安全区域底部到屏幕底部的高度(即底部安全区域高度)
const safeAreaBottomHeight = systemInfo.screenHeight - systemInfo.safeArea.bottom; const safeAreaBottomHeight = systemInfo.screenHeight - systemInfo.safeArea.bottom;
nextTick(() => { nextTick(() => {
uni.onKeyboardHeightChange(res => { uni.onKeyboardHeightChange((res) => {
let keyboardHeight = res.height; let keyboardHeight = res.height;
// 仅在iOS端补偿安全区域高度 // 仅在iOS端补偿安全区域高度
if (systemInfo.platform === 'ios') { if (systemInfo.platform === 'ios') {
keyboardHeight = Math.max(0, keyboardHeight - safeAreaBottomHeight); // 避免负数 keyboardHeight = Math.max(0, keyboardHeight - safeAreaBottomHeight); // 避免负数
} }
popup_input_bottom.value = `${keyboardHeight}px`; popup_input_bottom.value = `${keyboardHeight}px`;
}); });
}) });
} };
const reply_send = async () => { const reply_send = async () => {
if (reply_data.bbsContent === '') { if (reply_data.bbsContent === '') {
return common.msg('回复内容不能为空') return common.msg('回复内容不能为空');
} }
try { try {
popup.value.close() popup.value.close();
common.loading('发送中...') common.loading('发送中...');
await replyTraCrsBbsInfo({ await replyTraCrsBbsInfo({
parentBbsId: reply_data.parentBbsId, parentBbsId: reply_data.parentBbsId,
bbsId: reply_data.bbsId, bbsId: reply_data.bbsId,
bbsContent: reply_data.bbsContent, bbsContent: reply_data.bbsContent,
imgIdList: [] imgIdList: []
}) });
common.msg('发送成功') common.msg('发送成功');
const res = await qryChildTraCrsBbsInfo({ const res = await qryChildTraCrsBbsInfo({
bbsId: reply_data.parentBbsId bbsId: reply_data.parentBbsId
}) });
message_list[reply_data.index]['children'] = res.body message_list[reply_data.index]['children'] = res.body;
Object.assign(reply_data, reply_data_init) Object.assign(reply_data, reply_data_init);
} catch (e) { } catch (e) {
common.msg('发送失败') common.msg('发送失败');
} finally { } finally {
common.hideLoading() common.hideLoading();
} }
} };
const getData = (from = '') => { const getData = (from = '') => {
if (from == 'submit') { if (from == 'submit') {
status.value = '' status.value = '';
total.value = -1 total.value = -1;
page.value = 1 page.value = 1;
message_list.length = 0 message_list.length = 0;
} else { } else {
if (status.value !== 'loadmore' && status.value !== '') return if (status.value !== 'loadmore' && status.value !== '') return;
status.value = 'loading' status.value = 'loading';
page.value++ page.value++;
} }
queryTraCrsBbsInfoByCrsId({ queryTraCrsBbsInfoByCrsId({
crsId: props.crsId, crsId: props.crsId,
page: page.value, page: page.value,
limit: limit limit: limit
}).then(res => {
total.value = res.total
message_list.push(...res.body)
}).finally(() => {
if (message_list.length >= total.value) {
status.value = 'nomore'
} else {
status.value = 'loadmore'
}
}) })
} .then((res) => {
total.value = res.total;
message_list.push(...res.body);
})
.finally(() => {
if (message_list.length >= total.value) {
status.value = 'nomore';
} else {
status.value = 'loadmore';
}
});
};
// 大图展示 // 大图展示
const showPicture = (urls, item) => { const showPicture = (urls, item) => {
const base_url = get_base_url() const base_url = get_base_url();
const current = urls.findIndex(res => item === res) const current = urls.findIndex((res) => item === res);
if (current === -1) { if (current === -1) {
return false return false;
} }
const urlsList = urls.map(res => base_url + res) const urlsList = urls.map((res) => base_url + res);
console.log(urlsList, current); console.log(urlsList, current);
uni.previewImage({ uni.previewImage({
current: current || 0, current: current || 0,
@@ -219,22 +224,22 @@
longPressActions: { longPressActions: {
// itemList: ['发送给朋友', '保存图片', '收藏'], // itemList: ['发送给朋友', '保存图片', '收藏'],
itemList: ['保存图片'], itemList: ['保存图片'],
success: function(data) { success: function (data) {
uni.saveImageToPhotosAlbum({ uni.saveImageToPhotosAlbum({
filePath: urlsList[data.index], filePath: urlsList[data.index],
success: function() {} success: function () {}
}); });
}, },
fail: function(err) {} fail: function (err) {}
} }
}); });
} };
defineExpose({ defineExpose({
getData getData
}) });
onMounted(() => { onMounted(() => {
getData() getData();
}) });
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@@ -252,7 +257,7 @@
width: 20vw; width: 20vw;
// height: 276rpx; // height: 276rpx;
} }
.text{ .text {
margin-top: 24rpx; margin-top: 24rpx;
font-weight: 400; font-weight: 400;
font-size: 29rpx; font-size: 29rpx;
@@ -300,14 +305,13 @@
height: 92rpx; height: 92rpx;
} }
} }
} }
// 顶部随着输入法弹出的框 // 顶部随着输入法弹出的框
.bottom_reply_input_div { .bottom_reply_input_div {
width: 100%; width: 100%;
height: 100%; height: 100%;
background-color: #FFFFFF; background-color: #ffffff;
display: flex; display: flex;
bottom: 40vh; bottom: 40vh;
align-items: center; align-items: center;
@@ -316,8 +320,8 @@
.bottom_reply_button { .bottom_reply_button {
border: none; border: none;
border-radius: 4rpx; border-radius: 4rpx;
background-color: #0066FF; background-color: #0066ff;
color: #FFFFFF; color: #ffffff;
overflow: hidden; overflow: hidden;
:deep(.uv-button) { :deep(.uv-button) {
@@ -327,13 +331,12 @@
} }
.back_div { .back_div {
margin-left: 6rpx; margin-left: 6rpx;
.back-icon { .back-icon {
position: relative; position: relative;
width: 30rpx; width: 26rpx;
height: 30rpx; height: 26rpx;
cursor: pointer; cursor: pointer;
} }
@@ -349,8 +352,8 @@
left: 25%; left: 25%;
width: 50%; width: 50%;
height: 50%; height: 50%;
border-top: 3rpx solid #0066FF; border-top: 3rpx solid #0066ff;
border-left: 3rpx solid #0066FF; border-left: 3rpx solid #0066ff;
transform: rotate(-135deg); transform: rotate(-135deg);
} }
} }
@@ -382,7 +385,7 @@
.title_div { .title_div {
display: flex; display: flex;
align-items: center; align-items: center;
.name { .name {
height: 35rpx; height: 35rpx;
font-family: PingFangSC, PingFang SC; font-family: PingFangSC, PingFang SC;
@@ -392,21 +395,16 @@
line-height: 35rpx; line-height: 35rpx;
text-align: left; text-align: left;
font-style: normal; font-style: normal;
} }
.title_tip { .title_tip {
margin-left: 16rpx; margin-left: 16rpx;
width: 76rpx; display: flex;
height: 34rpx; align-items: center;
background: #FFFFFF; .img {
border-radius: 8rpx; width: 92rpx;
border: 2rpx solid #999999; }
font-family: PingFangSC;
font-weight: 400;
font-size: 23rpx;
color: #666666;
line-height: 36rpx;
text-align: center;
} }
.arrow { .arrow {
@@ -418,7 +416,7 @@
} }
.arrow::after { .arrow::after {
content: ""; content: '';
position: absolute; position: absolute;
background-color: #666666; background-color: #666666;
right: 4rpx; right: 4rpx;
@@ -439,7 +437,6 @@
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: wrap; white-space: wrap;
overflow-wrap: anywhere; overflow-wrap: anywhere;
} }
.time_and_reply { .time_and_reply {
@@ -461,7 +458,7 @@
font-family: PingFangSC, PingFang SC; font-family: PingFangSC, PingFang SC;
font-weight: 400; font-weight: 400;
font-size: 24rpx; font-size: 24rpx;
color: #0066FF; color: #0066ff;
line-height: 33rpx; line-height: 33rpx;
text-align: left; text-align: left;
font-style: normal; font-style: normal;
@@ -473,8 +470,8 @@
margin-bottom: 20rpx; margin-bottom: 20rpx;
font-family: PingFangSC, PingFang SC; font-family: PingFangSC, PingFang SC;
font-weight: 400; font-weight: 400;
font-size: 30rpx; font-size: 24rpx;
color: #456A8E; color: #0066FF;
line-height: 30rpx; line-height: 30rpx;
text-align: left; text-align: left;
font-style: normal; font-style: normal;
@@ -497,4 +494,4 @@
// height: calc(100vh - 88rpx - 162rpx - 256rpx - 70rpx); // height: calc(100vh - 88rpx - 162rpx - 256rpx - 70rpx);
// overflow: hidden; // overflow: hidden;
} }
</style> </style>
@@ -99,8 +99,9 @@
const click_list_item = (list_item) => { const click_list_item = (list_item) => {
const examLenTm = 666; const examLenTm = 666;
const papersName = item.value.examName; const papersName = item.value.examName;
common.navigateTo( common.navigateTo(
`/pages/examination/result?execId=${list_item.execId}&examLenTm=${examLenTm}&papersName=${papersName}` `/pages/examination/result?execId=${list_item.execId}&examLenTm=${examLenTm}&papersName=${papersName}&mode=record`
); );
}; };
</script> </script>
@@ -10,7 +10,7 @@
</view> </view>
<view class="prompt_div"> <view class="prompt_div">
<view class="prompt_div_left"> <view class="prompt_div_left">
<view class="top">{{ formatSeconds(item.stdyTm) || '' }}</view> <view class="top">{{ secondsToHours(item.stdyTm) }}h</view>
<view class="bottom">学习时长</view> <view class="bottom">学习时长</view>
</view> </view>
<view class="prompt_div_line"></view> <view class="prompt_div_line"></view>
@@ -58,7 +58,6 @@
<script setup> <script setup>
import { computed, ref, reactive } from 'vue'; import { computed, ref, reactive } from 'vue';
import { formatSeconds } from '@/common/common';
import common from '@/common/common'; import common from '@/common/common';
import { queryStdyBatchByCrsId } from '@/api/courseRecord.js'; import { queryStdyBatchByCrsId } from '@/api/courseRecord.js';
const status = ref('loadmore'); // loadmore - 加载前,loading - 加载中,nomore - 没有数据 const status = ref('loadmore'); // loadmore - 加载前,loading - 加载中,nomore - 没有数据
@@ -81,7 +80,14 @@
return data_list.length * 136 + 'rpx'; return data_list.length * 136 + 'rpx';
} }
}); });
const secondsToHours = (seconds) => {
console.log('typeof seconds', typeof seconds);
if (typeof seconds === 'number') {
// 转换为小时并保留一位小数
return (seconds / 3600).toFixed(1);
}
return '0.0';
};
// 点击获取详情 // 点击获取详情
const show_list_click = () => { const show_list_click = () => {
show_list_state.value = !show_list_state.value; show_list_state.value = !show_list_state.value;
@@ -99,11 +105,10 @@
// 跳转到详情 // 跳转到详情
const click_list_item = (list_item) => { const click_list_item = (list_item) => {
common.navigateTo( common.navigateTo(
`/pages/courseRecord/courseStudyRecordDetail?stdyId=${list_item.stdyBatch}&crsName=${item.value.crsName}` `/pages/courseRecord/courseStudyRecordDetail?stdyId=${list_item.stdyBatch}&crsName=${item.value.crsName || ''}`
); );
}; };
// 计算两个标准时间的时间差值 // 计算两个标准时间的时间差值
const formatDuration = (startTm, endTm) => { const formatDuration = (startTm, endTm) => {
// 解析时间字符串为时间戳(毫秒) // 解析时间字符串为时间戳(毫秒)
@@ -104,7 +104,6 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
height: 100vh; height: 100vh;
padding-top: var(--status-bar-height);
.top-bg { .top-bg {
background: linear-gradient(16deg, rgba(234, 246, 255, 0) 0%, #e6eafd 100%); background: linear-gradient(16deg, rgba(234, 246, 255, 0) 0%, #e6eafd 100%);
height: 480rpx; height: 480rpx;
@@ -128,7 +127,8 @@
box-sizing: border-box; box-sizing: border-box;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
padding-top: v-bind(pageTopPadding);
padding-top: var(--status-bar-height);
.body-title { .body-title {
height: 90rpx; height: 90rpx;
+1 -1
View File
@@ -50,7 +50,7 @@
import common from '@/common/common.js'; import common from '@/common/common.js';
const listItemRefs = ref([]); const listItemRefs = ref([]);
const taskRef = ref(null); const taskRef = ref(null);
const tabAct = ref(1); const tabAct = ref(0);
const tabList = ref([ const tabList = ref([
{ {
name: '学习记录' name: '学习记录'
+120 -67
View File
@@ -1,106 +1,160 @@
<template> <template>
<uni-popup ref="feedbackPopupRef" class="popup"> <uni-popup ref="feedbackPopupRef" class="popup">
<view class="feedback_popup_div" :style="{marginBottom:feedback_popup_bottom}"> <view class="feedback_popup_div" :style="{ marginBottom: feedback_popup_bottom }">
<view class="title_div"> <view class="title_div">
<view class="text"> <view class="text">反馈</view>
反馈
</view>
<view class="popup_back_div" @click="openFeedback(false)"> <view class="popup_back_div" @click="openFeedback(false)">
<image :src="optionErrorIcon('#000000')" class="img"></image> <image :src="optionErrorIcon('#000000')" class="img"></image>
</view> </view>
</view> </view>
<view class="feedback_item_div"> <view class="feedback_item_div">
<view class="feedback_item" <view
:class="{'feedback_item_clidked':feedbackInfo.type.includes(feedback_type_item.value)}" class="feedback_item"
@click="feedback_item_click(feedback_type_item.value)" :class="{ feedback_item_clidked: feedbackInfo.type.includes(feedback_type_item.value) }"
v-for="feedback_type_item in feedbackTypeList">场景问题</view> @click="feedback_item_click(feedback_type_item.label)"
v-for="feedback_type_item in feedbackTypeList"
>
{{ feedback_type_item.label }}
</view>
</view> </view>
<view class="feedback_text_area_div"> <view class="feedback_text_area_div">
<uv-textarea height="124" count :maxlength="100" placeholder="请输入反馈内容..." :adjustPosition="false" <uv-textarea
v-model="feedbackInfo.text"></uv-textarea> height="124"
count
:maxlength="100"
placeholder="请输入反馈内容..."
:adjustPosition="false"
v-model="feedbackInfo.text"
></uv-textarea>
</view> </view>
<view class="feedback_button"> <view class="feedback_button">
<uv-button class="button" type="primary" :throttleTime="100" text="提交" color="#0066FF" <uv-button
class="button"
type="primary"
:throttleTime="100"
text="提交"
color="#0066FF"
custom-style="color:#FFFFFF;borderRadius:16rpx;height:92rpx;" custom-style="color:#FFFFFF;borderRadius:16rpx;height:92rpx;"
@click="submit_feedback_fun()"></uv-button> @click="submit_feedback_fun()"
></uv-button>
</view> </view>
</view> </view>
</uni-popup> </uni-popup>
</template> </template>
<script setup> <script setup>
import { console.log('反馈组件');
ref, import { ref, reactive, computed, onMounted, onUnmounted, nextTick } from 'vue';
reactive, import { optionErrorIcon } from '@/common/imgSvg';
computed, import { saveTraQnsFeedback } from '@/api/examination.js';
onMounted, import common from '@/common/common';
nextTick import { setupKeyboardHeightListener } from '@/common/common';
} from 'vue'; const feedback_popup_bottom = ref('0px'); // 反馈框下距离
import { const feedbackPopupRef = ref(null);
optionErrorIcon const feedbackTypeList = reactive([
} from '@/common/imgSvg' {
const feedback_popup_bottom = ref('0px') // 反馈框下距离 value: '01',
const feedbackPopupRef = ref(null) label: '题干纠错'
const feedbackTypeList = reactive([{ },
value: "SN", {
label: "场景问题", value: '02',
}, { label: '答案纠错'
value: "GN", },
label: "功能问题", {
}, { value: '03',
value: "PN", label: '解析纠错'
label: "陪练内容", }
}]) ]);
const feedbackInfo = reactive({ const feedbackInfo = reactive({
type: [], // 反馈类型 type: [], // 反馈类型
text: '', // 反馈内容 text: '', // 反馈内容
examId: '', // 考试ID examId: '', // 考试ID
qnsId: '', // 试题IDID qnsId: '' // 试题IDID
}) });
// 选择反馈类型
const feedback_item_click = (value) => { const feedback_item_click = (value) => {
if (feedbackInfo.type.includes(value)) { if (feedbackInfo.type.includes(value)) {
feedbackInfo.type = feedbackInfo.type.filter(item => item !== value); // 如果已存在,则从数组中移除 feedbackInfo.type = feedbackInfo.type.filter((item) => item !== value); // 如果已存在,则从数组中移除
} else { } else {
feedbackInfo.type.push(value); // 如果不存在,则添加到数组中 feedbackInfo.type.push(value); // 如果不存在,则添加到数组中
} }
} };
// 反馈提交 // 反馈提交
const submit_feedback_fun = (value) => { const submit_feedback_fun = (value) => {
// todo 待接口 // 首先判断反馈类型是否
console.log('反馈提交'); if (feedbackInfo.type.length === 0) {
} return common.msg('请选择你要反馈的类型');
const openFeedback = (status) => { }
if (status) { if (feedbackInfo.text.trim() === '') {
feedbackPopupRef.value.open('bottom') return common.msg('请输入你要反馈的反馈内容');
// feedbackInfo.examId }
const systemInfo = uni.getSystemInfoSync(); common.loading('提交反馈信息');
// 安全区域底部到屏幕底部的高度(即底部安全区域高度) const startTime = Date.now();
const safeAreaBottomHeight = systemInfo.screenHeight - systemInfo.safeArea.bottom; saveTraQnsFeedback({
nextTick(() => { qnsId: feedbackInfo.qnsId,
uni.onKeyboardHeightChange(res => { fbComtent: feedbackInfo.text.trim(),
let keyboardHeight = res.height; fbTyp: feedbackInfo.type.join(',')
// 仅在iOS端补偿安全区域高度 })
if (systemInfo.platform === 'ios') { .then(async (res) => {
keyboardHeight = Math.max(0, keyboardHeight - safeAreaBottomHeight); // 避免负数 // 计算接口已经消耗的时间
} const elapsedTime = Date.now() - startTime;
feedback_popup_bottom.value = `${keyboardHeight}px`; // 计算需要补充的延迟时间(确保总耗时至少400ms)
const delayTime = Math.max(0, 500 - elapsedTime);
// 如果需要,补充延迟
if (delayTime > 0) {
await new Promise((resolve) => setTimeout(resolve, delayTime));
}
common.hideLoading();
nextTick(() => {
feedbackInfo.type = [];
feedbackInfo.text = '';
feedbackInfo.qnsId = '';
feedbackInfo.examId = '';
feedbackPopupRef.value.close();
setTimeout(() => {
common.msg('反馈信息提交成功');
}, 100);
}); });
}) })
.catch(() => {
common.hideLoading();
});
};
const openFeedback = (status) => {
if (status) {
feedbackPopupRef.value.open('bottom');
} else { } else {
feedbackPopupRef.value.close() feedbackPopupRef.value.close();
} }
} };
const open = () => openFeedback(true) // 组件挂载时设置监听
let removeListener = () => {};
onMounted(() => {
console.log('组件挂载时设置监听');
removeListener = setupKeyboardHeightListener((height) => {
feedback_popup_bottom.value = `${height}px`;
});
// 组件卸载时移除监听
});
onUnmounted(() => {
removeListener();
});
const open = (qnsId) => {
feedbackInfo.qnsId = qnsId;
openFeedback(true);
};
defineExpose({ defineExpose({
open open
}) });
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.feedback_popup_div { .feedback_popup_div {
width: 100%; width: 100%;
height: 100%; height: 100%;
background-color: #FFFFFF; background-color: #ffffff;
padding: 10rpx 34rpx; padding: 10rpx 34rpx;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -118,7 +172,7 @@
color: #000000; color: #000000;
text-align: center; text-align: center;
font-style: normal; font-style: normal;
border-bottom: 2rpx solid #EFEFEF; border-bottom: 2rpx solid #efefef;
} }
.popup_back_div { .popup_back_div {
@@ -138,7 +192,7 @@
} }
.feedback_item.feedback_item_clidked { .feedback_item.feedback_item_clidked {
border: 2rpx solid #0066FF !important; border: 2rpx solid #0066ff !important;
background-color: #fff !important; background-color: #fff !important;
} }
@@ -150,8 +204,8 @@
margin: 30rpx 0; margin: 30rpx 0;
.feedback_item { .feedback_item {
border: 2rpx solid #F0F0F0; border: 2rpx solid #f0f0f0;
background-color: #F0F0F0; background-color: #f0f0f0;
border-radius: 16rpx; border-radius: 16rpx;
padding: 26rpx 36rpx; padding: 26rpx 36rpx;
font-family: PingFangSC; font-family: PingFangSC;
@@ -159,7 +213,6 @@
font-size: 30rpx; font-size: 30rpx;
color: #000000; color: #000000;
text-align: center; text-align: center;
} }
} }
@@ -173,4 +226,4 @@
margin-bottom: 20rpx; margin-bottom: 20rpx;
} }
} }
</style> </style>
+2 -7
View File
@@ -48,7 +48,6 @@
:clearResult="true" :clearResult="true"
v-model="item.my_answer" v-model="item.my_answer"
></Subject> ></Subject>
{{ item.my_answer }}
</scroll-view> </scroll-view>
</view> </view>
</swiper-item> </swiper-item>
@@ -232,7 +231,7 @@
} }
questionNumber.value = newNumber; questionNumber.value = newNumber;
}; };
// 打开或者关闭弹出层 // 打开或者关闭弹出层
const openSheet = (status) => (status ? popupRef.value.open('bottom') : popupRef.value.close()); const openSheet = (status) => (status ? popupRef.value.open('bottom') : popupRef.value.close());
@@ -246,7 +245,7 @@
topicList[questionNumber.value]['mark'] = !topicList[questionNumber.value]['mark']; topicList[questionNumber.value]['mark'] = !topicList[questionNumber.value]['mark'];
} else if (type === 'feedback') { } else if (type === 'feedback') {
console.log('feedbackRef.value', feedbackRef.value); console.log('feedbackRef.value', feedbackRef.value);
feedbackRef.value.open(); feedbackRef.value.open(params.qnsId);
} }
}; };
@@ -280,7 +279,6 @@
} else if (type === 'text') { } else if (type === 'text') {
topic.value.es_status = '01'; topic.value.es_status = '01';
topic.value.anserResult = submitObj; // 正确答案文字内容 topic.value.anserResult = submitObj; // 正确答案文字内容
// submitProblemEssayQuestion({ anserResult: submitObj });
} }
}; };
@@ -337,8 +335,6 @@
// 交卷按钮点击,打开交卷确认框 // 交卷按钮点击,打开交卷确认框
const hand_in_paper_button_click = () => { const hand_in_paper_button_click = () => {
console.log('点击交卷', paperData); console.log('点击交卷', paperData);
// console.log('点击交卷topicList', topicList);
// return;
// 判断是否完成所有题目 // 判断是否完成所有题目
const completeList = topicList const completeList = topicList
.filter((res) => !!(Array.isArray(res.my_answer) ? res.my_answer.length : res.my_answer)) .filter((res) => !!(Array.isArray(res.my_answer) ? res.my_answer.length : res.my_answer))
@@ -448,7 +444,6 @@
if (res.body.flagQuery === 'Y') { if (res.body.flagQuery === 'Y') {
// 需要查分 // 需要查分
const examLenTm = 678; const examLenTm = 678;
common.redirectTo( common.redirectTo(
`/pages/examination/result?execId=${res.body.execId}&examLenTm=${examLenTm}&papersName=${paperData.papersName}&papersId=${paperData.papersId}&mode=${mode.value}&crsId=${paperData.crsId}` `/pages/examination/result?execId=${res.body.execId}&examLenTm=${examLenTm}&papersName=${paperData.papersName}&papersId=${paperData.papersId}&mode=${mode.value}&crsId=${paperData.crsId}`
); );
+3 -2
View File
@@ -81,7 +81,7 @@
时长{{ result.examLenTm }} 时长{{ result.examLenTm }}
</view> </view>
<view class="fl1"></view> <view class="fl1"></view>
<view class="examination" @click="goExamRanking"> <view class="examination" @click="goExamRanking" v-if="result.mode === 'examination'">
<view>考试排行榜</view> <view>考试排行榜</view>
<view class="back-icon"></view> <view class="back-icon"></view>
</view> </view>
@@ -245,7 +245,8 @@
result.execId = e.execId; result.execId = e.execId;
result.papersName = e.papersName; result.papersName = e.papersName;
result.examLenTm = e.examLenTm; result.examLenTm = e.examLenTm;
result.mode = e.mode; result.mode = e.mode || '';
result.papersId = e.papersId; result.papersId = e.papersId;
get_result(); get_result();
}); });
@@ -102,7 +102,7 @@
const examLenTm = 666; const examLenTm = 666;
const papersName = item.value.examName; const papersName = item.value.examName;
common.navigateTo( common.navigateTo(
`/pages/examination/result?execId=${list_item.execId}&examLenTm=${examLenTm}&papersName=${papersName}` `/pages/examination/result?execId=${list_item.execId}&examLenTm=${examLenTm}&papersName=${papersName}&mode=record`
); );
}; };
</script> </script>
+2 -2
View File
@@ -111,7 +111,7 @@
}; };
const status = ref('loadmore'); // loadmore - 加载前,loading - 加载中,nomore - 没有数据 const status = ref('loadmore'); // loadmore - 加载前,loading - 加载中,nomore - 没有数据
const limit = 15; const limit = 20;
const total = ref(-1); const total = ref(-1);
const page = ref(0); const page = ref(0);
const data_list = reactive([]); const data_list = reactive([]);
@@ -147,7 +147,7 @@
}; };
const getData = (from = '') => { const getData = (from = '') => {
if (from == 'first') { if (from === 'first') {
if (total.value !== -1) { if (total.value !== -1) {
return; return;
} }
+52 -96
View File
@@ -113,7 +113,7 @@
get: () => topicList[questionNumber.value], get: () => topicList[questionNumber.value],
set: (val) => (topicList[questionNumber.value] = val) set: (val) => (topicList[questionNumber.value] = val)
}); });
const showTouchBtn = computed(() => { const showTouchBtn = computed(() => {
return topic.value?.qnsTyp === 'ES'; return topic.value?.qnsTyp === 'ES';
}); });
@@ -139,9 +139,10 @@
}; };
const subject_click = (type, params) => { const subject_click = (type, params) => {
if (type === 'feedback') { if (type === 'feedback') {
// 反馈 // 反馈
feedbackRef.value.open(); feedbackRef.value.open(params.qnsId);
} else if (type === 'answer') { } else if (type === 'answer') {
//多选点击按钮答题 //多选点击按钮答题
if (params.qnsTyp === 'MU') { if (params.qnsTyp === 'MU') {
@@ -156,9 +157,13 @@
} }
} else if (type === 'complete') { } else if (type === 'complete') {
// 问答题点击完成 // 问答题点击完成
// ossAddr
// anserResult 作答结果
// ossKey 对象存储
submitProblem(params.answer);
} else if (type === 'again') { } else if (type === 'again') {
// 问答题点击重答 // 问答题点击重答
// const topic = topicList[questionNumber.value]; topic.value.my_answer = {};
topic.value.isPreview = false; topic.value.isPreview = false;
topic.value.es_status = ''; // 取消转圈 topic.value.es_status = ''; // 取消转圈
} }
@@ -175,127 +180,81 @@
qnsId: topic.value.qnsId, qnsId: topic.value.qnsId,
...answer ...answer
}) })
.then((res) => { .then(async (res) => {
common.hideLoading();
const body = res.body;
console.log('body', body);
topic.value.isPreview = true; // 不能在点击了,打开预览模式 topic.value.isPreview = true; // 不能在点击了,打开预览模式
topic.value.clearResult = false; // 不清除正确答案了 let traQnsInfoVo;
const traQnsInfoVo = body.traQnsInfoVo; // 是问答题
console.log('是问答题', topic.value.qnsTyp);
if (topic.value.qnsTyp === 'ES') {
const result = await queryWithRetry(res.body.execLogId);
traQnsInfoVo = result.body.traQnsInfoVo;
} else {
traQnsInfoVo = res.body.traQnsInfoVo;
}
common.hideLoading();
topic.value.clearResult = false; // 不清除正确答案
topic.value.analyContent = traQnsInfoVo.analyContent; // 答案解析 topic.value.analyContent = traQnsInfoVo.analyContent; // 答案解析
console.log('答题结果', traQnsInfoVo);
// 更新选项正确项目 // 更新选项正确项目
topic.value.itemVos = traQnsInfoVo.itemVos; // 答案选项(todo应该不这样替换) topic.value.itemVos = traQnsInfoVo.itemVos; // 答案选项(todo应该不这样替换)
topic.value.anserResult = traQnsInfoVo.anserResult; // 正确答案字符串分割 topic.value.anserResult = traQnsInfoVo.anserResult; // 正确答案字符串分割
const judgeResultStatus = traQnsInfoVo.judgeResult === 'P'; // P为正确U为错误 const judgeResultStatus = traQnsInfoVo.judgeResult === 'P'; // P为正确U为错误
if (!addProblem()) { if (!addProblem()) {
// 返回false说明到了最后一题 // 返回false说明到了最后一题
console.log('最后一题'); console.log('最后一题');
if (topic.value.qnsTyp === 'ES') {
// 最后一题是问答题,暂时不动
}
} }
// 自动切换下一题的条件 // 自动切换下一题的条件
if ( if (
judgeResultStatus && // 条件1.答题正确 judgeResultStatus && // 条件1.答题正确
// ['JD', 'SN', 'MU'].includes(topic.qnsTyp) && // 条件2.单选多选判断(这里改成了单选多选判断专属,所有不用判断这一条了) topic.value.qnsTyp !== 'ES' && // 问答题答对打答错都不切换
topicList.length < storageTopicList.value.length // 条件3.不是最后一题(暂定这样判断) topicList.length < storageTopicList.value.length // 条件3.不是最后一题(暂定这样判断)
) { ) {
questionNumber.value++; questionNumber.value++;
} else {
console.log('为什么没自动跳转', judgeResultStatus);
console.log(
'22',
topicList.length < storageTopicList.value.length,
topicList.length,
storageTopicList.value.length
);
} }
}) })
.catch(() => { .catch(() => {
common.hideLoading(); common.hideLoading();
}); });
}; };
// 问答题轮询结果方法 // 问答题轮询结果方法
const queryWithRetry = (execLogId, maxRetries = 30) => { const queryWithRetry = (execLogId, maxRetries = 30) => {
// 递归终止条件:达到最大重试次数 // 递归终止条件:达到最大重试次数
if (maxRetries <= 0) { if (maxRetries <= 0) {
return Promise.reject(new Error('超过最大重试次数,查询失败')); return Promise.reject(new Error('超过最大重试次数,查询失败'));
} }
return queryCrsPracticeAnswerResult({ execLogId })
return queryCrsPracticeAnswerResult({ execLogId }).then((resl) => {
const isWait = resl.body.isWait;
if (isWait === 'N') {
// 如果是Y,延迟2秒后重试,重试次数减1
return new Promise((resolve) => {
setTimeout(() => {
resolve(queryWithRetry(execLogId, maxRetries - 1));
}, 2000);
});
} else {
// 不是Y,直接返回结果
return resl;
}
});
};
// 单题回答(问答题专属)
const submitProblemEssayQuestion = (answer) => {
const topic = topicList[questionNumber.value];
const old_text = touchBtnRef.value?.clearinputText(); // 清除发送框数据
console.log('答题内容', answer);
console.log('答单题问答题专属', topic);
practiceAnswer({
exrId: paperData.exrId,
ossKey: '',
qnsId: topic.qnsId,
...answer
})
.then((res) => { .then((res) => {
topic.isPreview = true; // 不能在点击了,打开预览模式 console.log('queryCrsPracticeAnswerResult', res.body);
const isWait = res.body.isWait;
// touchBtnRef.value.clearinputText() if (isWait === 'Y') {
// topic.clearResult = false; // 不清除正确答案了 // 如果是Y,延迟2秒后重试,重试次数减1
// 使用方式 return new Promise((resolve) => {
queryWithRetry(res.body.execLogId) setTimeout(() => {
.then((result) => { resolve(queryWithRetry(execLogId, maxRetries - 1));
console.log('查询成功', result); }, 2000);
const body = result.body;
const traQnsInfoVo = body.traQnsInfoVo;
topic.analyContent = traQnsInfoVo?.analyContent || ''; // 答案解析
topic.anserResult = traQnsInfoVo?.anserResult || answer.anserResult; // 正确答案文字内容
const judgeResultStatus = traQnsInfoVo?.judgeResult === 'P'; // P为正确U为错误
// 判断是语音回答还是文字回答, 通过是否存在语音地址判断 // todo 后续可能改成ossid判断
if (answer.voicePath) {
topic.es_status = '02';
} else {
// 纯文字
topic.es_status = '01';
}
// 处理成功结果
})
.catch((error) => {
console.log('查询失败', error);
topic.isPreview = false;
topic.es_status = ''; // 取消转圈
touchBtnRef.value?.clearinputText(old_text); // 清除发送框数据
// 处理失败情况
}); });
} else {
// const traQnsInfoVo = body.traQnsInfoVo; // 不是Y,直接返回结果
// topic.analyContent = traQnsInfoVo.analyContent; // 答案解析 return res;
// console.log('答题结果', traQnsInfoVo); }
// const judgeResultStatus = traQnsInfoVo.judgeResult === 'P'; // P为正确U为错误
// if (!addProblem()) {
// // 返回false说明到了最后一题
// console.log('最后一题');
// }
}) })
.catch(() => { .catch((error) => {
topic.es_status = ''; // 取消转圈 // 对接口调用错误进行重试
if (maxRetries > 0) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(queryWithRetry(execLogId, maxRetries - 1));
}, 2000);
});
}
return Promise.reject(error);
}); });
}; };
// 录音组件提交 // 录音组件提交
const touchBtnSubmit = (type, submitObj) => { const touchBtnSubmit = (type, submitObj) => {
const old_text = touchBtnRef.value?.clearinputText(); // 清除发送框数据 const old_text = touchBtnRef.value?.clearinputText(); // 清除发送框数据
@@ -325,7 +284,6 @@
} else if (type === 'text') { } else if (type === 'text') {
topic.value.es_status = '01'; topic.value.es_status = '01';
topic.value.anserResult = submitObj; // 正确答案文字内容 topic.value.anserResult = submitObj; // 正确答案文字内容
// submitProblemEssayQuestion({ anserResult: submitObj });
} }
}; };
@@ -373,7 +331,7 @@
}); });
// #endif // #endif
}); });
const result_click = ({ key }) => { const result_click = ({ key }) => {
console.log('res', key); console.log('res', key);
if (key === 'hand_in_paper') { if (key === 'hand_in_paper') {
@@ -445,9 +403,7 @@
dialogRef.value.open(); dialogRef.value.open();
}; };
// 执行交卷 // 执行交卷
const hand_in_paper = () => { const hand_in_paper = () => {};
};
// 启动定时器 // 启动定时器
const startTimer = () => { const startTimer = () => {
if (practiceTimeTimer) { if (practiceTimeTimer) {
@@ -1,6 +1,6 @@
<template> <template>
<view class="scroll_div"> <view class="scroll_div">
<scroll-view class="scroll-Y" @scrolltolower="scrolltolower" :show-scrollbar="false"> <scroll-view class="scroll-Y" :show-scrollbar="false">
<view class="item"> <view class="item">
<image src="/src/static/images/common/default_img_2.png" mode="aspectFit"></image> <image src="/src/static/images/common/default_img_2.png" mode="aspectFit"></image>
</view> </view>
@@ -1,7 +1,7 @@
<template> <template>
<view class="scroll_div"> <view class="scroll_div">
<scroll-view scroll-y="true" class="scroll-Y" @scrolltolower="scrolltolower" :show-scrollbar="false"> <scroll-view scroll-y="true" class="scroll-Y" @scrolltolower="scrolltolower" :show-scrollbar="false">
<view class="item" v-for="item in data_list"> <view class="item" v-for="item in data_list" @click="item_click(item)">
<view class="img_div"> <view class="img_div">
<image-preview class="img" :imgId="item.imgId" mode="scaleToFill"></image-preview> <image-preview class="img" :imgId="item.imgId" mode="scaleToFill"></image-preview>
</view> </view>
@@ -59,6 +59,10 @@
const scrolltolower = (item) => { const scrolltolower = (item) => {
getData(); getData();
}; };
const item_click = (item) => {
};
defineExpose({ defineExpose({
getData getData
}); });
+1 -1
View File
@@ -6,7 +6,7 @@
</view> </view>
<view class="fixed_search_input_div fl1"> <view class="fixed_search_input_div fl1">
<uv-input v-model="inputValue" shape="circle" :customStyle="fixedCustomStyles" confirmType="search" <uv-input v-model="inputValue" shape="circle" :customStyle="fixedCustomStyles" confirmType="search"
placeholderStyle='color:#999999;font-size:26rpx' placeholder="请输入关键字" :focus="true" :adjust-position="false" placeholderStyle='color:#999999;font-size:26rpx' placeholder="请输入关键字" :adjust-position="false"
@confirm="search"> @confirm="search">
<template #suffix><uv-icon name="search" color="#2c8ef2" size="24" @click="search()"></uv-icon></template> <template #suffix><uv-icon name="search" color="#2c8ef2" size="24" @click="search()"></uv-icon></template>
</uv-input> </uv-input>
+100 -226
View File
@@ -6,10 +6,20 @@
<view class="back-icon"></view> <view class="back-icon"></view>
</view> </view>
<view class="fixed_search_input_div fl1"> <view class="fixed_search_input_div fl1">
<uv-input v-model="inputValue" shape="circle" :customStyle="fixedCustomStyles" confirmType="search" <uv-input
placeholderStyle='color:#999999;font-size:26rpx' placeholder="请输入关键字" :focus="true" :adjust-position="false" v-model="inputValue"
@confirm="search"> shape="circle"
<template #suffix><uv-icon name="search" color="#2c8ef2" size="24" @click="search()"></uv-icon></template> :customStyle="fixedCustomStyles"
confirmType="search"
placeholderStyle="color:#999999;font-size:26rpx"
placeholder="请输入关键字"
:focus="true"
:adjust-position="false"
@confirm="search"
>
<template #suffix>
<uv-icon name="search" color="#2c8ef2" size="24" @click="search(inputValue)"></uv-icon>
</template>
</uv-input> </uv-input>
</view> </view>
<view class="message_div" v-show="false"> <view class="message_div" v-show="false">
@@ -19,253 +29,121 @@
</view> </view>
</view> </view>
<!-- 主体 --> <!-- 主体 -->
<view class="search_content"> <view class="search_content" v-show="!no_list_state">
<!-- 历史搜索 --> <!-- 历史搜索 -->
<view class="title_div"> <view class="title_div">
<view class="text"> <view class="text">历史搜索</view>
历史搜索 <view class="fl1"></view>
</view> <view class="delete_img" @click="del_all">
<view class="fl1">
</view>
<view class="delete_img">
<image src="@/static/images/search/delete.png" class="img" mode="heightFix"></image> <image src="@/static/images/search/delete.png" class="img" mode="heightFix"></image>
</view> </view>
</view> </view>
<view class="history_div"> <view class="history_div">
<view class="history_item" v-for="item in historyList"> <view class="history_item" v-for="item in historyList" @click="search(item)">
{{item}} {{ item }}
</view>
<view class="history_item has-arrow" @click="show_all_history_fun">
<view class="down-corner-arrow" :class="show_all_history ? 'up' : 'down'"></view>
</view> </view>
</view> </view>
</view> </view>
<view class="host_search_div"> <view class="no_search" v-show="no_list_state">
<image class="img" src="@/static/images/search/no_search.png" mode="widthFix"></image>
</view> <view class="text">暂无任何搜索记录</view>
<image class="bg_img" style="width: 718rpx;height: 1126rpx;" src="@/static/images/test/search_img.png">
</image>
<!-- 隐藏的计算容器 -->
<view class="measure-container">
<view v-for="(item, index) in hiddenHistorySearchList" :key="index" class="history_item">
{{ item }}
</view>
</view> </view>
</view> </view>
</template> </template>
<script setup> <script setup>
import { import { ref, computed } from 'vue';
ref, import common from '@/common/common.js';
watchEffect, import { onShow, onLoad } from '@dcloudio/uni-app';
nextTick,
onMounted
} from 'vue';
import common from '@/common/common.js'
import {
onShow,
onLoad
} from '@dcloudio/uni-app';
const tagRefs = ref([]) // history_item
import {
getCurrentInstance,
computed,
watch
} from 'vue';
const {
proxy
} = getCurrentInstance() //
const currentHeight = ref(0); // px
const visibleCount = ref(0)
const hiddenHistorySearchList = ref([]);
//
const show_all_history_fun = async () => {
show_all_history.value = !show_all_history.value
}
const show_all_history = ref(false)
const historyList = computed(() => show_all_history.value ? hiddenHistorySearchList.value : hiddenHistorySearchList
.value.slice(0, visibleCount.value));
onShow(() => {
const historySearchList = ['房贷审批', '小微贷条件', '对公开户', '反洗钱指引', '盗刷处理', '小微贷条件', '征信解读']
if (historySearchList) {
hiddenHistorySearchList.value = [...historySearchList]
nextTick(() => {
const widthPromises = hiddenHistorySearchList.value.map((res, index) => {
return new Promise(resolve => {
const query = uni.createSelectorQuery().in(proxy);
query.select(`.history_item:nth-child(${index + 1})`)
.boundingClientRect(data => {
resolve(data ? data.width : 0); //
}).exec();
});
});
// Promise
Promise.all(widthPromises).then(widths => {
console.log('所有宽度计算完成:', widths);
calculateLayout(widths); //
});
})
}
})
const calculateLayout = (widths) => {
const gap = 12; //
const expandBtnWidth = 80
const query = uni.createSelectorQuery().in(proxy);
query.select('.history_div')
.boundingClientRect(data => {
const containerWidth = data.width
let row1Count = 0; //
let row2Count = 0; //
let currentWidth = 0; //
//
for (let i = 0; i < widths.length; i++) {
const itemWidth = widths[i] + (row1Count > 0 ? gap : 0);
if (currentWidth + itemWidth <= containerWidth) {
row1Count++;
currentWidth += itemWidth;
} else {
break;
}
}
//
currentWidth = 0;
// = -
const row2MaxWidth = containerWidth - expandBtnWidth - gap;
for (let i = row1Count; i < widths.length; i++) {
const itemWidth = widths[i] + (row2Count > 0 ? gap : 0);
if (currentWidth + itemWidth <= row2MaxWidth) {
row2Count++;
currentWidth += itemWidth;
} else {
break;
}
}
console.log(`第一行: ${row1Count}个,第二行: ${row2Count}个(含展开按钮)`);
visibleCount.value = row1Count + row2Count;
}).exec();
}
// //
const fixedCustomStyles = { const fixedCustomStyles = {
backgroundColor: "#ffffff", backgroundColor: '#ffffff',
paddingLeft: "40rpx", paddingLeft: '40rpx',
paddingTop: "5px", paddingTop: '5px',
paddingBottom: "5px", paddingBottom: '5px',
border: "none" border: 'none',
} borderRadius: '6px'
const suffixIconStyle = { };
color: "#2c8ef2", const storageKey = 'history_search_list';
fontSize: "28px" const asssss = ref(false);
}
const inputValue = ref(''); const inputValue = ref('');
const fromType = ref('');
const historyList = ref([]);
const no_list_state = computed(() => historyList.value.length === 0);
const fromType = ref('')
onLoad((params) => { onLoad((params) => {
fromType.value = params.from||''; fromType.value = params.from || '';
});
onShow(() => {
let storageList = common.getValue(storageKey);
if (!Array.isArray(storageList)) {
storageList = [];
}
historyList.value = storageList;
}); });
const del_all = () => {
common.show('', '确认删除全部搜索历史').then((res) => {
if (res) {
common.setValue(storageKey, []);
historyList.value = [];
}
});
};
const measureRef = ref(null) const search = (text) => {
const measureItems = ref([]) inputValue.value = text;
const search=() => { const searchValue = text.trim();
common.navigateTo('/pages/search/result?searchName='+ inputValue.value +'&from=' + fromType.value) if (searchValue) {
} //
let storageList = common.getValue(storageKey);
//
if (!Array.isArray(storageList)) {
storageList = [];
}
// -
const uniqueList = storageList.filter((item) => item !== searchValue);
//
uniqueList.unshift(searchValue);
// 10
const MAX_RECORDS = 10;
if (uniqueList.length > MAX_RECORDS) {
uniqueList.pop();
}
//
common.setValue(storageKey, uniqueList);
// 使encodeURIComponent
common.navigateTo(`/pages/search/result?searchName=${searchValue}&from=${fromType.value}`);
}
};
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.history_div { .no_search {
width: 100%;
display: flex; display: flex;
flex-wrap: wrap; flex-direction: column;
align-items: center;
margin-top: 100rpx;
.img {
width: 230rpx;
}
.text {
color: #999;
font-size: 30rpx;
}
}
//xxxxxxx
.history_div {
width: 100%; width: 100%;
} }
.measure-container {
display: flex;
position: fixed;
top:-500rpx; //
}
.history_item { .history_item {
white-space: nowrap; padding: 8rpx 0;
padding: 8rpx 22rpx;
border-radius: 40rpx 40rpx 40rpx 40rpx;
border: 1px solid #999;
color: #777; color: #777;
} }
.history_item {
margin-bottom: 16rpx;
}
/* 除最后一个子元素外,都添加右间距 */
.history_item:not(:last-child) {
margin-right: 12px;
}
.has-arrow {
border: 1px solid #bbb;
position: relative;
}
/* 朝下的拐角箭头容器 */
.down-corner-arrow {
padding: 10rpx 8rpx;
position: relative;
}
.down-corner-arrow.up {
transform: rotate(90deg);
top: 0%;
left: 0%;
}
.down-corner-arrow.down {
transform: rotate(-90deg);
top: 50%;
left: 0%;
}
/* 左半边箭头(向左旋转) */
.down-corner-arrow::before {
content: '';
position: absolute;
width: 20rpx;
/* 线段长度 */
height: 2rpx;
/* 线段粗细 */
background-color: #bbb;
transform-origin: right center;
/* 旋转中心在右侧 */
transform: rotate(-48deg);
/* 向左旋转50°,使总开口角100° */
}
/* 右半边箭头(向右旋转) */
.down-corner-arrow::after {
content: '';
position: absolute;
width: 20rpx;
/* 线段长度 */
height: 2rpx;
/* 线段粗细 */
background-color: #bbb;
transform-origin: right center;
/* 旋转中心在右侧 */
transform: rotate(48deg);
/* 向右旋转50°,使总开口角100° */
}
.title_div { .title_div {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
@@ -285,13 +163,10 @@
align-items: center; align-items: center;
justify-content: center; justify-content: center;
padding: 20rpx 0 20rpx 32rpx; padding: 20rpx 0 20rpx 32rpx;
// background-color: red;
.img { .img {
height: 24rpx; height: 24rpx;
} }
} }
} }
.search_content { .search_content {
@@ -301,7 +176,7 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
padding: 0 20rpx; padding: 0 30rpx;
min-height: 200rpx; min-height: 200rpx;
transition: max-height 0.3s ease; transition: max-height 0.3s ease;
.bg_img { .bg_img {
@@ -312,7 +187,7 @@
.fixed_search_div { .fixed_search_div {
padding: calc(10rpx + var(--status-bar-height)) 15rpx 24rpx 10rpx; padding: calc(10rpx + var(--status-bar-height)) 15rpx 24rpx 10rpx;
background: linear-gradient(90deg, #F1F7FF 0%, #D8EDFF 50%, #C3E6FF 100%); background: linear-gradient(90deg, #f1f7ff 0%, #d8edff 50%, #c3e6ff 100%);
display: flex; display: flex;
align-items: center; align-items: center;
@@ -340,8 +215,8 @@
left: 25%; left: 25%;
width: 40%; width: 40%;
height: 40%; height: 40%;
border-top: 4rpx solid #2B2C2E; border-top: 4rpx solid #2b2c2e;
border-left: 4rpx solid #2B2C2E; border-left: 4rpx solid #2b2c2e;
transform: rotate(-45deg); transform: rotate(-45deg);
} }
} }
@@ -350,6 +225,5 @@
.main_div { .main_div {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
</style> </style>
+2 -5
View File
@@ -22,7 +22,7 @@
import { queryTraCrsBbsInfoByCrsId } from '@/api/courseDetail.js'; import { queryTraCrsBbsInfoByCrsId } from '@/api/courseDetail.js';
import itemVue from './content-item.vue'; import itemVue from './content-item.vue';
const status = ref('loadmore'); // loadmore - loading - nomore - const status = ref('loadmore'); // loadmore - loading - nomore -
const limit = 15; const limit = 20;
const total = ref(-1); const total = ref(-1);
const page = ref(0); const page = ref(0);
const data_list = reactive([]); const data_list = reactive([]);
@@ -73,13 +73,10 @@
} }
queryTraExamPapersPage(params) queryTraExamPapersPage(params)
.then((res) => { .then((res) => {
console.log('resresresresres', res);
total.value = res.total; total.value = res.total;
data_list.push(...res.body); data_list.push(...res.body);
}) })
.catch((res) => { .catch((res) => {})
console.log('谢谢谢谢谢谢', res);
})
.finally(() => { .finally(() => {
if (data_list.length >= total.value) { if (data_list.length >= total.value) {
status.value = 'nomore'; status.value = 'nomore';
+1 -1
View File
@@ -66,7 +66,7 @@
const contentRefs = ref([]) const contentRefs = ref([])
const status = ref('loadmore') // loadmore - loading - nomore - const status = ref('loadmore') // loadmore - loading - nomore -
const limit = 15 const limit = 20;
const total = ref(-1) const total = ref(-1)
const page = ref(0) const page = ref(0)
const data_list = reactive([]) const data_list = reactive([])
@@ -29,7 +29,7 @@
</template> </template>
<script setup> <script setup>
const status = ref('loading') // loadmore - loading - nomore - const status = ref('loading') // loadmore - loading - nomore -
const limit = 15 const limit = 20;
const total = ref(-1) const total = ref(-1)
const page = ref(0) const page = ref(0)
const data_list = reactive([]) const data_list = reactive([])
@@ -11,7 +11,7 @@
</template> </template>
<script setup> <script setup>
const status = ref('loading'); // loadmore - loading - nomore - const status = ref('loading'); // loadmore - loading - nomore -
const limit = 15; const limit = 20;
const total = ref(-1); const total = ref(-1);
const page = ref(0); const page = ref(0);
const data_list = reactive([]); const data_list = reactive([]);
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB