dedsudiyu 5 days ago
parent
commit
f148fec456

+ 5 - 1
pages_safetyEducationExamination/utils/index.js

@@ -133,7 +133,11 @@ export async function confirmCommitmentAndStart(preData, onClose) {
 		console.log('确认2')
 		console.log('确认2')
 		if (data.code === 200) {
 		if (data.code === 200) {
 			console.log('确认3')
 			console.log('确认3')
-			navigateToExamPlay(preData.examId, attemptId, preData.examKind);
+			if (typeof onClose === 'function') {
+				onClose();
+			} else {
+				navigateToExamPlay(preData.examId, attemptId, preData.examKind);
+			}
 		} else {
 		} else {
 			uni.showToast({ title: data.message || '确认失败', icon: 'none' });
 			uni.showToast({ title: data.message || '确认失败', icon: 'none' });
 		}
 		}

+ 250 - 45
pages_safetyEducationExamination/views/exam/onlineExamPlay.vue

@@ -107,12 +107,54 @@
 		<!-- 交卷结果遮罩 -->
 		<!-- 交卷结果遮罩 -->
 		<view v-if="showResult" class="result-shade">
 		<view v-if="showResult" class="result-shade">
 			<view class="result-card">
 			<view class="result-card">
-				<view class="result-title">{{ resultData.title }}</view>
-				<view class="result-score">{{ resultData.score }} 分</view>
-				<view class="result-desc">{{ resultData.desc }}</view>
+				<view class="result-score-wrap">
+					<text class="result-score-num">{{ resultData.score }}</text>
+					<text class="result-score-unit">分</text>
+				</view>
+				<view class="result-score-label">本次考试成绩</view>
+				<view class="result-pass-pill" :class="resultData.isPass ? 'is-pass' : 'is-fail'">
+					<text class="result-pass-text">{{ resultData.isPass ? '考试通过' : '考试未通过' }}</text>
+				</view>
+				<view class="result-grid">
+					<view class="result-grid-item">
+						<view class="result-grid-label">满分</view>
+						<view class="result-grid-value">{{ resultData.totalScore }}</view>
+					</view>
+					<view class="result-grid-item">
+						<view class="result-grid-label">及格分</view>
+						<view class="result-grid-value text-orange">{{ resultData.passScore }}</view>
+					</view>
+					<view class="result-grid-item">
+						<view class="result-grid-label">用时</view>
+						<view class="result-grid-value">{{ resultData.usedTime }}</view>
+					</view>
+				</view>
+				<view class="result-stats">
+					<view class="result-stats-row">
+						<text class="result-stats-label">正确题数</text>
+						<text class="result-stats-value text-green">{{ resultData.correctCount }}</text>
+					</view>
+					<view class="result-stats-row">
+						<text class="result-stats-label">错误题数</text>
+						<text class="result-stats-value text-red">{{ resultData.wrongCount }}</text>
+					</view>
+					<view class="result-stats-row">
+						<text class="result-stats-label">正确率</text>
+						<text class="result-stats-value text-blue">{{ resultData.accuracy }}</text>
+					</view>
+				</view>
 				<view class="result-btns">
 				<view class="result-btns">
-					<view v-if="resultData.canRetake" class="result-btn result-btn-outline" @click="retakeExam">重新考试</view>
-					<view class="result-btn result-btn-primary" @click="closeResult">{{ resultData.canRetake ? '返回' : '确认' }}</view>
+					<view v-if="!resultData.isPass" class="result-btn result-btn-outline" @click="reviewMistakes">查看错题</view>
+					<view v-if="resultData.canRetake" class="result-btn result-btn-primary" @click="retakeExam">重新考试</view>
+					<view v-else class="result-btn result-btn-primary" @click="closeResult">返回工作台</view>
+				</view>
+				<view class="result-cert">
+					<text class="result-cert-icon">🏆</text>
+					<view class="result-cert-info">
+						<view class="result-cert-title">您已获得证书!</view>
+						<view class="result-cert-name">{{ resultData.certName }}</view>
+						<view class="result-cert-btn" @click="goCertificate(resultData.certUrl)">查看证书</view>
+					</view>
 				</view>
 				</view>
 			</view>
 			</view>
 		</view>
 		</view>
@@ -141,6 +183,7 @@
 				groupIndex: 0,
 				groupIndex: 0,
 				questionIndex: 0,
 				questionIndex: 0,
 				remainingSeconds: 0,
 				remainingSeconds: 0,
+				initialSeconds: 0,
 				timer: null,
 				timer: null,
 				hasWarnedTime: false,
 				hasWarnedTime: false,
 				showQuestionNav: false,
 				showQuestionNav: false,
@@ -257,6 +300,7 @@
 					const { data } = await examUserExamGetExamSurplusTime({ attemptId: this.attemptId });
 					const { data } = await examUserExamGetExamSurplusTime({ attemptId: this.attemptId });
 					if (data.code === 200) {
 					if (data.code === 200) {
 						this.remainingSeconds = Number(data.data) || 0;
 						this.remainingSeconds = Number(data.data) || 0;
+						if (!this.initialSeconds) this.initialSeconds = this.remainingSeconds;
 						this.clearTimer();
 						this.clearTimer();
 						this.startTimer();
 						this.startTimer();
 					}
 					}
@@ -422,29 +466,37 @@
 			},
 			},
 			buildResult(resp) {
 			buildResult(resp) {
 				const score = resp.totalScore || 0;
 				const score = resp.totalScore || 0;
-				const pass = score >= (this.examInfo.passScore || 0);
-				if (pass) {
-					this.resultData = {
-						title: '考试通过',
-						score: score,
-						desc: resp.needPrompt === 1 ? '恭喜!本次成绩为最佳记录' : '考试已通过',
-						canRetake: false,
-					};
-				} else if (resp.canContinueExam === 1) {
-					this.resultData = {
-						title: '考试未通过',
-						score: score,
-						desc: `未达及格线 ${this.examInfo.passScore} 分,可重新考试`,
-						canRetake: true,
-					};
-				} else {
-					this.resultData = {
-						title: '考试未通过',
-						score: score,
-						desc: '未达及格线,暂无剩余考试机会',
-						canRetake: false,
-					};
+				const passScore = this.examInfo.passScore || 0;
+				const pass = score >= passScore;
+				const correct = this.pickNumber(resp.correctCount, resp.correctNum);
+				const wrong = this.pickNumber(resp.wrongCount, resp.wrongNum);
+				const total = this.pickNumber(resp.totalQuestionNum, this.totalCount);
+				this.resultData = {
+					score: score,
+					isPass: pass,
+					totalScore: this.examInfo.totalScore || 0,
+					passScore: passScore,
+					usedTime: this.buildUsedTime(),
+					correctCount: correct == null ? '-' : `${correct} 题`,
+					wrongCount: wrong == null ? '-' : `${wrong} 题`,
+					accuracy: (correct == null || !total) ? '-' : `${Math.round((correct / total) * 100)}%`,
+					canRetake: !pass && resp.canContinueExam === 1,
+					certName: resp.certName || '',
+					certUrl: resp.certUrl || '',
+				};
+			},
+			pickNumber(...vals) {
+				for (const v of vals) {
+					if (v !== undefined && v !== null && v !== '') return Number(v);
 				}
 				}
+				return null;
+			},
+			buildUsedTime() {
+				if (!this.initialSeconds) return '-';
+				const used = Math.max(0, this.initialSeconds - Math.floor(this.remainingSeconds));
+				const m = Math.floor(used / 60);
+				const s = used % 60;
+				return `${m}'${s < 10 ? '0' : ''}${s}"`;
 			},
 			},
 			async retakeExam() {
 			async retakeExam() {
 				try {
 				try {
@@ -460,6 +512,7 @@
 						this.groupIndex = 0;
 						this.groupIndex = 0;
 						this.questionIndex = 0;
 						this.questionIndex = 0;
 						this.remainingSeconds = 0;
 						this.remainingSeconds = 0;
+						this.initialSeconds = 0;
 						this.pageLoading = true;
 						this.pageLoading = true;
 						this.loadSidebar();
 						this.loadSidebar();
 					} else {
 					} else {
@@ -474,6 +527,41 @@
 				this.showResult = false;
 				this.showResult = false;
 				uni.navigateBack();
 				uni.navigateBack();
 			},
 			},
+			reviewMistakes() {
+				uni.showToast({ title: '错题查看功能开发中', icon: 'none' });
+			},
+			goCertificate(url) {
+				if (!url) {
+					uni.showToast({ title: '暂无证书', icon: 'none' });
+					return;
+				}
+				uni.showLoading({ title: '加载中...', mask: true });
+				uni.downloadFile({
+					url: url,
+					success: (res) => {
+						if (res.statusCode === 200) {
+							uni.openDocument({
+								filePath: res.tempFilePath,
+								fileType: 'pdf',
+								showMenu: true,
+								fail: () => {
+									uni.showToast({ title: '打开证书失败', icon: 'none' });
+								},
+								complete: () => {
+									uni.hideLoading();
+								}
+							});
+						} else {
+							uni.hideLoading();
+							uni.showToast({ title: '证书加载失败', icon: 'none' });
+						}
+					},
+					fail: () => {
+						uni.hideLoading();
+						uni.showToast({ title: '证书加载失败', icon: 'none' });
+					}
+				});
+			},
 		}
 		}
 	}
 	}
 </script>
 </script>
