wangxy 4 kuukautta sitten
vanhempi
commit
d17957b4d7

+ 76 - 0
api/kaoshi.js

@@ -61,3 +61,79 @@ export function getClientZhuaPaiUpdate(data = {}) {
     timeout: 20000
   })
 }
+
+
+export function getClientQiepingCheat(data = {}) {
+  return request({
+    url: '/app/qieping/cheat',
+    headers: {
+      isToken: false
+    },
+    method: 'post',
+    data,
+    timeout: 20000
+  })
+}
+
+export function getClientQiepingTimes(data = {}) {
+  return request({
+    url: '/app/qieping/times',
+    headers: {
+      isToken: false
+    },
+    method: 'post',
+    data,
+    timeout: 20000
+  })
+}
+
+
+export function getClientUserInfo(data = {}) {
+  return request({
+    url: '/app/kaoshi/user/info',
+    headers: {
+      isToken: false
+    },
+    method: 'post',
+    data,
+    timeout: 20000
+  })
+}
+
+
+export function getClientKsSubmit(data = {}) {
+  return request({
+    url: '/app/kaoshi/submit',
+    headers: {
+      isToken: false
+    },
+    method: 'post',
+    data,
+    timeout: 20000
+  })
+}
+
+export function getClientKsStart(data = {}) {
+  return request({
+    url: '/app/kaoshi/start',
+    headers: {
+      isToken: false
+    },
+    method: 'post',
+    data,
+    timeout: 20000
+  })
+}
+
+
+export function getClientKsSave(data = {}) {
+  return request({
+    url: '/app/kaoshi/save',
+    headers: {
+      isToken: false
+    },
+    method: 'post',
+    data,
+    timeout: 20000
+  })
+}

+ 61 - 9
components/identification/identification.vue

@@ -1,18 +1,70 @@
 <template>
 	<view>
-		身份确认
+		<uni-popup ref="popupRef" type="dialog" :animation="false" :is-mask-click="false"
+			mask-background-color="rgba(0, 0, 0, 0.4);">
+			<uni-popup-dialog mode="input" class="phone-ksxz-dialog" title="身份确认" :duration="2000" :before-close="true"
+				cancelText="修改" @close="handleClose" @confirm="handleConfirm">
+				<view class="ksxz-content-box">
+					<view>头像:
+						<image :src="data.icon" />
+					</view>
+					<view>姓名:{{data.realName}}</view>
+					<view>电话:{{data.userName}}</view>
+					<view>职业:{{data.zyName}}</view>
+					<view>等级:{{data.zyLevelName}}</view>
+					<view>身份证号(护照号):{{data.idcard}}</view>
+					<view>性别:{{data.gender == 0?'未知': data.gender == '2' ? '女': '男'}}</view>
+				</view>
+			</uni-popup-dialog>
+		</uni-popup>
 	</view>
 </template>
 
