Files
tra-app/src/pages/examination/result.vue
T
2025-09-04 09:41:33 +08:00

754 lines
18 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="main_div max_page">
<nav-bar :is_seat="true"></nav-bar>
<view class="nav-div">
<view class="title ellipsis-text">考试结算</view>
<view class="back_div" @click="goto_back()">
<view class="back-icon"></view>
</view>
</view>
<scroll-view
:scroll-y="true"
@scroll="scroll"
:scroll-top="scrollTop"
class="scroll scroll-Y"
:scroll-with-animation="true"
:show-scrollbar="false"
>
<view class="information-details-div" :class="{ unable: flagQuery === 'N' }">
<view class="top">
<view class="left">
<view class="top">得分</view>
<view class="mid" v-show="flagQuery === 'Y'">
<text v-if="pass_status === 0" class="loading-pass">--</text>
<text v-if="pass_status === -1" class="no-pass">{{ result.examGrade }}</text>
<text v-if="pass_status === 1" class="pass">{{ result.examGrade }}</text>
</view>
<view class="bottom" v-if="result.passGrade && flagQuery === 'Y'">
<text class="">通过分数{{ result.passGrade }}</text>
</view>
<view class="unable_text_dev" v-if="flagQuery === 'N'">无法查看</view>
<view class="bottom">
<text>{{ statusText }}</text>
<image
v-if="pass_status === 1"
src="@/static/images/login/clap.png"
mode="widthFix"
style="width: 32rpx"
></image>
</view>
</view>
<view class="right">
<view class="img_div unable_img">
<image :src="statusImage" mode="aspectFit" class="img"></image>
</view>
</view>
</view>
<view class="bottom">
<!-- 考试标题 -->
<view class="title-div">
<view class="title-icon">
<image
src="@/static/images/examination/examination-icon.png"
mode="widthFix"
class="img"
></image>
</view>
<view class="title ellipsis-text">考试{{ result.papersName }}</view>
</view>
<!-- 时间和考试按钮 -->
<view class="time-and-exam-button">
<view class="time-icon">
<image src="@/static/images/examination/duration-icon.png" mode="widthFix" class="img"></image>
</view>
<view class="time">
<!-- 12 15 -->
时长{{ formatSecondsToText(result.examLenTm) }}
</view>
<view class="fl1"></view>
<view class="examination" @click="goExamRanking" v-if="result.mode === 'examination'">
<view>考试排行榜</view>
<view class="back-icon"></view>
</view>
</view>
</view>
</view>
<view class="main-div">
<view class="answer_title">答题详情</view>
<view class="sheet_table_div" v-if="pass_status !== 0 && topicList.length > 0">
<swiper
:indicator-dots="false"
class="sheet_swiper"
:style="{ height: topicList.length > 5 ? '230rpx' : '115rpx' }"
:current="sheet_swiper_index"
@change="sheet_swiper_change"
>
<swiper-item v-for="(group, groupIndex) in groupedTopicList" :key="groupIndex">
<uv-grid :col="5" class="sheet_table" @click="sheet_click">
<uv-grid-item v-for="(item, index) in group" :key="index" :name="groupIndex * 10 + index">
<view class="circle" :class="completedListStatus[groupIndex * 10 + index]">
{{ groupIndex * 10 + index + 1 }}
</view>
</uv-grid-item>
</uv-grid>
</swiper-item>
</swiper>
<view class="swiper-dots"></view>
</view>
<!-- 获取答题结果时候的图片 -->
<view class="loading-pass-image-div" v-if="pass_status === 0 && flagQueryDetail === 'Y'">
<image src="@/static/images/examination/getting_robot.png" mode="widthFix" class="await_img img"></image>
<view class="tip">你的等待会有更精确的结果要耐心哦~</view>
</view>
<view class="loading-pass-image-div" v-if="flagQueryDetail === 'N'">
<image src="@/static/images/examination/unable_img.png" mode="widthFix" class="img"></image>
<view class="tip">您暂无权限查看此试卷详情</view>
</view>
<view v-show="index === sheet_index" :class="getStatusClass(index)" v-for="(item, index) in topicList">
<Subject :item="item" mode="settlement" :clearResult="false" :isPreview="true"></Subject>
</view>
</view>
</scroll-view>
</view>
</template>
<script setup>
import qualified from '@/static/images/examination/qualified.png';
import unqualified from '@/static/images/examination/unqualified.png';
import getting_in from '@/static/images/examination/getting_in.gif';
import { onLoad, onUnload } from '@dcloudio/uni-app';
import { ref, reactive, computed, onMounted, nextTick, getCurrentInstance } from 'vue';
import { queryPaperExamScore, queryCrsExamScore } from '@/api/examination.js';
import Subject from '@/components/examination/subject.vue';
import common from '@/common/common';
const pass_status = ref(0);
const sheet_swiper_index = ref(0);
const result = reactive({
crsId: '',
execId: '',
papersId: '',
mode: '',
passGrade: '',
papersName: '',
examLenTm: '',
examGrade: ''
});
const sheet_swiper_change = (event) => {
sheet_swiper_index.value = event.detail.current;
};
const topicList = reactive([]);
// 答题卡计算属性
const groupedTopicList = computed(() => {
if (!topicList || topicList.length === 0) {
return [];
}
const groups = [];
for (let i = 0; i < topicList.length; i += 10) {
// 从索引i开始,截取最多10个元素
const group = topicList.slice(i, i + 10);
groups.push(group);
}
return groups;
});
// 答题卡判断每道题的状态
const completedListStatus = computed(() => {
return topicList.map((res) => {
if (res.judgeResult === 'P') {
return 'success';
} else if (res.judgeResult === 'U' && res.anserResult !== '') {
return 'error';
}
});
});
const flagQuery = ref('N');
const flagQueryDetail = ref('N');
const scrollTop = ref(0);
const topic = ref();
const sheet_index = ref(0);
const statusImage = computed(() => {
// 根据状态返回对应的图片资源
if (pass_status.value === 1) {
return qualified;
} else if (pass_status.value === -1 || flagQuery.value === 'N') {
return unqualified;
} else if (pass_status.value === 0 && flagQuery.value !== 'N') {
return getting_in;
}
// 可以添加默认图片,防止所有条件都不匹配的情况
return unqualified;
});
const statusText = computed(() => {
if (flagQuery.value === 'N') {
return '您无权限查看此试卷';
} else if (flagQuery.value === 'Y') {
switch (pass_status.value) {
case -1:
return '失败乃成功之母,继续努力';
case 1:
return '考的不错啊,继续加油';
case 0:
return '考试结果计算中...';
default:
return ''; // 处理未定义的状态
}
}
return ''; // 默认返回空文本
});
// 答题卡点击
const sheet_click = (index) => {
sheet_index.value = index;
};
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 goto_back = () => {
common.navigateBack();
};
const getStatusClass = (index) => {
const remainder = index % 3;
if (remainder === 0) return 'in_progress';
if (remainder === 1) return 'completed';
return 'expired';
};
const animationfinish = (event) => {
console.log('event', event);
};
// 定义请求计数器和定时器引用
const requestCount = ref(0);
const timerId = ref(null);
const get_result = () => {
// 检查是否已达到最大请求次数
if (requestCount.value >= 15) {
console.log('已达到最大请求次数(15次),停止请求');
requestCount.value = 0;
return;
}
// 确定请求方法
const req = result.crsId !== '' ? queryCrsExamScore : queryPaperExamScore;
// 抽离请求逻辑为单独函数
const fetchData = () => {
// 每次请求前计数器+1
requestCount.value++;
req({
execId: result.execId
})
.then(({ body }) => {
console.log(`第${requestCount.value}次请求返回`, body);
if (body.isWait === 'Y') {
// 未完成且未达上限,继续请求
if (requestCount.value < 15) {
// 这里用定时器延迟下次请求
timerId.value = setTimeout(fetchData, 2000);
} else {
console.log('已达到最大请求次数,停止等待');
requestCount.value = 0;
}
} else if (body.isWait === 'N') {
// 完成请求,处理结果
pass_status.value = body.examResult === 'P' ? 1 : -1;
result.examGrade = body.examGrade;
result.passGrade = body.passGrade || '';
if (result.examLenTm === undefined) {
result.examLenTm = body.examLenTm;
}
topicList.push(...body.qnsInfoVo);
requestCount.value = 0;
// 触发考试完成事件
}
})
.catch((err) => {
console.error('请求失败', err);
// 请求失败也计入次数,避免无限重试
if (requestCount.value < 15) {
timerId.value = setTimeout(fetchData, 2000);
} else {
// 失败次数达上限,重置计数器
requestCount.value = 0;
}
});
};
// 立即执行第一次请求
fetchData();
};
// 初始化时检查
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;
flagQueryDetail.value = e.flagQueryDetail;
flagQuery.value = e.flagQuery;
if (flagQueryDetail.value === 'Y' || flagQuery.value === 'Y') {
get_result();
}
});
// 页面卸载时清除定时器,终止请求
onUnload(() => {
if (timerId.value) {
clearTimeout(timerId.value);
timerId.value = null;
}
});
const formatSecondsToText = (milliseconds) => {
if (milliseconds === undefined) return '--';
// 尝试将输入转换为数字(处理字符串形式的数字)
const num = Number(milliseconds);
// 验证是否为有效数字且非负数
if (isNaN(num) || num < 0) {
return '0秒';
}
// 将毫秒转换为秒(取整)
const seconds = num;
// 计算小时、分钟、秒
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const secs = Math.floor(seconds % 60);
console.log('secs', secs);
// 存储各部分文本
const parts = [];
if (hours > 0) {
parts.push(`${hours}小时`);
}
if (minutes > 0) {
parts.push(`${minutes}分`);
}
// 确保至少显示秒数,即使为0
parts.push(`${secs}秒`);
// 拼接结果
return parts.join('');
};
</script>
<style scoped lang="scss">
// 无法查看
.unable {
.unable_text_dev {
font-weight: 600;
font-size: 46rpx;
color: #9f9fa1;
text-align: left;
}
.unable_img {
filter: blur(4px); /* 模糊程度,值越大越模糊 */
}
}
$bg-margin-left: 30rpx;
.answer_title {
font-weight: 600;
font-size: 32rpx;
color: #000000;
line-height: 44rpx;
text-align: left;
margin-left: $bg-margin-left;
}
.scroll-Y {
// max-height: calc(100vh - var(--status-bar-height) - 70rpx - 70rpx - 40rpx - 64rpx - 68rpx - 60rpx);
height: calc(100vh - var(--status-bar-height) - 70rpx - 10rpx);
// background-color: red;
}
.item {
border-radius: 16rpx;
margin: 18rpx 0;
padding: 22rpx 30rpx 34rpx 36rpx;
background-color: #fff;
display: flex;
flex-direction: column;
.top {
display: flex;
align-items: center;
.type {
width: 108rpx;
height: 46rpx;
background: #267eff;
border-radius: 8rpx;
font-family: PingFangSC;
font-weight: 500;
font-size: 25rpx;
color: #ffffff;
text-align: center;
line-height: 46rpx;
}
.score {
margin-left: 24rpx;
font-family: ArialMT;
font-size: 30rpx;
color: #333333;
text-align: left;
}
.right {
display: flex;
align-items: center;
.right_text {
font-family: PingFangSC;
font-weight: 600;
font-size: 28rpx;
color: #333;
line-height: 40rpx;
text-align: left;
font-style: normal;
}
}
}
.text {
font-family: PingFangSC;
font-weight: 500;
font-size: 30rpx;
color: #333333;
line-height: 48rpx;
text-align: left;
font-style: normal;
margin-top: 20rpx;
}
}
.sheet_table_div {
position: sticky;
left: 0;
top: -2rpx;
width: 100%;
z-index: 600;
background-color: #fff;
}
.sheet_swiper {
// max-height: 230rpx;
}
.swiper-dots {
display: none;
height: 10rpx;
width: 80rpx;
margin: 0 auto;
border-radius: 10rpx;
background-color: #fce8e9;
}
.sheet_table {
padding: 0 12rpx;
width: 100%;
display: flex;
.circle {
width: 78rpx;
height: 78rpx;
border-radius: 100%;
display: flex;
align-items: center;
justify-content: center;
border: 2rpx solid #e5e5e5;
margin: 20rpx;
}
.circle.success {
// 已完成
background-color: #e7f8ed;
border-color: #15b859;
color: #15b859;
}
.circle.error {
// 已完成
background-color: #fce8e9;
border-color: #f5212d;
color: #f5212d;
}
.current {
// background-color: rgba(0, 102, 255, 0.6);
// border-color: #0066ff;
// color: #ffffff;
}
}
// 主内容框
.main-div {
display: flex;
flex-direction: column;
padding: 34rpx 0 20rpx 0;
margin: 30rpx 28rpx 20rpx 28rpx;
border-radius: 16rpx;
position: relative;
background-color: #fff;
// min-height: calc(100vh - var(--status-bar-height) - 70rpx - 380rpx - 100rpx);
.main-div-title {
width: 100%;
height: 44rpx;
font-family: PingFangSC;
font-weight: 700;
font-size: 33rpx;
color: #000000;
line-height: 44rpx;
text-align: left;
font-style: normal;
margin-left: 48rpx;
}
.loading-pass-image-div {
display: flex;
flex-direction: column;
align-items: center;
padding-top: 80rpx;
padding-bottom: 100rpx;
// background-color: red;
.img {
width: 52vw;
}
.await_img {
margin-left: -100rpx;
}
.tip {
font-family: PingFangSC;
font-weight: 400;
font-size: 30rpx;
color: #666666;
line-height: 40rpx;
text-align: center;
font-style: normal;
}
}
}
// 信息框
.information-details-div {
display: flex;
flex-direction: column;
padding: 34rpx 20rpx 30rpx 48rpx;
margin: 44rpx 28rpx 0 28rpx;
border-radius: 16rpx;
position: relative;
background-color: #fff;
opacity: 0.8;
box-shadow: 2rpx -2rpx 2rpx #fff;
& > .top {
display: flex;
justify-content: space-between;
& > .left {
display: flex;
flex-direction: column;
justify-content: space-between;
& > .top {
font-family: PingFangSC;
font-weight: 700;
font-size: 34rpx;
color: #000000;
line-height: 34rpx;
text-align: left;
font-style: normal;
}
& > .mid {
font-weight: 700;
font-size: 66rpx;
line-height: 66rpx;
text-align: left;
font-style: normal;
height: 100rpx;
// margin-top: 34rpx;
// margin-bottom: 30rpx;
}
& > .bottom {
display: flex;
align-items: center;
font-family: PingFangSC;
font-weight: 400;
font-size: 24rpx;
color: #999999;
line-height: 24rpx;
text-align: left;
font-style: normal;
}
}
& > .right {
margin-left: 20rpx;
.img_div {
.img {
width: 270rpx;
height: 190rpx;
}
}
}
}
& > .bottom {
font-family: PingFangSC;
font-weight: 400;
font-size: 26rpx;
color: #666666;
line-height: 26rpx;
text-align: left;
font-style: normal;
margin-top: 26rpx;
.title-icon,
.time-icon {
display: flex;
align-items: center;
padding-right: 12rpx;
.img {
width: 26rpx;
}
}
.title-div {
display: flex;
align-items: center;
padding-bottom: 24rpx;
}
.time-and-exam-button {
display: flex;
align-items: center;
padding-right: 12rpx;
.examination {
display: flex;
align-items: center;
color: #0066ff;
.back-icon {
position: relative;
width: 40rpx;
height: 40rpx;
cursor: pointer;
}
.back-icon::before,
.back-icon::after {
content: '';
position: absolute;
transition: all 0.3s ease;
}
.back-icon::after {
top: 25%;
left: 25%;
width: 40%;
height: 40%;
border-top: 4rpx solid #0066ff;
border-left: 4rpx solid #0066ff;
transform: rotate(135deg);
}
}
}
}
.loading-pass {
color: #0066ff;
font-family: DINAlternate, DINAlternate;
font-weight: bold;
font-size: 65rpx;
color: #0066ff;
letter-spacing: 12rpx;
}
.no-pass {
color: #d70c18;
}
.pass {
color: #0066ff;
}
}
.nav-div {
position: relative;
.title {
height: 70rpx;
font-family: PingFangSC;
font-weight: 500;
font-size: 34rpx;
color: #000000;
line-height: 70rpx;
text-align: center;
font-style: normal;
}
.back_div {
margin-right: 10rpx;
position: absolute;
left: 25rpx;
top: 0;
padding: 15rpx;
.back-icon {
position: relative;
width: 40rpx;
height: 40rpx;
cursor: pointer;
}
.back-icon::before,
.back-icon::after {
content: '';
position: absolute;
transition: all 0.3s ease;
}
.back-icon::after {
top: 25%;
left: 25%;
width: 40%;
height: 40%;
border-top: 4rpx solid #000;
border-left: 4rpx solid #000;
transform: rotate(-45deg);
}
}
}
.main_div {
display: flex;
flex-direction: column;
background-color: #f0f5f9;
/* 添加30度角的斜向上渐变,高度限制为400px */
background-image: linear-gradient(30deg, #f0f5f9, #ebf1ff);
/* 渐变背景高度400px,宽度充满容器 */
background-size: 100% 600rpx;
/* 防止渐变背景重复 */
background-repeat: no-repeat;
min-height: 100vh;
margin: 0;
overflow: hidden;
position: relative;
}
</style>