dedsudiyu 1 day ago
parent
commit
a56501a7d2
26 changed files with 1878 additions and 2110 deletions
  1. 13 0
      pages.json
  2. 64 0
      pages_safetyEducationExamination/api/index.js
  3. 33 49
      pages_safetyEducationExamination/component/richTextPdf.vue
  4. 0 468
      pages_safetyEducationExamination/component/workbench/BindLabModal.vue
  5. 0 41
      pages_safetyEducationExamination/component/workbench/index.vue
  6. 0 513
      pages_safetyEducationExamination/component/workbench/userInfoCard.vue
  7. 0 346
      pages_safetyEducationExamination/component/workbench/workbenchStatsCards.vue
  8. 2 1
      pages_safetyEducationExamination/views/exam/incorrectQuestionsList.vue
  9. 14 5
      pages_safetyEducationExamination/views/exam/index.vue
  10. 18 3
      pages_safetyEducationExamination/views/exam/mockExamInfo.vue
  11. 24 4
      pages_safetyEducationExamination/views/exam/onlineExamInfo.vue
  12. 5 4
      pages_safetyEducationExamination/views/exam/onlineExamPlay.vue
  13. 64 49
      pages_safetyEducationExamination/views/exam/scoreDetail.vue
  14. 58 11
      pages_safetyEducationExamination/views/exam/scoresList.vue
  15. 0 101
      pages_safetyEducationExamination/views/home.vue
  16. 655 401
      pages_safetyEducationExamination/views/home/component/BindLabModal.vue
  17. 9 2
      pages_safetyEducationExamination/views/home/component/academicYearStats.vue
  18. 59 18
      pages_safetyEducationExamination/views/home/component/dataStatisticsReport.vue
  19. 4 2
      pages_safetyEducationExamination/views/home/component/examTask.vue
  20. 14 7
      pages_safetyEducationExamination/views/home/component/specialExam.vue
  21. 28 67
      pages_safetyEducationExamination/views/home/component/userInfoCard.vue
  22. 3 2
      pages_safetyEducationExamination/views/home/index.vue
  23. 3 1
      pages_safetyEducationExamination/views/practice/index.vue
  24. 783 0
      pages_safetyEducationExamination/views/practice/practicePlay.vue
  25. 13 8
      pages_safetyEducationExamination/views/webViewPage/courseLearning.vue
  26. 12 7
      pages_safetyEducationExamination/views/webViewPage/coursewareLearning.vue

+ 13 - 0
pages.json

@@ -1322,6 +1322,19 @@
 					}
 					}
 				},
 				},
 				{
 				{
+					"path": "views/practice/practicePlay",
+					"style": {
+						"navigationBarTitleText": "刷题练习",
+						"navigationBarButtons": [
+							{
+								"type": "text",
+								"text": "题号",
+								"color": "#1857d4"
+							}
+						]
+					}
+				},
+				{
 					"path": "views/approval/index",
 					"path": "views/approval/index",
 					"style": {
 					"style": {
 						"navigationBarTitleText": "实验室审批"
 						"navigationBarTitleText": "实验室审批"

+ 64 - 0
pages_safetyEducationExamination/api/index.js

@@ -173,6 +173,30 @@ export const examUserPracticeList = (data) => {
 		data: { ...data }
 		data: { ...data }
 	})
 	})
 };
 };
+//刷题练习-题目面板(左侧题号列表)
+export const examUserPracticePanel = (data) => {
+	return apiResquest({
+		url: `/exam/user/practice/panel`,
+		method: 'GET',
+		data: { ...data }
+	})
+};
+//刷题练习-单题详情
+export const examUserPracticeQuestionDetail = (data) => {
+	return apiResquest({
+		url: `/exam/user/practice/questionDetail`,
+		method: 'GET',
+		data: { ...data }
+	})
+};
+//刷题练习-提交单题答案
+export const examUserPracticeSubmit = (data) => {
+	return apiResquest({
+		url: `/exam/user/practice/submit`,
+		method: 'POST',
+		data: { ...data }
+	})
+};
 //查询毕业报告
 //查询毕业报告
 export const examElLabEduRelationGraduationReport = (data) => {
 export const examElLabEduRelationGraduationReport = (data) => {
   return apiResquest({
   return apiResquest({
@@ -181,6 +205,14 @@ export const examElLabEduRelationGraduationReport = (data) => {
 		data: { ...data }
 		data: { ...data }
   })
   })
 }
 }
+//绑定实验室
+export const examElLabEduRelationBind = (data) => {
+	return apiResquest({
+		url: `/exam/elLabEduRelation/bind`,
+		method: 'POST',
+		data: { ...data }
+	})
+};
 //查询实验室负责人 待审批的列表
 //查询实验室负责人 待审批的列表
 export const examElLabEduRelationAuditList = (data) => {
 export const examElLabEduRelationAuditList = (data) => {
 	return apiResquest({
 	return apiResquest({
@@ -228,4 +260,36 @@ export const examElUserExamAttemptAttemptRecord = (data) => {
 		method: 'POST',
 		method: 'POST',
 		data: { ...data }
 		data: { ...data }
 	})
 	})
+};
+//考试记录详情-试卷详情
+export const examElExamUserResultMyScoreDetail = (data) => {
+	return apiResquest({
+		url: `/exam/elExamUserResult/myScoreDetail`,
+		method: 'POST',
+		data: { ...data }
+	})
+};
+//考试记录详情-题目列表
+export const examElExamAttemptPaperQuestionPage = (data) => {
+	return apiResquest({
+		url: `/exam/elExamAttempt/paper-question-page`,
+		method: 'POST',
+		data: { ...data }
+	})
+};
+//在线考试-详情
+export const examUserExamDetail = (data) => {
+	return apiResquest({
+		url: `/exam/user/exam/detail`,
+		method: 'POST',
+		data: { ...data }
+	})
+};
+//模拟考试-详情
+export const examUserExamMockDetail = (data) => {
+	return apiResquest({
+		url: `/exam/user/exam/mockDetail`,
+		method: 'POST',
+		data: { ...data }
+	})
 };
 };

+ 33 - 49
pages_safetyEducationExamination/component/richTextPdf.vue

@@ -16,6 +16,10 @@
 			content: {
 			content: {
 				type: String,
 				type: String,
 				default: ''
 				default: ''
+			},
+			examId: {
+				type: [String, Number],
+				default: ''
 			}
 			}
 		},
 		},
 		data() {
 		data() {
@@ -46,7 +50,7 @@
 					// 由于小程序无法直接将 rich-text 渲染到 canvas,
 					// 由于小程序无法直接将 rich-text 渲染到 canvas,
 					// 这里采用保存为图片的简化方案
 					// 这里采用保存为图片的简化方案
 
 
-					await this._saveAsImage();
+					this._saveAsImage();
 
 
 				} catch (error) {
 				} catch (error) {
 					console.error('PDF 生成失败:', error);
 					console.error('PDF 生成失败:', error);
@@ -60,55 +64,35 @@
 				}
 				}
 			},
 			},
 
 
-			// 简化方案:保存为图片
-			async _saveAsImage() {
-				// 微信小程序中,rich-text 无法直接转为 canvas
-				// 需要使用以下方案之一:
-				// 1. 后端生成PDF接口
-				// 2. 使用 web-view 加载HTML后截图
-				// 3. 提示用户长按保存或分享
-
-				uni.showModal({
-					title: '提示',
-					content: '微信小程序暂不支持直接生成PDF,建议在电脑端下载或长按保存内容',
-					showCancel: false
-				});
+			// 调用后端PDF下载接口
+			_saveAsImage() {
+				const { config } = require('@/api/request/config.js');
+				const token = uni.getStorageSync('token');
+				const url = config.base_url + '/exam/user/exam/commitment/download?examId=' + this.examId;
 
 
-				// 如果有后端PDF生成接口,可以这样调用:
-				// const result = await this._callBackendPdfApi();
-				// if (result.pdfUrl) {
-				//   uni.downloadFile({
-				//     url: result.pdfUrl,
-				//     success: (res) => {
-				//       uni.saveFile({
-				//         tempFilePath: res.tempFilePath,
-				//         success: () => {
-				//           uni.showToast({ title: '保存成功' });
-				//         }
-				//       });
-				//     }
-				//   });
-				// }
-			},
-
-			// 调用后端PDF生成接口(示例)
-			async _callBackendPdfApi() {
-				return new Promise((resolve, reject) => {
-					uni.request({
-						url: '/api/generate-pdf', // 替换为实际接口
-						method: 'POST',
-						data: {
-							htmlContent: this.content
-						},
-						success: (res) => {
-							if (res.data.code === 200) {
-								resolve(res.data.data);
-							} else {
-								reject(new Error(res.data.message));
-							}
-						},
-						fail: reject
-					});
+				uni.downloadFile({
+					url,
+					header: {
+						'Authorization': token
+					},
+					success: (res) => {
+						if (res.statusCode === 200) {
+							uni.saveFile({
+								tempFilePath: res.tempFilePath,
+								success: () => {
+									uni.showToast({ title: '下载成功', icon: 'success' });
+								},
+								fail: () => {
+									uni.showToast({ title: '保存失败,请重试', icon: 'none' });
+								}
+							});
+						} else {
+							uni.showToast({ title: 'PDF下载失败', icon: 'none' });
+						}
+					},
+					fail: () => {
+						uni.showToast({ title: 'PDF下载失败,请重试', icon: 'none' });
+					}
 				});
 				});
 			}
 			}
 		}
 		}

+ 0 - 468
pages_safetyEducationExamination/component/workbench/BindLabModal.vue

@@ -1,468 +0,0 @@
-<template>
-	<!-- 弹窗遮罩层 -->
-	<view class="bind-lab-modal" v-if="visible">
-		<view class="bind-lab-content" @tap.stop>
-			<!-- 标题栏 -->
-			<view class="modal-header">
-				<text class="modal-title">绑定实验室</text>
-				<text class="modal-close" @click="$emit('close')">×</text>
-			</view>
-
-			<view class="modal-body">
-				<!-- 搜索框区域:使用 view 包裹以兼容点击事件 -->
-				<view class="search-box" @tap="handleFocus">
-					<input class="search-input" type="text" placeholder="输入实验室名称、房号或楼栋关键词搜索…" v-model="keyword"
-						@input="handleSearch" />
-					<text class="search-icon">⌄</text>
-				</view>
-
-				<!-- 搜索提示 -->
-				<view class="search-tip">
-					<text>此处请选择需要后续进入的科研实验室的名称,输入实验室名称、房号或楼栋关键词,再从检索结果中选择目标实验室。</text>
-				</view>
-
-				<!-- 搜索结果下拉 -->
-				<scroll-view v-if="showDropdown" scroll-y class="bind-lab-dropdown">
-					<!-- 情况1:有数据,显示列表 -->
-					<view v-if="searchList.length > 0">
-						<view class="bind-lab-option" v-for="(lab, index) in searchList" :key="index" @click="selectLab(lab)">
-							<text>{{ lab.deptName }}{{ lab.subName }}{{ lab.roomNum?'('+lab.roomNum+')':'' }}{{ lab.buildName }}{{ lab.floorName }}</text>
-						</view>
-					</view>
-					<!-- 情况2:无数据,显示暂无数据 -->
-					<view v-else class="bind-lab-empty">
-						<text>暂无数据</text>
-					</view>
-				</scroll-view>
-
-				<!-- 实验室信息表格 -->
-				<view class="bind-lab-info" v-if="selectedLab">
-					<view class="info-row">
-						<view class="info-label">实验室名称</view>
-						<view class="info-value text-bold">{{ selectedLab.labName }}</view>
-						<view class="info-label">所在位置</view>
-						<view class="info-value text-bold">{{ selectedLab.location }}</view>
-					</view>
-					<view class="info-row">
-						<view class="info-label">负责人</view>
-						<view class="info-value">{{ selectedLab.head }}</view>
-						<view class="info-label">所属单位</view>
-						<view class="info-value">{{ selectedLab.dept }}</view>
-					</view>
-					<view class="info-row">
-						<view class="info-label">安全分级</view>
-						<view class="info-value">{{ selectedLab.level }}</view>
-						<view class="info-label">安全分类</view>
-						<view class="info-value">{{ selectedLab.category }}</view>
-					</view>
-					<view class="info-row">
-						<view class="info-label">实验室类型</view>
-						<view class="info-value full-width">{{ selectedLab.type }}</view>
-					</view>
-				</view>
-
-				<!-- 无需进入实验室 -->
-				<view class="no-lab-box">
-					<view class="checkbox-row" @click="isNoLabChecked = !isNoLabChecked">
-						<view class="custom-checkbox" :class="{ 'checked': isNoLabChecked }"></view>
-						<text class="no-lab-title">无需进入实验室</text>
-					</view>
-					<view class="no-lab-desc">
-						<text>人文、体育等相关学院,无需进入实验室的新研究生可选择此项,选择后学时要求为4小时,需通过应知应会考试。</text>
-					</view>
-				</view>
-
-				<!-- 阅读协议 -->
-				<view class="agree-row" @click="isAgreeChecked = !isAgreeChecked">
-					<view class="custom-checkbox" :class="{ 'checked': isAgreeChecked }"></view>
-					<view class="agree-text">
-						<text>我已阅读并同意</text>
-						<text class="agree-link">《新生绑定实验室须知及责任说明》</text>
-					</view>
-				</view>
-
-				<!-- 底部按钮 -->
-				<view class="modal-footer">
-					<view class="btn-cancel" @click="$emit('close')">取消</view>
-					<view class="btn-confirm" @click="handleConfirm">确认绑定</view>
-				</view>
-			</view>
-		</view>
-	</view>
-</template>
-
-<script>
-	import {
-		examElLabEduRelationLabLists
-	} from '@/pages_safetyEducationExamination/api/index.js'
-
-	export default {
-		name: 'BindLabModal',
-		props: {
-			visible: {
-				type: Boolean,
-				default: false
-			}
-		},
-		data() {
-			return {
-				keyword: '',
-				showDropdown: false,
-				searchList: [],
-				selectedLab: null,
-				isNoLabChecked: false,
-				isAgreeChecked: false,
-				searchTimer: null
-			}
-		},
-		watch: {
-			visible(val) {
-				if (val) {
-					this.resetState();
-				}
-			}
-		},
-		methods: {
-			resetState() {
-				this.keyword = '';
-				this.showDropdown = false;
-				this.searchList = [];
-				this.selectedLab = null;
-				this.isNoLabChecked = false;
-				this.isAgreeChecked = false;
-			},
-			// 搜索输入处理
-			handleSearch() {
-				clearTimeout(this.searchTimer);
-				const trimKeyword = this.keyword.trim();
-
-				// 逻辑1:少于2个字符,隐藏下拉框
-				if (trimKeyword.length < 2) {
-					this.showDropdown = false;
-					return;
-				}
-
-				// 逻辑2:防抖请求
-				this.searchTimer = setTimeout(async () => {
-					try {
-						const res = await examElLabEduRelationLabLists({
-							searchValue: trimKeyword
-						});
-						if (res.data.code === 200) {
-							this.searchList = res.data.data || [];
-							// 只要有搜索结果(哪怕为空数组),都显示下拉框,由模板决定显示列表还是“暂无数据”
-							this.showDropdown = true;
-						} else {
-							this.searchList = [];
-							this.showDropdown = true; // 接口报错也显示“暂无数据”
-						}
-					} catch (e) {
-						console.error('搜索失败', e);
-						this.searchList = [];
-						this.showDropdown = true;
-					}
-				}, 300);
-			},
-			// 搜索框聚焦/点击处理
-			handleFocus() {
-				// 逻辑3:点击时,如果有关键词且之前搜过有结果,直接显示列表
-				// 注意:这里需要加一个延时,确保 input 获取焦点后再显示,避免键盘弹起冲突
-				setTimeout(() => {
-					if (this.keyword.trim().length >= 2 && this.searchList.length > 0) {
-						this.showDropdown = true;
-					}
-				}, 100);
-			},
-			selectLab(lab) {
-				this.selectedLab = lab;
-				this.showDropdown = false;
-				this.keyword = lab.labName;
-			},
-			handleConfirm() {
-				if (!this.isAgreeChecked) {
-					return uni.showToast({
-						title: '请先阅读并同意协议',
-						icon: 'none'
-					});
-				}
-				if (!this.selectedLab && !this.isNoLabChecked) {
-					return uni.showToast({
-						title: '请选择实验室或勾选无需进入',
-						icon: 'none'
-					});
-				}
-				this.$emit('bind-success');
-				this.$emit('close');
-				uni.showToast({
-					title: '绑定成功',
-					icon: 'success'
-				});
-			}
-		}
-	}
-</script>
-
-<style lang="stylus" scoped>
-	.bind-lab-modal {
-		position: fixed;
-		top: 0;
-		left: 0;
-		right: 0;
-		bottom: 0;
-		z-index: 800;
-		background: rgba(0, 0, 0, 0.5);
-	}
-
-	.bind-lab-content {
-		position: absolute;
-		top: 50%;
-		left: 50%;
-		transform: translate(-50%, -50%);
-		background: #ffffff;
-		border-radius: 24rpx;
-		width: 85%;
-		max-width: 840rpx;
-		min-height: 400rpx;
-		max-height: 90vh;
-		overflow-y: auto;
-		box-shadow: 0 16rpx 64rpx rgba(0, 0, 0, 0.18);
-		box-sizing: border-box;
-	}
-
-	.modal-header {
-		display: flex;
-		align-items: center;
-		justify-content: space-between;
-		padding: 32rpx 40rpx 24rpx;
-	}
-
-	.modal-title {
-		font-size: 34rpx;
-		font-weight: 700;
-		color: #1a1a1a;
-	}
-
-	.modal-close {
-		font-size: 40rpx;
-		color: #999;
-		line-height: 1;
-	}
-
-	.modal-body {
-		padding: 0 40rpx 32rpx;
-	}
-
-	.search-box {
-		position: relative;
-		margin-bottom: 16rpx;
-		/* 确保点击区域覆盖整个输入框 */
-		z-index: 10;
-	}
-
-	.search-input {
-		width: 100%;
-		box-sizing: border-box;
-		height: 80rpx;
-		line-height: 80rpx;
-		padding: 0 42rpx 0 24rpx;
-		border: 1px solid #d9d9d9;
-		border-radius: 16rpx;
-		font-size: 26rpx;
-		color: #333;
-		background: #fafafa;
-		/* 确保 input 在最上层,可点击 */
-		position: relative;
-		z-index: 11;
-	}
-
-	.search-icon {
-		position: absolute;
-		right: 20rpx;
-		top: 40%;
-		transform: translateY(-50%);
-		color: #bbb;
-		font-size: 30rpx;
-		pointer-events: none;
-	}
-
-	.search-tip {
-		font-size: 22rpx;
-		color: #1677ff;
-		margin-bottom: 24rpx;
-		line-height: 1.5;
-	}
-
-	/* 下拉列表样式 */
-	.bind-lab-dropdown {
-		max-height: 600rpx;
-		border: 1px solid #e8e8e8;
-		border-radius: 16rpx;
-		margin-bottom: 24rpx;
-		background: #fff;
-		box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
-		z-index: 99;
-		position: relative;
-	}
-
-	.bind-lab-option {
-		padding: 24rpx 28rpx;
-		font-size: 26rpx;
-		color: #333;
-		border-bottom: 1px solid #f0f0f0;
-		box-sizing: border-box;
-
-		&:last-child {
-			border-bottom: none;
-		}
-
-		&:active {
-			background: #f9f9f9;
-		}
-	}
-
-	/* 暂无数据样式 */
-	.bind-lab-empty {
-		padding: 30rpx 0;
-		text-align: center;
-		color: #999;
-		font-size: 26rpx;
-	}
-
-	.bind-lab-info {
-		border: 1px solid #e8e8e8;
-		border-radius: 16rpx;
-		overflow: hidden;
-		margin-bottom: 28rpx;
-	}
-
-	.info-row {
-		display: flex;
-		border-bottom: 1px solid #f0f0f0;
-
-		&:last-child {
-			border-bottom: none;
-		}
-	}
-
-	.info-label {
-		padding: 18rpx 24rpx;
-		color: #888;
-		background: #fafafa;
-		width: 28%;
-		font-size: 24rpx;
-		white-space: nowrap;
-	}
-
-	.info-value {
-		padding: 18rpx 24rpx;
-		color: #333;
-		font-size: 24rpx;
-		flex: 1;
-	}
-
-	.full-width {
-		flex: 3;
-	}
-
-	.no-lab-box {
-		background: #f6f8ff;
-		border-radius: 16rpx;
-		padding: 24rpx 28rpx;
-		margin-bottom: 28rpx;
-	}
-
-	.checkbox-row {
-		display: flex;
-		align-items: flex-start;
-		gap: 16rpx;
-	}
-
-	.custom-checkbox {
-		position: relative;
-		width: 32rpx;
-		height: 32rpx;
-		border-radius: 6rpx;
-		border: 2rpx solid #ccc;
-		margin-top: 4rpx;
-		flex-shrink: 0;
-		box-sizing: border-box;
-
-		&.checked {
-			background: #1677ff;
-			border-color: #1677ff;
-
-			&::after {
-				content: '';
-				position: absolute;
-				top: 45%;
-				left: 20%;
-				width: 10rpx;
-				height: 20rpx;
-				border: solid #ffffff;
-				border-width: 0 3rpx 3rpx 0;
-				transform: rotate(45deg) translate(-50%, -50%);
-			}
-		}
-	}
-
-	.no-lab-title {
-		font-size: 26rpx;
-		font-weight: 600;
-		color: #1a1a1a;
-	}
-
-	.no-lab-desc {
-		font-size: 22rpx;
-		color: #888;
-		margin-top: 10rpx;
-		line-height: 1.6;
-		padding-left: 40rpx;
-	}
-
-	.agree-row {
-		display: flex;
-		align-items: center;
-		gap: 16rpx;
-		margin-bottom: 36rpx;
-	}
-
-	.agree-text {
-		display: flex;
-		font-size: 24rpx;
-		color: #555;
-	}
-
-	.agree-link {
-		color: #1677ff;
-		font-weight: 500;
-	}
-
-	.modal-footer {
-		display: flex;
-		gap: 20rpx;
-		justify-content: flex-end;
-	}
-
-	.btn-cancel,
-	.btn-confirm {
-		padding: 16rpx 44rpx;
-		border-radius: 12rpx;
-		font-size: 26rpx;
-	}
-
-	.btn-cancel {
-		border: 1px solid #d9d9d9;
-		background: #fff;
-		color: #555;
-	}
-
-	.btn-confirm {
-		background: #1677ff;
-		color: #fff;
-		font-weight: 600;
-	}
-
-	.text-bold {
-		font-weight: 700;
-	}
-</style>