-<script>
-	export default {
-		name:"identification",
-		data() {
-			return {
-				
-			};
-		}
+<script setup>
+	import {
+		ref,
+		reactive
+	} from "vue"
+	const popupRef = ref(null)
+	const data = reactive({
+		realName: '',
+		userName: '',
+		zyLevelName: '',
+		zyName: '',
+		idcard: '',
+		icon: '',
+		gender: '',
+		userId: '',
+	})
+
+	const emits = defineEmits(['confirm', 'changeData'])
+
+	function showDialog(options) {
+		data.realName = options.realName;
+		data.userName = options.userName;
+		data.zyLevelName = options.zyLevelName;
+		data.zyName = options.zyName;
+		data.idcard = options.idcard;
+		data.icon = options.icon;
+		data.userId = options.userId;
+		data.gender = options.gender; // 1男 2女 0未知
+
+		popupRef.value.open()
+	}
+
+	function handleClose() {
+		emits('changeData');
+		popupRef.value.close()
+	}
+
+	function handleConfirm() {
+		emits('confirm', data);
+		popupRef.value.close()
 	}
+
+	defineExpose({
+		showDialog
+	})
 </script>
 
 <style lang="scss">

+ 23 - 23
components/kaoshixuzhi/kaoshixuzhi.vue

@@ -1,28 +1,28 @@
 <template>
-
-	<uni-popup ref="popupRef" type="dialog" :animation="false" :is-mask-click="false"
-	mask-background-color="rgba(0, 0, 0, 0.4);">
-		<uni-popup-dialog mode="input"
-		class="phone-ksxz-dialog"
-			title="考试须知"
-			:duration="2000" 
-			:before-close="true"
-			@close="handleClose"
-			@confirm="handleConfirm">
-			<view class="ksxz-content-box">
-				<view v-if="data.ksName">考试名称:{{data.ksName}}</view>
-				<view v-if="data.zyName">职业:{{data.zyName}}</view>
-				<view v-if="data.zyLevelName">等级:{{data.zyLevelName}}</view>
-				<view v-if="data.ksScore">总分:{{data.ksScore}}</view>
-				<view v-if="data.okScore">及格分:{{data.okScore}}</view>
-				<view>
-					<view v-if="data.intro" class="ksms-row">考试描述:</view>
-					<rich-text v-if="data.intro" :nodes="data.intro" class="ksms-intro"></rich-text>
+	<view>
+		<uni-popup ref="popupRef" type="dialog" :animation="false" :is-mask-click="false"
+		mask-background-color="rgba(0, 0, 0, 0.4);">
+			<uni-popup-dialog mode="input"
+			class="phone-ksxz-dialog"
+				title="考试须知"
+				:duration="2000" 
+				:before-close="true"
+				@close="handleClose"
+				@confirm="handleConfirm">
+				<view class="ksxz-content-box">
+					<view v-if="data.ksName">考试名称:{{data.ksName}}</view>
+					<view v-if="data.zyName">职业:{{data.zyName}}</view>
+					<view v-if="data.zyLevelName">等级:{{data.zyLevelName}}</view>
+					<view v-if="data.ksScore">总分:{{data.ksScore}}</view>
+					<view v-if="data.okScore">及格分:{{data.okScore}}</view>
+					<view>
+						<view v-if="data.intro" class="ksms-row">考试描述:</view>
+						<rich-text v-if="data.intro" :nodes="data.intro" class="ksms-intro"></rich-text>
+					</view>
 				</view>
-			</view>
-		</uni-popup-dialog>
-	</uni-popup>
-
+			</uni-popup-dialog>
+		</uni-popup>
+	</view>
 </template>
 
 <script setup>

+ 57 - 0
components/zhuapaiConfirm/answerQueren.vue

@@ -0,0 +1,57 @@
+<template>
+	<view>
+		<uni-popup ref="popupRef" type="dialog" :animation="false" :is-mask-click="false"
+		mask-background-color="rgba(0, 0, 0, 0.4);">
+			<uni-popup-dialog mode="input"
+			class="phone-ksxz-dialog"
+				title="提示"
+				:duration="2000" 
+				:before-close="true"
+				:showClose="false"
+				@close="handleClose"
+				@confirm="handleConfirm">
+				<text>
+					您已经回答了{{data.answercartsCount}}题(共{{data.answercartsTotal}}题),确认交卷?
+				</text>
+			</uni-popup-dialog>
+		</uni-popup>
+	</view>
+</template>
+
+<script setup>
+	import {
+		ref,reactive
+	} from "vue";
+	const popupRef = ref(null)
+	const data = reactive({
+		answercartsCount: '',
+		answercartsTotal: '',
+
+	})
+	
+	const emits = defineEmits(['confirm', 'cancel'])
+	
+	function showDialog(options) {
+		data.answercartsCount = options.answercartsCount;
+		data.answercartsTotal = options.answercartsTotal;
+		popupRef.value.open()
+	}
+	
+	function handleClose() {
+		emits('cancel');
+		popupRef.value.close()
+	}
+	
+	function handleConfirm() {
+		emits('confirm', data);
+		popupRef.value.close()
+	}
+	
+	defineExpose({
+		showDialog
+	})
+	
+</script>
+
+<style>
+</style>

+ 129 - 5
components/zhuapaiConfirm/qieping.vue

@@ -3,11 +3,135 @@
 </template>
 
 <script setup>
-	import {ref} from "vue";
-	
-	defineEmits([ ])
-	
-	function init() {}
+	import {
+		ref,
+		onUnmounted,
+		nextTick,
+		computed
+	} from "vue";
+	import {
+		getClientQiepingCheat,getClientQiepingTimes
+	} from "@/api/kaoshi.js"
+	import {
+		useZhuapaiStore
+	} from "@/store/zhuapai.js"
+
+	const zhuapaiStore = useZhuapaiStore();
+
+	const emits = defineEmits(['zhuapai', 'forceSubmit'])
+	const zhuapaiFlag = ref(false);
+	const zhuapaixiaoshi = ref(0);
+	const leaveTime = ref('');
+	const toggleScreenSecond = ref(0);
+	const toggleScreenFlag = ref(0);
+	const ksId = ref('')
+
+	function cheatingFun() {
+		// 用户离开了当前页面
+		if (document.visibilityState === 'hidden') {
+			console.log('页面不可见');
+			if (zhuapaiFlag.value) {
+				// 此时 切出后台 发生抓拍情况下 要传递固定图片
+				console.log('切出去zhuapaixiaoshi 恢复正常');
+				zhuapaixiaoshi.value = 1
+				zhuapaiStore.setStatus(1)
+			}
+			//  计时
+			leaveTime.value = new Date().getTime();
+		} else if (document.visibilityState === 'visible') {
+			console.log('切回来 恢复正常');
+			// 用户打开或回到页面
+			if (zhuapaiFlag.value) {
+				zhuapaixiaoshi.value = 0
+				zhuapaiStore.setStatus(0)
+				emits('zhuapai') // 重置抓拍
+			}
+			zhuapaixiaoshi.value = 0
+			zhuapaiStore.setStatus(0)
+			console.log('页面可见');
+			let nowTime = new Date().getTime();
+			if (Number(nowTime) - Number(leaveTime.value) > toggleScreenSecond.value + '000') {
+				let req = {
+					ksId: ksId.value,
+				};
+				getClientQiepingCheat(req).then(res => {
+					//cutScreenDialog   是否超限 true:超过限制 false:未超过限制 ,
+					if (res.code === 0 && res.data.flag) {
+						emits('forceSubmit') // 强制交卷
+					} else {
+						emits('qiepingToast', res.data.times) // 提示警告
+					}
+				});
+			}
+		}
+	}
+
+	function zhuapaiFun() {
+		if (document.visibilityState === 'hidden') {
+			console.log('页面不可见');
+			zhuapaixiaoshi.value = 1
+			zhuapaiStore.setStatus(1)
+		} else if (document.visibilityState === 'visible') {
+			zhuapaixiaoshi.value = 0
+			zhuapaiStore.setStatus(0)
+			emits('zhuapai')
+		}
+	}
+
+	function init(options) {
+		console.log('init', options)
+		toggleScreenFlag.value = options.toggleScreenFlag;
+		toggleScreenSecond.value = options.toggleScreenSecond;
+		zhuapaiFlag.value = options.zhuapaiFlag;
+		ksId.value = options.ksId;
+		// #ifdef H5
+		if (toggleScreenFlag.value !== 0) {
+			console.log("有切屏");
+			document.addEventListener('visibilitychange', cheatingFun, false);
+			cheatingNumberSearch();
+		}
+		if (zhuapaiFlag.value && toggleScreenFlag.value == 0) {
+			console.log("有抓拍 无切屏");
+			// 有抓拍 没有切屏  此方法是 解决切到后台,抓拍停留一帧的问题
+			document.addEventListener('visibilitychange', zhuapaiFun, false);
+		}
+		// #endif
+	}
+
+	function cheatingNumberSearch() {
+		let req = {
+			ksId: ksId.value,
+		};
+		getClientQiepingTimes(req).then(res => {
+			if (res.code === 0) {
+				if (res.data.times > 0 && res.data.times <= res.data.toggleScreenFlag) {
+					emits('qiepingToast', res.data.times) // 提示警告
+				} else if (res.data.times > 0 && res.data.times >= res.data.toggleScreenFlag) {
+					emits('forceSubmit') // 强制交卷
+				} 
+			}
+		});
+	}
+
+	function stopListen() {
+		if (toggleScreenFlag.value !== 0) {
+			console.log("有切屏 销毁");
+			document.removeEventListener('visibilitychange', cheatingFun, false);
+		}
+		if (zhuapaiFlag.value && toggleScreenFlag.value == 0) {
+			console.log("有抓拍 无切屏 销毁");
+			document.removeEventListener('visibilitychange', zhuapaiFun, false);
+		}
+	}
+
+	onUnmounted(() => {
+		// 组件销毁时移除监听
+		stopListen()
+	})
+
+	defineExpose({
+		init
+	})
 </script>
 
 <style>

+ 71 - 0
components/zhuapaiConfirm/submitScore.vue

@@ -0,0 +1,71 @@
+<template>
+	<uni-popup ref="popupRef" :animation="false" :is-mask-click="false" type="bottom"
+		mask-background-color="rgba(0, 0, 0, 0.4);" >
+		<view class="score-content">
+			<view class="icon-title-navBar-box">
+				<view @click="handleClose" class="nav-bar-icon"></view>
+				<text class="nav-bar-title">考试得分</text>
+			</view>
+			
+			<view class="popup-height">
+				<view>考试名称:{{data.ksName}}</view>
+				<view>考试总分:{{data.ksScore}}</view>
+				<view>及格分数:{{data.okScore}}</view>
+				<view>正确数量:{{data.rightCount}}</view>
+				<view>试题总数:{{data.shitiTotal}}</view>
+				<view>用户得分:{{data.userScore}}</view>
+			</view>
+			<button type="primary" @click="handleCheckSj">查看成绩</button>
+		</view>
+	</uni-popup>
+</template>
+
+<script setup>
+	import {
+		ref,reactive
+	} from "vue";
+	const popupRef = ref(null)
+	const data = reactive({
+		ksName: '',
+		ksScore: '',
+		okScore: '',
+		rightCount: '',
+		shitiTotal: '',
+		userScore: '',
+	})
+	
+	const emits = defineEmits(['confirm', 'close'])
+	
+	function handleClose() {
+		emits('close');
+		popupRef.value.close()
+	}
+	
+	function handleCheckSj() {
+		emits('confirm', data);
+		popupRef.value.close()
+	}
+	
+	function showDialog(options) {
+		data.ksName = options.ksName;
+		data.ksScore = options.ksScore;
+		data.okScore = options.okScore;
+		data.rightCount = options.rightCount;
+		data.shitiTotal = options.shitiTotal;
+		data.userScore = options.userScore;
+		popupRef.value.open()
+	}
+
+	defineExpose({
+		showDialog
+	})
+	
+</script>
+
+<style lang="scss" scoped>
+	.score-content {
+		height: 100vh;background-color: #fff;
+	}
+	.popup-height {
+	}
+</style>

+ 33 - 8
components/zhuapaiConfirm/useCamera.js

@@ -301,6 +301,7 @@ import {
 	ref,
 	nextTick
 } from "vue"
+import {useZhuapaiStore} from "@/store/zhuapai.js"
 // H5 播放抓拍功能
 export function useH5Camera({
 	elVideoId,
@@ -309,6 +310,10 @@ export function useH5Camera({
 	onVideoError, // 失败回调
 	zhuapaiHttp, // 抓拍接口将base64 上传
 }) {
+
+	const zhuapaiStore = useZhuapaiStore();
+
+
 	const videoRef = ref('');
 	videoRef.value = document.querySelector(`${elVideoId} .uni-video-video`);
 
@@ -382,18 +387,38 @@ export function useH5Camera({
 		}
 
 		try {
-			let canvas = document.querySelector(`${elCanvasId} .uni-canvas-canvas`);
-			let context = canvas.getContext('2d');
-			context.drawImage(videoRef.value,  0, 0, videoRef.value.clientWidth, videoRef.value.clientHeight);
-			const ImageFile = context.canvas.toDataURL('image/png');
-			getSnapShotImage(ImageFile);
+			if (zhuapaiStore.status == 0) {
+				// status 为 如果抓拍过程中 应用进入后台 传递固定图片 0为正常 1为进入后台
+				let canvas = document.querySelector(`${elCanvasId} .uni-canvas-canvas`);
+				let context = canvas.getContext('2d');
+				context.drawImage(videoRef.value, 0, 0, videoRef.value.clientWidth, videoRef.value.clientHeight);
+				const ImageFile = context.canvas.toDataURL('image/png');
+				getSnapShotImage(ImageFile);
+			} else {
+				 const ImageFile = this.getBase64Image()
+				getSnapShotImage(ImageFile);
+			}
 		} catch (err) {
 			console.error('源 :绘图失败', err);
 		}
 	}
 
+	function getBase64Image() {
+		var canvas = document.createElement("canvas");
+		var ctx = canvas.getContext("2d");
+		canvas.width = 480;
+		canvas.height = 320;
+		ctx.drawImage(document.querySelector('#gdImg'), 0, 0, 480, 320);
+		let image = new Image();
+		image.src = canvas.toDataURL('image/png', 1)
+		setTimeout(res => {
+			ctx.clearRect(0, 0, 480, 320)
+		}, 500)
+		return image.src
+	}
+
 	function getSnapShotImage(data) {
-		console.log('base64',data)
+		console.log('base64', data)
 		const imgData = data.split(';base64,');
 		if (!imgData.length) {
 			console.error('【源 :拍照数据异常,未找到图片二进制数据分割节点: `;base64,`】');
@@ -404,8 +429,8 @@ export function useH5Camera({
 			prefix: 'kaoshi/zhuapai',
 			suffix: 'png',
 		};
-		
-		console.log('optoptopt',opt)
+
+		console.log('optoptopt', opt)
 		zhuapaiHttp && zhuapaiHttp(opt)
 			.then(res => {
 				console.log('【源 : 获取抓拍数据】');

+ 3 - 4
components/zhuapaiConfirm/zhuapai.vue

@@ -2,15 +2,14 @@
 	<view class="zhuapai-drop-container" id="Drop" ref="DropRef" :style="style" @touchmove="touchmove($event)" @touchstart="touchstart($event)">
 		<view class="phone-camera-box-zhuapai">
 			<video ref="videoRef" class="video-view-box" :class="myClass" id="videoZhaPai" :controls="false"></video>
-
 			<!-- 隐藏抓拍绘制图片 -->
 			<canvas id="canvasZhuaPai" class="video-view-box" :class="myClass"></canvas>
 			<!-- 用于抓拍切出去传递固定img-->
 			<!-- #ifdef H5 -->
-			<img :src="imgUrl" alt="" ref="gudingImg" v-show="false">
+			<img :src="imgUrl" alt="" id="gdImg" v-show="false">
 			<!-- #endif -->
 			<!-- 测试抓拍使用 -->
-			<button @click="handleZhua">抓拍</button>
+			<!-- <button @click="handleZhua">抓拍</button> -->
 		</view>
 		<span v-show="showVideo" @click="noShowVideoBtn" class="shiti-video-hidden-btn">开<i></i></span>
 		<span v-show="!showVideo" @click="showVideoBtn" class="shiti-video-show-btn">关<i></i></span>
@@ -162,7 +161,7 @@
 	})
 
 	defineExpose({
-		init
+		init,showVideoBtn
 	})
 </script>
 

+ 13 - 13
pages/Login/components/loginBox.vue

@@ -1,4 +1,4 @@
-<template>
+<template>
 	<view class="phone-login-page">
 		<view class="login-wrap-box">
 			<view class="bjcx-head-box">
@@ -23,16 +23,16 @@
 		<!-- 已加密的:{{lliPassword}} -->
 		</view>
 		<passwordLli ref="passLLiRef" :password="password" @lli-password="onLliPassword" />
-	</view>
-</template>
-
+	</view>
+</template>
+
 <script setup>
 	import cacheManager from '@/utils/cacheManager.js'
 	import * as httpApi from "@/api/login.js"
 	import passwordLli from "@/components/password-lli/password-lli.vue";
 	import {ref} from "vue"
 	import {toast} from "@/utils/common";
-	const userName = ref('18640920672') // 用户名
+	const userName = ref('18604088413') // 用户名
 	const password = ref('') // 密码
 	const lliPassword = ref('') // 加密后的密码
 	const passLLiRef = ref(null)
@@ -81,8 +81,8 @@
 	}
 
 	// 密码显隐
-	function changePassword() {
-      showPassword.value = !showPassword.value;
+	function changePassword() {
+      showPassword.value = !showPassword.value;
     }
 	
 	// 登录
@@ -116,7 +116,7 @@
 		// 客户端
 		// #ifdef H5
 		uni.navigateTo({
-			url: `/pages/admin/Jiazheng/index`
+			url: `/pages/client/Kaoshi/list`
 		})
 		// #endif
 		
@@ -128,8 +128,8 @@
 		// #endif
 
 	}
-</script>
-
-<style>
-
-</style>
+</script>
+
+<style>
+
+</style>

+ 1 - 1
pages/admin/Kaoshi/exam.vue

@@ -147,7 +147,7 @@
 	
 	const dlName = computed(() => {
 		if (data.StListForSearch && activeSt.value) {
-			return data.StListForSearch[activeSt.value.onlyNum].paragraphName
+			return data.StListForSearch[activeSt.value.onlyNum-1].paragraphName
 		} else {
 			return ''
 		}

+ 1 - 1
pages/admin/Kecheng/study.vue

@@ -31,7 +31,7 @@
 	import kechengMuluVue from "@/components/kecheng-mulu/kecheng-mulu.vue";
 	import {
 		useUserCache
-	} from "@/utils/kechengCache.js"
+	} from "@/utils/userCache.js"
 	import {
 		formatDuration
 	} from "@/utils/common.js"

+ 267 - 57
pages/client/Kaoshi/exam.vue

@@ -10,7 +10,8 @@
 			<!--  倒计时 -->
 			<view v-if="!!data.endSecond">
 				<text>考试倒计时:</text>
-				<uni-countdown :show-day="true" :second="1000" @timeup="onTimeUp" :start="startCountDown"></uni-countdown>
+				<uni-countdown :show-day="true" :second="1000" @timeup="onTimeUp"
+					:start="startCountDown"></uni-countdown>
 			</view>
 			<view v-if="activeSt" class="title-types">{{dlName}}</view>
 			<view>100分钟</view>
@@ -25,7 +26,7 @@
 					<!-- 单选 -->
 					<danxuan :question="activeSt" :key="activeSt.stId"></danxuan>
 				</template>
-				<template v-if="activeSt.stTypeId == 2" >
+				<template v-if="activeSt.stTypeId == 2">
 					<!-- 多选 -->
 					<duoxuan :question="activeSt" :key="activeSt.stId"></duoxuan>
 				</template>
@@ -42,16 +43,23 @@
 		</view>
 
 		<view class="kaoshi-bottom-box">
-			<button class="phone-green-btn bj-btn" hover-class="none" type="default" size="mini" @click="handleBiaoji">标记</button>
+			<button class="phone-green-btn bj-btn" hover-class="none" type="default" size="mini"
+				@click="handleBiaoji">标记</button>
 			<view @click="showAnswerCard" class="shiti-num-box">
 				<icon class="shiti-num-icon"></icon>
-				<text class="active-num">{{activeSt ? activeSt.onlyNum: 0}}</text>/<text>{{data.StListForSearch.length}}</text>
+				<text
+					class="active-num">{{activeSt ? activeSt.onlyNum: 0}}</text>/<text>{{data.StListForSearch.length}}</text>
 			</view>
+			<button class="phone-green-btn" hover-class="none" type="default" size="mini"
+				@click="handleSave(true)">保存</button>
 		</view>
 		<template v-if="activeSt">
-			<button type="default" size="mini" hover-class="none" class="phone-green-btn ks-btn-prev" @click="handlePrev" v-if="!isFistStId">上一题</button>
-			<button type="default" size="mini" hover-class="none"class="phone-green-btn ks-btn-next" @click="handleNext" v-if="!isLastStId">下一题</button>
-			<button type="default" size="mini" hover-class="none"class="phone-green-btn ks-btn-next" @click="handleBack" v-if="isLastStId">交卷</button>
+			<button type="default" size="mini" hover-class="none" class="phone-green-btn ks-btn-prev"
+				@click="handlePrev" v-if="!isFistStId">上一题</button>
+			<button type="default" size="mini" hover-class="none" class="phone-green-btn ks-btn-next"
+				@click="handleNext" v-if="!isLastStId">下一题</button>
+			<button type="default" size="mini" hover-class="none" class="phone-green-btn ks-btn-next"
+				@click="handleJiaojuan" v-if="isLastStId">交卷</button>
 		</template>
 
 		<!-- 答题卡 -->
@@ -61,29 +69,29 @@
 					<view @click="handlePopupBack" class="nav-bar-icon"> </view>
 					<text class="nav-bar-title">答题卡</text>
 				</view>
-				<view class="answer-card-content" v-for="(paragraph,paragraphIndex) in questionData" :key="paragraphIndex">
+				<view class="answer-card-content" v-for="(paragraph,paragraphIndex) in questionData"
+					:key="paragraphIndex">
 					<view class="paragraph-title">
 						{{paragraph.name}}
 					</view>
-					<view class="paragraph-qa" v-for="(qa,qaIndex) in paragraph.qas" :key="qaIndex" 
-					:class="getQaClass(qa)" @click="answerCardItemClick(qa)">{{qa.onlyNum}}
+					<view class="paragraph-qa" v-for="(qa,qaIndex) in paragraph.qas" :key="qaIndex"
+						:class="getQaClass(qa)" @click="answerCardItemClick(qa)">{{qa.onlyNum}}
 					</view>
 				</view>
 			</view>
 		</uni-popup>
-		<zhuapaiConfirm ref="zhuapaiConfirmRef" 
-			@success="zpConfirmSuccess"
-			@error="zpConfirmError"
-			@cancel="zpConfirmCancel"
-		
-		></zhuapaiConfirm>
+		<!-- 摄像头确认 -->
+		<zhuapaiConfirm ref="zhuapaiConfirmRef" @success="zpConfirmSuccess" @error="zpConfirmError"
+			@cancel="zpConfirmCancel" key="1"></zhuapaiConfirm>
 		<!-- 抓拍 -->
-		<zhuapaiVue ref="zhuapaiRef"
-			@error="zpError"
-		></zhuapaiVue>
+		<zhuapaiVue ref="zhuapaiRef" @error="zpError" @success="zpSuccess" key="2"></zhuapaiVue>
 		<!-- 切屏 -->
-		<qiepingVue ref="qiepingRef"></qiepingVue>
-		
+		<qiepingVue ref="qiepingRef" @zhuapai="qpZhuapai" @forceSubmit="forceSubmit" @qiepingToast="qiepingToast"
+			key="3"></qiepingVue>
+		<!-- 交卷确认 -->
+		<answerQueren ref="answerQrRef" @confirm="handleQuerenConfirm"></answerQueren>
+		<!-- 考试得分 -->
+		<submitScoreVue ref="subScoreRef" @confirm="handleScoreConfirm" @close="handleScoreClose"></submitScoreVue>
 	</view>
 </template>
 
@@ -97,7 +105,9 @@
 	} from "vue";
 	import zhuapaiVue from "@/components/zhuapaiConfirm/zhuapai.vue";
 	import qiepingVue from "@/components/zhuapaiConfirm/qieping.vue";
-	import zhuapaiConfirm from "@/components/zhuapaiConfirm/index.vue"
+	import zhuapaiConfirm from "@/components/zhuapaiConfirm/index.vue";
+	import answerQueren from "@/components/zhuapaiConfirm/answerQueren.vue";
+	import submitScoreVue from "@/components/zhuapaiConfirm/submitScore.vue";
 	import {
 		onLoad
 	} from "@dcloudio/uni-app";
@@ -106,7 +116,13 @@
 	import duoxuan from "@/components/questions/duoxuan.vue";
 	import tiankong from "@/components/questions/tiankong.vue";
 	import panduan from "@/components/questions/panduan.vue";
-	import {useQuestionTools} from "@/components/questions/useQuestionTools.js";
+	import {
+		useQuestionTools
+	} from "@/components/questions/useQuestionTools.js";
+	import {
+		useKaoShiCache
+	} from "./examTools"
+
 	const {
 		checkDanxuanReply,
 		checkDuoxuanReply,
@@ -114,12 +130,17 @@
 		checkTiankongReply,
 		getLetterByIndex
 	} = useQuestionTools();
+	const {
+		saveCacheKs,
+		getCacheKs,
+		removeCacheKs
+	} = useKaoShiCache();
 
 	onLoad((option) => {
 		data.ksId = option.ksId;
 		data.zhuapai = option.zhuapai
-		
-		if (data.zhuapai) {
+
+		if (data.zhuapai && data.zhuapai != 0) {
 			// 考试前确认摄像头
 			nextTick(() => {
 				initBeforKaoshi();
@@ -134,11 +155,15 @@
 	const zhuapaiRef = ref(null)
 	const qiepingRef = ref(null)
 	const zhuapaiConfirmRef = ref(null)
-
+	const answerQrRef = ref(null);
 	const startCountDown = ref(false);
+	const subScoreRef = ref(null)
+	
+	const timer1 = ref(null);
 
 	const data = reactive({
 		ksId: null,
+		operId: null,
 		ksName: '',
 		stTotal: 0,
 		stScore: 0,
@@ -159,10 +184,10 @@
 		dlIndex: 0,
 		dtIndex: 0
 	})
-	
+
 	const dlName = computed(() => {
 		if (data.StListForSearch && activeSt.value) {
-			return data.StListForSearch[activeSt.value.onlyNum].paragraphName
+			return data.StListForSearch[activeSt.value.onlyNum - 1].paragraphName
 		} else {
 			return ''
 		}
@@ -196,11 +221,175 @@
 			return false
 		}
 	});
-
-	// 摄像头抓拍相关功能 start
-	function zpSuccess() {
+	
+	
+	function handleScoreClose() {
+		uni.redirectTo({
+			url: '/pages/client/Kaoshi/list'
+		})
+	}
+	
+	// 考试得分相关 start
+	function handleScoreConfirm() {
+		uni.redirectTo({
+			url: '/pages/client/Score/list'
+		})
+	}
+	
+	// 考试得分相关 end
+	
+	// 保存相关
+	function handleSave(showToast) {
+		if (timer1.value) {
+			uni.showToast({
+				title: '请勿连续保存',
+				icon: 'none'
+			})
+			return 
+		}
+		timer1.value = setTimeout(() => {
+			clearTimeout(timer1.value);
+		},10*1000);
+		console.log(questionData.value)
 		
+		const result = []
+		const option = {
+			force: false,
+			operId: data.operId,
+			replyList: []
+		}
+		questionData.value.forEach(dl => {
+			dl.qas.forEach(st => {
+				const opt = {
+					stId: st.stId,
+					reply: st.reply,
+				}
+				result.push(opt)
+				option.replyList.push(opt)
+			})
+		})
+		// 保存试题答案
+		saveCacheKs(data.operId, {replyList:result, position: {dlIndex:progress.dlIndex, dtIndex: progress.dtIndex}})
+		// 保存答题进度
+		ksApi.getClientKsSave(option).then(res => {
+			if (res.data && showToast) {
+				uni.showToast({
+					title: '保存成功',
+				})
+			}
+		})
+	}
+
+
+	//  交卷相关功能 start
+	function checkJiaojuan() {
+		const result = []
+		let count = 0;
+		let total = 0;
+		questionData.value.forEach(dl => {
+			dl.qas.forEach(st => {
+				const opt = {
+					stId: st.stId,
+					reply: st.reply,
+					stTypeId: st.stTypeId
+				}
+				result.push(opt)
+			})
+		})
+
+		result.forEach(item => {
+			total++;
+			if (item.stTypeId == 1 && !checkDanxuanReply(item)) {
+				count++;
+			}
+			if (item.stTypeId == 2 && !checkDuoxuanReply(item)) {
+				count++;
+			}
+			if (item.stTypeId == 3 && !checkPanduanReply(item)) {
+				count++;
+			}
+			if (item.stTypeId == 4 && !checkTiankongReply(item)) {
+				count++;
+			}
+		})
+		return {
+			total,
+			count,
+		}
+	}
+
+	function handleJiaojuan() {
+		const result = checkJiaojuan()
+		console.log(result)
+		if (result.count) {
+			// 提示
+			answerQrRef.value.showDialog({
+				answercartsCount: result.count,
+				answercartsTotal: result.total,
+			})
+		} else {
+			handleSubmit()
+		}
+	}
+
+	function handleQuerenConfirm() {
+		handleSubmit()
 	}
+	
+	function handleSubmit(force = false) {
+		const result = {
+			force,
+			operId: data.operId,
+			replyList: []
+		};
+		console.log(questionData.value)
+		questionData.value.forEach(dl => {
+			dl.qas.forEach(st => {
+				const opt = {
+					stId: st.stId,
+					reply: st.reply
+				}
+				result.replyList.push(opt)
+			})
+		})
+	
+		ksApi.getClientKsSubmit(result).then(res => {
+			console.log('result', result)
+			if (res.code == 0) {
+				subScoreRef.value.showDialog(res.data)
+			}
+		})
+	}
+	
+	function onTimeUp() {
+		handleSubmit(true);
+	}
+	//  交卷相关功能 end
+
+
+	// 切屏功能 start
+	function qiepingToast(count) {
+		uni.showToast({
+			title: '请在考试界面操作,注意考试纪律!'
+		})
+	}
+
+	function forceSubmit() {
+		// 强制交卷
+		console.log('强制交卷')
+		handleSubmit(true)
+	}
+
+	function qpZhuapai() {
+		// 重新开启抓拍
+		zhuapaiRef.value && zhuapaiRef.value.showVideoBtn()
+	}
+	// 切屏功能 end
+
+	// 摄像头抓拍相关功能 start
+
+	function zpSuccess() {}
+
 	function zpError() {
 		uni.showToast({
 			title: '摄像头唤起异常'
@@ -209,12 +398,13 @@
 			url: '/pages/client/Kaoshi/list'
 		})
 	}
-	
+
 	// 摄像头抓拍相关功能 end
 	// 摄像头确认相关功能 start
 	function zpConfirmSuccess() {
 		initKaoshi();
 	}
+
 	function zpConfirmError() {
 		uni.showToast({
 			title: '摄像头唤起异常'
@@ -223,14 +413,14 @@
 			url: '/pages/client/Kaoshi/list'
 		})
 	}
+
 	function zpConfirmCancel() {
 		uni.redirectTo({
 			url: '/pages/client/Kaoshi/list'
 		})
 	}
-	
+
 	// 摄像头确认相关功能 end
-	
 
 	function getQaClass(qa) {
 		if (qa.marked && qa.marked === true) {
@@ -278,14 +468,10 @@
 
 	function handleBack() {
 		uni.redirectTo({
-			url: "/pages/admin/Kaoshi/list"
+			url: "/pages/client/Kaoshi/list"
 		})
 	}
 
-	function onTimeUp() {
-		console.log('end')
-	}
-
 	function showAnswerCard() {
 		popupRef.value.open('bottom')
 	}
@@ -354,6 +540,7 @@
 				iDuoxuan.order = order;
 				paragraph.qas.push(iDuoxuan);
 				iDuoxuan.iQa = iQa;
+				iDuoxuan.reply = [];
 				uIndex++;
 				order++;
 				iQa++;
@@ -394,6 +581,7 @@
 				iTiankong.order = order;
 				paragraph.qas.push(iTiankong);
 				iTiankong.iQa = iQa;
+				iTiankong.reply = new Array(iTiankong.count).fill('');;
 				uIndex++;
 				order++;
 				iQa++;
@@ -408,34 +596,48 @@
 			}
 			iDuanluo++;
 			questionData.value.push(paragraph)
-			console.log('1',questionData.value)
-			console.log('2',data.StListForSearch)
+			console.log('1', questionData.value)
+			console.log('2', data.StListForSearch)
 		}
 	}
-	
+
 	function handleBiaoji() {
 		activeSt.value.marked = !activeSt.value.marked;
 	}
-	
-	function saveKsCache() {}
-	function getKsCache() {}
-	function removeKsCache() {}
-	function formatKaoshiData() {}
-	
+
+	function formatKaoshiData() {
+		const historyData = getCacheKs(data.operId);
+		if (historyData) {
+			const { replyList ,position } = historyData;
+			if (replyList) {
+				questionData.value.forEach(dl => {
+					dl.qas.forEach(st => {
+						st.reply = replyList.find(item => item.stId == st.stId).reply
+					})
+				})
+			}
+			if (position) {
+				progress.dlIndex = position.dlIndex;
+				progress.dtIndex = position.dtIndex;
+			}
+		
+		}
+	}
+
 	// 摄像头确认初始化
 	function initBeforKaoshi() {
 		console.log(zhuapaiConfirmRef.value)
 		zhuapaiConfirmRef.value.showDialog()
 	}
-	
-	
-	
+
+
 	function initKaoshi() {
-		ksApi.getKaoshiInfo({
+		ksApi.getClientKsStart({
 			ksId: data.ksId
 		}).then(res => {
 			const {
 				ksId,
+				operId,
 				ksName,
 				stTotal,
 				stScore,
@@ -447,7 +649,9 @@
 				zhuapai,
 				duanluoList
 			} = res.data;
+
 			data.ksId = ksId;
+			data.operId = operId;
 			data.ksName = ksName;
 			data.stTotal = stTotal;
 			data.stScore = stScore;
@@ -459,18 +663,24 @@
 			data.zhuapai = zhuapai;
 			data.duanluo = duanluoList;
 			formatDuanluoList(data.duanluo);
-			
 			// 设置缓存
 			formatKaoshiData();
 			// 设置抓拍监听
-			zhuapaiRef.value.init({zhuapai: 1});
+			zhuapaiRef.value.init({
+				zhuapai: zhuapai
+			});
 			// 设置切屏监听
-			// qiepingRef.value.init()
-			
+			qiepingRef.value.init({
+				zhuapaiFlag: true,
+				toggleScreenFlag: toggleScreenFlag,
+				toggleScreenSecond: toggleScreenSecond,
+				ksId: data.ksId
+			})
+
 			uni.setNavigationBarTitle({
 				title: data.ksName
 			});
 			startCountDown.value = true;
 		})
 	}
-</script>
+</script>

+ 53 - 0
pages/client/Kaoshi/examTools.js

@@ -0,0 +1,53 @@
+import {
+	useUserCache
+} from "@/utils/userCache.js"
+
+// 身份确认缓存
+const identificationKey = 'ShenFenQueRen'
+export function useIdentificationTools() {
+	const {saveCache,getCache,removeCache} = useUserCache();
+	
+	function saveIdentCache(key,data) {
+		saveCache(identificationKey,key, data)
+	}
+
+	function getIdentCache(key) {
+		return getCache(identificationKey, key)
+	}
+
+	function removeIdentCache(key) {
+		removeCache(identificationKey,key)
+	}
+
+
+	return {
+		saveIdentCache,
+		getIdentCache,
+		removeIdentCache
+	}
+}
+
+
+const ksCache = 'kaoshiCache'
+export function useKaoShiCache() {
+	const {saveCache,getCache,removeCache} = useUserCache();
+	
+	function saveCacheKs(key,data) {
+		saveCache(ksCache,key, data)
+	}
+	
+	function getCacheKs(key) {
+		return getCache(ksCache, key)
+	}
+	
+	function removeCacheKs(key) {
+		removeCache(ksCache,key)
+	}
+	
+	
+	return {
+		saveCacheKs,
+		getCacheKs,
+		removeCacheKs
+	}
+}

+ 37 - 9
pages/client/Kaoshi/list.vue

@@ -39,9 +39,9 @@
 		<!-- 页面底端 -->
 		<customTabbarClientVue></customTabbarClientVue>
 		<!-- 考试须知 -->
-		<kaoshixuzhiVue ref="ksxzRef" @confirm="handleConfirmKs"></kaoshixuzhiVue>
+		<kaoshixuzhiVue ref="ksxzRef" @confirm="handleConfirmKs" key="1"></kaoshixuzhiVue>
 		<!-- 身份确认 -->
-		<!-- <identificationVue ref="shenfenRef"></identificationVue> -->
+		<identificationVue ref="shenfenRef" @confirm="handleConfirmIdent" @changeData="handleChangeIdentification" key="2"></identificationVue>
 	</view>
 </template>
 
@@ -49,6 +49,8 @@
 	import customTabbarClientVue from "@/components/custom-tabbar/custom-tabbar-client.vue";
 	import kaoshixuzhiVue from "@/components/kaoshixuzhi/kaoshixuzhi.vue";
 	import identificationVue from "@/components/identification/identification.vue";
+	import {useIdentificationTools} from "./examTools.js"
+	
 	import {
 		ref,
 		reactive
@@ -58,8 +60,11 @@
 	} from "@dcloudio/uni-app";
 	import * as kaoshiApi from "@/api/kaoshi.js";
 	
+	const {	saveIdentCache, getIdentCache, removeIdentCache } = useIdentificationTools();
+	
 	const ksxzRef = ref(null);
-	const shenfenRef = ref(null)
+	const shenfenRef = ref(null);
+	const activeks = ref(null);
 
 	const data = reactive({
 		zyName: '', // 职业名称
@@ -77,17 +82,41 @@
 	
 	function goUpPage() {
 		uni.redirectTo({
-			url: '/pages/admin/ShouYe/shouye'
+			url: '/pages/client/ShouYe/shouye'
+		})
+	}
+	// 修改身份
+	function handleChangeIdentification() {
+		uni.redirectTo({
+			url:'/pages/client/my/info'
 		})
 	}
 	
+	function handleConfirmIdent(data) {
+		saveIdentCache(activeks.value.ksId, true);
+		ksxzRef.value.showDialog(activeks.value)
+	}
+	
 	function handleConfirmKs(data) {
-		console.log('ddd',data)
 		checkKaoshi(data)
 	}
 	
 	function checkKsXz(data) {
-		ksxzRef.value.showDialog(data)
+		activeks.value = data;
+		if (data.status == 3) {
+			// 考试中 直接进入考试
+			checkKaoshi(data)
+			return;
+		} 
+		
+		const result = getIdentCache(data.ksId);
+		if (result) {
+			ksxzRef.value.showDialog(data)
+		} else {
+			kaoshiApi.getClientUserInfo({ksId: data.ksId}).then(res => {
+				shenfenRef.value.showDialog(res.data);
+			})
+		}
 	}
 
 	function handleSearch() {
@@ -97,7 +126,7 @@
 
 	function checkKaoshi(item) {
 		uni.navigateTo({
-			url: `/pages/client/Kaoshi/exam?ksId=${item.ksId}`
+			url: `/pages/client/Kaoshi/exam?ksId=${item.ksId}&zhuapai=${activeks.value.zhuapai}`
 		})
 	}
 
@@ -148,8 +177,7 @@
 		data.state = 'loading';
 		data.page++;
 		opt.page = data.page;
-		// kaoshiApi.getClientKaoshiList(opt).then(res => {
-		kaoshiApi.getKaoshiList(opt).then(res => {
+		kaoshiApi.getClientKaoshiList(opt).then(res => {
 			data.list = data.list.concat(res.data.data);
 			data.loading = false;
 

+ 15 - 0
store/zhuapai.js

@@ -0,0 +1,15 @@
+import { defineStore } from 'pinia';
+
+export const useZhuapaiStore = defineStore('zhuapai', {
+	state: () => {
+		return { status: 0,showV:true };
+	},
+	actions: {
+		setStatus(data) {
+			this.status = data;
+		},
+		setShowV(data) {
+			this.showV = data;
+		}
+	},
+});

+ 2 - 2
utils/request.js

@@ -54,7 +54,7 @@ const request = config => {
 								cacheManager.clearAll()
 							}
 							uni.reLaunch({
-								url: '/pages/login/index'
+								url: '/pages/Login/index'
 							})
 						}
 					})
@@ -66,7 +66,7 @@ const request = config => {
 								cacheManager.clearAll()
 							}
 							uni.reLaunch({
-								url: '/pages/login/index'
+								url: '/pages/Login/index'
 							})
 						}
 

+ 5 - 4
utils/kechengCache.js → utils/userCache.js

@@ -1,6 +1,6 @@
 import cacheManager from "@/utils/cacheManager.js"
 
-const TokenKey = 'Mta-Kecheng'
+const TokenKey = 'Mta-UserCache'
 
 export function useUserCache() {
 	function saveCache() {
@@ -25,11 +25,12 @@ export function useUserCache() {
 			return;
 		}
 		const userId = auth.userId;
+		console.log('userId:', userId)
 		if (!userId) {
 			throw new Error('数据异常用户Id异常!')
 			return;
 		}
-
+		
 		let pageData = uni.getStorageSync(TokenKey);
 		if (!pageData) {
 			pageData = {};
@@ -76,10 +77,10 @@ export function useUserCache() {
 		}
 
 		let pageData = uni.getStorageSync(TokenKey);
-		if (!pageCache || !pageCache[userId] || !pageCache[userId][pageId]) {
+		if (!pageData || !pageData[userId] || !pageData[userId][pageId]) {
 			return null;
 		}
-		return pageCache[userId][pageId][key];
+		return pageData[userId][pageId][key];
 	}
 
 	function removeCache() {