@@ -794,34 +882,105 @@
 		width 580rpx
 		width 580rpx
 		background #fff
 		background #fff
 		border-radius 24rpx
 		border-radius 24rpx
-		padding 48rpx 40rpx 36rpx
+		padding 40rpx 32rpx 32rpx
 		display flex
 		display flex
 		flex-direction column
 		flex-direction column
 		align-items center
 		align-items center
 
 
-	.result-title
-		font-size 32rpx
+	.result-score-wrap
+		display flex
+		align-items baseline
+		line-height 1
+
+	.result-score-num
+		font-size 112rpx
+		font-weight 900
+		color #1857d4
+
+	.result-score-unit
+		font-size 30rpx
 		font-weight 700
 		font-weight 700
-		color #222
+		color #1857d4
+		margin-left 6rpx
+
+	.result-score-label
+		font-size 24rpx
+		color #8896a7
+		margin-top 4rpx
 		margin-bottom 20rpx
 		margin-bottom 20rpx
 
 
-	.result-score
-		font-size 72rpx
+	.result-pass-pill
+		display inline-flex
+		align-items center
+		justify-content center
+		border-radius 40rpx
+		padding 8rpx 40rpx
+		margin-bottom 28rpx
+
+	.result-pass-pill.is-pass
+		background #f6ffed
+
+	.result-pass-pill.is-fail
+		background #fff5f5
+
+	.result-pass-text
+		font-size 30rpx
 		font-weight 700
 		font-weight 700