+ 0 - 41
pages_safetyEducationExamination/component/workbench/index.vue

@@ -1,41 +0,0 @@
-<template>
-	<view class='safetyEducationExamination-workbench'>
-		<user-info-card></user-info-card>
-		<Workbench-stats-cards></Workbench-stats-cards>
-	</view>
-</template>
-
-<script>
-	import {
-		userInfoCard
-	} from '@/pages_safetyEducationExamination/component/workbench/userInfoCard'
-	import {
-		workbenchStatsCards
-	} from '@/pages_safetyEducationExamination/component/workbench/workbenchStatsCards.vue'
-	export default {
-		components: {
-			userInfoCard,
-			workbenchStatsCards,
-		},
-		data() {
-			return {
-				
-			}
-		},
-		created() {
-			
-		},
-		methods: {
-			
-		}
-	}
-</script>
-
-<style lang="stylus" scoped>
-	.safetyEducationExamination-workbench {
-		flex:1;
-		display: flex;
-		flex-direction: column;
-		overflow-y:scroll;
-	}
-</style>

+ 0 - 513
pages_safetyEducationExamination/component/workbench/userInfoCard.vue

@@ -1,513 +0,0 @@
-<template>
-	<view class='safetyEducationExamination-workbench-userInfoCard'>
-		<!-- 顶部用户信息卡片 -->
-		<view class="top-max-big-box">
-			<img v-if="userInfo.avatar" class="avatar-img" :src="baseUrl+userInfo.avatar">
-			<img v-else class="avatar-img" :src="imagesUrl('commonality/icon_01.png')">
-			<view class="center-info-box">
-				<view class="name-box">
-					<view>{{userInfo.userName}}</view>
-					<view>奖励分 {{userInfo.totalPoints}}</view>
-					<view>信用分 {{userInfo.totalPoints}}</view>
-				</view>
-				<view class="account-box">
-					<view>{{userInfo.userType == 2?userInfo.educationName:(userInfo.userType == 1?'教职工':'系统用户')}}</view>
-					<view> · </view>
-					<view>{{userInfo.account}}</view>
-				</view>
-				<view class="dept-box">
-					<view>{{userInfo.deptName}}</view>
-				</view>
-			</view>
-			<!-- <view class="right-type-box">
-				<view></view>
-				<view>正常</view>
-			</view> -->
-		</view>
-
-		<!-- 底部详情卡片 -->
-		<view class="bottom-max-big-box">
-			<!-- 1. 绑定实验室 -->
-			<view class="lab-bind-box" v-if="userInfo.userType == 2">
-				<view class="lab-title">{{userInfo.bound?'实验室信息':'绑定实验室'}}</view>
-				<view class="lab-info-row">
-					<view class="dot dot-green" v-if="userInfo.bound"></view>
-					<view class="dot dot-green" style="background-color: #999;" v-if="!userInfo.bound"></view>
-					<text class="lab-name" v-if="userInfo.bound">{{ userInfo.labName }}</text>
-					<text class="lab-name" style="color:#999;" v-if="!userInfo.bound&&userInfo.educationType == 1">暂无绑定实验室</text>
-					<text class="lab-name" style="color:#999;" v-if="!userInfo.bound&&userInfo.educationType == 2">{{userInfo.isFinishExam?'暂无绑定实验室':'请先完成入学考试'}}</text>
-					<text class="tag" style="color:#1857d4;font-size:28rpx;font-weight:600;" v-if="(!userInfo.bound&&userInfo.educationType == 1)||(!userInfo.bound&&userInfo.educationType == 2&&userInfo.isFinishExam)" @click="shadeButton(4)">去绑定</text>
-					<text class="tag tag-green" v-if="userInfo.bound">{{userInfo.bindStatus}}</text>
-				</view>
-			</view>
-
-			<!-- 2. 学年进度 -->
-			<view class="year-progress-card">
-				<view class="card-header">
-					<view class="tags-group">
-						<text class="tag tag-blue">{{userInfo.gradeRange}}</text>
-						<text class="tag" :style="'color:'+userInfo.levelColor+';border:1rpx solid '+userInfo.levelColor+';'"
-							v-if="userInfo.levelName">{{userInfo.levelName}}</text>
-						<text class="tag tag-gray" v-if="userInfo.typeName">{{userInfo.typeName}}</text>
-					</view>
-				</view>
-				<view class="progress-info" v-if="userInfo.totalStudyHours>0">
-					<view class="info-row">
-						<text class="label">当前学年学时完成进度</text>
-						<text class="value text-blue text-bold">{{ userInfo.completedStudyHours }} / {{ userInfo.totalStudyHours }} 学时</text>
-					</view>
-					<view class="progress-wrap">
-						<view class="progress-bar" :style="{ width: (userInfo.studyHourRate) + '%' }"></view>
-					</view>
-				</view>
-				<view class="card-footer">
-					<view class="footer-left">
-						<text class="text-sm text-gray">{{userInfo.totalStudyHours>0?'学时完成度':'学时要求'}}</text>
-						<text class="text-bold text-blue">{{userInfo.totalStudyHours>0?userInfo.studyHourRate+'%':'无'}}</text>
-					</view>
-				</view>
-				<view class="card-footer" style="margin-top:10rpx;">
-					<view class="footer-right">
-						<text class="text-sm text-gray">目标考试</text>
-						<text class="exam-link"
-							@click="handleExamClick">{{userInfo.targetExamName?userInfo.targetExamName:'无'}}</text>
-					</view>
-				</view>
-			</view>
-
-			<!-- 3. 任务状态 -->
-			<view class="task-status-box">
-				<view class="task-title">当前学年任务状态</view>
-				<view class="task-grid">
-					<view class="task-item">
-						<view class="task-label" :class="userInfo.examPassStatus==1?'task-item-active':''">考试状态</view>
-						<text class="tag" :class="!userInfo.examPassStatus||userInfo.examPassStatus==1?'tag-gray':(userInfo.examPassStatus==2?'tag-green':(userInfo.examPassStatus==3||userInfo.examPassStatus==4?'tag-error':''))">
-						{{!userInfo.examPassStatus?'暂无':(userInfo.examPassStatus==1?'考试中':((userInfo.examPassStatus==2?'已通过':(userInfo.examPassStatus==3?'未通过':(userInfo.examPassStatus==4?'缺考':'')))))}}
-						</text>
-					</view>
-					<view class="task-item" :class="userInfo.certificateStatus==1?'task-item-active':''">
-						<view class="task-label">证书状态</view>
-						<text class="tag" :class="!userInfo.certificateStatus?'tag-gray':(userInfo.certificateStatus==1?'tag-green':(userInfo.certificateStatus==2||userInfo.certificateStatus==3||userInfo.certificateStatus==4?'tag-error':''))">
-						{{!userInfo.certificateStatus?'暂无':(userInfo.certificateStatus==1?'已通过':(userInfo.certificateStatus==2?'失效':(userInfo.certificateStatus==3?'已撤销':(userInfo.certificateStatus==4?'未签发':''))))}}
-						</text>
-					</view>
-					<view class="task-item" :class="userInfo.approvalStatus==1?'task-item-active':''" v-if="userInfo.approvalStatus!=3&&userInfo.userType == 2">
-						<view class="task-label">审批状态</view>
-						<text class="tag" :class="userInfo.approvalStatus==0?'tag-gray':(userInfo.approvalStatus==1?'tag-green':(userInfo.approvalStatus==2?'tag-error':'tag-gray'))">
-						{{userInfo.bound&&userInfo.approvalStatus==0?'待审批':(userInfo.approvalStatus==1?'已通过':(userInfo.approvalStatus==2?'未通过':'待绑定'))}}
-						</text>
-					</view>
-				</view>
-			</view>
-		</view>
-		<BindLabModal 
-			:visible="showBindLabModal" 
-			@close="showBindLabModal = false"
-			@bind-success="refreshPageData"
-		/>
-	</view>
-</template>
-
-<script>
-	import {
-		examElLabEduRelationBindStatus
-	} from '@/pages_safetyEducationExamination/api/index.js'
-	import BindLabModal from './BindLabModal.vue'
-	export default {
-		components: { BindLabModal },
-		data() {
-			return {
-				userInfo: {
-					userType: '',
-					userName: '',
-					account: '',
-					educationName: '',
-					deptName: '',
-					labName: '',
-					bound: '',
-					approvalStatus: '',
-					certificateStatus: '',
-					examPassStatus: '',
-					gradeRange: '',
-					typeName: '',
-					completedStudyHours: '',
-					totalStudyHours: '',
-					studyHourRate: '',
-					targetExamName: '',
-				},
-				showBindLabModal:true,
-			}
-		},
-		mounted() {
-			this.examElLabEduRelationBindStatus();
-		},
-		methods: {
-			// 绑定成功后的回调
-			refreshPageData() {
-				// 重新获取用户信息或刷新页面数据
-				this.examElLabEduRelationBindStatus();
-			},
-			handleExamClick() {
-				// 这里写跳转到考试详情页的逻辑
-				uni.navigateTo({
-					url: '/pages_safetyEducationExamination/screen-exam-detail/screen-exam-detail' // 根据实际路径修改
-				});
-			},
-			// 获取用户信息
-			async examElLabEduRelationBindStatus() {
-				const {
-					data
-				} = await examElLabEduRelationBindStatus({});
-				if (data.code == 200) {
-					// 使用 Object.assign 保留默认值,同时更新接口返回的数据
-					// Object.assign(this.userInfo, data.data);
-					this.$set(this,'userInfo',data.data);
-				}
-			},
-			//关闭弹层
-			shadeButton(type){
-				let self = this;
-				if(type == 1){
-					//提交
-					this.$refs["shadeForm"].validate(valid => {
-						if (valid) {
-							this.$confirm('确认提交?', "提示", {
-								confirmButtonText: "确认",
-								cancelButtonText: "取消",
-								type: "warning"
-							}).then(function() {
-							}).then(() => {
-								self.examElLabEduRelationBind();
-							}).catch(() => {});
-						}
-					})
-				}else if(type == 2){
-					//查看通知
-					this.$set(this,'shadeType',2);
-				}else if(type == 3 || type == 33){
-					if(type == 3){
-						//关闭通知
-						this.$set(this.shadeFormData,'checkType',true);
-						this.$set(this,'shadeType',1);
-					}else if(type == 33){
-						//关闭通知
-						this.$set(this.shadeFormData,'checkType',false);
-						this.$set(this,'shadeType',1);
-					}
-				}else if(type == 4||type == 5){
-					//开启
-					this.$set(this,'shadeFormData',{
-						subId:null,
-						reason:'',
-						isEnter:false,
-						checkType:false,
-						offType:type == 4?true:false,
-					});
-					this.$set(this,'shadeType',1);
-				}else if(type == 6){
-					//关闭
-					this.$set(this,'shadeType',null);
-				}
-			},
-		}
-	}
-</script>
-
-<style lang="stylus" scoped>
-	.safetyEducationExamination-workbench-userInfoCard {
-		.top-max-big-box {
-			z-index: 0;
-			height: 200rpx;
-			background-color: #1959D5;
-			overflow: hidden;
-			display: flex;
-
-			.avatar-img {
-				border: 4rpx solid #87A8E9;
-				border-radius: 50%;
-				width: 98rpx;
-				height: 98rpx;
-				margin: 32rpx 32rpx 0 32rpx;
-			}
-
-			.center-info-box {
-				flex: 1;
-
-				.name-box {
-					display: flex;
-					margin-top: 18rpx;
-
-					view:nth-child(1) {
-						font-size: 34rpx;
-						font-weight: 700;
-						color: #fff;
-						margin-right: 20rpx;
-					}
-
-					view:nth-child(2) {
-						background: rgba(255, 215, 0, .2);
-						color: #ffe066;
-						font-size: 22rpx;
-						padding: 4rpx 16rpx;
-						border-radius: 40rpx;
-						border: 2rpx solid rgba(255, 215, 0, .35);
-						margin-right: 20rpx;
-					}
-
-					view:nth-child(3) {
-						background: rgba(0, 210, 140, .18);
-						color: #7fffd4;
-						font-size: 22rpx;
-						padding: 4rpx 16rpx;
-						border-radius: 40rpx;
-						border: 2rpx solid rgba(0, 210, 140, .4);
-						margin-right: 20rpx;
-					}
-				}
-
-				.account-box {
-					display: flex;
-					margin-top: 10rpx;
-
-					view {
-						color: rgba(255, 255, 255, .8);
-						font-size: 24rpx;
-					}
-
-					view:nth-child(2) {
-						margin: 0 10rpx;
-					}
-				}
-
-				.dept-box {
-					display: flex;
-					margin-top: 10rpx;
-
-					view {
-						color: rgba(255, 255, 255, .8);
-						font-size: 24rpx;
-					}
-				}
-			}
-
-			.right-type-box {
-				width: 70rpx;
-				height: 30rpx;
-				background: rgba(0, 210, 140, .18);
-				color: #7fffd4;
-				font-size: 22rpx;
-				padding: 4rpx 16rpx;
-				border-radius: 40rpx;
-				border: 2rpx solid rgba(0, 210, 140, .4);
-				display: flex;
-				margin: 60rpx 20rpx 0 0;
-
-				view:nth-child(1) {
-					width: 10rpx;
-					height: 10rpx;
-					border-radius: 50%;
-					background-color: rgba(0, 210, 140, 1);
-					text-align: center;
-					margin: 10rpx 10rpx 0 0;
-				}
-			}
-		}
-
-		.bottom-max-big-box {
-			z-index: 1;
-			position: relative;
-			border-radius: 32rpx;
-			padding: 32rpx;
-			margin: -36rpx 24rpx 24rpx;
-			background-color: #fff;
-
-			// 1. 绑定实验室样式
-			.lab-bind-box {
-				background: #eef3fd;
-				border-radius: 16rpx;
-				padding: 20rpx;
-				margin-bottom: 28rpx;
-
-				.lab-title {
-					font-size: 24rpx;
-					color: #8896a7;
-					margin-bottom: 16rpx;
-				}
-
-				.lab-info-row {
-					display: flex;
-					align-items: center;
-					gap: 12rpx;
-
-					.dot-green {
-						width: 12rpx;
-						height: 12rpx;
-						border-radius: 50%;
-						background-color: #0d8f6f;
-					}
-
-					.lab-name {
-						font-weight: 500;
-						color: #1857d4;
-						font-size: 28rpx;
-					}
-				}
-			}
-
-			// 2. 学年进度样式
-			.year-progress-card {
-				background-color: #f4f6fa;
-				border-radius: 20rpx;
-				padding: 28rpx;
-				margin-bottom: 24rpx;
-
-				.card-header {
-					margin-bottom: 24rpx;
-				}
-
-				.tags-group {
-					display: flex;
-					align-items: center;
-					gap: 16rpx;
-				}
-
-				.progress-info {
-					margin-bottom: 16rpx;
-				}
-
-				.info-row {
-					display: flex;
-					justify-content: space-between;
-					align-items: center;
-					margin-bottom: 8rpx;
-				}
-
-				.label {
-					font-size: 24rpx;
-					color: #8896a7;
-				}
-
-				.value {
-					font-size: 24rpx;
-				}
-
-				.progress-wrap {
-					background-color: #e8edf6;
-					border-radius: 8rpx;
-					height: 14rpx;
-					overflow: hidden;
-				}
-
-				.progress-bar {
-					height: 100%;
-					border-radius: 8rpx;
-					background: linear-gradient(90deg, #2e6fe0, #1857d4);
-					transition: width 0.4s;
-				}
-
-				.card-footer {
-					display: flex;
-					justify-content: space-between;
-					align-items: center;
-				}
-
-				.footer-left,
-				.footer-right {
-					display: flex;
-					align-items: center;
-					gap: 8rpx;
-				}
-
-				.exam-link {
-					color: #1857d4;
-					font-weight: 600;
-					background-color: #eef3fd;
-					padding: 4rpx 16rpx;
-					border-radius: 12rpx;
-					border: 1px solid rgba(24, 87, 212, 0.25);
-					font-size: 24rpx;
-				}
-			}
-
-			// 3. 任务状态样式
-			.task-status-box {
-				.task-title {
-					font-size: 28rpx;
-					color: #8896a7;
-					font-weight: 700;
-					margin-bottom: 16rpx;
-				}
-
-				.task-grid {
-					display: grid;
-					grid-template-columns: 1fr 1fr 1fr;
-					gap: 16rpx;
-
-					.task-item {
-						border: 1px solid #e8edf6;
-						border-radius: 16rpx;
-						padding: 20rpx;
-						text-align: center;
-
-						.task-label {
-							font-size: 24rpx;
-							color: #8896a7;
-							margin-bottom: 8rpx;
-						}
-					}
-
-					.task-item-active {
-						border: 1px solid #e0ffc1;
-						background-color: #f6ffed;
-					}
-				}
-			}
-		}
-
-		// 全局标签样式
-		.tag {
-			display: inline-flex;
-			align-items: center;
-			padding: 4rpx 16rpx;
-			border-radius: 40rpx;
-			font-size: 22rpx;
-			font-weight: 500;
-		}
-
-		.tag-blue {
-			background-color: #eef3fd;
-			color: #1857d4;
-		}
-
-		.tag-green {
-			background-color: #edfaf6;
-			color: #0d8f6f;
-		}
-
-		.tag-gray {
-			background-color: #f4f6fa;
-			color: #8896a7;
-		}
-		
-		.tag-error {
-			background-color: #fff7d8;
-			color: #e67e22;
-		}
-
-		.text-sm {
-			font-size: 24rpx;
-		}
-
-		.text-bold {
-			font-weight: 600;
-		}
-
-		.text-blue {
-			color: #1857d4;
-		}
-
-		.text-gray {
-			color: #8896a7;
-		}
-	}
-</style>

+ 0 - 346
pages_safetyEducationExamination/component/workbench/workbenchStatsCards.vue

@@ -1,346 +0,0 @@
-<template>
-	<view class="workbench-stats-cards">
-		<!-- 1. 学年数据统计 -->
-		<view class="card">
-			<view class="card-header">
-				<text class="card-title">📊 学年数据统计</text>
-				<view class="btn-outline" @click="handleShowReport">毕业报告</view>
-			</view>
-			<view class="card-body">
-				<view class="year-row" v-for="(item, index) in yearStatsList" :key="index">
-					<text class="year-label">{{ item.year }}</text>
-					<text :class="['year-status', item.statusClass]">{{ item.statusText }}</text>
-				</view>
-				<view v-if="!yearStatsList.length" class="empty-tip">暂无统计数据</view>
-			</view>
-		</view>
-
-		<!-- 2. 学习任务 -->
-		<view class="card">
-			<view class="card-header">
-				<view class="header-left">
-					<text class="card-title">📚 学习任务 <text class="badge" v-if="taskListTotal">{{ taskListTotal }}</text></text>
-					<text class="header-subtitle">待完成的学习任务</text>
-				</view>
-				<text class="header-link" @click="goToPage('/pages_safetyEducationExamination/screen-knowledge-list/screen-knowledge-list?tab=2')">更多 ›</text>
-			</view>
-			<view class="card-body">
-				<view class="list-item" v-for="(item, index) in taskList" :key="index" @click="goToPage('/pages_safetyEducationExamination/screen-study-detail/screen-study-detail?id=' + item.id)">
-					<view class="list-left">
-						<text class="item-title">{{ item.courseName }}</text>
-						<text class="item-desc">{{ item.deptName }}下发 · <text class="text-orange">待完成</text></text>
-					</view>
-					<text class="list-arrow">›</text>
-				</view>
-				<view v-if="!taskList.length" class="empty-tip">暂无待完成任务</view>
-			</view>
-		</view>
-
-		<!-- 3. 模拟考试 -->
-		<view class="card">
-			<view class="card-header">
-				<view class="header-left">
-					<text class="card-title">📝 模拟考试 <text class="badge" v-if="mockExamListTotal">{{ mockExamListTotal }}</text></text>
-					<text class="header-subtitle">待完成的模拟练习</text>
-				</view>
-				<text class="header-link" @click="goToPage('/pages_safetyEducationExamination/screen-exam-list/screen-exam-list?tab=1')">更多 ›</text>
-			</view>
-			<view class="card-body">
-				<view class="list-item" v-for="(item, index) in mockExamList" :key="index" @click="goToPage('/pages_safetyEducationExamination/screen-mock-detail/screen-mock-detail?id=' + item.id)">
-					<view class="list-left">
-						<text class="item-title">{{ item.examName }}</text>
-						<text class="item-desc">最高成绩: <text class="text-orange">{{ item.maxScore?item.maxScore:0 }} 分</text> · 及格: {{ item.passScore }}分</text>
-					</view>
-					<text class="list-arrow">›</text>
-				</view>
-				<view v-if="!mockExamList.length" class="empty-tip">暂无模拟考试</view>
-			</view>
-		</view>
-
-		<!-- 4. 在线考试 -->
-		<view class="card">
-			<view class="card-header">
-				<view class="header-left">
-					<text class="card-title">🎓 在线考试</text>
-					<text class="header-subtitle">待完成的在线考试</text>
-				</view>
-				<text class="header-link" @click="goToPage('/pages_safetyEducationExamination/screen-exam-list/screen-exam-list?tab=0')">更多 ›</text>
-			</view>
-			<view class="card-body">
-				<view 
-					class="exam-task-card" 
-					:class="{ 'active-exam': item.isActive }" 
-					v-for="(item, index) in onlineExamList" 
-					:key="index"
-					@click="goToPage('/pages_safetyEducationExamination/screen-exam-detail/screen-exam-detail?id=' + item.id)"
-				>
-					<view class="exam-header-row">
-						<view class="exam-tag-row">
-							<text class="tag" :class="item.isActive ? 'tag-blue' : 'tag-orange'">{{ item.examTypeName }}</text>
-							<view class="dot dot-orange" v-if="item.isActive"></view>
-							<text class="text-orange text-xs" v-if="item.isActive">进行中</text>
-						</view>
-						<text class="list-arrow" :class="item.isActive ? 'text-blue' : ''">›</text>
-					</view>
-					<text class="exam-title">{{ item.title }}</text>
-				</view>
-				<view v-if="!onlineExamList[0]" class="empty-tip">暂无在线考试</view>
-			</view>
-		</view>
-	</view>
-</template>
-
-<script>
-	import { 
-		examElLabEduRelationAcademicYearProgress, 
-		examUserCourseLearningList, 
-		examUserExamMockList, 
-		examUserExamStudyTaskList 
-	} from '@/pages_safetyEducationExamination/api/index.js'
-
-	export default {
-		name: 'WorkbenchStatsCards',
-		data() {
-			return {
-				yearStatsList: [],
-				taskList: [],
-				taskListTotal:0,
-				mockExamList: [],
-				mockExamListTotal: 0,
-				onlineExamList: [],
-			}
-		},
-		created() {
-			this.fetchAllData();
-		},
-		methods: {
-			async fetchAllData() {
-				// 并行请求所有数据,提升加载速度
-				await Promise.all([
-					this.examElLabEduRelationAcademicYearProgress(),
-					this.examUserCourseLearningList(),
-					this.examUserExamMockList(),
-					this.examUserExamStudyTaskList(),
-				]);
-			},
-			//年度数据
-			async examElLabEduRelationAcademicYearProgress() {
-				try {
-					const { data } = await examElLabEduRelationAcademicYearProgress();
-					if (data.code === 200) {
-						this.yearStatsList = (data.data || []).map(item => ({
-							year: item.academicYear,
-							statusText: item.statusCode == '0' ? `未开始` : (item.statusCode == '2' ? `通过丨`+item.score : (item.statusCode == '1' ? `进行中` : '未通过')),
-							statusClass: item.statusCode == '2' ? 'year-status-pass' : (item.statusCode == '1' ? 'year-status-going' : 'year-status-text')
-						}));
-					}
-				} catch (e) { console.error('获取学年统计失败', e); }
-			},
-			//学习任务
-			async examUserCourseLearningList() {
-				try {
-					const { data } = await examUserCourseLearningList({page:1,pageSize:2,learnStatus:'01'});
-					if (data.code === 200) {
-						this.taskList = data.data.records || [];
-						this.taskListTotal = data.data.total || 0;
-					}
-				} catch (e) { console.error('获取学习任务失败', e); }
-			},
-			//模拟考试
-			async examUserExamMockList() {
-				try {
-					const { data } = await examUserExamMockList({page:1,pageSize:1});
-					if (data.code === 200){
-						this.mockExamList = data.data.records || [];
-						this.mockExamListTotal = data.data.total || 0;
-					}
-				} catch (e) { console.error('获取模拟考试失败', e); }
-			},
-			//在线考试(查询1条特殊考试1条普通考试并组合数据)
-			async examUserExamStudyTaskList() {
-				try {
-        const [res1, res2] = await Promise.all([
-					examUserExamStudyTaskList({ page: 1, pageSize: 1, examStatus: 2, isSpecialExam: true }),
-					examUserExamStudyTaskList({ page: 1, pageSize: 1, examStatus: 2, isSpecialExam: false })
-        ]);
-        let list = [];
-        if (res1.data.code === 200) {
-					list = [...list, ...(res1.data.data.records || [])];
-        }
-        if (res2.data.code === 200) {
-					list = [...list, ...(res2.data.data.records || [])];
-        }
-				console.log('res1',res1)
-				console.log('res2',res2)
-				console.log('list',list)
-				this.$set(this,'onlineExamList',list);
-				} catch (e) { console.error('获取在线考试失败', e); }
-			},
-
-			// 根据考试类型动态生成底部信息字段
-			buildExamFields(item) {
-				const fields = [];
-				if (item.requiredHours !== undefined) {
-					fields.push({
-						label: '学时要求',
-						value: `${item.completedHours || 0}/${item.requiredHours} 学时`,
-						valueClass: item.hoursMet ? '' : 'text-orange',
-						extra: item.hoursMet ? '' : '未达标'
-					});
-				}
-				if (item.taskStatus) {
-					fields.push({ label: '学习任务', value: item.taskStatus, valueClass: item.taskStatus === '已完成' ? '' : 'text-orange' });
-				}
-				if (item.mockStatus) {
-					fields.push({ label: '模拟考试', value: item.mockStatus, valueClass: item.mockStatus === '已完成' ? '' : 'text-orange' });
-				}
-				if (item.examConfirmStatus) {
-					fields.push({ label: '考试状态', value: item.examConfirmStatus, valueClass: 'text-gray' });
-				}
-				return fields;
-			},
-
-			goToPage(url) {
-				uni.navigateTo({ url });
-			},
-			
-			handleShowReport() {
-				// 触发事件让父组件处理弹窗,或直接在组件内处理
-				uni.navigateTo({ url: '/pages_safetyEducationExamination/graduation-report/graduation-report' });
-			},
-		}
-	}
-</script>
-
-<style lang="stylus" scoped>
-	.workbench-stats-cards {
-		padding:0 32rpx;
-		.card {
-			background: #fff;
-			border-radius: 24rpx;
-			overflow: hidden;
-			margin-bottom: 24rpx;
-			box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.03);
-			border:1px solid #dde3ed;
-		}
-
-		.card-header {
-			background: #f4f6fa;
-			padding: 20rpx 28rpx;
-			display: flex;
-			justify-content: space-between;
-			align-items: center;
-		}
-
-		.header-left {
-			display: flex;
-			align-items: center;
-			gap: 12rpx;
-		}
-
-		.card-title {
-			font-size: 28rpx;
-			font-weight: 700;
-			color: #333;
-		}
-
-		.header-subtitle {
-			font-size: 22rpx;
-			color: #8896a7;
-		}
-
-		.header-link {
-			font-size: 24rpx;
-			color: #1857d4;
-		}
-
-		.btn-outline {
-			font-size: 22rpx;
-			color: #1857d4;
-			border: 1px solid rgba(24, 87, 212, 0.3);
-			padding: 6rpx 16rpx;
-			border-radius: 8rpx;
-		}
-
-		.badge {
-			background: #ff4d4f;
-			color: #fff;
-			font-size: 20rpx;
-			padding: 2rpx 10rpx;
-			border-radius: 20rpx;
-			margin-left: 8rpx;
-			font-weight: 400;
-		}
-
-		.card-body {
-			padding: 16rpx 28rpx 24rpx;
-		}
-
-		// 学年统计样式
-		.year-row {
-			display: flex;
-			justify-content: space-between;
-			align-items: center;
-			padding: 12rpx 0;
-			border-bottom: 1px solid #f4f6fa;
-			&:last-child { border-bottom: none; }
-		}
-		.year-label { font-size: 26rpx; color: #333; font-weight: 500; }
-		.year-status { font-size: 24rpx; font-weight: 500; }
-		.year-status-pass { color: #0d8f6f; }
-		.year-status-going { color: #1857d4; }
-		.year-status-text { color: #333; }
-
-		// 列表项样式
-		.list-item {
-			display: flex;
-			justify-content: space-between;
-			align-items: center;
-			padding: 16rpx 0;
-			border-bottom: 1px solid #f4f6fa;
-			&:last-child { border-bottom: none; }
-		}
-		.item-title { font-size: 28rpx; font-weight: 500; color: #333; margin-bottom: 8rpx; display: block; }
-		.item-desc { font-size: 22rpx; color: #8896a7; }
-		.list-arrow { font-size: 32rpx; color: #ccc; }
-
-		// 在线考试卡片样式
-		.exam-task-card {
-			background: #f4f6fa;
-			border-radius: 16rpx;
-			padding: 20rpx;
-			margin-bottom: 16rpx;
-			&:last-child { margin-bottom: 0; }
-		}
-		.active-exam {
-			background: #eef3fd;
-			border: 1px solid rgba(24, 87, 212, 0.15);
-		}
-		.exam-header-row {
-			display: flex;
-			justify-content: space-between;
-			align-items: center;
-			margin-bottom: 12rpx;
-		}
-		.exam-tag-row { display: flex; align-items: center; gap: 8rpx; }
-		.exam-title { font-size: 28rpx; font-weight: 500; color: #333; margin-bottom: 16rpx; display: block; }
-		
-		// 通用工具类
-		.tag {
-			display: inline-flex;
-			padding: 4rpx 12rpx;
-			border-radius: 8rpx;
-			font-size: 22rpx;
-			font-weight: 500;
-		}
-		.tag-blue { background: #eef3fd; color: #1857d4; }
-		.tag-orange { background: #fef6ec; color: #e67e22; }
-		.dot { width: 12rpx; height: 12rpx; border-radius: 50%; }
-		.dot-orange { background: #e67e22; }
-		.text-xs { font-size: 22rpx; }
-		.text-orange { color: #e67e22; }
-		.text-blue { color: #1857d4; }
-		.text-gray { color: #8896a7; }
-		.empty-tip { text-align: center; padding: 30rpx 0; color: #ccc; font-size: 24rpx; }
-	}
-</style>

+ 2 - 1
pages_safetyEducationExamination/views/exam/incorrectQuestionsList.vue

@@ -26,7 +26,8 @@
 						<text class="tag text-xs" :class="item.errorCount >= 3 ? 'tag-red' : 'tag-orange'">答错{{ item.errorCount || 0 }}次</text>
 						<text class="tag text-xs" :class="item.errorCount >= 3 ? 'tag-red' : 'tag-orange'">答错{{ item.errorCount || 0 }}次</text>
 					</view>
 					</view>
 					<view class="q-content">
 					<view class="q-content">
-						<text class="text-sm">{{ item.questionContent }}</text>
+						<!-- <text class="text-sm">{{ item.questionContent }}</text> -->
+						<rich-text class="text-sm" :nodes="item.questionContent || ''"></rich-text>
 					</view>
 					</view>
 					<view v-if="item.userAnswer || item.correctAnswer" class="answer-box">
 					<view v-if="item.userAnswer || item.correctAnswer" class="answer-box">
 						<view v-if="item.userAnswer" class="mb4">
 						<view v-if="item.userAnswer" class="mb4">

+ 14 - 5
pages_safetyEducationExamination/views/exam/index.vue

@@ -120,7 +120,7 @@
 					<picker mode="date" :value="endDate" @change="onEndDateChange" >
 					<picker mode="date" :value="endDate" @change="onEndDateChange" >
 						<view class="picker-input top-picker-input-box" :style="!endDate?'color:#999;font-size:22rpx;':''">{{ endDate?endDate:'请选择' }}</view>
 						<view class="picker-input top-picker-input-box" :style="!endDate?'color:#999;font-size:22rpx;':''">{{ endDate?endDate:'请选择' }}</view>
 					</picker>
 					</picker>
-					<view class="top-right-box-button" @click="resetScoreList()">重置</view>
+					<view class="top-right-box-button" @click="resetButton()">重置</view>
 				</view>
 				</view>
 				<!-- 考试类型标签 -->
 				<!-- 考试类型标签 -->
 				<scroll-view scroll-x class="tag-scroll">
 				<scroll-view scroll-x class="tag-scroll">
@@ -294,8 +294,16 @@
 			resetScoreList() {
 			resetScoreList() {
 				this.scorePage = 1;
 				this.scorePage = 1;
 				this.scoreNoMore = false;
 				this.scoreNoMore = false;
-				this.startTime = '';
-				this.endTime = '';
+				this.$set(this, 'scoreList', []);
+				this.loadScoreList();
+			},
+			resetButton(){
+				this.scorePage = 1;
+				this.scoreNoMore = false;
+				this.startDate = '';
+				this.endDate = '';
+				this.resultIndex = 0;
+				this.activeTagIndex = 0;
 				this.$set(this, 'scoreList', []);
 				this.$set(this, 'scoreList', []);
 				this.loadScoreList();
 				this.loadScoreList();
 			},
 			},
@@ -313,7 +321,7 @@
 						resultStatus: result.value || '',
 						resultStatus: result.value || '',
 						startTime: this.startDate?this.startDate + 'T00:00:00':'',
 						startTime: this.startDate?this.startDate + 'T00:00:00':'',
 						endTime: this.endDate?this.endDate + 'T23:59:59':'',
 						endTime: this.endDate?this.endDate + 'T23:59:59':'',
-						examScene:null
+						examScene:1,
 					};
 					};
 					const { data } = await examElExamUserResultMyScoreListPage(obj);
 					const { data } = await examElExamUserResultMyScoreListPage(obj);
 					if (data.code === 200) {
 					if (data.code === 200) {
@@ -373,9 +381,10 @@
 				});
 				});
 			},
 			},
 			goToScoreList(item) {
 			goToScoreList(item) {
+				console.log('item',item);
 				// 跳转答题详情,传递 attemptId / examId
 				// 跳转答题详情,传递 attemptId / examId
 				uni.navigateTo({
 				uni.navigateTo({
-					url: `/pages_safetyEducationExamination/views/exam/scoreList?examId=${item.examId}`
+					url: `/pages_safetyEducationExamination/views/exam/scoresList?examId=${item.examId}`
 				});
 				});
 			},
 			},
 			goToCert(item) {
 			goToCert(item) {

+ 18 - 3
pages_safetyEducationExamination/views/exam/mockExamInfo.vue

@@ -55,6 +55,7 @@
 	} from '@/pages_safetyEducationExamination/utils/index.js'
 	} from '@/pages_safetyEducationExamination/utils/index.js'
 	import {
 	import {
 		examUserExamStart,
 		examUserExamStart,
+		examUserExamMockDetail,
 	} from '@/pages_safetyEducationExamination/api/index.js'
 	} from '@/pages_safetyEducationExamination/api/index.js'
 	import {
 	import {
 		parseTime
 		parseTime
@@ -63,7 +64,8 @@
 	export default {
 	export default {
 		data() {
 		data() {
 			return {
 			return {
-				examData: {}
+				examData: {},
+				newData: {}
 			}
 			}
 		},
 		},
 		computed: {
 		computed: {
@@ -74,14 +76,27 @@
 		onLoad(options) {
 		onLoad(options) {
 			if (options.examData) {
 			if (options.examData) {
 				try {
 				try {
-					this.examData = JSON.parse(decodeURIComponent(options.examData));
-					this.formatExamData();
+					this.newData = JSON.parse(decodeURIComponent(options.examData));
 				} catch (e) {
 				} catch (e) {
 					console.error('解析模拟考试数据失败', e);
 					console.error('解析模拟考试数据失败', e);
 				}
 				}
 			}
 			}
 		},
 		},
+		onShow(){
+			this.examUserExamMockDetail();
+		},
 		methods: {
 		methods: {
+			async examUserExamMockDetail() {
+				try {
+					const { data } = await examUserExamMockDetail({examId:this.newData.examId});
+					if (data.code === 200 && data.data) {
+						this.$set(this,'examData',data.data);
+						this.formatExamData();
+					}
+				} catch (e) {
+					console.error('获取考试详情失败', e);
+				}
+			},
 			formatExamData() {
 			formatExamData() {
 				if (this.examData.startTime) {
 				if (this.examData.startTime) {
 					this.examData.startTime = parseTime(this.examData.startTime, '{y}-{m}-{d} {h}:{i}');
 					this.examData.startTime = parseTime(this.examData.startTime, '{y}-{m}-{d} {h}:{i}');

+ 24 - 4
pages_safetyEducationExamination/views/exam/onlineExamInfo.vue

@@ -58,14 +58,14 @@
 				</view>
 				</view>
 		</view>
 		</view>
 		<!-- 考试承诺书弹窗 -->
 		<!-- 考试承诺书弹窗 -->
-		<uni-popup ref="preExamDialog" type="center" :mask-click="false">
+		<uni-popup ref="preExamDialog" type="center" :mask-click="true">
 			<view class="pre-exam-dialog">
 			<view class="pre-exam-dialog">
 				<view class="dialog-header">
 				<view class="dialog-header">
 					<text class="dialog-title">考试承诺书</text>
 					<text class="dialog-title">考试承诺书</text>
 				</view>
 				</view>
 				<view class="dialog-body">
 				<view class="dialog-body">
 					<scroll-view scroll-y class="content-scroll">
 					<scroll-view scroll-y class="content-scroll">
-						<richTextPdf ref="richTextPdf" :content="richTextHtml" />
+						<richTextPdf ref="richTextPdf" :content="richTextHtml" :examId="richTextExamId" />
 					</scroll-view>
 					</scroll-view>
 				</view>
 				</view>
 				<view class="dialog-footer">
 				<view class="dialog-footer">
@@ -82,6 +82,9 @@
 		parseTime
 		parseTime
 	} from '@/component/public.js'
 	} from '@/component/public.js'
 	import {
 	import {
+		examUserExamDetail, 
+	} from '@/pages_safetyEducationExamination/api/index.js'
+	import {
 		goToExamPage,
 		goToExamPage,
 		confirmCommitmentAndStart,
 		confirmCommitmentAndStart,
 		startFormalExam,
 		startFormalExam,
@@ -95,9 +98,11 @@
 		data() {
 		data() {
 			return {
 			return {
 				examData: {},
 				examData: {},
+				newData:{},
 				// 承诺书相关
 				// 承诺书相关
 				preButtonType: false,
 				preButtonType: false,
 				richTextHtml: '',
 				richTextHtml: '',
+				richTextExamId:'',
 				preData: null,
 				preData: null,
 				itemData:{},
 				itemData:{},
 			}
 			}
@@ -111,14 +116,28 @@
 			// 接收从上个页面传递的考试数据
 			// 接收从上个页面传递的考试数据
 			if (options.examData) {
 			if (options.examData) {
 				try {
 				try {
-					this.examData = JSON.parse(decodeURIComponent(options.examData));
-					this.formatExamData();
+					this.newData = JSON.parse(decodeURIComponent(options.examData));
+					// this.examUserExamDetail();
 				} catch (e) {
 				} catch (e) {
 					console.error('解析考试数据失败', e);
 					console.error('解析考试数据失败', e);
 				}
 				}
 			}
 			}
 		},
 		},
+		onShow(){
+			this.examUserExamDetail();
+		},
 		methods: {
 		methods: {
+			async examUserExamDetail() {
+				try {
+					const { data } = await examUserExamDetail({examId:this.newData.examId});
+					if (data.code === 200 && data.data) {
+						this.$set(this,'examData',data.data);
+						this.formatExamData();
+					}
+				} catch (e) {
+					console.error('获取考试详情失败', e);
+				}
+			},
 			formatExamData() {
 			formatExamData() {
 				// 格式化时间
 				// 格式化时间
 				if (this.examData.startTime) {
 				if (this.examData.startTime) {
@@ -146,6 +165,7 @@
 					openCommitmentDialog: (examItem) => {
 					openCommitmentDialog: (examItem) => {
 						this.preButtonType = examItem.commitmentPrintEnabled == 1;
 						this.preButtonType = examItem.commitmentPrintEnabled == 1;
 						this.richTextHtml = examItem.commitmentContent;
 						this.richTextHtml = examItem.commitmentContent;
+						this.richTextExamId = examItem.examId;
 						this.preData = examItem;
 						this.preData = examItem;
 						this.$refs.preExamDialog.open();
 						this.$refs.preExamDialog.open();
 					},
 					},

+ 5 - 4
pages_safetyEducationExamination/views/exam/onlineExamPlay.vue

@@ -3,7 +3,8 @@
 	<view class="exam-screen">
 	<view class="exam-screen">
 		<!-- 顶部栏 -->
 		<!-- 顶部栏 -->
 		<view class="topbar">
 		<view class="topbar">
-			<view class="topbar-left" @click="requestSubmit(false)">‹ 交卷</view>
+			<!-- <view class="topbar-left" @click="requestSubmit(false)">‹ 交卷</view> -->
+			<view class="topbar-left"></view>
 			<view class="topbar-title">{{ examInfo.examName || '考试中' }}</view>
 			<view class="topbar-title">{{ examInfo.examName || '考试中' }}</view>
 			<view class="topbar-right" @click="toggleQuestionNav">题号</view>
 			<view class="topbar-right" @click="toggleQuestionNav">题号</view>
 		</view>
 		</view>
@@ -148,12 +149,12 @@
 					<!-- <view v-if="resultData.canRetake" class="result-btn result-btn-primary" @click="retakeExam">重新考试</view> -->
 					<!-- <view v-if="resultData.canRetake" class="result-btn result-btn-primary" @click="retakeExam">重新考试</view> -->
 					<view class="result-btn result-btn-primary" @click="closeResult">返回</view>
 					<view class="result-btn result-btn-primary" @click="closeResult">返回</view>
 				</view>
 				</view>
-				<view class="result-cert">
+				<view class="result-cert" v-if="resultData.certificatePath">
 					<text class="result-cert-icon">🏆</text>
 					<text class="result-cert-icon">🏆</text>
 					<view class="result-cert-info">
 					<view class="result-cert-info">
 						<view class="result-cert-title">您已获得证书!</view>
 						<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 class="result-cert-name">{{ resultData.certificateName }}</view>
+						<view class="result-cert-btn" @click="goCertificate(resultData.certificatePath)">查看证书</view>
 					</view>
 					</view>
 				</view>
 				</view>
 			</view>
 			</view>

+ 64 - 49
pages_safetyEducationExamination/views/exam/scoreDetail.vue

@@ -44,7 +44,7 @@
 							<view class="text-xs text-gray">错误</view>
 							<view class="text-xs text-gray">错误</view>
 						</view>
 						</view>
 						<view class="stat-item">
 						<view class="stat-item">
-							<view class="stat-num text-blue">{{ detail.accuracy }}</view>
+							<view class="stat-num text-blue">{{ detail.accuracy }}%</view>
 							<view class="text-xs text-gray">正确率</view>
 							<view class="text-xs text-gray">正确率</view>
 						</view>
 						</view>
 					</view>
 					</view>
@@ -55,7 +55,7 @@
 			<view class="detail-panel">
 			<view class="detail-panel">
 				<view class="detail-title">
 				<view class="detail-title">
 					答题详情
 					答题详情
-					<text class="text-gray detail-title-sub">(共{{ detail.totalQuestionNum || 0 }}题)</text>
+					<text class="text-gray detail-title-sub">(共{{ total || 0 }}题)</text>
 				</view>
 				</view>
 
 
 				<view v-for="(item, index) in answerList" :key="index" class="question-card">
 				<view v-for="(item, index) in answerList" :key="index" class="question-card">
@@ -69,17 +69,23 @@
 							{{ isCorrect(item) ? '答对' : '答错' }}
 							{{ isCorrect(item) ? '答对' : '答错' }}
 						</text>
 						</text>
 					</view>
 					</view>
-					<view class="q-content">{{ item.questionContent }}</view>
+					<view class="q-content">
+						<rich-text :nodes="item.questionContent || ''"></rich-text>
+					</view>
 					<view class="answer-box" :class="isCorrect(item) ? 'answer-box-correct' : 'answer-box-wrong'">
 					<view class="answer-box" :class="isCorrect(item) ? 'answer-box-correct' : 'answer-box-wrong'">
 						<view class="mb4">
 						<view class="mb4">
 							<text class="text-xs text-gray">我的答案:</text>
 							<text class="text-xs text-gray">我的答案:</text>
-							<text class="text-xs fw500" :class="isCorrect(item) ? 'text-green' : 'text-red'">
-								{{ item.userAnswer }}{{ answerSuffix(item) }}
+							<text v-if="item.answerTags[0]" class="text-xs fw500" :class="isCorrect(item) ? 'text-green' : 'text-red'">
+								<!-- {{ item.answerTags }}{{ answerSuffix(item) }} -->
+								{{ item.answerTags }}
+							</text>
+							<text v-if="!item.answerTags[0]" class="text-xs fw500" :class="isCorrect(item) ? 'text-green' : 'text-red'">
+								未作答
 							</text>
 							</text>
 						</view>
 						</view>
 						<view>
 						<view>
 							<text class="text-xs text-gray">正确答案:</text>
 							<text class="text-xs text-gray">正确答案:</text>
-							<text class="text-xs text-green fw500">{{ item.correctAnswer }}</text>
+							<text class="text-xs text-green fw500">{{ item.correctAnswerTags }}</text>
 						</view>
 						</view>
 					</view>
 					</view>
 					<view v-if="item.analysis" class="analysis-box">解析:{{ item.analysis }}</view>
 					<view v-if="item.analysis" class="analysis-box">解析:{{ item.analysis }}</view>
@@ -94,6 +100,13 @@
 </template>
 </template>
 
 
 <script>
 <script>
+	import {
+		parseTime
+	} from '@/component/public.js'
+	import {
+		examElExamUserResultMyScoreDetail,
+		examElExamAttemptPaperQuestionPage
+	} from '@/pages_safetyEducationExamination/api/index.js'
 	// 题型映射:1-单选题,2-多选题,3-判断题
 	// 题型映射:1-单选题,2-多选题,3-判断题
 	const QUESTION_TYPE_MAP = {
 	const QUESTION_TYPE_MAP = {
 		1: '单选题',
 		1: '单选题',
@@ -180,27 +193,30 @@
 			// 上部分:考试概况(独立详情接口)
 			// 上部分:考试概况(独立详情接口)
 			async loadExamDetail() {
 			async loadExamDetail() {
 				try {
 				try {
-					// TODO 后台接口未提供,先占位;接口就绪后放开以下代码
-					// const { data } = await examElExamAttemptAnswerDetail({ attemptId: this.attemptId });
-					// if (data.code === 200 && data.data) {
-					//     const d = data.data;
-					//     this.detail = {
-					//         examTypeName: d.examTypeName || '',
-					//         examName: d.examName || '',
-					//         isPass: d.examStatus === 1,
-					//         examTime: parseTime(d.startTime, '{y}-{m}-{d}'),
-					//         usedTime: d.myDuration || '-',
-					//         totalScore: d.totalScore || 0,
-					//         passScore: d.passScore || 0,
-					//         attemptCount: d.attemptCount || 0,
-					//         attemptLimit: d.attemptLimit || 0,
-					//         bestScore: d.bestScore || 0,
-					//         totalQuestionNum: d.totalQuestionNum || 0,
-					//         correctCount: d.correctCount || 0,
-					//         wrongCount: d.wrongCount || 0,
-					//         accuracy: d.accuracy || '-',
-					//     };
-					// }
+					let obj = {
+						examId:this.examId,
+						examScene:1,
+					}
+					const { data } = await examElExamUserResultMyScoreDetail(obj);
+					if (data.code === 200 && data.data) {
+					    const d = data.data;
+					    this.detail = {
+					        examTypeName: d.examTypeName || '',
+					        examName: d.examName || '',
+					        isPass: d.examStatus === 1,
+					        examTime: parseTime(d.startTime, '{y}-{m}-{d}'),
+					        usedTime: d.myDuration || '-',
+					        totalScore: d.totalScore || 0,
+					        passScore: d.passScore || 0,
+					        attemptCount: d.attemptCount || 0,
+					        attemptLimit: d.attemptLimit || 0,
+					        bestScore: d.bestScore || 0,
+					        totalQuestionNum: d.totalQuestionNum || 0,
+					        correctCount: d.correctCount || 0,
+					        wrongCount: d.wrongCount || 0,
+					        accuracy: d.accuracy || '-',
+					    };
+					}
 				} catch (e) {
 				} catch (e) {
 					console.error('获取考试详情失败', e);
 					console.error('获取考试详情失败', e);
 				}
 				}
@@ -216,23 +232,22 @@
 				if (this.listLoading || this.noMore) return;
 				if (this.listLoading || this.noMore) return;
 				this.listLoading = true;
 				this.listLoading = true;
 				try {
 				try {
-					// TODO 后台接口未提供,先占位;接口就绪后放开以下代码
-					// const { data } = await examElExamAttemptAnswerAttemptAnswer({
-					//     page: this.page,
-					//     pageSize: this.pageSize,
-					//     attemptId: this.attemptId,
-					// });
-					// if (data.code === 200) {
-					//     const records = data.data.records || [];
-					//     const newList = this.page === 1 ? records : [...this.answerList, ...records];
-					//     this.$set(this, 'answerList', newList);
-					//     this.total = data.data.total;
-					//     if (newList.length >= this.total) {
-					//         this.noMore = true;
-					//     } else {
-					//         this.page++;
-					//     }
-					// }
+					const { data } = await examElExamAttemptPaperQuestionPage({
+					    page: this.page,
+					    pageSize: this.pageSize,
+					    attemptId: this.attemptId,
+					});
+					if (data.code === 200) {
+					    const records = data.data.records || [];
+					    const newList = this.page === 1 ? records : [...this.answerList, ...records];
+					    this.$set(this, 'answerList', newList);
+					    this.total = data.data.total;
+					    if (newList.length >= this.total) {
+					        this.noMore = true;
+					    } else {
+					        this.page++;
+					    }
+					}
 				} catch (e) {
 				} catch (e) {
 					console.error('获取答题详情失败', e);
 					console.error('获取答题详情失败', e);
 				} finally {
 				} finally {
@@ -349,15 +364,15 @@
 		border-color #b7eb8f
 		border-color #b7eb8f
 
 
 	.diff-normal
 	.diff-normal
-		background #e6f4ff
-		color #1677ff
-		border-color #91caff
-
-	.diff-hard
 		background #fff7e6
 		background #fff7e6
 		color #fa8c16
 		color #fa8c16
 		border-color #ffd591
 		border-color #ffd591
 
 
+	.diff-hard
+		background #fff2f0
+		color #f5222d
+		border-color #ffccc7
+
 	.diff-extreme
 	.diff-extreme
 		background #f9f0ff
 		background #f9f0ff
 		color #722ed1
 		color #722ed1

+ 58 - 11
pages_safetyEducationExamination/views/exam/scoresList.vue

@@ -5,9 +5,13 @@
 				v-for="(item, index) in scoreList"
 				v-for="(item, index) in scoreList"
 				:key="index"
 				:key="index"
 				class="score-card"
 				class="score-card"
-				@click="goToScoreDetail(item)"
-			>
-				<view>123</view>
+				@click="goToScoreDetail(item)">
+				<view class="left-box">
+					<view>考试时间:{{item.startTime}}</view>
+					<view>得分:{{item.totalScore}}</view>
+					<view>及格分:{{item.passScore}}</view>
+				</view>
+				<view class="right-box" :class="item.passStatus ==0?'colorA':'colorB'">{{item.passStatus ==0?'未通过':'通过'}}</view>
 			</view>
 			</view>
 			<view v-if="!scoreList[0] && !scoreLoading" class="empty-tip">暂无考试记录</view>
 			<view v-if="!scoreList[0] && !scoreLoading" class="empty-tip">暂无考试记录</view>
 			<view v-if="scoreLoading" class="loading-tip">加载中...</view>
 			<view v-if="scoreLoading" class="loading-tip">加载中...</view>
@@ -32,16 +36,20 @@
 				scorePageSize: 10,
 				scorePageSize: 10,
 				scoreTotal: 0,
 				scoreTotal: 0,
 				scoreNoMore: false,
 				scoreNoMore: false,
+				examId:'',
 			}
 			}
 		},
 		},
 		created() {
 		created() {
 			
 			
 		},
 		},
+		onLoad(options){
+			this.$set(this,'examId',options.examId);
+		},
 		onShow() {
 		onShow() {
-			
+			this.loadScoreList();
 		},
 		},
 		methods: {
 		methods: {
-			goToScoreDetail(){
+			goToScoreDetail(item){
 				// 跳转答题详情,传递 attemptId / examId
 				// 跳转答题详情,传递 attemptId / examId
 				uni.navigateTo({
 				uni.navigateTo({
 					url: `/pages_safetyEducationExamination/views/exam/scoreDetail?attemptId=${item.attemptId || ''}&examId=${item.examId || ''}`
 					url: `/pages_safetyEducationExamination/views/exam/scoreDetail?attemptId=${item.attemptId || ''}&examId=${item.examId || ''}`
@@ -59,7 +67,7 @@
 					const obj = {
 					const obj = {
 						page: this.scorePage,
 						page: this.scorePage,
 						pageSize: this.scorePageSize,
 						pageSize: this.scorePageSize,
-						examId: '',
+						examId: this.examId,
 					};
 					};
 					const { data } = await examElUserExamAttemptAttemptRecord(obj);
 					const { data } = await examElUserExamAttemptAttemptRecord(obj);
 					if (data.code === 200) {
 					if (data.code === 200) {
@@ -88,10 +96,49 @@
 
 
 <style lang="stylus" scoped>
 <style lang="stylus" scoped>
 	.scoreList{
 	.scoreList{
-		height 100%
-		display flex
-		flex-direction column
-		overflow hidden
-		background-color #f0f4fb
+		height: 100%;
+		display: flex;
+		flex-direction: column;
+		overflow: hidden;
+		background-color: #f0f4fb;
+		.scroll-content{
+			height:100%;
+		}
+		.score-card{
+			margin:28rpx 28rpx 0;
+			background-color: #fff;
+			border-radius:10rpx;
+			padding:20rpx;
+			box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.04);
+			border: 1rpx solid #dde3ed;
+			display: flex;
+			font-size:28rpx;
+			.left-box{
+				flex:1;
+			}
+			.right-box{
+				line-height:110rpx;
+				margin-right:20rpx;
+			}
+			.colorA{
+				color:#f5222d;
+			}
+			.colorB{
+				color:#52c41a;
+			}
+		}
+		.empty-tip{
+			text-align: center;
+			line-height: 100rpx;
+			color: #aaa;
+			font-size: 24rpx;
+		}
+		
+		.loading-tip{
+			text-align: center;
+			line-height: 100rpx;
+			color: #aaa;
+			font-size: 22rpx;
+		}
 	}
 	}
 </style>
 </style>

+ 0 - 101
pages_safetyEducationExamination/views/home.vue

@@ -1,101 +0,0 @@
-<!-- 教育考试首页 -->
-<template>
-	<view class="safetyEducationExamination-home">
-		<view class="safetyEducationExamination-home-page">
-			<workbench class="safetyEducationExamination-content" v-show="currentTab == 1"></workbench>
-			<learning class="safetyEducationExamination-content" v-show="currentTab == 2"></learning>
-			<online-exams class="safetyEducationExamination-content" v-show="currentTab == 3"></online-exams>
-			<practice class="safetyEducationExamination-content" v-show="currentTab == 4"></practice>
-		</view>
-		<tab-bar></tab-bar>
-	</view>
-</template>
-
-<script>
-	import {
-		systemMineUserEdit
-	} from '@/pages_safetyEducationExamination/api/index.js'
-	import {
-		workbench
-	} from '@/pages_safetyEducationExamination/component/workbench/index'
-	import {
-		learning
-	} from '@/pages_safetyEducationExamination/component/learning/index'
-	import {
-		onlineExams
-	} from '@/pages_safetyEducationExamination/component/onlineExams/index'
-	import {
-		practice
-	} from '@/pages_safetyEducationExamination/component/practice/index'
-	import {
-		tabBar
-	} from '@/pages_safetyEducationExamination/component/tabBar'
-	export default {
-		components: {
-			workbench,
-			learning,
-			onlineExams,
-			practice,
-			tabBar,
-		},
-		data() {
-			return {
-				currentTab: 1 ,
-				tabTitles: {
-					1: { title: '安全教育考试', bgColor: '#1857d4', frontColor: '#ffffff' },
-					2: { title: '学习培训', bgColor: '#10a37f', frontColor: '#ffffff' },
-					3: { title: '在线考试', bgColor: '#e67e22', frontColor: '#ffffff' },
-					4: { title: '刷题练习', bgColor: '#ffffff', frontColor: '#000000' }
-				}
-			}
-		},
-		created() {
-			uni.$on('update-tab-bar', (type) => {
-				this.currentTab = type;
-				const style = this.tabTitles[type];
-				uni.setNavigationBarTitle({
-					title: style.title
-				});
-				uni.setNavigationBarColor({
-					frontColor: style.frontColor,      // 仅支持 #ffffff 和 #000000
-					backgroundColor: style.bgColor,    // 必须是十六进制颜色
-					animation: {
-						duration: 200,                 // 设置 200ms 的过渡动画,体验更丝滑
-						timingFunc: 'easeIn'
-					}
-				});
-			});
-		},
-		onShow() {
-			uni.$emit('update-tab-bar', this.currentTab);
-		},
-		methods: {
-
-		},
-		beforeDestroy() {
-			uni.$off('update-tab-bar');
-		}
-	}
-</script>
-
-<style lang="stylus" scoped>
-	.safetyEducationExamination-home {
-		height: 100%;
-		display: flex;
-		flex-direction: column;
-		overflow: hidden;
-		background-color: #f0f4fb;
-		.safetyEducationExamination-home-page{
-			flex:1;
-			display: flex;
-			flex-direction: column;
-			overflow: hidden;
-			.safetyEducationExamination-content{
-				flex:1;
-				display: flex;
-				flex-direction: column;
-				overflow: hidden;
-			}
-		}
-	}
-</style>

File diff suppressed because it is too large
+ 655 - 401
pages_safetyEducationExamination/views/home/component/BindLabModal.vue


+ 9 - 2
pages_safetyEducationExamination/views/home/component/academicYearStats.vue

@@ -14,7 +14,11 @@
 				<view v-if="!yearStatsList.length" class="empty-tip">暂无统计数据</view>
 				<view v-if="!yearStatsList.length" class="empty-tip">暂无统计数据</view>
 			</view>
 			</view>
 		</view>
 		</view>
-		<dataStatisticsReport v-if="dialogType" :dataStatisticsReportPropsData="dataStatisticsReportPropsData"></dataStatisticsReport>
+		<dataStatisticsReport
+		:visible="dialogType"
+		:dataStatisticsReportPropsData="dataStatisticsReportPropsData"
+		@close="closeReport"
+	></dataStatisticsReport>
 	</view>
 	</view>
 </template>
 </template>
 
 
@@ -23,7 +27,7 @@
 		examElLabEduRelationAcademicYearProgress, 
 		examElLabEduRelationAcademicYearProgress, 
 		examElLabEduRelationGraduationReport,
 		examElLabEduRelationGraduationReport,
 	} from '@/pages_safetyEducationExamination/api/index.js'
 	} from '@/pages_safetyEducationExamination/api/index.js'
-	import { dataStatisticsReport } from './dataStatisticsReport'
+	import dataStatisticsReport from './dataStatisticsReport.vue'
 	export default {
 	export default {
 		components: {
 		components: {
 			dataStatisticsReport,
 			dataStatisticsReport,
@@ -45,6 +49,9 @@
 			resetMethod(){
 			resetMethod(){
 				this.examElLabEduRelationAcademicYearProgress();
 				this.examElLabEduRelationAcademicYearProgress();
 			},
 			},
+			closeReport() {
+				this.dialogType = false;
+			},
 			//年度数据
 			//年度数据
 			async examElLabEduRelationAcademicYearProgress() {
 			async examElLabEduRelationAcademicYearProgress() {
 				try {
 				try {

+ 59 - 18
pages_safetyEducationExamination/views/home/component/dataStatisticsReport.vue

@@ -1,8 +1,11 @@
 <!-- 毕业教育考试数据统计报告 -->
 <!-- 毕业教育考试数据统计报告 -->
 <template>
 <template>
-  <view class="dataStatisticsReport-addPage">
-    <!-- 报告主体 -->
-    <view class="report-wrap" v-if="showType">
+  <view class="dataStatisticsReport-addPage" v-if="visible" @tap="onOverlayTap">
+    <!-- 弹窗容器 -->
+    <view class="report-wrap" @tap.stop="stopProp">
+      <!-- 关闭按钮 -->
+      <view class="close-btn" @tap="handleClose">✕</view>
+      <scroll-view scroll-y class="report-scroll">
       <view class="report-wrap-big-box">
       <view class="report-wrap-big-box">
         <view class="report-wrap-min-box">
         <view class="report-wrap-min-box">
           <!-- logo -->
           <!-- logo -->
@@ -87,6 +90,7 @@
           </view>
           </view>
         </view>
         </view>
       </view>
       </view>
+      </scroll-view>
     </view>
     </view>
   </view>
   </view>
 </template>
 </template>
@@ -95,6 +99,10 @@
   export default {
   export default {
     name: 'dataStatisticsReport',
     name: 'dataStatisticsReport',
     props: {
     props: {
+      visible: {
+        type: Boolean,
+        default: false
+      },
       dataStatisticsReportPropsData: {
       dataStatisticsReportPropsData: {
         type: Object,
         type: Object,
         default: () => ({})
         default: () => ({})
@@ -115,16 +123,27 @@
           issuingUnit: '',
           issuingUnit: '',
           issuingTime: '',
           issuingTime: '',
         },
         },
-        showType: false,
       }
       }
     },
     },
-    mounted() {
-      this.initialize()
+    watch: {
+      dataStatisticsReportPropsData: {
+        immediate: true,
+        handler(val) {
+          if (val && Object.keys(val).length) {
+            this.$set(this, 'newData', val);
+          }
+        }
+      }
     },
     },
     methods: {
     methods: {
-      initialize() {
-        this.$set(this, 'newData', this.dataStatisticsReportPropsData)
-        this.$set(this, 'showType', true)
+      stopProp(e) {
+        e.stopPropagation && e.stopPropagation();
+      },
+      handleClose() {
+        this.$emit('close');
+      },
+      onOverlayTap() {
+        this.$emit('close');
       },
       },
     }
     }
   }
   }
@@ -132,28 +151,49 @@
 
 
 <style scoped lang="scss">
 <style scoped lang="scss">
 .dataStatisticsReport-addPage {
 .dataStatisticsReport-addPage {
-  background: #f5f7fa;
-  flex: 1;
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  z-index: 999;
+  background: rgba(0, 0, 0, 0.5);
   display: flex;
   display: flex;
-  flex-direction: column;
+  align-items: center;
+  justify-content: center;
 
 
   .report-wrap {
   .report-wrap {
-    flex: 1;
+    position: relative;
+    width: 90%;
+    max-height: 80vh;
+    background: #fffdf5;
+    border-radius: 8rpx;
     display: flex;
     display: flex;
     flex-direction: column;
     flex-direction: column;
 
 
+    .close-btn {
+      position: absolute;
+      top: 16rpx;
+      right: 24rpx;
+      z-index: 10;
+      font-size: 36rpx;
+      color: #8b452f;
+      line-height: 1;
+      padding: 8rpx;
+    }
+
+    .report-scroll {
+      flex: 1;
+      max-height: 80vh;
+    }
+
     .report-wrap-big-box {
     .report-wrap-big-box {
       margin: 20rpx;
       margin: 20rpx;
       border: 1rpx solid #c9a84c;
       border: 1rpx solid #c9a84c;
       border-radius: 4rpx;
       border-radius: 4rpx;
-      position: relative;
-      flex: 1;
-      display: flex;
-      flex-direction: column;
       background-color: #fffdf5;
       background-color: #fffdf5;
 
 
       .report-wrap-min-box {
       .report-wrap-min-box {
-        flex: 1;
         display: flex;
         display: flex;
         flex-direction: column;
         flex-direction: column;
         border: 1rpx solid #c9a84c;
         border: 1rpx solid #c9a84c;
@@ -382,3 +422,4 @@
   }
   }
 }
 }
 </style>
 </style>
+

+ 4 - 2
pages_safetyEducationExamination/views/home/component/examTask.vue

@@ -23,14 +23,14 @@
 		</view>
 		</view>
 
 
 		<!-- 考试承诺书弹窗 -->
 		<!-- 考试承诺书弹窗 -->
-		<uni-popup ref="preExamDialog" type="center" :mask-click="false">
+		<uni-popup ref="preExamDialog" type="center" :mask-click="true">
 			<view class="pre-exam-dialog">
 			<view class="pre-exam-dialog">
 				<view class="dialog-header">
 				<view class="dialog-header">
 					<text class="dialog-title">考试承诺书</text>
 					<text class="dialog-title">考试承诺书</text>
 				</view>
 				</view>
 				<view class="dialog-body">
 				<view class="dialog-body">
 					<scroll-view scroll-y class="content-scroll">
 					<scroll-view scroll-y class="content-scroll">
-						<richTextPdf ref="richTextPdf" :content="richTextHtml" />
+						<richTextPdf ref="richTextPdf" :content="richTextHtml" :examId="richTextExamId" />
 					</scroll-view>
 					</scroll-view>
 				</view>
 				</view>
 				<view class="dialog-footer">
 				<view class="dialog-footer">
@@ -63,6 +63,7 @@
 				// 承诺书相关
 				// 承诺书相关
 				preButtonType: false,
 				preButtonType: false,
 				richTextHtml: '',
 				richTextHtml: '',
+				richTextExamId:'',
 				preData: null,
 				preData: null,
 				itemData:{},
 				itemData:{},
 			}
 			}
@@ -84,6 +85,7 @@
 						openCommitmentDialog: (examItem) => {
 						openCommitmentDialog: (examItem) => {
 							this.preButtonType = examItem.commitmentPrintEnabled == 1;
 							this.preButtonType = examItem.commitmentPrintEnabled == 1;
 							this.richTextHtml = examItem.commitmentContent;
 							this.richTextHtml = examItem.commitmentContent;
+							this.richTextExamId = examItem.examId;
 							this.preData = examItem;
 							this.preData = examItem;
 							this.$refs.preExamDialog.open();
 							this.$refs.preExamDialog.open();
 						},
 						},

+ 14 - 7
pages_safetyEducationExamination/views/home/component/specialExam.vue

@@ -15,20 +15,20 @@
 						<view style="height:24rpx;line-height:24rpx;margin-top:10rpx;">学时要求</view>
 						<view style="height:24rpx;line-height:24rpx;margin-top:10rpx;">学时要求</view>
 						<view style="height:24rpx;line-height:24rpx;">{{examData.completedStudyHours}} / {{examData.studyHoursRequirement}} 学时</view>
 						<view style="height:24rpx;line-height:24rpx;">{{examData.completedStudyHours}} / {{examData.studyHoursRequirement}} 学时</view>
 					</view>
 					</view>
-					<view class="card-min-right-box-1" @click="tableButton(1,item)"
+					<view class="card-min-right-box-1" @click="goToPage()"
 					v-if="examData.needStudy">开始学习</view>
 					v-if="examData.needStudy">开始学习</view>
 				</view>
 				</view>
 				<view class="card-min-box" v-if="examData.examStudy">
 				<view class="card-min-box" v-if="examData.examStudy">
 					<view class="card-min-left-box">
 					<view class="card-min-left-box">
 						<view>考前学习</view>
 						<view>考前学习</view>
 					</view>
 					</view>
-					<view class="card-min-right-box-1" @click="tableButton(2,item)">开始学习</view>
+					<view class="card-min-right-box-1" @click="goToPage()">开始学习</view>
 				</view>
 				</view>
 				<view class="card-min-box" v-if="examData.mockExam">
 				<view class="card-min-box" v-if="examData.mockExam">
 					<view class="card-min-left-box">
 					<view class="card-min-left-box">
 						<view>模拟考试</view>
 						<view>模拟考试</view>
 					</view>
 					</view>
-					<view class="card-min-right-box-1" @click="tableButton(3,item)"
+					<view class="card-min-right-box-1" @click="goToPage()"
 					v-if="!examData.examStudy">开始考试</view>
 					v-if="!examData.examStudy">开始考试</view>
 					<view class="card-min-right-box-2" v-if="examData.examStudy">待通过模拟考试后解锁</view>
 					<view class="card-min-right-box-2" v-if="examData.examStudy">待通过模拟考试后解锁</view>
 				</view>
 				</view>
@@ -36,7 +36,7 @@
 					<view class="card-min-left-box">
 					<view class="card-min-left-box">
 						<view>在线考试</view>
 						<view>在线考试</view>
 					</view>
 					</view>
-					<view class="card-min-right-box-1" @click="tableButton(4,item)"
+					<view class="card-min-right-box-1" @click="goToPage()"
 					v-if="examData.examKind==1&&examData.conditionType == 4">开始考试</view>
 					v-if="examData.examKind==1&&examData.conditionType == 4">开始考试</view>
 					<view class="card-min-right-box-2" v-if="examData.examKind==1&&examData.conditionType != 4">待通过模拟考试后解锁</view>
 					<view class="card-min-right-box-2" v-if="examData.examKind==1&&examData.conditionType != 4">待通过模拟考试后解锁</view>
 				</view>
 				</view>
@@ -44,14 +44,14 @@
 		</view>
 		</view>
 		
 		
 		<!-- 考试承诺书弹窗 -->
 		<!-- 考试承诺书弹窗 -->
-		<uni-popup ref="preExamDialog" type="center" :mask-click="false">
+		<uni-popup ref="preExamDialog" type="center" :mask-click="true">
 			<view class="pre-exam-dialog">
 			<view class="pre-exam-dialog">
 				<view class="dialog-header">
 				<view class="dialog-header">
 					<text class="dialog-title">考试承诺书</text>
 					<text class="dialog-title">考试承诺书</text>
 				</view>
 				</view>
 				<view class="dialog-body">
 				<view class="dialog-body">
 					<scroll-view scroll-y class="content-scroll">
 					<scroll-view scroll-y class="content-scroll">
-						<richTextPdf ref="richTextPdf" :content="richTextHtml" />
+						<richTextPdf ref="richTextPdf" :content="richTextHtml" :examId="richTextExamId" />
 					</scroll-view>
 					</scroll-view>
 				</view>
 				</view>
 				<view class="dialog-footer">
 				<view class="dialog-footer">
@@ -64,6 +64,11 @@
 </template>
 </template>
 
 
 <script>
 <script>
+	import {
+		goToExamPage,
+		confirmCommitmentAndStart,
+		startFormalExam,
+	} from '@/pages_safetyEducationExamination/utils/index.js'
 	import { 
 	import { 
 		examUserExamStudyTaskList, 
 		examUserExamStudyTaskList, 
 	} from '@/pages_safetyEducationExamination/api/index.js'
 	} from '@/pages_safetyEducationExamination/api/index.js'
@@ -79,6 +84,7 @@
 				// 承诺书相关
 				// 承诺书相关
 				preButtonType: false,
 				preButtonType: false,
 				richTextHtml: '',
 				richTextHtml: '',
+				richTextExamId:'',
 				preData: null,
 				preData: null,
 				itemData:{},
 				itemData:{},
 			}
 			}
@@ -93,12 +99,13 @@
 			resetMethod(){
 			resetMethod(){
 				this.examUserExamStudyTaskList();
 				this.examUserExamStudyTaskList();
 			},
 			},
-			goToPage(item) {
+			goToPage() {
 				this.$set(this,'itemData',this.examData);
 				this.$set(this,'itemData',this.examData);
 				goToExamPage(this.examData, {
 				goToExamPage(this.examData, {
 					openCommitmentDialog: (examItem) => {
 					openCommitmentDialog: (examItem) => {
 						this.preButtonType = examItem.commitmentPrintEnabled == 1;
 						this.preButtonType = examItem.commitmentPrintEnabled == 1;
 						this.richTextHtml = examItem.commitmentContent;
 						this.richTextHtml = examItem.commitmentContent;
+						this.richTextExamId = examItem.examId;
 						this.preData = examItem;
 						this.preData = examItem;
 						this.$refs.preExamDialog.open();
 						this.$refs.preExamDialog.open();
 					},
 					},

+ 28 - 67
pages_safetyEducationExamination/views/home/component/userInfoCard.vue

@@ -71,8 +71,7 @@
 				<view class="card-footer" style="margin-top:10rpx;">
 				<view class="card-footer" style="margin-top:10rpx;">
 					<view class="footer-right">
 					<view class="footer-right">
 						<text class="text-sm text-gray">目标考试</text>
 						<text class="text-sm text-gray">目标考试</text>
-						<text class="exam-link"
-							@click="handleExamClick">{{userInfo.targetExamName?userInfo.targetExamName:'无'}}</text>
+						<text class="exam-link">{{userInfo.targetExamName?userInfo.targetExamName:'无'}}</text>
 					</view>
 					</view>
 				</view>
 				</view>
 			</view>
 			</view>
@@ -102,8 +101,10 @@
 				</view>
 				</view>
 			</view>
 			</view>
 		</view>
 		</view>
-		<BindLabModal 
-			:visible="showBindLabModal" 
+		<BindLabModal
+			:visible="showBindLabModal"
+			:off-type="bindLabOffType"
+			:education-type="userInfo.educationType"
 			@close="showBindLabModal = false"
 			@close="showBindLabModal = false"
 			@bind-success="refreshPageData"
 			@bind-success="refreshPageData"
 		/>
 		/>
@@ -141,83 +142,40 @@
 					studyHourRate: '',
 					studyHourRate: '',
 					targetExamName: '',
 					targetExamName: '',
 				},
 				},
-				showBindLabModal:true,
+				showBindLabModal: false,
+				bindLabOffType: true,
 			}
 			}
 		},
 		},
 		mounted() {
 		mounted() {
-			this.examElLabEduRelationBindStatus();
+			this.loadUserInfo();
 		},
 		},
 		methods: {
 		methods: {
-			// 绑定成功后的回调
 			refreshPageData() {
 			refreshPageData() {
-				// 重新获取用户信息或刷新页面数据
-				this.examElLabEduRelationBindStatus();
+				this.loadUserInfo();
 			},
 			},
-			handleExamClick() {
-				// 这里写跳转到考试详情页的逻辑
-				uni.navigateTo({
-					url: '/pages_safetyEducationExamination/screen-exam-detail/screen-exam-detail' // 根据实际路径修改
-				});
-			},
-			// 获取用户信息
-			async examElLabEduRelationBindStatus() {
-				const {
-					data
-				} = await examElLabEduRelationBindStatus({});
+			async loadUserInfo() {
+				const { data } = await examElLabEduRelationBindStatus({});
 				if (data.code == 200) {
 				if (data.code == 200) {
-					// 使用 Object.assign 保留默认值,同时更新接口返回的数据
-					// Object.assign(this.userInfo, data.data);
-					this.$set(this,'userInfo',data.data);
+					this.$set(this, 'userInfo', data.data);
+					if (data.data.isPopup) {
+						this.bindLabOffType = false;
+						this.showBindLabModal = true;
+					}
 				}
 				}
 			},
 			},
-			//关闭弹层
-			shadeButton(type){
-				let self = this;
-				if(type == 1){
-					//提交
-					this.$refs["shadeForm"].validate(valid => {
-						if (valid) {
-							this.$confirm('确认提交?', "提示", {
-								confirmButtonText: "确认",
-								cancelButtonText: "取消",
-								type: "warning"
-							}).then(function() {
-							}).then(() => {
-								self.examElLabEduRelationBind();
-							}).catch(() => {});
-						}
-					})
-				}else if(type == 2){
-					//查看通知
-					this.$set(this,'shadeType',2);
-				}else if(type == 3 || type == 33){
-					if(type == 3){
-						//关闭通知
-						this.$set(this.shadeFormData,'checkType',true);
-						this.$set(this,'shadeType',1);
-					}else if(type == 33){
-						//关闭通知
-						this.$set(this.shadeFormData,'checkType',false);
-						this.$set(this,'shadeType',1);
-					}
-				}else if(type == 4||type == 5){
-					//开启
-					this.$set(this,'shadeFormData',{
-						subId:null,
-						reason:'',
-						isEnter:false,
-						checkType:false,
-						offType:type == 4?true:false,
-					});
-					this.$set(this,'shadeType',1);
-				}else if(type == 6){
-					//关闭
-					this.$set(this,'shadeType',null);
+			shadeButton(type) {
+				if (type == 4) {
+					this.bindLabOffType = true;
+					this.showBindLabModal = true;
+				} else if (type == 5) {
+					this.bindLabOffType = false;
+					this.showBindLabModal = true;
 				}
 				}
 			},
 			},
 		}
 		}
 	}
 	}
-</script>
+
+	</script>
 
 
 <style lang="stylus" scoped>
 <style lang="stylus" scoped>
 	.safetyEducationExamination-workbench-userInfoCard {
 	.safetyEducationExamination-workbench-userInfoCard {
@@ -353,6 +311,7 @@
 					}
 					}
 
 
 					.lab-name {
 					.lab-name {
+						width: 400rpx;
 						font-weight: 500;
 						font-weight: 500;
 						color: #1857d4;
 						color: #1857d4;
 						font-size: 28rpx;
 						font-size: 28rpx;
@@ -448,8 +407,10 @@
 					display: grid;
 					display: grid;
 					grid-template-columns: 1fr 1fr 1fr;
 					grid-template-columns: 1fr 1fr 1fr;
 					gap: 16rpx;
 					gap: 16rpx;
+					display: flex;
 
 
 					.task-item {
 					.task-item {
+						flex:1;
 						border: 1px solid #e8edf6;
 						border: 1px solid #e8edf6;
 						border-radius: 16rpx;
 						border-radius: 16rpx;
 						padding: 20rpx;
 						padding: 20rpx;

+ 3 - 2
pages_safetyEducationExamination/views/home/index.vue

@@ -7,7 +7,7 @@
 			<!-- 学年数据统计 -->
 			<!-- 学年数据统计 -->
 			<academic-year-stats ref="academicYearStats"></academic-year-stats>
 			<academic-year-stats ref="academicYearStats"></academic-year-stats>
 			<!-- 实验室绑定 -->
 			<!-- 实验室绑定 -->
-			<lab-binding ref="labBinding"></lab-binding>
+			<lab-binding ref="labBinding" v-show="userType == 1"></lab-binding>
 			<!-- 考试任务 -->
 			<!-- 考试任务 -->
 			<exam-task ref="examTask"></exam-task>
 			<exam-task ref="examTask"></exam-task>
 			<!-- 特殊考试 -->
 			<!-- 特殊考试 -->
@@ -84,6 +84,7 @@
 		},
 		},
 		data() {
 		data() {
 			return {
 			return {
+				userType:0,
 				baseUrl: config.base_url,
 				baseUrl: config.base_url,
 				topCheckIndex:0,
 				topCheckIndex:0,
 				tableList:[],
 				tableList:[],
@@ -102,7 +103,7 @@
 			
 			
 		},
 		},
 		mounted() {
 		mounted() {
-			console.log('mounted')
+			this.userType = uni.getStorageSync('userType')
 			// this.examElDisciplineTypeList();
 			// this.examElDisciplineTypeList();
 		},
 		},
 		onShow(){
 		onShow(){

+ 3 - 1
pages_safetyEducationExamination/views/practice/index.vue

@@ -140,7 +140,9 @@
 				this.loadPracticeList();
 				this.loadPracticeList();
 			},
 			},
 			goToPractice(item) {
 			goToPractice(item) {
-				// 跳转刷题详情,传递 knowledgePointId
+				uni.navigateTo({
+					url: `/pages_safetyEducationExamination/views/practice/practicePlay?knowledgePointId=${item.knowledgePointId || ''}&attemptId=${item.attemptId || ''}&examId=${item.examId || ''}`,
+				});
 			},
 			},
 		},
 		},
 	}
 	}

+ 783 - 0
pages_safetyEducationExamination/views/practice/practicePlay.vue

@@ -0,0 +1,783 @@
+<!-- 刷题练习页 -->
+<template>
+	<view class="practice-screen">
+		<!-- 练习用时顶栏(绿色) -->
+		<view class="timer-bar">
+			<text class="timer-label">练习用时</text>
+			<text class="timer-text">{{ timerDisplay }}</text>
+			<text class="timer-mode">刷题练习模式</text>
+		</view>
+
+		<!-- 答题统计行 -->
+		<view class="stat-row">
+			<view class="stat-item">
+				<text class="stat-num">{{ answered }}</text>
+				<text class="stat-label">已答</text>
+			</view>
+			<view class="stat-divider"></view>
+			<view class="stat-item">
+				<text class="stat-num stat-correct">{{ correct }}</text>
+				<text class="stat-label">正确</text>
+			</view>
+			<view class="stat-divider"></view>
+			<view class="stat-item">
+				<text class="stat-num stat-wrong">{{ incorrect }}</text>
+				<text class="stat-label">错误</text>
+			</view>
+			<view class="stat-divider"></view>
+			<view class="stat-item">
+				<text class="stat-num">{{ accuracyText }}</text>
+				<text class="stat-label">正确率</text>
+			</view>
+		</view>
+
+		<!-- 题号导航面板(底部弹出,仿 mockExamPlay) -->
+		<view v-if="showQuestionNav" class="q-nav-panel" @tap="closeNav">
+			<view class="q-nav-scroll-wrapper" @tap.stop>
+				<view class="q-nav-header">
+					<text class="q-nav-title">题目列表</text>
+					<text class="q-nav-close" @tap="closeNav">✕</text>
+				</view>
+				<scroll-view scroll-y class="q-nav-scroll" :style="'height:' + navScrollHeight + 'px'">
+					<view v-for="(group, gi) in questionTypeGroups" :key="gi" class="q-group">
+						<view class="q-group-title">{{ group.questionTypeName }}({{ (group.questions || []).length }}题)</view>
+						<view class="q-num-row">
+							<view
+								v-for="(item, qi) in group.questions"
+								:key="qi"
+								class="q-num"
+								:class="getNavNumClass(item, gi, qi)"
+								:data-gi="gi"
+								:data-qi="qi"
+								@tap="onNavNumTap"
+							>{{ item.seq }}</view>
+						</view>
+					</view>
+					<!-- 图例 -->
+					<view class="nav-legend">
+						<view class="legend-item"><view class="q-num legend-dot answered"></view><text>已答对</text></view>
+						<view class="legend-item"><view class="q-num legend-dot wrong"></view><text>答错</text></view>
+						<view class="legend-item"><view class="q-num legend-dot current"></view><text>当前</text></view>
+					</view>
+				</scroll-view>
+			</view>
+		</view>
+
+		<!-- 题目内容 -->
+		<scroll-view scroll-y class="q-scroll-content" :style="scrollStyle">
+			<view v-if="currentQuestion && currentQuestion.id" class="q-card">
+				<!-- 题目头部 -->
+				<view class="q-header">
+					<view class="q-header-left">
+						<text class="q-type-badge">{{ currentQuestion.questionTypeName }}</text>
+						<text class="q-num-text">第 {{ currentSeq }} 题</text>
+						<text v-if="currentQuestion.difficulty" class="diff-badge" :class="diffClass(currentQuestion.difficulty)">{{ diffName(currentQuestion.difficulty) }}</text>
+					</view>
+					<view class="view-answer-btn" @tap="toggleAnswer">
+						{{ viewAnswerShowType ? '关闭答案' : '查看答案' }}
+					</view>
+				</view>
+
+				<!-- 题干 -->
+				<view class="q-content">
+					<rich-text :nodes="currentQuestion.questionContent || ''"></rich-text>
+				</view>
+
+				<!-- 选项 -->
+				<view class="q-options">
+					<view
+						v-for="(opt, oi) in (currentQuestion.options || [])"
+						:key="oi"
+						class="q-option"
+						:class="{ selected: isSelected(opt.optionTag) }"
+						:data-tag="opt.optionTag"
+						@tap="onOptionTap"
+					>
+						<view class="q-option-label">{{ opt.optionTag }}</view>
+						<view class="q-option-content">
+							<rich-text :nodes="opt.optionContent || ''"></rich-text>
+						</view>
+					</view>
+				</view>
+
+				<!-- 查看答案区域 -->
+				<view v-if="viewAnswerShowType" class="answer-result-box">
+					<view class="answer-row">
+						<text class="answer-label">答案:</text>
+						<text class="answer-value">{{ formatAnswer(currentQuestion.correctAnswer) }}</text>
+					</view>
+					<view class="analysis-title">试题解析</view>
+					<view class="analysis-content">
+						<rich-text v-if="currentQuestion.analysis" :nodes="currentQuestion.analysis"></rich-text>
+						<text v-else class="analysis-empty">无</text>
+					</view>
+				</view>
+			</view>
+			<view v-else-if="loading" class="empty-tip">加载中...</view>
+			<view v-else class="empty-tip">暂无题目</view>
+			<view class="scroll-bottom-space"></view>
+		</scroll-view>
+
+		<!-- 上一题/下一题 -->
+		<view class="q-nav">
+			<view class="q-nav-btn" :class="{ disabled: isFirstQuestion }" @tap="prevQuestion">上一题</view>
+			<view class="q-nav-btn q-nav-center" @tap="toggleQuestionNav">选题</view>
+			<view class="q-nav-btn q-nav-btn-next" :class="{ disabled: isLastQuestion }" @tap="nextQuestion">下一题</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	import {
+		examUserPracticePanel,
+		examUserPracticeQuestionDetail,
+		examUserPracticeSubmit,
+		examUserExamStart,
+	} from '../../api/index.js';
+
+	const DIFFICULTY_MAP = {
+		1: { name: '简单', cls: 'diff-easy' },
+		2: { name: '一般', cls: 'diff-normal' },
+		3: { name: '较难', cls: 'diff-hard' },
+		4: { name: '极难', cls: 'diff-extreme' },
+	};
+
+	export default {
+		data() {
+			return {
+				newData: {},
+				examInfo: {
+					knowledgePointName: '',
+					totalCount: 0,
+				},
+				questionTypeGroups: [],
+				currentQuestion: null,
+				groupIndex: 0,
+				questionIndex: 0,
+				viewAnswerShowType: false,
+				showQuestionNav: false,
+				navScrollHeight: 300,
+				loading: false,
+				// 计时器
+				elapsedSeconds: 0,
+				timerInterval: null,
+			}
+		},
+		computed: {
+			answered() {
+				let n = 0;
+				this.questionTypeGroups.forEach(g => {
+					if (g.questions) g.questions.forEach(q => { if (q.answerStatus === 1 || q.answerStatus === 2) n++; });
+				});
+				return n;
+			},
+			correct() {
+				let n = 0;
+				this.questionTypeGroups.forEach(g => {
+					if (g.questions) g.questions.forEach(q => { if (q.answerStatus === 1) n++; });
+				});
+				return n;
+			},
+			incorrect() {
+				let n = 0;
+				this.questionTypeGroups.forEach(g => {
+					if (g.questions) g.questions.forEach(q => { if (q.answerStatus === 2) n++; });
+				});
+				return n;
+			},
+			accuracyText() {
+				let total = 0;
+				this.questionTypeGroups.forEach(g => { if (g.questions) total += g.questions.length; });
+				if (total === 0 || this.correct === 0) return '0%';
+				return (this.correct / total * 100).toFixed(0) + '%';
+			},
+			timerDisplay() {
+				const m = Math.floor(this.elapsedSeconds / 60);
+				const s = this.elapsedSeconds % 60;
+				return String(m).padStart(2, '0') + ':' + String(s).padStart(2, '0');
+			},
+			isFirstQuestion() {
+				return this.groupIndex === 0 && this.questionIndex === 0;
+			},
+			isLastQuestion() {
+				const last = this.questionTypeGroups.length - 1;
+				if (last < 0) return true;
+				const lastQ = this.questionTypeGroups[last].questions.length - 1;
+				return this.groupIndex === last && this.questionIndex === lastQ;
+			},
+			currentSeq() {
+				const g = this.questionTypeGroups[this.groupIndex];
+				if (!g || !g.questions[this.questionIndex]) return '-';
+				return g.questions[this.questionIndex].seq;
+			},
+			scrollStyle() {
+				// timer-bar ~48px + stat-row ~52px + bottom-nav ~44px
+				const sys = uni.getSystemInfoSync();
+				const h = sys.windowHeight - 48 - 52 - 44;
+				return 'height:' + (h > 200 ? h : 400) + 'px;';
+			},
+		},
+		onLoad(options) {
+			this.newData = {
+				knowledgePointId: options.knowledgePointId || '',
+				attemptId: options.attemptId || '',
+				examId: options.examId || '',
+			};
+			uni.setNavigationBarTitle({ title: '刷题练习' });
+			this.startTimer();
+			// 计算题号面板可滚动高度
+			const sys = uni.getSystemInfoSync();
+			this.navScrollHeight = Math.floor(sys.windowHeight * 0.5);
+			this.loadPanel();
+		},
+		onUnload() {
+			this.stopTimer();
+		},
+		// 支持导航栏"题号"按钮(pages.json 中配置 navigationBarButtons)
+		onNavigationBarButtonTap(e) {
+			if (e.index === 0) {
+				this.showQuestionNav = !this.showQuestionNav;
+			}
+		},
+		methods: {
+			closeNav() {
+				this.showQuestionNav = false;
+			},
+			toggleQuestionNav() {
+				this.showQuestionNav = !this.showQuestionNav;
+			},
+			toggleAnswer() {
+				this.viewAnswerShowType = !this.viewAnswerShowType;
+			},
+			onOptionTap(e) {
+				const tag = e.currentTarget.dataset.tag;
+				if (tag !== undefined) this.selectOption(tag);
+			},
+			onNavNumTap(e) {
+				const gi = Number(e.currentTarget.dataset.gi);
+				const qi = Number(e.currentTarget.dataset.qi);
+				this.jumpToQuestion(gi, qi);
+			},
+			startTimer() {				this.timerInterval = setInterval(() => {
+					this.elapsedSeconds++;
+				}, 1000);
+			},
+			stopTimer() {
+				if (this.timerInterval) clearInterval(this.timerInterval);
+			},
+			diffName(d) { return DIFFICULTY_MAP[d] ? DIFFICULTY_MAP[d].name : ''; },
+			diffClass(d) { return DIFFICULTY_MAP[d] ? DIFFICULTY_MAP[d].cls : ''; },
+			isSelected(tag) {
+				if (!this.currentQuestion || !this.currentQuestion.userAnswer) return false;
+				return this.currentQuestion.userAnswer.indexOf(tag) !== -1;
+			},
+			formatAnswer(ans) {
+				if (!ans) return '';
+				return ans.split(',').map(a => {
+					if (a === 'Y') return '对';
+					if (a === 'N') return '错';
+					return a;
+				}).join('、');
+			},
+			getNavNumClass(item, gi, qi) {
+				const isCurrent = this.groupIndex === gi && this.questionIndex === qi;
+				if (isCurrent) return 'current';
+				if (item.answerStatus === 1) return 'answered';
+				if (item.answerStatus === 2) return 'wrong';
+				return '';
+			},
+			// 加载左侧面板数据
+			loadPanel() {
+				if (this.loading) return;
+				this.loading = true;
+				examUserPracticePanel({ knowledgePointId: this.newData.knowledgePointId }).then(res => {
+					const resp = res.data || {};
+					if (resp.code !== 200) {
+						uni.showToast({ title: resp.message || '加载失败', icon: 'none' });
+						return;
+					}
+					const d = resp.data || {};
+					this.examInfo = {
+						knowledgePointName: d.knowledgePointName || '',
+						totalCount: d.totalCount || 0,
+					};
+					this.$set(this, 'questionTypeGroups', d.questionTypeGroups || []);
+					this.groupIndex = 0;
+					this.questionIndex = 0;
+					const first = (d.questionTypeGroups || [])[0];
+					if (first && first.questions && first.questions[0]) {
+						this.loadQuestionDetail(first.questions[0].questionId, first.questions[0].userAnswer);
+					}
+				}).catch(e => {
+					console.error('loadPanel error', e);
+					uni.showToast({ title: '加载失败', icon: 'none' });
+				}).finally(() => {
+					this.loading = false;
+				});
+			},
+			// 加载单题详情
+			loadQuestionDetail(questionId, userAnswer) {
+				examUserPracticeQuestionDetail({ questionId }).then(res => {
+					const resp = res.data || {};
+					if (resp.code !== 200) return;
+					const q = resp.data || {};
+					// 判断题补充选项
+					if (q.questionType == 3) {
+						q.options = [
+							{ optionContent: '正确', optionTag: 'Y', sortOrder: 1 },
+							{ optionContent: '错误', optionTag: 'N', sortOrder: 2 },
+						];
+					}
+					q.userAnswer = Array.isArray(userAnswer)
+						? userAnswer
+						: (userAnswer ? userAnswer.split(',') : []);
+					this.$set(this, 'currentQuestion', q);
+					this.$set(this, 'viewAnswerShowType', false);
+				}).catch(e => {
+					console.error('loadQuestionDetail error', e);
+				});
+			},
+			// 选择选项
+			selectOption(tag) {
+				if (!this.currentQuestion) return;
+				const type = this.currentQuestion.questionType;
+				let ans = [...(this.currentQuestion.userAnswer || [])];
+				if (type == 1 || type == 3) {
+					// 单选 / 判断:单选
+					ans = [tag];
+				} else if (type == 2) {
+					// 多选
+					const idx = ans.indexOf(tag);
+					if (idx === -1) {
+						ans.push(tag);
+					} else {
+						ans.splice(idx, 1);
+					}
+				}
+				this.$set(this.currentQuestion, 'userAnswer', ans);
+			},
+			// 提交当前题答案并切换题目
+			_submitThenGo(nextGroupIndex, nextQuestionIndex) {
+				const q = this.currentQuestion;
+				const hasAnswer = q && q.userAnswer && q.userAnswer[0];
+				if (!hasAnswer) {
+					this._goTo(nextGroupIndex, nextQuestionIndex);
+					return;
+				}
+				const userAnswerStr = q.userAnswer.join(',');
+				examUserPracticeSubmit({
+					attemptId: this.newData.attemptId,
+					questionId: q.id,
+					userAnswer: userAnswerStr,
+				}).then(res => {
+					const data = (res.data || {}).data || {};
+					// 回写 answerStatus 和 userAnswer 到面板 item
+					const answerStatus = data.answerStatus;
+					const g = this.questionTypeGroups[this.groupIndex];
+					if (g && g.questions[this.questionIndex]) {
+						this.$set(g.questions[this.questionIndex], 'answerStatus', answerStatus);
+						this.$set(g.questions[this.questionIndex], 'userAnswer', userAnswerStr);
+					}
+					this._goTo(nextGroupIndex, nextQuestionIndex);
+				}).catch(() => {
+					this._goTo(nextGroupIndex, nextQuestionIndex);
+				});
+			},
+			_goTo(gi, qi) {
+				this.groupIndex = gi;
+				this.questionIndex = qi;
+				const g = this.questionTypeGroups[gi];
+				if (!g) return;
+				const item = g.questions[qi];
+				if (!item) return;
+				this.loadQuestionDetail(item.questionId, item.userAnswer);
+			},
+			prevQuestion() {
+				if (this.isFirstQuestion) return;
+				let gi = this.groupIndex;
+				let qi = this.questionIndex;
+				if (qi > 0) {
+					qi--;
+				} else {
+					gi--;
+					qi = (this.questionTypeGroups[gi] || { questions: [] }).questions.length - 1;
+				}
+				this._submitThenGo(gi, qi);
+			},
+			nextQuestion() {
+				if (this.isLastQuestion) return;
+				let gi = this.groupIndex;
+				let qi = this.questionIndex;
+				const curGroup = this.questionTypeGroups[gi];
+				if (qi < curGroup.questions.length - 1) {
+					qi++;
+				} else {
+					gi++;
+					qi = 0;
+				}
+				this._submitThenGo(gi, qi);
+			},
+			// 从题号面板跳题
+			jumpToQuestion(gi, qi) {
+				this.showQuestionNav = false;
+				if (gi === this.groupIndex && qi === this.questionIndex) return;
+				this._submitThenGo(gi, qi);
+			},
+		},
+	}
+</script>
+
+<style lang="stylus" scoped>
+	.practice-screen
+		height 100%
+		display flex
+		flex-direction column
+		background #f0f4fb
+		overflow hidden
+
+	// 顶部绿色计时器
+	.timer-bar
+		display flex
+		align-items center
+		justify-content center
+		gap 16rpx
+		background linear-gradient(90deg, #52c41a, #389e0d)
+		padding 12rpx 32rpx
+		flex-shrink 0
+
+	.timer-label
+		color rgba(255,255,255,.8)
+		font-size 24rpx
+
+	.timer-text
+		color #fff
+		font-size 44rpx
+		font-weight 700
+		letter-spacing 4rpx
+
+	.timer-mode
+		color rgba(255,255,255,.6)
+		font-size 22rpx
+
+	// 统计行
+	.stat-row
+		display flex
+		align-items center
+		background #fff
+		padding 12rpx 0
+		flex-shrink 0
+		border-bottom 1rpx solid #e8edf3
+
+	.stat-item
+		flex 1
+		display flex
+		flex-direction column
+		align-items center
+
+	.stat-num
+		font-size 36rpx
+		font-weight 700
+		color #333
+
+	.stat-correct
+		color #52c41a
+
+	.stat-wrong
+		color #ff4d4f
+
+	.stat-label
+		font-size 22rpx
+		color #999
+		margin-top 2rpx
+
+	.stat-divider
+		width 1rpx
+		height 48rpx
+		background #e8edf3
+
+	// 题目内容滚动区
+	.q-scroll-content
+		flex 1
+		overflow hidden
+
+	.q-card
+		margin 24rpx
+		padding 28rpx
+		background #fff
+		border-radius 20rpx
+		border 1rpx solid #dde3ed
+
+	// 题头
+	.q-header
+		display flex
+		align-items center
+		justify-content space-between
+		margin-bottom 20rpx
+
+	.q-header-left
+		display flex
+		align-items center
+		flex-wrap wrap
+		gap 10rpx
+
+	.q-type-badge
+		background #1677ff
+		color #fff
+		font-size 22rpx
+		padding 4rpx 14rpx
+		border-radius 8rpx
+
+	.q-num-text
+		font-size 26rpx
+		font-weight 600
+		color #333
+
+	.diff-badge
+		font-size 20rpx
+		padding 2rpx 12rpx
+		border-radius 8rpx
+		border 1rpx solid transparent
+
+	.diff-easy
+		background #f6ffed
+		color #52c41a
+		border-color #b7eb8f
+
+	.diff-normal
+		background #e6f4ff
+		color #1677ff
+		border-color #91caff
+
+	.diff-hard
+		background #fff7e6
+		color #fa8c16
+		border-color #ffd591
+
+	.diff-extreme
+		background #f9f0ff
+		color #722ed1
+		border-color #d3adf7
+
+	.view-answer-btn
+		background #1677ff
+		color #fff
+		font-size 24rpx
+		padding 8rpx 24rpx
+		border-radius 10rpx
+		flex-shrink 0
+
+	// 题干
+	.q-content
+		font-size 28rpx
+		color #333
+		line-height 1.7
+		margin-bottom 24rpx
+
+	// 选项
+	.q-options
+		display flex
+		flex-direction column
+		gap 16rpx
+		margin-bottom 16rpx
+
+	.q-option
+		display flex
+		align-items center
+		border 1rpx solid #e8edf3
+		border-radius 12rpx
+		padding 20rpx 24rpx
+
+	.q-option.selected
+		border-color #1677ff
+		background #e6f7ff
+
+		.q-option-label
+			background #1677ff
+			color #fff
+
+	.q-option-label
+		width 48rpx
+		height 48rpx
+		line-height 48rpx
+		text-align center
+		border-radius 50%
+		background #f0f0f0
+		color #666
+		font-size 24rpx
+		font-weight 600
+		flex-shrink 0
+		margin-right 20rpx
+
+	.q-option-content
+		flex 1
+		font-size 28rpx
+		color #333
+
+	// 答案区
+	.answer-result-box
+		background #f5f5f5
+		border-radius 12rpx
+		padding 24rpx
+		margin-top 16rpx
+
+	.answer-row
+		font-size 26rpx
+		color #333
+		margin-bottom 12rpx
+
+	.answer-label
+		font-weight 600
+
+	.answer-value
+		color #52c41a
+		font-weight 600
+
+	.analysis-title
+		font-size 26rpx
+		font-weight 600
+		color #333
+		margin-bottom 8rpx
+
+	.analysis-content
+		font-size 26rpx
+		color #666
+		line-height 1.7
+		margin 0 20rpx
+
+	.analysis-empty
+		color #999
+
+	.scroll-bottom-space
+		height 40rpx
+
+	// 底部上一题/选题/下一题
+	.q-nav
+		display flex
+		background #fff
+		border-top 1rpx solid #e8edf3
+		flex-shrink 0
+
+	.q-nav-btn
+		flex 1
+		height 88rpx
+		line-height 88rpx
+		text-align center
+		font-size 28rpx
+		color #1677ff
+
+	.q-nav-btn.q-nav-btn-next
+		border-left 1rpx solid #e8edf3
+
+	.q-nav-center
+		border-left 1rpx solid #e8edf3
+		border-right 1rpx solid #e8edf3
+
+	.q-nav-btn.disabled
+		color #ccc
+
+	// 题号导航底部弹层
+	.q-nav-panel
+		position fixed
+		top 0
+		left 0
+		right 0
+		bottom 0
+		background rgba(0,0,0,0.45)
+		z-index 100
+		display flex
+		flex-direction column
+		justify-content flex-end
+
+	.q-nav-scroll-wrapper
+		background #fff
+		border-radius 32rpx 32rpx 0 0
+		max-height 70vh
+		display flex
+		flex-direction column
+
+	.q-nav-header
+		display flex
+		align-items center
+		justify-content space-between
+		padding 28rpx 28rpx 16rpx
+		flex-shrink 0
+
+	.q-nav-title
+		font-size 30rpx
+		font-weight 600
+		color #222
+
+	.q-nav-close
+		font-size 32rpx
+		color #999
+		padding 10rpx
+
+	.q-nav-scroll
+		overflow hidden
+		padding-bottom 40rpx
+
+	.q-group
+		padding 0 28rpx 16rpx
+
+	.q-group-title
+		font-size 24rpx
+		color #666
+		line-height 60rpx
+
+	.q-num-row
+		display flex
+		flex-wrap wrap
+
+	.q-num
+		width 72rpx
+		height 72rpx
+		line-height 72rpx
+		text-align center
+		font-size 24rpx
+		border-radius 12rpx
+		margin 8rpx
+		background #f4f6fa
+		color #333
+		border 1rpx solid transparent
+
+	.q-num.answered
+		background #f6ffed
+		color #52c41a
+		border-color #b7eb8f
+
+	.q-num.wrong
+		background #fff2f0
+		color #ff4d4f
+		border-color #ffa39e
+
+	.q-num.current
+		background #1677ff
+		color #fff
+
+	// 图例
+	.nav-legend
+		display flex
+		gap 24rpx
+		flex-wrap wrap
+		padding 12rpx 28rpx 24rpx
+
+	.legend-item
+		display flex
+		align-items center
+		gap 8rpx
+		font-size 22rpx
+		color #666
+
+	.legend-dot
+		width 40rpx
+		height 40rpx
+		margin 0
+		font-size 0
+
+	// 空态
+	.empty-tip
+		text-align center
+		line-height 120rpx
+		color #aaa
+		font-size 24rpx
+</style>

+ 13 - 8
pages_safetyEducationExamination/views/webViewPage/courseLearning.vue

@@ -15,14 +15,19 @@
 			}
 			}
 		},
 		},
 		onLoad(option) {
 		onLoad(option) {
-			this.$set(this,'dataText',
-				uni.getStorageSync('cameraExtranetAgent')+
-				// 'http://192.168.1.8/stream/#/'+
-				'?pageType=learningCourse&touken='+uni.getStorageSync('token')+
-				'&courseId='+option.courseId+
-				'&examId='+option.examId
-			);
-			this.$set(this,'webViewType',true);
+			if(uni.getStorageSync('weChatProgramVersion')){
+				this.$set(this,'dataText',
+					uni.getStorageSync('cameraExtranetAgent')+
+					// 'http://192.168.1.8/stream/#/'+
+					'?pageType=learningCourse&touken='+uni.getStorageSync('token')+
+					'&courseId='+option.courseId+
+					'&examId='+option.examId
+				);
+				this.$set(this,'webViewType',true);
+			}else{
+				this.$set(this,'dataText','');
+				this.$set(this,'webViewType',false);
+			}
 		},
 		},
 		mounted() {
 		mounted() {
 
 

+ 12 - 7
pages_safetyEducationExamination/views/webViewPage/coursewareLearning.vue

@@ -16,13 +16,18 @@
 			}
 			}
 		},
 		},
 		onLoad(option) {
 		onLoad(option) {
-			this.$set(this,'dataText',
-				uni.getStorageSync('cameraExtranetAgent')+
-				// 'http://192.168.1.8/stream/#/'+
-				'?pageType=learningMaterials&touken='+uni.getStorageSync('token')+
-				'&coursewareId='+option.coursewareId
-			);
-			this.$set(this,'webViewType',true);
+			if(uni.getStorageSync('weChatProgramVersion')){
+				this.$set(this,'dataText',
+					uni.getStorageSync('cameraExtranetAgent')+
+					// 'http://192.168.1.8/stream/#/'+
+					'?pageType=learningMaterials&touken='+uni.getStorageSync('token')+
+					'&coursewareId='+option.coursewareId
+				);
+				this.$set(this,'webViewType',true);
+			}else{
+				this.$set(this,'dataText','');
+				this.$set(this,'webViewType',false);
+			}
 		},
 		},
 		mounted() {
 		mounted() {