|
@@ -0,0 +1,1571 @@
|
|
|
+<template>
|
|
|
+ <div class="test">
|
|
|
+ <Header @confirmExit="confirmExit" @setMusic="setMusic"></Header>
|
|
|
+ <div class="main">
|
|
|
+ <div class="main-left">
|
|
|
+ <div class="main-left-top">
|
|
|
+ <div class="top-left" @click="getChooseStudent">
|
|
|
+ <div class="top-left-center">
|
|
|
+ <div class="pic" :class="{ 'pic2': faceCheckStu.student_id }" v-if="faceCheckStu.student_id">
|
|
|
+ <img :src="faceCheckStu.face_pic || faceCheckStu.logo_url" />
|
|
|
+ </div>
|
|
|
+ <div class="pic" v-else>
|
|
|
+ <img src="@/assets/images/test/profilePicture.png" />
|
|
|
+ </div>
|
|
|
+ <div class="name" :class="{ 'name2': faceCheckStu.student_id }">
|
|
|
+ {{ faceCheckStu.student_id ? faceCheckStu.name : "虚位以待" }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="top-right">
|
|
|
+ <Transition :enter-active-class="proxy?.animate.dialog.enter" :leave-active-class="proxy?.animate.dialog.leave">
|
|
|
+ <div class="time" v-show="needStart && [42].includes(examState) && !['basketballv1', 'footballv1'].includes(parameter.project)">
|
|
|
+ {{
|
|
|
+ time.countdownNum
|
|
|
+ }}
|
|
|
+ </div>
|
|
|
+ </Transition>
|
|
|
+ <div class="tips" v-if="examState == 41">
|
|
|
+ <img v-if="parameter.gesture" src="@/assets/images/test/ready1.png" />
|
|
|
+ <img v-if="!parameter.gesture" src="@/assets/images/test/ready2.png" />
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ class="complete"
|
|
|
+ :class="{ 'complete2': needStart && [42].includes(examState) }"
|
|
|
+ v-if="faceCheckStu.student_id && time.ready <= 0 && examState != 43 && examState != 41"
|
|
|
+ >
|
|
|
+ <div class="scoreBox">
|
|
|
+ <div class="score" v-if="currentResultObj?.count && currentResultObj.count>=0">{{ currentResultObj.count }}</div>
|
|
|
+ <div class="prompt" v-if="currentResultObj?.count && currentResultObj.count==0 && examState == 42">请开始测试</div>
|
|
|
+ <div class="unit" v-if="!['basketballv1', 'footballv1'].includes(parameter.project) && currentResultObj.count && !needStart">
|
|
|
+ {{ unit }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="fractionViolation">
|
|
|
+ <div class="fraction">
|
|
|
+ <div class="lable">得分:</div>
|
|
|
+ <div class="value">{{ currentResultObj.score || "" }}</div>
|
|
|
+ </div>
|
|
|
+ <div class="violation">
|
|
|
+ <div class="lable">
|
|
|
+ {{ ['jumprope', 'jumpingjack', 'highknees'].includes(parameter.project) ? '中断' : '犯规'
|
|
|
+ }}
|
|
|
+ </div>
|
|
|
+ <div class="value">{{ currentResultObj.back_num || 0 }}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="foulBox" v-if="examState == 42 && backReason.length">
|
|
|
+ <Transition :enter-active-class="proxy?.animate.mask.enter" :leave-active-class="proxy?.animate.mask.leave">
|
|
|
+ <div class="foul" v-show="backReasonStr ? true : false">
|
|
|
+ <div class="lable">!</div>
|
|
|
+ <div class="value">{{ backReasonStr }}</div>
|
|
|
+ </div>
|
|
|
+ </Transition>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div v-show="examState == 43 && time.ready">
|
|
|
+ <div class="readyBox">
|
|
|
+ <div class="value" :class="{ 'transparent': time.ready > 5 }">{{ time.ready }}</div>
|
|
|
+ <div class="lable">倒计时</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div v-show="examState == 43 && faceCheckStu.student_id && !time.ready && readyState">
|
|
|
+ <div class="readyBoxBefore">
|
|
|
+ <div class="item" v-if="parameter.handcontroller">
|
|
|
+ <div><img src="@/assets/images/test/jushou.png" /></div>
|
|
|
+ <div class="lable">
|
|
|
+ <div>请举左手开始</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="item" v-else>
|
|
|
+ <div><img src="@/assets/images/test/bujushou.png" /></div>
|
|
|
+ <div class="lable">
|
|
|
+ <div>请点击开始</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div v-show="examState == 43 && !faceCheckStu.student_id">
|
|
|
+ <div class="btn btn2" @click="getChooseStudent">点击重新识别</div>
|
|
|
+ </div>
|
|
|
+ <div class="btn" @click="getReady" v-if="needStart && examState == 43 && faceCheckStu.student_id && !time.ready && readyState">开 始</div>
|
|
|
+ <!-- <div v-if="needStart"> -->
|
|
|
+ <!-- <div class="btn" @click="getOpenOneTestAndStartFace" v-if="examState < 41">开始识别</div> -->
|
|
|
+ <!-- <div class="btn" @click="getStopFace" v-if="examState == 41 && !parameter.gesture">停止人脸识别</div> -->
|
|
|
+ <!-- <div class="btn" @click="getStartOneTest" v-if="examState == 43">开始测试</div> -->
|
|
|
+ <!-- <div @click="getRetestFace" v-if="examState == 43 || examState == 42">重新识别</div> -->
|
|
|
+ <!-- </div> -->
|
|
|
+ <!-- <div>当前状态:({{ examState == 3 ? "初始化完成" : examState == 40 ? "创建测试" : examState == 41 ? "正在人脸识别":examState == 43 ? "停止人脸识别" : examState == 42 ? "正在测试" : "请初始化" }})</div> -->
|
|
|
+ <!-- <div @click="getAgain" v-if="examState == 42 || showTestAgain">再测一次</div> -->
|
|
|
+ </div>
|
|
|
+ <i></i>
|
|
|
+ </div>
|
|
|
+ <div class="main-left-bottom">
|
|
|
+ <div class="bottom-left">
|
|
|
+ <div class="tips"><img src="/src/assets/images/test/tips.png" /></div>
|
|
|
+ <div class="pic"><img :src="'static/images/tips/' + parameter.project + '.png'" /></div>
|
|
|
+ </div>
|
|
|
+ <div class="bottom-right" v-html="dic.projectNote[parameter.project]"></div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="main-right">
|
|
|
+ <ReportList ref="reportListRef" :parameter="parameter" :showQRCode="true" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <FaceWindow ref="faceWindowRef" :faceCheckStu="faceCheckStu" :gesture="parameter.gesture" />
|
|
|
+ <ChooseStudent ref="chooseStudentRef" @returnData="returnStudent" />
|
|
|
+ <JumpRopeGame ref="gameContainer" v-if="['test'].includes(parameter.project) && (!readyState || [42].includes(examState))" />
|
|
|
+ <div class="close" @click="confirmExit"></div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup name="TrainTest" lang="ts">
|
|
|
+import useAppStore from '@/store/modules/app';
|
|
|
+import { useWs } from '@/utils/trainWs';
|
|
|
+// import { initWs, examEnds, openOneTest, startFace, stopFace, faceConfirmOnly, startOneTest, finishOneTest, closeOneTest, suspendFaceRecognitionChannels, resumeFaceRecognitionChannels } from '@/utils/ws'
|
|
|
+import { initSpeech, speckText, playMusic, controlMusic, speckCancel, chineseNumber } from '@/utils/speech';
|
|
|
+import { useWebSocket } from '@/utils/handWs';
|
|
|
+import dayjs from 'dayjs';
|
|
|
+import dataDictionary from '@/utils/dataDictionary';
|
|
|
+const { handWs, startDevice, startHand, stateHand } = useWebSocket();
|
|
|
+const {
|
|
|
+ initWs,
|
|
|
+ examEnds,
|
|
|
+ openOneTest,
|
|
|
+ startFace,
|
|
|
+ stopFace,
|
|
|
+ faceConfirmOnly,
|
|
|
+ startOneTest,
|
|
|
+ finishOneTest,
|
|
|
+ closeOneTest,
|
|
|
+ suspendFaceRecognitionChannels,
|
|
|
+ resumeFaceRecognitionChannels
|
|
|
+} = useWs();
|
|
|
+const { proxy } = getCurrentInstance() as any;
|
|
|
+const router = useRouter();
|
|
|
+const route = useRoute();
|
|
|
+const faceWindowRef = ref();
|
|
|
+const chooseStudentRef = ref();
|
|
|
+const reportListRef = ref();
|
|
|
+const gameContainer = ref();
|
|
|
+const myInfo: any = localStorage.getItem('userInfo');
|
|
|
+const dic: any = dataDictionary;
|
|
|
+const data = reactive<any>({
|
|
|
+ timerManager: {}, //计时器管理
|
|
|
+ parameter: {}, //参数
|
|
|
+ time: {
|
|
|
+ testTime: 60, //时长
|
|
|
+ countdownNum: 0, //计时
|
|
|
+ ready: 0, //预备
|
|
|
+ exit: 0 //退出倒计时
|
|
|
+ },
|
|
|
+ userInfo: {}, //用户信息
|
|
|
+ examState: 0, //当前状态
|
|
|
+ resultId: null, //测试ID
|
|
|
+ currentResultObj: {}, //成绩
|
|
|
+ faceCheckStu: {}, //人脸信息
|
|
|
+ unit: '', //单位
|
|
|
+ backReason: [], //犯规项
|
|
|
+ backReasonStr: '', //犯规提示
|
|
|
+ needStart: false, //是否需要按钮
|
|
|
+ showTestAgain: false, //再测一次按钮
|
|
|
+ readyState: true, //倒计时按钮状态
|
|
|
+ exitStatus: 0, //退出响应次数
|
|
|
+ sid: null, //WS的id
|
|
|
+ deviceInfo: {}, //设备信息
|
|
|
+ musicList: [], //音乐列表
|
|
|
+});
|
|
|
+const {
|
|
|
+ timerManager,
|
|
|
+ parameter,
|
|
|
+ time,
|
|
|
+ userInfo,
|
|
|
+ examState,
|
|
|
+ resultId,
|
|
|
+ faceCheckStu,
|
|
|
+ currentResultObj,
|
|
|
+ unit,
|
|
|
+ backReason,
|
|
|
+ backReasonStr,
|
|
|
+ needStart,
|
|
|
+ showTestAgain,
|
|
|
+ readyState,
|
|
|
+ exitStatus,
|
|
|
+ sid,
|
|
|
+ deviceInfo,
|
|
|
+ musicList
|
|
|
+} = toRefs(data);
|
|
|
+
|
|
|
+/**
|
|
|
+ * 接收消息
|
|
|
+ */
|
|
|
+const getMessage = (e: any) => {
|
|
|
+ //console.log("WS响应:", e)
|
|
|
+ //获取sid
|
|
|
+ if (e.cmd === 'mySid') {
|
|
|
+ console.log('e.data.sid', e.data.sid);
|
|
|
+ sid.value = e.data.sid;
|
|
|
+ }
|
|
|
+ //实时状态
|
|
|
+ if (e.cmd === 'exam_status') {
|
|
|
+ examState.value = e.data;
|
|
|
+ }
|
|
|
+ //工作站状态
|
|
|
+ if (e.cmd === 'init_result') {
|
|
|
+ }
|
|
|
+ //测试违规
|
|
|
+ if (e.cmd === 'warning_result') {
|
|
|
+ console.log('eeeeeeeeeee', e);
|
|
|
+ if ((e.status + '')[0] === '2') {
|
|
|
+ proxy?.$modal.msgError(e.data.message);
|
|
|
+ speckText(e.data.message);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ //后端播报语音
|
|
|
+ if (e.cmd === 'return_audio_msg') {
|
|
|
+ if (e.data.message) {
|
|
|
+ proxy?.$modal.msgError(e.data.message);
|
|
|
+ speckText(e.data.message);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ //错误提示
|
|
|
+ if (e.cmd === 'info_result') {
|
|
|
+ proxy?.$modal.msgError(e.data.message);
|
|
|
+ }
|
|
|
+ //错误提示
|
|
|
+ if (e.cmd === 'error_result') {
|
|
|
+ proxy?.$modal.msgError(e.data.message);
|
|
|
+ }
|
|
|
+ //测试中违规提示
|
|
|
+ if (e.cmd === 'warning_notify') {
|
|
|
+ let message = e.data?.message;
|
|
|
+ if (message) {
|
|
|
+ proxy?.$modal.msgError(message);
|
|
|
+ speckText(message);
|
|
|
+ }
|
|
|
+ if (message == '工作站已断开!') {
|
|
|
+ getExit();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ //断线状态
|
|
|
+ if (e.cmd === 'disconnect_request') {
|
|
|
+ let message = e.data.message;
|
|
|
+ if (message) {
|
|
|
+ proxy?.$modal.msgError(`${message}`);
|
|
|
+ speckText(message);
|
|
|
+ }
|
|
|
+ getExit();
|
|
|
+ }
|
|
|
+ //状态变更
|
|
|
+ if (e.cmd === 'set_exam_state') {
|
|
|
+ examState.value = e.data;
|
|
|
+ if (e.data === 3) {
|
|
|
+ initProject();
|
|
|
+ }
|
|
|
+ if (e.data === 40) {
|
|
|
+ cleanData();
|
|
|
+ }
|
|
|
+ if (e.data == 41) {
|
|
|
+ getFaceWindow(true);
|
|
|
+ }
|
|
|
+ if (e.data == 43) {
|
|
|
+ }
|
|
|
+ if (e.data == 42) {
|
|
|
+ getClearTimer('readyTimer');
|
|
|
+ time.value.ready = 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ //新建测试后返回信息,获取result_id
|
|
|
+ if (e.cmd === 'open_one_test_ack') {
|
|
|
+ resultId.value = e.data.result_id;
|
|
|
+ }
|
|
|
+ //人脸识别状态
|
|
|
+ if (e.cmd === 'face_check_result') {
|
|
|
+ let myData = e.data[0] || e.data;
|
|
|
+ returnStudent(myData);
|
|
|
+ }
|
|
|
+ //测试结束结果
|
|
|
+ if (e.cmd === 'oneresult') {
|
|
|
+ if (e.data.length) {
|
|
|
+ let data = e.data[0];
|
|
|
+ getAchievement(data);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ //结果生成完成(视频图片)
|
|
|
+ if (e.cmd === 'static_urls_finished') {
|
|
|
+ }
|
|
|
+ //选择学生或测试结束后返回的数据
|
|
|
+ if (e.cmd === 'result_info') {
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+/**
|
|
|
+ * 开始识别
|
|
|
+ */
|
|
|
+const getOpenOneTestAndStartFace = async () => {
|
|
|
+ if (examState.value > 3) {
|
|
|
+ await closeOneTest();
|
|
|
+ }
|
|
|
+ await openOneTest();
|
|
|
+ await startFace();
|
|
|
+};
|
|
|
+
|
|
|
+/**
|
|
|
+ * 停止人脸识别
|
|
|
+ */
|
|
|
+const getStopFace = async () => {
|
|
|
+ // 旧版识别成功直接43了这里先屏蔽
|
|
|
+ // if (examState.value != 41) {
|
|
|
+ // return false;
|
|
|
+ // }
|
|
|
+ getClearTimer('face');
|
|
|
+ if (needStart.value) {
|
|
|
+ let txt = parameter.value.handcontroller ? ',请举左手开始测试' : ',请准备';
|
|
|
+ speckText(faceCheckStu.value.name + txt);
|
|
|
+ }
|
|
|
+ if (examState.value == 41) {
|
|
|
+ await stopFace();
|
|
|
+ }
|
|
|
+ if (faceCheckStu.value.student_id) {
|
|
|
+ getFaceConfirmOnly();
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+/**
|
|
|
+ * 确定人脸信息
|
|
|
+ */
|
|
|
+const getFaceConfirmOnly = (data?: any) => {
|
|
|
+ if (data) {
|
|
|
+ faceCheckStu.value = data;
|
|
|
+ }
|
|
|
+ faceConfirmOnly(
|
|
|
+ {
|
|
|
+ result_id: resultId.value,
|
|
|
+ student_id: faceCheckStu.value.student_id,
|
|
|
+ gender: faceCheckStu.value.gender
|
|
|
+ },
|
|
|
+ () => {
|
|
|
+ faceWindowRef.value?.close();
|
|
|
+ //不需要按钮的自动进入下一步
|
|
|
+ if (needStart.value == false) {
|
|
|
+ getStartOneTest();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ );
|
|
|
+};
|
|
|
+
|
|
|
+/**
|
|
|
+ * 重新识别
|
|
|
+ */
|
|
|
+const getRetestFace = () => {
|
|
|
+ proxy?.$modal
|
|
|
+ .confirm('确定重新识别吗?')
|
|
|
+ .then(() => {
|
|
|
+ cleanData();
|
|
|
+ if (needStart.value == false) {
|
|
|
+ //自动流程项目重新识别直接返回3
|
|
|
+ closeOneTest();
|
|
|
+ } else {
|
|
|
+ //手动流程项目重新识别43返回41,42返回3
|
|
|
+ if (examState.value == 43) {
|
|
|
+ startFace();
|
|
|
+ } else if (examState.value == 42) {
|
|
|
+ finishOneTest();
|
|
|
+ } else {
|
|
|
+ closeOneTest();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ .finally(() => {});
|
|
|
+};
|
|
|
+
|
|
|
+/**
|
|
|
+ * 开始测试
|
|
|
+ */
|
|
|
+const getStartOneTest = () => {
|
|
|
+ if (examState.value != 43 || !faceCheckStu.value.student_id) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ if (!faceCheckStu.value.student_id) {
|
|
|
+ proxy?.$modal.msgWarning('请选择人员!');
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ startOneTest(null, () => {
|
|
|
+ //显示再测一次按钮
|
|
|
+ showTestAgain.value = true;
|
|
|
+ //停止播报;
|
|
|
+ speckCancel();
|
|
|
+ //计时项目才开
|
|
|
+ if (needStart.value == true) {
|
|
|
+ speckText('哨声');
|
|
|
+ if (parameter.value.music && musicList.value.length) {
|
|
|
+ let obj = musicList.value.find((item: any) => {
|
|
|
+ return item.id == parameter.value.music;
|
|
|
+ });
|
|
|
+ if (obj != undefined) {
|
|
|
+ playMusic(obj.url);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ //时间为0的为正计时,大于0的为倒计时
|
|
|
+ if (time.value.testTime == 0) {
|
|
|
+ getCounting('+');
|
|
|
+ } else {
|
|
|
+ getCounting('-');
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ speckText(faceCheckStu.value.name + ',请开始测试');
|
|
|
+ }
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+/**
|
|
|
+ * 再测一次
|
|
|
+ */
|
|
|
+const getAgain = async () => {
|
|
|
+ let txt = '是否再测一次?';
|
|
|
+ await proxy?.$modal.confirm(txt);
|
|
|
+ getClearTimer();
|
|
|
+ //预存测试人员
|
|
|
+ let student = JSON.parse(JSON.stringify(faceCheckStu.value));
|
|
|
+ //测试中
|
|
|
+ if (examState.value == 42) {
|
|
|
+ await finishOneTest();
|
|
|
+ }
|
|
|
+ //其他状态
|
|
|
+ if (examState.value > 3) {
|
|
|
+ await closeOneTest();
|
|
|
+ }
|
|
|
+ //重新走一次流程
|
|
|
+ await openOneTest();
|
|
|
+ await startFace();
|
|
|
+ await stopFace();
|
|
|
+ getFaceConfirmOnly(student);
|
|
|
+};
|
|
|
+
|
|
|
+/**
|
|
|
+ * 确认退出
|
|
|
+ */
|
|
|
+const confirmExit = () => {
|
|
|
+ let handcontroller_id = parameter.value.handcontroller;
|
|
|
+ proxy?.$modal
|
|
|
+ .confirm(handcontroller_id ? `请保持两秒确认退出` : `确定退出吗?`)
|
|
|
+ .then(() => {
|
|
|
+ getExit();
|
|
|
+ })
|
|
|
+ .finally(() => {});
|
|
|
+};
|
|
|
+
|
|
|
+/**
|
|
|
+ * 退出
|
|
|
+ */
|
|
|
+const getExit = () => {
|
|
|
+ getClearTimer(); //清除计时器
|
|
|
+ examEnds(); //通知工作站关闭
|
|
|
+ speckCancel(); //停止播报
|
|
|
+ window.onbeforeunload = null; //移除事件处理器
|
|
|
+ let handcontroller_id = parameter.value.handcontroller;
|
|
|
+ if (handcontroller_id) {
|
|
|
+ router.push({ path: '/gesture' });
|
|
|
+ } else {
|
|
|
+ if (parameter.value.taskId) {
|
|
|
+ router.push({ path: '/test' });
|
|
|
+ } else {
|
|
|
+ router.push({ path: '/train' });
|
|
|
+ }
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+/**
|
|
|
+ * 清空定时任务
|
|
|
+ */
|
|
|
+const getClearTimer = (data?: any) => {
|
|
|
+ if (data) {
|
|
|
+ //清除指定
|
|
|
+ clearInterval(timerManager.value[data]);
|
|
|
+ timerManager.value[data] = null;
|
|
|
+ } else {
|
|
|
+ //清除全部
|
|
|
+ for (let key in timerManager.value) {
|
|
|
+ if (timerManager.value.hasOwnProperty(key)) {
|
|
|
+ clearInterval(timerManager.value[key]);
|
|
|
+ timerManager.value[key] = null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+/**
|
|
|
+ * 选择学生
|
|
|
+ */
|
|
|
+const getChooseStudent = () => {
|
|
|
+ if (examState.value < 41) {
|
|
|
+ proxy?.$modal.msgWarning('请等待');
|
|
|
+ }
|
|
|
+ if (examState.value == 41) {
|
|
|
+ stopFace();
|
|
|
+ chooseStudentRef.value.open();
|
|
|
+ //然后定时自动关闭
|
|
|
+ setTimeout(() => {
|
|
|
+ faceWindowRef.value.close();
|
|
|
+ }, 3000);
|
|
|
+ }
|
|
|
+ if (examState.value == 43) {
|
|
|
+ getRetestFace();
|
|
|
+ }
|
|
|
+ if (examState.value == 42) {
|
|
|
+ getRetestFace();
|
|
|
+ // proxy?.$modal.msgWarning(`正在测试请结束后再操作,当前状态:${examState.value}`);
|
|
|
+ // return false;
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+/**
|
|
|
+ * 返回被选学生
|
|
|
+ */
|
|
|
+const returnStudent = (data: any) => {
|
|
|
+ speckCancel();
|
|
|
+ chooseStudentRef.value.close();
|
|
|
+ faceCheckStu.value = data;
|
|
|
+ faceWindowRef.value.open();
|
|
|
+ //然后定时自动关闭
|
|
|
+ setTimeout(() => {
|
|
|
+ faceWindowRef.value.close();
|
|
|
+ }, 1000);
|
|
|
+ getStopFace();
|
|
|
+};
|
|
|
+
|
|
|
+/**
|
|
|
+ * 清除历史记录
|
|
|
+ */
|
|
|
+const cleanData = () => {
|
|
|
+ time.value.countdownNum = time.value.testTime;
|
|
|
+ showTestAgain.value = false;
|
|
|
+ faceCheckStu.value = {};
|
|
|
+ currentResultObj.value = {};
|
|
|
+ backReason.value = [];
|
|
|
+};
|
|
|
+
|
|
|
+/**
|
|
|
+ * 自动初始化项目
|
|
|
+ */
|
|
|
+const initProject = () => {
|
|
|
+ //停止计时
|
|
|
+ getClearTimer('countdownTimer');
|
|
|
+ //恢复倒计时按钮状态
|
|
|
+ readyState.value = true;
|
|
|
+ //自动项目定时进入下一步
|
|
|
+ let time = 0;
|
|
|
+ //控制新建测试的时间,第一次快,之后就慢
|
|
|
+ if (!faceCheckStu.value.student_id) {
|
|
|
+ time = 1000;
|
|
|
+ } else {
|
|
|
+ time = 6000;
|
|
|
+ }
|
|
|
+ setTimeout(() => {
|
|
|
+ //再加一个判断以免和再测一次冲突
|
|
|
+ if (examState.value == 3) {
|
|
|
+ getOpenOneTestAndStartFace();
|
|
|
+ }
|
|
|
+ }, time);
|
|
|
+};
|
|
|
+
|
|
|
+/**
|
|
|
+ * 倒计时
|
|
|
+ */
|
|
|
+const getCounting = (type: string) => {
|
|
|
+ timerManager.value.countdownTimer = setInterval(() => {
|
|
|
+ //正计时
|
|
|
+ if (type == '+') {
|
|
|
+ time.value.countdownNum++;
|
|
|
+ }
|
|
|
+ //倒计时
|
|
|
+ if (type == '-') {
|
|
|
+ if (time.value.countdownNum <= 0) {
|
|
|
+ getClearTimer('countdownTimer');
|
|
|
+ } else {
|
|
|
+ time.value.countdownNum--;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }, 1000);
|
|
|
+};
|
|
|
+
|
|
|
+/**
|
|
|
+ * 人脸窗口
|
|
|
+ */
|
|
|
+const getFaceWindow = (data: boolean, num: number = 0) => {
|
|
|
+ let total = num + 1; //叠加三次后不再播放
|
|
|
+ let txt = parameter.value.gesture === true ? '请举右手看摄像头人脸识别' : '请看摄像头进行人脸识别';
|
|
|
+ speckText(txt);
|
|
|
+ //data=true为弹出框,data=false为不要弹出框
|
|
|
+ if (data) {
|
|
|
+ faceWindowRef.value.open();
|
|
|
+ //然后定时自动关闭
|
|
|
+ setTimeout(() => {
|
|
|
+ if (examState.value == 41 && faceWindowRef.value?.faceState == true) {
|
|
|
+ faceWindowRef.value.close();
|
|
|
+ }
|
|
|
+ }, 3000);
|
|
|
+ }
|
|
|
+ //定时检查如果一直停留在人脸识别就提示
|
|
|
+ let timeout = 16000;
|
|
|
+ timerManager.value.face = setInterval(() => {
|
|
|
+ getClearTimer('face');
|
|
|
+ if (examState.value == 41 && total < 3) {
|
|
|
+ getFaceWindow(false, total);
|
|
|
+ }
|
|
|
+ }, timeout);
|
|
|
+};
|
|
|
+
|
|
|
+/**
|
|
|
+ * 成绩
|
|
|
+ */
|
|
|
+const getAchievement = (data: any) => {
|
|
|
+ //console.log("成绩", data);
|
|
|
+ let type = parameter.value.project;
|
|
|
+ let result = data?.[dic.typeResultKey[type]] || 0;
|
|
|
+ let count = null;
|
|
|
+ if (['trijump', 'solidball', 'shotput', 'longjump'].includes(type)) {
|
|
|
+ count = (Math.round(result) / 100).toFixed(2);
|
|
|
+ } else if (['basketballv1', 'footballv1'].includes(type)) {
|
|
|
+ count = proxy?.$utils.runTime(result, true, 1);
|
|
|
+ } else {
|
|
|
+ count = result;
|
|
|
+ }
|
|
|
+ data.count = count || 0;
|
|
|
+ data.score = data.score || '0';
|
|
|
+ currentResultObj.value = data;
|
|
|
+ //违规处理
|
|
|
+ let arr = backReason.value;
|
|
|
+ if (['situp', 'pullup', 'sidepullup', 'jumprope', 'jumpingjack', 'highknees', 'jump', 'longjump', 'verticaljump'].indexOf(type) > -1) {
|
|
|
+ if (['pullup', 'situp', 'jumprope', 'jumpingjack', 'highknees'].indexOf(type) > -1) {
|
|
|
+ currentResultObj.value.back_num = data?.all_failed_num;
|
|
|
+ }
|
|
|
+ if (type === 'sidepullup') {
|
|
|
+ currentResultObj.value.back_num = data?.['0']?.hip_failed_num;
|
|
|
+ }
|
|
|
+ if (['jump', 'longjump', 'verticaljump'].includes(type)) {
|
|
|
+ if (data?.startline_check == 0) {
|
|
|
+ let txt = '踩线违规';
|
|
|
+ speckText(txt);
|
|
|
+ arr.push(txt);
|
|
|
+ }
|
|
|
+ if (data?.singleleg_jump_check == 0) {
|
|
|
+ let txt = '单脚跳违规';
|
|
|
+ speckText(txt);
|
|
|
+ arr.push(txt);
|
|
|
+ }
|
|
|
+ if (data?.outside_check == 0) {
|
|
|
+ let txt = '跳出测试区违规';
|
|
|
+ speckText(txt);
|
|
|
+ arr.push(txt);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (data?.elbow_check == false) {
|
|
|
+ let txt = '肘部违规';
|
|
|
+ speckText(txt);
|
|
|
+ arr.push(txt);
|
|
|
+ }
|
|
|
+ if (['situp', 'pullup'].indexOf(type) > -1 && data?.knee_check === false) {
|
|
|
+ let txt = '腿部违规';
|
|
|
+ speckText(txt);
|
|
|
+ if (!arr.includes(txt)) {
|
|
|
+ }
|
|
|
+ arr.push(txt);
|
|
|
+ }
|
|
|
+ if (['situp'].indexOf(type) > -1 && data?.hand_check === false) {
|
|
|
+ let txt = '手部违规';
|
|
|
+ speckText(txt);
|
|
|
+ if (!arr.includes(txt)) {
|
|
|
+ }
|
|
|
+ arr.push(txt);
|
|
|
+ }
|
|
|
+ if (['pullup'].indexOf(type) > -1 && data?.['0']?.elbow_check === false) {
|
|
|
+ let txt = '手部违规';
|
|
|
+ speckText(txt);
|
|
|
+ if (!arr.includes(txt)) {
|
|
|
+ }
|
|
|
+ arr.push(txt);
|
|
|
+ }
|
|
|
+ if (['situp'].indexOf(type) > -1 && data?.['0']?.back_check === false) {
|
|
|
+ let txt = '背部违规';
|
|
|
+ speckText(txt);
|
|
|
+ if (!arr.includes(txt)) {
|
|
|
+ }
|
|
|
+ arr.push(txt);
|
|
|
+ }
|
|
|
+ if (['sidepullup', 'situp'].indexOf(type) > -1 && data?.['0']?.hip_check === false) {
|
|
|
+ let txt = '臀部违规';
|
|
|
+ speckText(txt);
|
|
|
+ if (!arr.includes(txt)) {
|
|
|
+ }
|
|
|
+ arr.push(txt);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ backReason.value = arr;
|
|
|
+ if (data.isfinish) {
|
|
|
+ if (['jump'].includes(type) && backReason.value.length) {
|
|
|
+ speckText('请重新测试');
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ if (['basketballv1', 'footballv1'].includes(type)) {
|
|
|
+ speckText(
|
|
|
+ faceCheckStu?.value.name +
|
|
|
+ '成绩为' +
|
|
|
+ (chineseNumber(proxy?.$utils.runTime(data?.[dic.typeResultKey[type]], false, 0, 1)) || 0) +
|
|
|
+ ',请下一位准备!' || ''
|
|
|
+ );
|
|
|
+ } else {
|
|
|
+ speckText(faceCheckStu?.value.name + '成绩为' + (chineseNumber(count) || 0) + unit.value + ',请下一位准备!' || '');
|
|
|
+ }
|
|
|
+ reportListRef.value.getIniReportList();
|
|
|
+ faceWindowRef.value.open();
|
|
|
+ //然后定时自动关闭
|
|
|
+ setTimeout(() => {
|
|
|
+ faceWindowRef.value.close('right');
|
|
|
+ }, 1000);
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+/**
|
|
|
+ * 准备开始
|
|
|
+ */
|
|
|
+const getReady = () => {
|
|
|
+ if (needStart.value && examState.value == 43 && !time.value.ready && readyState.value) {
|
|
|
+ if (time.value.ready) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ speckCancel();
|
|
|
+ readyState.value = false;
|
|
|
+ time.value.ready = 6;
|
|
|
+ timerManager.value.readyTimer = setInterval(() => {
|
|
|
+ time.value.ready--;
|
|
|
+ if (time.value.ready <= 0) {
|
|
|
+ getClearTimer('readyTimer');
|
|
|
+ getStartOneTest();
|
|
|
+ } else {
|
|
|
+ speckText(time.value.ready);
|
|
|
+ }
|
|
|
+ }, 1000);
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+/**
|
|
|
+ * 获取音乐
|
|
|
+ */
|
|
|
+const getMusic = async () => {
|
|
|
+ const list: any = useAppStore().getMusic();
|
|
|
+ if (list.length) {
|
|
|
+ musicList.value = list;
|
|
|
+ } else {
|
|
|
+ await proxy?.$http.train.musicList().then((res: any) => {
|
|
|
+ if (res.data.length > 0) {
|
|
|
+ let myList: any = res.data;
|
|
|
+ musicList.value = myList;
|
|
|
+ useAppStore().setMusic(myList);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+/**
|
|
|
+ * 设置音乐
|
|
|
+ */
|
|
|
+const setMusic = async (data:any) => {
|
|
|
+ //console.log("data",data)
|
|
|
+ parameter.value.music = data;
|
|
|
+};
|
|
|
+
|
|
|
+/**
|
|
|
+ * 获取设备项目
|
|
|
+ */
|
|
|
+const getDevice = async () => {
|
|
|
+ let deviceid = localStorage.getItem('deviceid') || '';
|
|
|
+ if (deviceid) {
|
|
|
+ startDevice({ deviceid: deviceid });
|
|
|
+ } else {
|
|
|
+ proxy?.$modal.msgError(`缺少设备信息请重新登录!`);
|
|
|
+ await proxy?.$http.common.logout({}).then((res: any) => {});
|
|
|
+ proxy?.$modal?.closeLoading();
|
|
|
+ //清空缓存
|
|
|
+ // localStorage.clear();
|
|
|
+ localStorage.removeItem('token');
|
|
|
+ localStorage.removeItem('userInfo');
|
|
|
+ //跳转
|
|
|
+ router.push({ path: '/login/qrcode' });
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+/**
|
|
|
+ * 加载手势WS
|
|
|
+ */
|
|
|
+const initHand = () => {
|
|
|
+ handWs((e: any) => {
|
|
|
+ if (router.currentRoute.value.path != '/train/test' || parameter.value.handcontroller == undefined || examState.value == 0) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ console.log('eeeee', e);
|
|
|
+ if (e?.wksid) {
|
|
|
+ //获取设备项目
|
|
|
+ getDevice();
|
|
|
+ }
|
|
|
+ //接收设备信息
|
|
|
+ if (e?.device_info) {
|
|
|
+ deviceInfo.value = e.device_info;
|
|
|
+ let handcontroller_id = deviceInfo.value.handcontroller_id;
|
|
|
+ stateHand(handcontroller_id);
|
|
|
+ }
|
|
|
+ //获取手势状态
|
|
|
+ if (e?.cmd == 'get_handcontroller_state' && e?.state == 0) {
|
|
|
+ let handcontroller_id = deviceInfo.value.handcontroller_id;
|
|
|
+ startHand(handcontroller_id);
|
|
|
+ }
|
|
|
+ //刷新
|
|
|
+ if (e?.data?.result == 'refresh') {
|
|
|
+ getExit();
|
|
|
+ //刷新
|
|
|
+ window.location.reload();
|
|
|
+ }
|
|
|
+ //没初始化完成不监听手势动作
|
|
|
+ if (examState.value == 0) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ //左滑动
|
|
|
+ // if (e?.data?.result == "next_item") {
|
|
|
+ // proxy?.$modal.msgSuccess('手势指令:左滑动');
|
|
|
+ // if(examState.value == 43 && time.value.ready){
|
|
|
+ // return false;
|
|
|
+ // }
|
|
|
+ // if (examState.value == 43 || examState.value == 42) {
|
|
|
+ // speckCancel();//停止播报
|
|
|
+ // if (needStart.value == false) {
|
|
|
+ // //自动流程项目重新识别直接返回3
|
|
|
+ // closeOneTest();
|
|
|
+ // } else {
|
|
|
+ // //手动流程项目重新识别43返回41,42返回3
|
|
|
+ // if (examState.value == 43) {
|
|
|
+ // cleanData();
|
|
|
+ // startFace();
|
|
|
+ // } else {
|
|
|
+ // closeOneTest();
|
|
|
+ // }
|
|
|
+ // }
|
|
|
+ // }
|
|
|
+ // }
|
|
|
+ //举左手
|
|
|
+ if (e?.data?.result == 'left_hand') {
|
|
|
+ //proxy?.$modal.msgSuccess('手势指令:举左手');
|
|
|
+ //举左手确认退出
|
|
|
+ // if (exitStatus.value) {
|
|
|
+ // exitStatus.value = 0;
|
|
|
+ // //确认退出
|
|
|
+ // let keyEvent: any = null;
|
|
|
+ // keyEvent = new KeyboardEvent('keydown', {
|
|
|
+ // key: 'Enter', // 键值
|
|
|
+ // code: 'Enter', // 键盘代码
|
|
|
+ // keyCode: 13, // 旧的键盘代码
|
|
|
+ // which: 13, // 新的键盘代码
|
|
|
+ // shiftKey: false, // 是否按下Shift键
|
|
|
+ // ctrlKey: false, // 是否按下Ctrl键
|
|
|
+ // metaKey: false, // 是否按下Meta键(Win键或Command键)
|
|
|
+ // bubbles: true, // 事件是否冒泡
|
|
|
+ // cancelable: true // 是否可以取消事件的默认行为
|
|
|
+ // });
|
|
|
+ // document.activeElement?.dispatchEvent(keyEvent);
|
|
|
+ // return false;
|
|
|
+ // }
|
|
|
+ //开始识别
|
|
|
+ if (needStart.value && examState.value < 41) {
|
|
|
+ getOpenOneTestAndStartFace();
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ //停止人脸识别
|
|
|
+ // if (needStart.value && examState.value == 41) {
|
|
|
+ // getStopFace();
|
|
|
+ // }
|
|
|
+ //开始测试
|
|
|
+ if (examState.value == 43) {
|
|
|
+ if (needStart.value) {
|
|
|
+ getReady();
|
|
|
+ } else {
|
|
|
+ getStartOneTest();
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ //退出
|
|
|
+ // if (e?.data?.result == "exit") {
|
|
|
+ // proxy?.$modal.msgSuccess('手势指令:交叉手');
|
|
|
+ // // console.log("exitStatus.value", exitStatus.value)
|
|
|
+ // if (exitStatus.value == 0) {
|
|
|
+ // speckText("请保持两秒确认退出");
|
|
|
+ // //第一次才弹出
|
|
|
+ // confirmExit();
|
|
|
+ // setTimeout(() => {
|
|
|
+ // let keyEvent: any = null;
|
|
|
+ // let myKey = null;
|
|
|
+ // //如果交叉手两秒后返回超过4次就确认退出
|
|
|
+ // if (exitStatus.value >= 4) {
|
|
|
+ // myKey = 'Enter';
|
|
|
+ // } else {
|
|
|
+ // myKey = 'Esc';
|
|
|
+ // }
|
|
|
+ // if (myKey == 'Esc') {
|
|
|
+ // keyEvent = new KeyboardEvent('keydown', {
|
|
|
+ // key: 'Escape', // 键值
|
|
|
+ // code: 'Escape', // 键盘代码
|
|
|
+ // keyCode: 27, // 旧的键盘代码
|
|
|
+ // which: 27, // 新的键盘代码
|
|
|
+ // shiftKey: false, // 是否按下Shift键
|
|
|
+ // ctrlKey: false, // 是否按下Ctrl键
|
|
|
+ // metaKey: false, // 是否按下Meta键(Win键或Command键)
|
|
|
+ // bubbles: true, // 事件是否冒泡
|
|
|
+ // cancelable: true // 是否可以取消事件的默认行为
|
|
|
+ // });
|
|
|
+ // exitStatus.value = 0;
|
|
|
+ // }
|
|
|
+ // if (myKey == 'Enter') {
|
|
|
+ // keyEvent = new KeyboardEvent('keydown', {
|
|
|
+ // key: 'Enter', // 键值
|
|
|
+ // code: 'Enter', // 键盘代码
|
|
|
+ // keyCode: 13, // 旧的键盘代码
|
|
|
+ // which: 13, // 新的键盘代码
|
|
|
+ // shiftKey: false, // 是否按下Shift键
|
|
|
+ // ctrlKey: false, // 是否按下Ctrl键
|
|
|
+ // metaKey: false, // 是否按下Meta键(Win键或Command键)
|
|
|
+ // bubbles: true, // 事件是否冒泡
|
|
|
+ // cancelable: true // 是否可以取消事件的默认行为
|
|
|
+ // });
|
|
|
+ // }
|
|
|
+ // document.activeElement?.dispatchEvent(keyEvent);
|
|
|
+ // }, 2500)
|
|
|
+ // }
|
|
|
+ // exitStatus.value = exitStatus.value + 1
|
|
|
+ // }
|
|
|
+ // if (e?.data?.result == "exit") {
|
|
|
+ // console.log("exitStatus.value", exitStatus.value)
|
|
|
+ // if (exitStatus.value == 0) {
|
|
|
+ // exitStatus.value = 1
|
|
|
+ // speckText("请5秒内举左手确认退出");
|
|
|
+ // //第一次才弹出
|
|
|
+ // confirmExit();
|
|
|
+ // time.value.exit = 6;
|
|
|
+ // timerManager.value.exitTimer = setInterval(() => {
|
|
|
+ // time.value.exit--;
|
|
|
+ // console.log("取消倒计时", time.value.exit)
|
|
|
+ // proxy?.$modal.msgWarning(`取消倒计时:${time.value.exit}`)
|
|
|
+ // if (time.value.exit == 0) {
|
|
|
+ // exitStatus.value = 0;
|
|
|
+ // getClearTimer("exitTimer");
|
|
|
+ // let keyEvent: any = null;
|
|
|
+ // keyEvent = new KeyboardEvent('keydown', {
|
|
|
+ // key: 'Escape', // 键值
|
|
|
+ // code: 'Escape', // 键盘代码
|
|
|
+ // keyCode: 27, // 旧的键盘代码
|
|
|
+ // which: 27, // 新的键盘代码
|
|
|
+ // shiftKey: false, // 是否按下Shift键
|
|
|
+ // ctrlKey: false, // 是否按下Ctrl键
|
|
|
+ // metaKey: false, // 是否按下Meta键(Win键或Command键)
|
|
|
+ // bubbles: true, // 事件是否冒泡
|
|
|
+ // cancelable: true // 是否可以取消事件的默认行为
|
|
|
+ // });
|
|
|
+ // document.activeElement?.dispatchEvent(keyEvent);
|
|
|
+ // }
|
|
|
+ // }, 1000);
|
|
|
+ // }
|
|
|
+ // }
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+/**
|
|
|
+ * 输出犯规
|
|
|
+ */
|
|
|
+watch(
|
|
|
+ () => backReason.value.length,
|
|
|
+ (v) => {
|
|
|
+ backReasonStr.value = backReason.value[backReason.value.length - 1];
|
|
|
+ setTimeout(() => {
|
|
|
+ backReasonStr.value = '';
|
|
|
+ }, 1500);
|
|
|
+ },
|
|
|
+ { immediate: true }
|
|
|
+);
|
|
|
+
|
|
|
+/**
|
|
|
+ * 播报时间
|
|
|
+ */
|
|
|
+watch(
|
|
|
+ () => time.value.countdownNum,
|
|
|
+ (newData) => {
|
|
|
+ if (examState.value != 42) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ if (newData >= 30 && newData < time.value.testTime) {
|
|
|
+ if (newData % 30 == 0) {
|
|
|
+ speckText(`还有${newData}秒`);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (newData == 10) {
|
|
|
+ speckText('还有10秒');
|
|
|
+ }
|
|
|
+ if (newData <= 5) {
|
|
|
+ speckText(newData);
|
|
|
+ }
|
|
|
+ if (newData == 0) {
|
|
|
+ speckText('哨声');
|
|
|
+ }
|
|
|
+ },
|
|
|
+ { immediate: true }
|
|
|
+);
|
|
|
+
|
|
|
+/**
|
|
|
+ * 成绩整数播报
|
|
|
+ */
|
|
|
+watch(
|
|
|
+ () => currentResultObj.value,
|
|
|
+ (newData: any, oldData: any) => {
|
|
|
+ if (examState.value != 42 || newData.count <= 0) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ let project = parameter.value.project;
|
|
|
+ //引体向上比较慢所以都播报
|
|
|
+ if (['pullup'].includes(project) && newData.count > 0 && oldData.back_num == oldData.back_num) {
|
|
|
+ speckText(newData.count);
|
|
|
+ }
|
|
|
+ if (
|
|
|
+ ['situp', 'sidepullup', 'jumprope', 'jumpingjack', 'highknees'].includes(project) &&
|
|
|
+ newData.count > 0 &&
|
|
|
+ newData.count % 10 == 0 &&
|
|
|
+ oldData.back_num == oldData.back_num
|
|
|
+ ) {
|
|
|
+ speckText(newData.count);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ { immediate: true }
|
|
|
+);
|
|
|
+
|
|
|
+/**
|
|
|
+ * 播报时间
|
|
|
+ */
|
|
|
+watch(
|
|
|
+ () => currentResultObj.value.count,
|
|
|
+ (newData) => {
|
|
|
+ if (newData > 0 && ['jumprope'].includes(parameter.value.project)) {
|
|
|
+ //let frameRate = 12;
|
|
|
+ //gameContainer.value.sports(frameRate);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ { immediate: true }
|
|
|
+);
|
|
|
+
|
|
|
+/**
|
|
|
+ * 时间转换
|
|
|
+ */
|
|
|
+// const countdownNumFormat = computed(() => {
|
|
|
+// return time.value.countdownNum;
|
|
|
+// //return proxy?.$utils.timeFormat(time.value.countdownNum);
|
|
|
+// });
|
|
|
+
|
|
|
+onBeforeMount(() => {
|
|
|
+ parameter.value = route.query;
|
|
|
+ let project = parameter.value.project;
|
|
|
+ let area = parameter.value.area;
|
|
|
+ parameter.value.examId = `${project}_${area}`; //项目+区
|
|
|
+ if (parameter.value.time) {
|
|
|
+ time.value.testTime = parameter.value.time;
|
|
|
+ }
|
|
|
+ time.value.countdownNum = time.value.testTime;
|
|
|
+ userInfo.value = JSON.parse(myInfo);
|
|
|
+ unit.value = dic.unit[project];
|
|
|
+ if (parameter.value.gesture == 'true') {
|
|
|
+ parameter.value.gesture = true;
|
|
|
+ } else {
|
|
|
+ parameter.value.gesture = false;
|
|
|
+ }
|
|
|
+ //需要开始按钮的项目
|
|
|
+ let myList = ['situp', 'jumprope', 'jumpingjack', 'highknees', 'footballv1', 'basketballv1', 'pingpong'];
|
|
|
+ if (myList.includes(project)) {
|
|
|
+ needStart.value = true;
|
|
|
+ }
|
|
|
+ //加载WS
|
|
|
+ initWs({ parameter: parameter.value, testTime: time.value.testTime }, (data: any) => {
|
|
|
+ getMessage(data);
|
|
|
+ });
|
|
|
+ //初始化语音
|
|
|
+ initSpeech();
|
|
|
+ //初始化手势
|
|
|
+ initHand();
|
|
|
+ //加载音乐
|
|
|
+ getMusic();
|
|
|
+ //刷新关闭
|
|
|
+ window.onbeforeunload = function (e) {
|
|
|
+ var confirmationMessage = '刷新/关闭页面将会关闭页面,是否确认退出页面?';
|
|
|
+ (e || window.event).returnValue = confirmationMessage; // 兼容 Gecko + IE
|
|
|
+ let bUrl = import.meta.env.VITE_APP_BASE_API;
|
|
|
+ let classId = parameter.value.classes;
|
|
|
+ let project = parameter.value.project;
|
|
|
+ let area = parameter.value.area;
|
|
|
+ let examId = `${project}_${area}`;
|
|
|
+ let mySid = sid.value;
|
|
|
+ let token: any = localStorage.getItem('token');
|
|
|
+ let formData = new FormData();
|
|
|
+ formData.append('exam_id', examId);
|
|
|
+ formData.append('class_id', classId);
|
|
|
+ formData.append('token', token);
|
|
|
+ formData.append('sid', mySid);
|
|
|
+ navigator.sendBeacon(bUrl + '/exam/close_exam', formData);
|
|
|
+ return confirmationMessage; // 兼容 Gecko + Webkit, Safari, Chrome
|
|
|
+ };
|
|
|
+});
|
|
|
+
|
|
|
+onBeforeUnmount(() => {
|
|
|
+ getExit();
|
|
|
+});
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+$topPadding: 5.19rem;
|
|
|
+$waiPadding: 6.51rem;
|
|
|
+
|
|
|
+.main {
|
|
|
+ width: calc(100% - ($waiPadding * 2));
|
|
|
+ height: 78vh;
|
|
|
+ padding-top: 10rem;
|
|
|
+ margin: 0 auto;
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ overflow: hidden;
|
|
|
+
|
|
|
+ .main-left {
|
|
|
+ width: 71.5%;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ justify-content: space-between;
|
|
|
+
|
|
|
+ .main-left-top {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ height: 55.8%;
|
|
|
+ position: relative;
|
|
|
+
|
|
|
+ .top-left {
|
|
|
+ width: 37.4%;
|
|
|
+ height: 100%;
|
|
|
+ border-radius: 1.6rem;
|
|
|
+ background: radial-gradient(122% 126% at 97% 6%, #35ffc6 0%, #00ffe8 100%);
|
|
|
+ text-align: center;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ cursor: pointer;
|
|
|
+
|
|
|
+ .top-left-center {
|
|
|
+ .pic {
|
|
|
+ width: 22.3vh;
|
|
|
+ height: 22.3vh;
|
|
|
+ border-radius: 50%;
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+ overflow: hidden;
|
|
|
+ margin: 0 auto 2vh auto;
|
|
|
+
|
|
|
+ img {
|
|
|
+ width: 100%;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .pic2 {
|
|
|
+ box-sizing: border-box;
|
|
|
+ border: 0.44rem solid rgba(26, 41, 58, 0.6315);
|
|
|
+ }
|
|
|
+
|
|
|
+ .name {
|
|
|
+ width: 100%;
|
|
|
+ color: #1a293a;
|
|
|
+ font-size: 2.21rem;
|
|
|
+ }
|
|
|
+
|
|
|
+ .name2 {
|
|
|
+ padding: 0 0.3rem;
|
|
|
+ border-radius: 1.1rem;
|
|
|
+ background: radial-gradient(96% 96% at 2% 32%, #ffffff 0%, #fcfdfd 54%, #e1e4e7 100%);
|
|
|
+ box-shadow: inset 0px 1px 0px 2px rgba(255, 255, 255, 0.9046), inset 0px 3px 6px 0px rgba(0, 0, 0, 0.0851);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .top-right {
|
|
|
+ width: 62%;
|
|
|
+ height: 100%;
|
|
|
+ border-radius: 1.6rem;
|
|
|
+ opacity: 1;
|
|
|
+ background: #ffffff;
|
|
|
+ box-sizing: border-box;
|
|
|
+ border: 0.55rem solid #13ed84;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ flex-direction: column;
|
|
|
+ position: relative;
|
|
|
+
|
|
|
+ .time {
|
|
|
+ width: 28vh;
|
|
|
+ height: 28vh;
|
|
|
+ line-height: 28vh;
|
|
|
+ border-radius: 50%;
|
|
|
+ color: #ff9402;
|
|
|
+ font-size: 11vh;
|
|
|
+ text-align: center;
|
|
|
+ background-image: url('@/assets/images/test/time.png');
|
|
|
+ background-position: center;
|
|
|
+ background-repeat: no-repeat;
|
|
|
+ background-size: 100% 100%;
|
|
|
+ position: absolute;
|
|
|
+ right: -1.5vh;
|
|
|
+ top: -11vh;
|
|
|
+ font-family: 'Saira-BlackItalic';
|
|
|
+ }
|
|
|
+
|
|
|
+ .tips {
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+ height: 100%;
|
|
|
+
|
|
|
+ img {
|
|
|
+ max-height: 80%;
|
|
|
+ max-height: 80%;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .complete {
|
|
|
+ width: 100%;
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ flex-direction: column;
|
|
|
+
|
|
|
+ .scoreBox {
|
|
|
+ height: 10vh;
|
|
|
+ color: #1a293a;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ margin-bottom: 5vh;
|
|
|
+
|
|
|
+ .score {
|
|
|
+ font-size: 8.5rem;
|
|
|
+ line-height: 8.5rem;
|
|
|
+ font-family: 'Saira-BlackItalic';
|
|
|
+ }
|
|
|
+
|
|
|
+ .prompt {
|
|
|
+ font-size: 3.5rem;
|
|
|
+ line-height: 3.5rem;
|
|
|
+ }
|
|
|
+
|
|
|
+ .unit {
|
|
|
+ font-size: 2rem;
|
|
|
+ margin-left: 10px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .fractionViolation {
|
|
|
+ height: 10vh;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+
|
|
|
+ .fraction {
|
|
|
+ height: 10vh;
|
|
|
+ line-height: 10vh;
|
|
|
+ border-radius: 5vh;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ padding: 0 6%;
|
|
|
+ background: linear-gradient(138deg, #38536c 21%, #1a293a 75%);
|
|
|
+ box-shadow: inset 0px 1px 13px 0px rgba(255, 255, 255, 0.9452);
|
|
|
+
|
|
|
+ .lable {
|
|
|
+ font-size: 4vh;
|
|
|
+ color: #13ed84;
|
|
|
+ }
|
|
|
+
|
|
|
+ .value {
|
|
|
+ font-size: 7vh;
|
|
|
+ color: #00ffe8;
|
|
|
+ font-family: 'Saira-BlackItalic';
|
|
|
+ min-width: 7vh;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .violation {
|
|
|
+ height: 6.1vh;
|
|
|
+ line-height: 6.1vh;
|
|
|
+ border-radius: 4vh;
|
|
|
+ border: 0.25rem solid #ed7905;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ margin-left: 11px;
|
|
|
+ padding: 3px;
|
|
|
+ box-sizing: content-box;
|
|
|
+
|
|
|
+ .lable {
|
|
|
+ font-size: 1.2rem;
|
|
|
+ color: #ffffff;
|
|
|
+ width: 6.1vh;
|
|
|
+ height: 6.1vh;
|
|
|
+ line-height: 6.1vh;
|
|
|
+ background: #ed7905;
|
|
|
+ border-radius: 50%;
|
|
|
+
|
|
|
+ text-align: center;
|
|
|
+ }
|
|
|
+
|
|
|
+ .value {
|
|
|
+ margin-left: 1.5vh;
|
|
|
+ font-size: 3.2rem;
|
|
|
+ color: #ed7905;
|
|
|
+ font-family: 'Saira-BlackItalic';
|
|
|
+ min-width: 6vh;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .complete2 {
|
|
|
+ padding-left: 20%;
|
|
|
+ padding-top: 5vh;
|
|
|
+
|
|
|
+ .scoreBox {
|
|
|
+ margin-bottom: 3vh;
|
|
|
+ justify-content: left;
|
|
|
+ }
|
|
|
+
|
|
|
+ .fractionViolation {
|
|
|
+ justify-content: left;
|
|
|
+
|
|
|
+ .fraction {
|
|
|
+ padding: 0 8%;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .foulBox {
|
|
|
+ height: calc(4.2vh + 0.5rem + 6px);
|
|
|
+ overflow: hidden;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ padding-top: 3vh;
|
|
|
+
|
|
|
+ .foul {
|
|
|
+ height: 4.2vh;
|
|
|
+ line-height: 4.2vh;
|
|
|
+ border-radius: 3vh;
|
|
|
+ border: 0.25rem solid #ed7905;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ margin-left: 11px;
|
|
|
+ padding: 3px;
|
|
|
+ box-sizing: content-box;
|
|
|
+
|
|
|
+ .lable {
|
|
|
+ font-size: 2rem;
|
|
|
+ color: #ffffff;
|
|
|
+ width: 4.2vh;
|
|
|
+ height: 4.2vh;
|
|
|
+ line-height: 4.2vh;
|
|
|
+ background: #ed7905;
|
|
|
+ border-radius: 50%;
|
|
|
+ text-align: center;
|
|
|
+ }
|
|
|
+
|
|
|
+ .value {
|
|
|
+ margin-left: 1.5vh;
|
|
|
+ font-size: 2rem;
|
|
|
+ color: #ed7905;
|
|
|
+ font-family: 'Saira-BlackItalic';
|
|
|
+ min-width: 6vh;
|
|
|
+ padding: 0 10px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .readyBoxBefore {
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ font-size: 2.5rem;
|
|
|
+ color: #1a293a;
|
|
|
+ padding-top: 4vh;
|
|
|
+ line-height: 0;
|
|
|
+ margin-bottom: 2vh;
|
|
|
+
|
|
|
+ .item {
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+
|
|
|
+ .lable {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ margin-left: 10px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ img {
|
|
|
+ height: 20vh;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .readyBox {
|
|
|
+ text-align: center;
|
|
|
+ color: #1a293a;
|
|
|
+
|
|
|
+ .value {
|
|
|
+ font-size: 8.5rem;
|
|
|
+ line-height: 8.5rem;
|
|
|
+ font-family: 'Saira-BlackItalic';
|
|
|
+ }
|
|
|
+
|
|
|
+ .lable {
|
|
|
+ font-size: 3.5rem;
|
|
|
+ }
|
|
|
+
|
|
|
+ .transparent {
|
|
|
+ opacity: 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .btn {
|
|
|
+ font-size: 2.21rem;
|
|
|
+ color: #ffffff;
|
|
|
+ text-align: center;
|
|
|
+ width: 50%;
|
|
|
+ line-height: 8vh;
|
|
|
+ line-height: 8vh;
|
|
|
+ border-radius: 15px;
|
|
|
+ opacity: 1;
|
|
|
+ background: radial-gradient(159% 126% at 5% 93%, #f99f02 0%, #ed7905 100%);
|
|
|
+ box-shadow: 3px 6px 4px 1px rgba(0, 0, 0, 0.1874), inset 0px 1px 0px 2px rgba(255, 255, 255, 0.3);
|
|
|
+ cursor: pointer;
|
|
|
+ }
|
|
|
+
|
|
|
+ .btn2 {
|
|
|
+ width: auto;
|
|
|
+ padding: 0 10px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .exitBtn {
|
|
|
+ color: #ffffff;
|
|
|
+ background: radial-gradient(159% 126% at 5% 93%, #931b1b 0%, #ec5624 0%, #ff6860 76%);
|
|
|
+ box-shadow: 3px 6px 4px 1px rgba(0, 0, 0, 0.1874), inset 0px 1px 0px 2px rgba(255, 255, 255, 0.5577);
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ background: #ec5624;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ i {
|
|
|
+ width: 4vw;
|
|
|
+ height: 4vw;
|
|
|
+ display: block;
|
|
|
+ position: absolute;
|
|
|
+ top: 50%;
|
|
|
+ left: 37.5%;
|
|
|
+ margin-top: calc(4vw * -0.5);
|
|
|
+ margin-left: calc(4vw * -0.5);
|
|
|
+ background-image: url('@/assets/images/test/yuan.png');
|
|
|
+ background-position: center;
|
|
|
+ background-repeat: no-repeat;
|
|
|
+ background-size: 100% 100%;
|
|
|
+ border-radius: 50%;
|
|
|
+ flex-shrink: 0;
|
|
|
+ transition: all 0.5s;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .main-left-bottom {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ height: calc(100% - 55.8% - 3vh);
|
|
|
+ overflow: hidden;
|
|
|
+
|
|
|
+ .bottom-left {
|
|
|
+ width: 58%;
|
|
|
+ padding-right: 1rem;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+
|
|
|
+ .tips {
|
|
|
+ height: 2.8vh;
|
|
|
+
|
|
|
+ img {
|
|
|
+ height: 100%;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .pic {
|
|
|
+ text-align: center;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ overflow: hidden;
|
|
|
+
|
|
|
+ img {
|
|
|
+ max-width: 100%;
|
|
|
+ max-height: 100%;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .bottom-right {
|
|
|
+ width: 41%;
|
|
|
+ height: 100%;
|
|
|
+ overflow-y: scroll;
|
|
|
+ color: #f9f9f9;
|
|
|
+ font-size: 1.1rem;
|
|
|
+ line-height: 1.6rem;
|
|
|
+
|
|
|
+ &::-webkit-scrollbar {
|
|
|
+ width: 10px;
|
|
|
+ }
|
|
|
+
|
|
|
+ &::-webkit-scrollbar-thumb {
|
|
|
+ border-width: 2px;
|
|
|
+ border-radius: 4px;
|
|
|
+ border-style: dashed;
|
|
|
+ border-color: transparent;
|
|
|
+ background-color: rgba(26, 62, 78, 0.9);
|
|
|
+ background-clip: padding-box;
|
|
|
+ }
|
|
|
+
|
|
|
+ &::-webkit-scrollbar-button:hover {
|
|
|
+ border-radius: 6px;
|
|
|
+ background: rgba(26, 62, 78, 1);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .main-right {
|
|
|
+ width: 27%;
|
|
|
+ border-radius: 1.6rem;
|
|
|
+ background: linear-gradient(29deg, #092941 -82%, #2a484b 94%);
|
|
|
+ box-shadow: inset 0px 1px 0px 2px rgba(255, 255, 255, 0.4);
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ overflow: hidden;
|
|
|
+ }
|
|
|
+}
|
|
|
+.close {
|
|
|
+ position: absolute;
|
|
|
+ // right: calc($waiPadding - 3.2rem);
|
|
|
+ left: auto;
|
|
|
+ right: calc($topPadding / 2 - 3.2rem / 4);
|
|
|
+ top: auto;
|
|
|
+ bottom: calc($topPadding / 2 - 3.2rem / 4);
|
|
|
+}
|
|
|
+</style>
|