-		color #1677ff
-		line-height 1
-		margin-bottom 16rpx
 
 
-	.result-desc
-		font-size 26rpx
-		color #666
+	.result-pass-pill.is-pass .result-pass-text
+		color #10a37f
+
+	.result-pass-pill.is-fail .result-pass-text
+		color #e53e3e
+
+	.result-grid
+		display flex
+		width 100%
+		gap 16rpx
+		margin-bottom 24rpx
+
+	.result-grid-item
+		flex 1
+		background #f4f6fa
+		border-radius 16rpx
+		padding 18rpx 12rpx
 		text-align center
 		text-align center
-		margin-bottom 36rpx
-		line-height 1.6
+
+	.result-grid-label
+		font-size 22rpx
+		color #8896a7
+		margin-bottom 8rpx
+
+	.result-grid-value
+		font-size 28rpx
+		font-weight 500
+		color #1a2233
+
+	.result-stats
+		width 100%
+		background #f4f6fa
+		border-radius 16rpx
+		padding 22rpx 24rpx
+		margin-bottom 28rpx
+		box-sizing border-box
+
+	.result-stats-row
+		display flex
+		justify-content space-between
+		align-items center
+		font-size 26rpx
+		color #4a5568
+
+	.result-stats-row + .result-stats-row
+		margin-top 16rpx
+
+	.result-stats-value
+		font-weight 500
 
 
 	.result-btns
 	.result-btns
 		display flex
 		display flex
 		width 100%
 		width 100%
+		gap 16rpx
 
 
 	.result-btn
 	.result-btn
 		flex 1
 		flex 1
@@ -832,13 +991,59 @@
 		font-size 28rpx
 		font-size 28rpx
 
 
 	.result-btn-outline
 	.result-btn-outline
-		border 1rpx solid #1677ff
-		color #1677ff
-		margin-right 20rpx
+		border 1rpx solid #1857d4
+		color #1857d4
 
 
 	.result-btn-primary
 	.result-btn-primary
-		background #1677ff
+		background #1857d4
+		color #fff
+
+	.result-cert
+		display flex
+		align-items center
+		width 100%
+		margin-top 24rpx
+		padding 24rpx 20rpx
+		background #f4f6fa
+		border-radius 16rpx
+		box-sizing border-box
+
+	.result-cert-icon
+		font-size 56rpx
+		margin-right 20rpx
+
+	.result-cert-info
+		flex 1
+
+	.result-cert-title
+		font-size 28rpx
+		font-weight 500
+		color #1a2233
+		margin-bottom 8rpx
+
+	.result-cert-name
+		font-size 24rpx
+		color #8896a7
+
+	.result-cert-btn
+		display inline-block
+		margin-top 12rpx
+		padding 0 24rpx
+		height 56rpx
+		line-height 56rpx
+		background #1857d4
 		color #fff
 		color #fff
+		font-size 24rpx
+		border-radius 10rpx
+
+	.text-orange
+		color #e67e22
+	.text-green
+		color #10a37f
+	.text-red
+		color #e53e3e
+	.text-blue
+		color #1857d4
 
 
 	.empty-tip
 	.empty-tip
 		text-align center
 		text-align center