林旭祥 пре 9 месеци
родитељ
комит
79b9568b5d

+ 375 - 0
src/components/MultipleItem/index.vue

@@ -0,0 +1,375 @@
+<template>
+  <div>
+    <div>{{ area }}</div>
+    <div><el-avatar :src="faceCheckStu.logo_url || faceCheckStu.face_pic" @click="getChooseStudent" /></div>
+    <div>{{ faceCheckStu.name }}</div>
+    <div>成绩:{{ currentResultObj.count }} {{ unit }}</div>
+    <div>得分:{{ currentResultObj.score }}</div>
+    <div>
+      <div v-if="['jumprope'].includes(project)">中断</div>
+      <div v-else>犯规</div>
+      <div>{{ currentResultObj.back_num }}</div>
+    </div>
+    <div v-if="backReason.length">
+      <div>违规项</div>
+      <div v-for="(item, index) in backReason" :key="index">{{ item }}</div>
+    </div>
+    <div>当前状态:({{ examState == 3 ? "初始化完成" : examState == 40 ? "创建测试" : examState == 41 ? "正在人脸识别"
+      :
+      examState ==
+        43 ? "停止人脸识别" : examState == 42 ? "正在测试" : "请初始化" }})</div>
+    <div @click="getChooseStudent" v-if="examState == 41 || (examState == 43 && !faceCheckStu.student_id)">选择学生</div>
+    <div @click="getRetestFace" v-if="examState == 43">重新识别</div>
+    <ChooseStudent ref="chooseStudentRef" @returnData="returnStudent" />
+  </div>
+</template>
+
+<script setup lang="ts">
+import { initSpeech, speckText, speckCancel, chineseNumber } from '@/utils/speech'
+import { openOneTest, startFace, stopFace, faceConfirmOnly, startOneTest, finishOneTest, closeOneTest, suspendFaceRecognitionChannels, resumeFaceRecognitionChannels } from '@/utils/ws'
+import dataDictionary from "@/utils/dataDictionary"
+const route = useRoute();
+const { proxy } = getCurrentInstance() as any;
+const chooseStudentRef = ref();
+const emit = defineEmits(['returnData']);
+
+//父值
+const props = defineProps({
+  area: {
+    type: String,
+    default: ""
+  },
+});
+
+
+let project: any = route.query.project;
+let area: any = props.area;
+let examId: string = `${project}_${area}`; //项目+区
+
+const data = reactive<any>({
+  examState: 0,//当前状态
+  resultId: null,//测试ID
+  currentResultObj: {},//成绩
+  faceCheckStu: {},//人脸信息
+  unit: "",//单位
+  backReason: [],//犯规项
+});
+const { examState, resultId, faceCheckStu, currentResultObj, unit, backReason } = toRefs(data);
+
+/**
+ * 接收消息
+*/
+const getMessage = (e: any) => {
+  //console.log("WS响应:", e)
+  //实时状态
+  if (e.cmd === 'exam_status') {
+    examState.value = e.data;
+  }
+  //工作站状态
+  if (e.cmd === 'init_result') {
+
+  }
+  //测试违规
+  if (e.cmd === 'warning_result') {
+
+  }
+  //后端播报语音
+  if (e.cmd === 'return_audio_msg') {
+
+  }
+  //错误提示
+  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') {
+
+  }
+  //断线状态
+  if (e.cmd === 'disconnect_request') {
+    if (e.data.message) {
+      speckText(e.data.message);
+    }
+  }
+  //状态变更
+  if (e.cmd === 'set_exam_state') {
+    examState.value = e.data;
+    if (e.data === 3) {
+    }
+    if (e.data === 40) {
+      cleanData();
+    }
+    if (e.data == 41) {
+
+    }
+    if (e.data == 43) {
+
+    }
+    if (e.data == 42) {
+
+    }
+  }
+  //新建测试后返回信息,获取result_id
+  if (e.cmd === 'open_one_test_ack') {
+    resultId.value = e.data.result_id;
+  }
+  //人脸识别状态
+  if (e.cmd === 'face_check_result') {
+    faceCheckStu.value = e.data[0] || e.data;
+    //工作站识别成功后停止识别并确定人脸
+    getStopFace();
+  }
+  //测试结束结果
+  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 () => {
+  console.log("examId", examId)
+  await openOneTest(examId);
+  await startFace(examId);
+};
+
+/**
+ * 停止人脸识别
+*/
+const getStopFace = async () => {
+  await stopFace(examId);
+  if (faceCheckStu.value.student_id) {
+    getFaceConfirmOnly();
+  }
+};
+
+/**
+ * 确定人脸信息
+*/
+const getFaceConfirmOnly = (data?: any) => {
+  if (data) {
+    faceCheckStu.value = data;
+  }
+  faceConfirmOnly({
+    exam_id: examId,
+    result_id: resultId.value,
+    student_id: faceCheckStu.value.student_id,
+    gender: faceCheckStu.value.gender
+  }, () => {
+  });
+};
+
+/**
+ * 重新识别
+*/
+const getRetestFace = () => {
+  proxy?.$modal.confirm("确定重新识别吗?").then(() => {
+    //手动流程项目重新识别43返回41,42返回3
+    if (examState.value == 43) {
+      cleanData();
+      startFace(examId);
+    } else {
+      closeOneTest(examId);
+    }
+  }).finally(() => {
+  });
+};
+
+/**
+ * 开始测试
+*/
+const getStartOneTest = () => {
+  if (!faceCheckStu.value.student_id) {
+    proxy?.$modal.msgError("请选择人员!");
+    return false;
+  }
+  startOneTest(examId, () => { })
+};
+
+/**
+ * 再测一次
+*/
+const getAgain = async () => {
+  //预存测试人员
+  let student = JSON.parse(JSON.stringify(faceCheckStu.value));
+  //测试中
+  if (examState.value == 42) {
+    await finishOneTest(examId);
+  }
+  //重新走一次流程
+  await openOneTest(examId);
+  await startFace(examId);
+  await stopFace(examId);
+  getFaceConfirmOnly(student);
+};
+
+/**
+ * 选择学生
+*/
+const getChooseStudent = () => {
+  if (examState.value == 41) {
+    chooseStudentRef.value.open();
+  }
+  if (examState.value == 43) {
+    getRetestFace();
+  }
+};
+
+/**
+ * 返回被选学生
+*/
+const returnStudent = (data: any) => {
+  faceCheckStu.value = data;
+  getStopFace();
+};
+
+/**
+ * 清除历史记录
+*/
+const cleanData = () => {
+  faceCheckStu.value = {};
+  currentResultObj.value = {};
+  backReason.value = [];
+};
+
+/**
+ * 成绩
+*/
+const getAchievement = (data: any) => {
+  //console.log("成绩", data);
+  let dic: any = dataDictionary;
+  let type = project;
+  let count =
+    data?.[dic.typeResultKey[type]]?.toFixed(0);
+  if (["trijump", "solidball", "shotput", "longjump"].includes(type)) {
+    count =
+      data?.[dic.typeResultKey[type]]?.toFixed(2);
+    count = Math.round(count) / 100;
+  }
+  data.count = count || "0";
+  data.score = data.score || "0";
+  currentResultObj.value = data;
+  //违规处理
+  let arr = backReason.value;
+  if (["situp", "pullup", "sidepullup", "jumprope", "jumpingjack", "jump", "longjump", "verticaljump"]
+    .indexOf(type) > -1) {
+    if (["pullup", "situp", "jumprope", "jumpingjack"].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 = "踩线违规";
+        arr.push(txt)
+      }
+      if (data?.singleleg_jump_check == 0) {
+        let txt = "单脚跳违规";
+        arr.push(txt)
+      }
+      if (data?.outside_check == 0) {
+        let txt = "跳出测试区违规";
+        arr.push(txt)
+      }
+    }
+    if (
+      data?.elbow_check == false
+    ) {
+      let txt = "肘部违规";
+      arr.push(txt);
+    }
+    if (
+      ["situp", "pullup"].indexOf(type) > -1 &&
+      data?.knee_check === false
+    ) {
+      let txt = "腿部违规";
+      if (!arr.includes(txt)) {
+        arr.push(txt);
+      }
+    }
+    if (["situp"].indexOf(type) > -1 && data?.hand_check === false) {
+      let txt = "手部违规";
+      if (!arr.includes(txt)) {
+        arr.push(txt);
+      }
+    }
+    if (
+      ["pullup"].indexOf(type) > -1 &&
+      data?.["0"]?.elbow_check === false
+    ) {
+      let txt = "手部违规";
+      if (!arr.includes(txt)) {
+        arr.push(txt);
+      }
+    }
+    if (["situp"].indexOf(type) > -1 && data?.["0"]?.back_check === false) {
+      let txt = "背部违规";
+      if (!arr.includes(txt)) {
+        arr.push(txt);
+      }
+    }
+    if (
+      ["sidepullup", "situp"].indexOf(type) > -1 &&
+      data?.["0"]?.hip_check === false
+    ) {
+      let txt = "臀部违规";
+      if (!arr.includes(txt)) {
+        arr.push(txt);
+      }
+    }
+  }
+  backReason.value = arr;
+  if (data.isfinish) {
+    // if (['jump'].includes(type) && backReason.value.length) {
+    //   speckText("请重新测试");
+    //   return false;
+    // }
+    // speckText(faceCheckStu?.value.name + "成绩为" + (chineseNumber(count) || 0) + unit.value + ",请下一位准备!" || "");
+  }
+};
+
+/**
+ * 状态抛给父组件
+*/
+watch(() => examState.value, (newVal, oldVal) => {
+  emit('returnData', { examState: examState.value, area: area });
+}, { deep: true });
+
+/**
+ * 人脸信息抛给父组件
+*/
+watch(() => faceCheckStu.value, (newVal, oldVal) => {
+  emit('returnData', { faceCheckStu: faceCheckStu.value, area: area });
+}, { deep: true });
+
+onMounted(() => {
+  let dic: any = dataDictionary;
+  unit.value = dic.unit[project];
+})
+
+//暴露给父组件用
+defineExpose({
+  getMessage,
+  getOpenOneTestAndStartFace,
+  getStopFace,
+  getStartOneTest,
+  getAgain,
+})
+</script>
+
+<style scoped lang="scss"></style>

+ 8 - 1
src/components/OptionWindow/index.vue

@@ -266,11 +266,18 @@ const confirm = (data: any) => {
     ElMessage({ message: message, type: 'error', duration: 3 * 1000 });
     return false;
   }
-  if (["run50", "run70", "run100", "run200", "run400", "run800", "run1000", "run15x4", "run10x4", "run50x8"].includes(project.value.key)) {
+  if (chooseArea.value.length > 1) {
+    //多区域
+    router.push({ path: '/train/multiple', query: optionForm.value });
+  }
+  else if (["run50", "run70", "run100", "run200", "run400", "run800", "run1000", "run15x4", "run10x4", "run50x8"].includes(project.value.key)) {
+    //跑步项目
     router.push({ path: '/train/run', query: optionForm.value });
   } else {
+    //单项
     router.push({ path: '/train/test', query: optionForm.value });
   }
+
 };
 
 onMounted(() => {

+ 2 - 1
src/router/index.ts

@@ -8,7 +8,8 @@ const router = createRouter({
     { path: '/login', component: () => import('@/views/login/index.vue') },
     { path: '/train', component: () => import('@/views/train/index.vue') },
     { path: '/train/test', component: () => import('@/views/train/test.vue') },
-    { path: '/train/run', component: () => import('@/views/train/run.vue') }
+    { path: '/train/run', component: () => import('@/views/train/run.vue') },
+    { path: '/train/multiple', component: () => import('@/views/train/multiple.vue') }
   ]
 });
 

+ 1 - 0
src/types/components.d.ts

@@ -19,6 +19,7 @@ declare module 'vue' {
     ElTable: typeof import('element-plus/es')['ElTable']
     ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
     FaceWindow: typeof import('./../components/FaceWindow/index.vue')['default']
+    MultipleItem: typeof import('./../components/MultipleItem/index.vue')['default']
     OptionTest: typeof import('./../components/OptionTest/index.vue')['default']
     OptionWindow: typeof import('./../components/OptionWindow/index.vue')['default']
     RouterLink: typeof import('vue-router')['RouterLink']

+ 164 - 52
src/utils/ws.ts

@@ -5,18 +5,22 @@ const myInfo: any = localStorage.getItem('userInfo');
 let socket: any = {}; //ws实例对象
 let timerManager: any = {}; //计时器管理
 let parameter: any = {}; //参数
-let testTime: number = 60; //默认时长
+let testTime: number = 0; //默认时长
 let userInfo: any = JSON.parse(myInfo); //用户信息
 let beatTime: number = 10000; //心跳频率
 let beatNumber: number = 0; //心跳次数
 let loading: any = null; //遮罩层
 let version: string = ''; //ws接口版本v2表示单ws多项目
+let examState: number = 0; //当前状态码
+let testList: any = []; //区列表
+let wkList: any = []; //工作站列表
 
 export const initWs = (data: any, callback: any) => {
   loading = ElLoading.service({ text: '正在初始化,请稍候', background: 'rgba(0, 0, 0, 0.7)' });
   parameter = data.parameter;
   testTime = data.testTime;
   version = data.version || '';
+  testList = data.parameter.area.split(',');
   socket = io(address + '/midexam', {
     transports: ['websocket', 'polling'],
     query: {
@@ -24,26 +28,52 @@ export const initWs = (data: any, callback: any) => {
       sysuuid: token
     }
   });
-
+  let loadingTime = setTimeout(() => {
+    //30秒还在0状态就算超时
+    if (examState == 0) {
+      clearTimeout(loadingTime);
+      callback({ cmd: 'disconnect_request', data: { message: 'WS连接超时' } });
+      getExit();
+    }
+  }, 30000);
   socket.on('connect', (e: any) => {
-    getExamStarts();
-    getNetWork((e: any) => {
-      if (!e.status) {
-        callback({ cmd: 'disconnect_request', data: { message: '工作站未响应' } });
-      }
-    });
+    if (testList.length > 1) {
+      //单WS多区
+      testList.forEach((item: any) => {
+        let examId = `${parameter.project}_${item}`;
+        getExamStarts(examId);
+        getNetWork(examId, (e: any) => {
+          if (!e.status) {
+            callback({ cmd: 'disconnect_request', data: { message: '工作站未响应' } });
+          }
+        });
+      });
+    } else {
+      //单WS单区
+      getExamStarts();
+      getNetWork(data == null, (e: any) => {
+        if (!e.status) {
+          callback({ cmd: 'disconnect_request', data: { message: '工作站未响应' } });
+        }
+      });
+    }
   });
 
   socket.on('msg2frontend', (e: any) => {
     callback(e);
     //实时状态
     if (e.cmd === 'exam_status') {
+      examState = e.data;
       beatNumber++;
     }
     //工作站状态
     if (e.cmd === 'init_result') {
       if (e.status == 666) {
-        parameter.wk_id = e.data.wk_id;
+        let data = {
+          wk_id: e.data.wk_id,
+          examId: e.exam_id
+        };
+        wkList.push(data);
       }
     }
     //测试违规
@@ -71,9 +101,11 @@ export const initWs = (data: any, callback: any) => {
     }
     //状态变更
     if (e.cmd === 'set_exam_state') {
+      examState = e.data;
       if (e.data == 3) {
         //关闭遮罩层
         loading?.close();
+        clearTimeout(loadingTime);
       }
     }
     //新建测试后返回信息,获取result_id
@@ -138,11 +170,12 @@ export const sendMessage = (type: string, data: any, callback?: () => void) => {
 /**
  * 连接成功
  */
-const getExamStarts = () => {
+const getExamStarts = (data?: any) => {
+  let examId = data ? data : parameter.examId;
   sendMessage(
     'exam_starts',
     {
-      data: 'start_' + parameter.examId,
+      data: 'start_' + examId,
       class_id: parameter.classes,
       exam_type: parameter.standard,
       gesture: parameter.gesture,
@@ -155,39 +188,87 @@ const getExamStarts = () => {
 /**
  * 创建测试
  */
-export const openOneTest = () => {
-  let examId = parameter.examId;
-  sendMessage('msgfrom_frontend', {
-    data: {
-      cmd: 'open_one_test',
-      exam_id: examId
-    }
+export const openOneTest = (data?: any) => {
+  return new Promise((resolve, reject) => {
+    let examId = data ? data : parameter.examId;
+    sendMessage('msgfrom_frontend', {
+      data: {
+        cmd: 'open_one_test',
+        exam_id: examId
+      }
+    });
+    let timer1 = setInterval(() => {
+      if (examState == 40) {
+        clearInterval(timer1);
+        clearTimeout(timer2);
+        resolve({ data: examState });
+      }
+    }, 250);
+    let timer2 = setTimeout(() => {
+      if (examState == 3) {
+        clearInterval(timer1);
+        clearTimeout(timer2);
+        reject({ cmd: 'disconnect_request', data: { message: '超时:open_one_test' } });
+      }
+    }, 30000);
   });
 };
 
 /**
  * 开始人脸识别
  */
-export const startFace = () => {
-  let examId = parameter.examId;
-  sendMessage('msgfrom_frontend', {
-    data: {
-      cmd: 'start_face_recognition',
-      exam_id: examId
-    }
+export const startFace = (data?: any) => {
+  return new Promise((resolve, reject) => {
+    let examId = data ? data : parameter.examId;
+    sendMessage('msgfrom_frontend', {
+      data: {
+        cmd: 'start_face_recognition',
+        exam_id: examId
+      }
+    });
+    let timer1 = setInterval(() => {
+      if (examState == 41) {
+        clearInterval(timer1);
+        clearTimeout(timer2);
+        resolve({ data: examState });
+      }
+    }, 250);
+    let timer2 = setTimeout(() => {
+      if (examState == 40) {
+        clearInterval(timer1);
+        clearTimeout(timer2);
+        reject({ cmd: 'disconnect_request', data: { message: '超时:start_face_recognition' } });
+      }
+    }, 30000);
   });
 };
 
 /**
  * 停止人脸识别
  */
-export const stopFace = () => {
-  let examId = parameter.examId;
-  sendMessage('msgfrom_frontend', {
-    data: {
-      cmd: 'stop_face_recognition',
-      exam_id: examId
-    }
+export const stopFace = (data?: any) => {
+  return new Promise((resolve, reject) => {
+    let examId = data ? data : parameter.examId;
+    sendMessage('msgfrom_frontend', {
+      data: {
+        cmd: 'stop_face_recognition',
+        exam_id: examId
+      }
+    });
+    let timer1 = setInterval(() => {
+      if (examState == 43) {
+        clearInterval(timer1);
+        clearTimeout(timer2);
+        resolve({ data: examState });
+      }
+    }, 250);
+    let timer2 = setTimeout(() => {
+      if (examState == 41) {
+        clearInterval(timer1);
+        clearTimeout(timer2);
+        reject({ cmd: 'disconnect_request', data: { message: '超时:stop_face_recognition' } });
+      }
+    }, 30000);
   });
 };
 
@@ -195,8 +276,7 @@ export const stopFace = () => {
  * 确认并提交人脸
  */
 export const faceConfirmOnly = (data: any, callback?: any) => {
-  console.log(Array.isArray(data));
-  let examId = parameter.examId;
+  let examId = data.exam_id ? data.examId : parameter.examId;
   let myData = null;
   if (Array.isArray(data)) {
     //数组类型
@@ -228,8 +308,8 @@ export const faceConfirmOnly = (data: any, callback?: any) => {
 /**
  * 开始测试
  */
-export const startOneTest = (callback?: any) => {
-  let examId = parameter.examId;
+export const startOneTest = (data?: any, callback?: any) => {
+  let examId = data ? data : parameter.examId;
   sendMessage(
     'msgfrom_frontend',
     {
@@ -247,21 +327,37 @@ export const startOneTest = (callback?: any) => {
 /**
  * 停止测试
  */
-export const finishOneTest = () => {
-  let examId = parameter.examId;
-  sendMessage('msgfrom_frontend', {
-    data: {
-      cmd: 'finish_one_test',
-      exam_id: examId
-    }
+export const finishOneTest = (data?: any) => {
+  return new Promise((resolve, reject) => {
+    let examId = data ? data : parameter.examId;
+    sendMessage('msgfrom_frontend', {
+      data: {
+        cmd: 'finish_one_test',
+        exam_id: examId
+      }
+    });
+    let timer1 = setInterval(() => {
+      if (examState == 3) {
+        clearInterval(timer1);
+        clearTimeout(timer2);
+        resolve({ data: examState });
+      }
+    }, 250);
+    let timer2 = setTimeout(() => {
+      if (examState == 42) {
+        clearInterval(timer1);
+        clearTimeout(timer2);
+        reject({ cmd: 'disconnect_request', data: { message: '超时:finish_one_test' } });
+      }
+    }, 60000);
   });
 };
 
 /**
  * 关闭测试测试
  */
-export const closeOneTest = () => {
-  let examId = parameter.examId;
+export const closeOneTest = (data?: any) => {
+  let examId = data ? data : parameter.examId;
   sendMessage('msgfrom_frontend', {
     data: {
       cmd: 'close_one_test',
@@ -299,10 +395,13 @@ export const resumeFaceRecognitionChannels = (data: any) => {
 /**
  * 心跳
  */
-export const getNetWork = (callback?: any) => {
+export const getNetWork = (data: any, callback?: any) => {
   timerManager.beat = setInterval(() => {
-    let examId = parameter.examId;
-    let wk_id = parameter.wk_id;
+    let obj = wkList.find((item: any) => {
+      return item.examId == data;
+    });
+    let examId = data ? data : parameter.examId;
+    let wk_id = obj.wk_id;
     sendMessage(
       'get_exam_status',
       {
@@ -341,10 +440,23 @@ const getExit = () => {
   //清除计时器
   getClearTimer();
   //通知工作站关闭
-  sendMessage('exam_ends', {
-    data: 'end_' + parameter.examId,
-    class_id: parameter.classes
-  });
+  if (testList.length > 1) {
+    //单WS多区
+    testList.forEach((item: any) => {
+      let examId = `${parameter.project}_${item}`;
+      sendMessage('exam_ends', {
+        data: 'end_' + examId,
+        class_id: parameter.classes
+      });
+    });
+  } else {
+    //单WS单区
+    let examId = parameter.examId;
+    sendMessage('exam_ends', {
+      data: 'end_' + examId,
+      class_id: parameter.classes
+    });
+  }
   //如果正在连接就关闭
   if (socket?.connected) {
     socket?.close();

+ 260 - 0
src/views/train/multiple.vue

@@ -0,0 +1,260 @@
+<template>
+  <div>
+    <div @click="confirmExit">退出</div>
+    <div style="text-align: center;">{{
+      countdownNumFormat
+    }}</div>
+    <div style="display: flex; justify-content: space-between; margin-bottom: 40px;">
+      <MultipleItem :ref="(el: any) => { multipleItemRef(el, index, item.area) }" v-for="(item, index) in testList"
+        :query="parameter" :area="item.area" :key="index" @returnData="returnData" />
+    </div>
+    <div>
+      <div @click="getAgain" v-if="examState == 42 || showTestAgain">再测一次</div>
+      <div @click="getOpenOneTestAndStartFace" v-if="examState < 41">1、开始识别</div>
+      <div @click="getStopFace" v-if="examState == 41">2、停止人脸识别</div>
+      <div @click="getStartOneTest" v-if="examState == 43">3、开始测试</div>
+    </div>
+  </div>
+</template>
+
+<script setup name="Multiple" lang="ts">
+import { initSpeech, speckText, speckCancel, chineseNumber } from '@/utils/speech'
+import { initWs, examEnds, openOneTest, startFace, stopFace, faceConfirmOnly, startOneTest, finishOneTest, closeOneTest, suspendFaceRecognitionChannels, resumeFaceRecognitionChannels } from '@/utils/ws'
+const { proxy } = getCurrentInstance() as any;
+const router = useRouter();
+const route = useRoute();
+const data = reactive<any>({
+  timerManager: {},//计时器管理
+  parameter: {},//参数
+  time: {
+    testTime: 60,//时长
+    countdownNum: 0,//计时
+  },
+  userInfo: {},//用户信息
+  examState: 0,//当前状态
+  showTestAgain: false,//再测一次按钮
+  testList: [],//获取区列表
+  multipleItemRefList: [],//获取区列表
+});
+const { timerManager, parameter, time, userInfo, examState, showTestAgain, testList, multipleItemRefList } = toRefs(data);
+
+/**
+ * 创建组件实例
+*/
+const multipleItemRef = (el: any, index: number, area: any) => {
+  el.area = area;
+  multipleItemRefList.value[index] = el;
+}
+
+/**
+ * 开始识别
+*/
+const getOpenOneTestAndStartFace = () => {
+  examState.value = 41;
+  for (let i = 0; i < multipleItemRefList.value.length; i++) {
+    multipleItemRefList.value[i].getOpenOneTestAndStartFace()
+  }
+  cleanData();
+};
+
+/**
+ * 停止人脸识别
+*/
+const getStopFace = async () => {
+  // let flag = false;
+  // for (let i = 0; i < testList.value.length; i++) {
+  //   if (testList.value[i] && testList.value[i].faceCheckStu?.student_id) {
+  //     flag = true;
+  //   }
+  // }
+  // if (!flag) {
+  //   proxy?.$modal.msgError("请选择人员!");
+  //   return false;
+  // }
+  examState.value = 43;
+  for (let i = 0; i < multipleItemRefList.value.length; i++) {
+    multipleItemRefList.value[i].getStopFace()
+  }
+};
+
+/**
+ * 开始测试
+*/
+const getStartOneTest = () => {
+  let flag = false;
+  for (let i = 0; i < testList.value.length; i++) {
+    if (testList.value[i] && testList.value[i].faceCheckStu?.student_id) {
+      flag = true;
+    }
+  }
+  if (!flag) {
+    proxy?.$modal.msgError("请选择人员!");
+    return false;
+  }
+  examState.value = 42;
+  for (let i = 0; i < multipleItemRefList.value.length; i++) {
+    if (testList.value[i].examState == 43) {
+      multipleItemRefList.value[i].getStartOneTest()
+    }
+  }
+  //显示再测一次按钮
+  showTestAgain.value = true;
+  //停止播报;
+  speckCancel()
+  //时间为0的为正计时,大于0的为倒计时
+  if (time.value.testTime == 0) {
+    getCounting("+");
+  } else {
+    getCounting("-");
+  }
+};
+
+/**
+ * 再测一次
+*/
+const getAgain = async () => {
+  cleanData();
+  examState.value = 43;
+  for (let i = 0; i < multipleItemRefList.value.length; i++) {
+    multipleItemRefList.value[i].getAgain()
+  }
+};
+
+/**
+ * 确认退出
+*/
+const confirmExit = () => {
+  proxy?.$modal.confirm("确定退出吗?").then(() => {
+    getExit();
+  }).finally(() => {
+  });
+};
+
+/**
+ * 退出
+*/
+const getExit = () => {
+  getClearTimer();//清除计时器
+  examEnds();//通知工作站关闭
+  speckCancel()//停止播报;
+  router.push({ path: '/' });//跳转
+};
+
+/**
+ * 清空定时任务
+*/
+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 cleanData = () => {
+  getClearTimer("countdownTimer");
+  time.value.countdownNum = time.value.testTime;
+  showTestAgain.value = false;
+};
+
+/**
+ * 时间转换
+*/
+const countdownNumFormat = computed(() => {
+  return proxy?.$utils.timeFormat(time.value.countdownNum);
+});
+
+/**
+ * 倒计时
+*/
+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 returnData = (data: any) => {
+  let index = testList.value.findIndex((item: any) => {
+    return item.area == data.area;
+  });
+  testList.value[index] = Object.assign(testList.value[index], data);
+  console.log("testList.value", testList.value)
+  //测试完成后回退状态
+  if (examState.value == 42) {
+    let flag = false;
+    for (let i = 0; i < testList.value.length; i++) {
+      if (testList.value[i] && testList.value[i].faceCheckStu?.student_id && testList.value[i].examState == 3) {
+        flag = true;
+      } else {
+        return false;
+      }
+    }
+    if (flag) {
+      examState.value = 3;
+    }
+  }
+};
+
+onMounted(() => {
+  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;
+  let myInfo: any = localStorage.getItem("userInfo");
+  userInfo.value = JSON.parse(myInfo);
+  if (parameter.value.gesture == 'true') {
+    parameter.value.gesture = true
+  } else {
+    parameter.value.gesture = false
+  }
+  testList.value = parameter.value.area.split(',').map((item: any) => {
+    let obj = {
+      area: item
+    }
+    return obj;
+  });
+  //加载WS
+  initWs({ parameter: parameter.value, testTime: time.value.testTime, version: "v2" }, (data: any) => {
+    let obj = multipleItemRefList.value.find((item: any) => {
+      return project + '_' + item.area == data.exam_id;
+    })
+    obj?.getMessage(data);
+  });
+  initSpeech();
+})
+
+onUnmounted(() => {
+  getExit();
+})
+</script>
+
+<style scoped lang="scss"></style>

+ 40 - 59
src/views/train/run.vue

@@ -32,13 +32,14 @@
       :
       examState ==
         43 ? "停止人脸识别" : examState == 42 ? "正在测试" : "请初始化" }})</div>
-    <div @click="getRetestFace" v-if="examState == 43 || examState == 42">4、重新识别</div>
+
     <div v-if="needStart">
       <div @click="getOpenOneTestAndStartFace" v-if="examState == 3 || examState == 40">1、开始识别</div>
-      <div @click="getStopFace" v-if="examState == 41">3、停止人脸识别</div>
-      <div @click="getStartOneTest" v-if="examState == 43">5、开始测试</div>
+      <div @click="getStopFace" v-if="examState == 41">2、停止人脸识别</div>
+      <div @click="getStartOneTest" v-if="examState == 43">3、开始测试</div>
     </div>
-    <div @click="getRetest" v-if="examState == 42 || showTestAgain">再测一次</div>
+    <div @click="getRetestFace" v-if="examState == 43 || examState == 42">重新识别</div>
+    <div @click="getAgain" v-if="examState == 42 || showTestAgain">再测一次</div>
     <div @click="confirmExit">退出</div>
     <ChooseStudent ref="chooseStudentRef" @returnData="returnStudent" />
   </div>
@@ -68,13 +69,13 @@ const data = reactive<any>({
   currentTrack: null,//当前跑道
   showTestAgain: false,//再测一次按钮
 });
-const { timerManager, parameter, time, userInfo, examState, resultId, unit, needStart, faceStudentList, currentTrack, showTestAgain } = toRefs(data);
+const { timerManager, parameter, time, userInfo, examState, resultId, faceStudentList, currentTrack, unit, needStart, showTestAgain } = toRefs(data);
 
 /**
  * 接收消息
 */
 const getMessage = (e: any) => {
-  console.log("WS响应:", e)
+  //console.log("WS响应:", e)
   //实时状态
   if (e.cmd === 'exam_status') {
     examState.value = e.data;
@@ -163,30 +164,26 @@ const getMessage = (e: any) => {
 /**
  * 开始识别
 */
-const getOpenOneTestAndStartFace = () => {
-  if (examState.value == 3) {
-    openOneTest();
-  }
-  timerManager.value.startFace = setInterval(() => {
-    if (examState.value == 40) {
-      getClearTimer("startFace");
-      startFace();
-    }
-  }, 250);
+const getOpenOneTestAndStartFace = async () => {
+  await openOneTest();
+  await startFace();
 };
 
 /**
  * 停止人脸识别
 */
-const getStopFace = () => {
-  stopFace();
+const getStopFace = async () => {
+  await stopFace();
   getFaceConfirmOnly();
 };
 
 /**
  * 确定人脸信息
 */
-const getFaceConfirmOnly = () => {
+const getFaceConfirmOnly = (data?: any) => {
+  if (data) {
+    faceStudentList.value = data;
+  }
   faceConfirmOnly(faceStudentList.value, () => {
   });
 };
@@ -195,18 +192,21 @@ const getFaceConfirmOnly = () => {
  * 重新识别
 */
 const getRetestFace = () => {
-  if (needStart.value == false) {
-    //自动流程项目重新识别直接返回3
-    closeOneTest();
-  } else {
-    //手动流程项目重新识别43返回41,42返回3
-    if (examState.value == 43) {
-      cleanData();
-      startFace();
-    } else {
+  proxy?.$modal.confirm("确定重新识别吗?").then(() => {
+    if (needStart.value == false) {
+      //自动流程项目重新识别直接返回3
       closeOneTest();
+    } else {
+      //手动流程项目重新识别43返回41,42返回3
+      if (examState.value == 43) {
+        cleanData();
+        startFace();
+      } else {
+        closeOneTest();
+      }
     }
-  }
+  }).finally(() => {
+  });
 };
 
 /**
@@ -220,8 +220,11 @@ const getStartOneTest = () => {
     proxy?.$modal.msgError("请选择人员!");
     return false;
   }
-  startOneTest(() => {
+  startOneTest(data == null, () => {
+    //显示再测一次按钮
     showTestAgain.value = true;
+    //停止播报;
+    speckCancel()
     //计时项目才开
     if (needStart.value == true) {
       //时间为0的为正计时,大于0的为倒计时
@@ -231,7 +234,6 @@ const getStartOneTest = () => {
         getCounting("-");
       }
     } else {
-      speckCancel()//停止播报;
       speckText("请开始测试");
     }
   })
@@ -240,35 +242,18 @@ const getStartOneTest = () => {
 /**
  * 再测一次
 */
-const getRetest = () => {
+const getAgain = async () => {
   //预存测试人员
   let student = JSON.parse(JSON.stringify(faceStudentList.value));
   //测试中
   if (examState.value == 42) {
-    finishOneTest();
-  }
-  //已测完
-  if (examState.value == 3) {
-    openOneTest();
+    await finishOneTest();
   }
-  //获取最新状态逐步发送命令
-  timerManager.value.retest = setInterval(() => {
-    if (examState.value == 3) {
-      openOneTest();
-    }
-    if (examState.value == 40) {
-      startFace();
-    }
-    if (examState.value == 41) {
-      getStopFace();
-    }
-    if (examState.value == 43) {
-      faceStudentList.value = student;
-      getFaceConfirmOnly();
-      //停止自动执行
-      getClearTimer("retest");
-    }
-  }, 250);
+  //重新走一次流程
+  await openOneTest();
+  await startFace();
+  await stopFace();
+  getFaceConfirmOnly(student);
 };
 
 /**
@@ -504,10 +489,6 @@ onMounted(() => {
   initWs({ parameter: parameter.value, testTime: time.value.testTime }, (data: any) => {
     getMessage(data);
   });
-  //连接状态
-  // getNetWork(() => {
-  //   getExit();
-  // });
   initSpeech();
 })
 

+ 48 - 62
src/views/train/test.vue

@@ -20,14 +20,14 @@
       :
       examState ==
         43 ? "停止人脸识别" : examState == 42 ? "正在测试" : "请初始化" }})</div>
-    <div @click="getChooseStudent" v-if="examState == 41 || (examState == 43 && !faceCheckStu.student_id)">2、选择学生</div>
-    <div @click="getRetestFace" v-if="examState == 43 || examState == 42">4、重新识别</div>
     <div v-if="needStart">
       <div @click="getOpenOneTestAndStartFace" v-if="examState == 3 || examState == 40">1、开始识别</div>
-      <div @click="getStopFace" v-if="examState == 41">3、停止人脸识别</div>
-      <div @click="getStartOneTest" v-if="examState == 43">5、开始测试</div>
+      <div @click="getStopFace" v-if="examState == 41">2、停止人脸识别</div>
+      <div @click="getStartOneTest" v-if="examState == 43">3、开始测试</div>
     </div>
-    <div @click="getRetest" v-if="(examState == 3 && faceCheckStu.student_id) || examState == 42">再测一次</div>
+    <div @click="getChooseStudent" v-if="examState == 41 || (examState == 43 && !faceCheckStu.student_id)">选择学生</div>
+    <div @click="getRetestFace" v-if="examState == 43 || examState == 42">重新识别</div>
+    <div @click="getAgain" v-if="examState == 42 || showTestAgain">再测一次</div>
     <div @click="confirmExit">退出</div>
     <FaceWindow ref="faceWindowRef" :faceCheckStu="faceCheckStu" />
     <ChooseStudent ref="chooseStudentRef" @returnData="returnStudent" />
@@ -58,14 +58,15 @@ const data = reactive<any>({
   unit: "",//单位
   backReason: [],//犯规项
   needStart: false,//是否需要按钮
+  showTestAgain: false,//再测一次按钮
 });
-const { timerManager, parameter, time, userInfo, examState, resultId, currentResultObj, unit, faceCheckStu, backReason, needStart } = toRefs(data);
+const { timerManager, parameter, time, userInfo, examState, resultId, faceCheckStu, currentResultObj, unit, backReason, needStart, showTestAgain } = toRefs(data);
 
 /**
  * 接收消息
 */
 const getMessage = (e: any) => {
-  console.log("WS响应:", e)
+  //console.log("WS响应:", e)
   //实时状态
   if (e.cmd === 'exam_status') {
     examState.value = e.data;
@@ -148,23 +149,16 @@ const getMessage = (e: any) => {
 /**
  * 开始识别
 */
-const getOpenOneTestAndStartFace = () => {
-  if (examState.value == 3) {
-    openOneTest();
-  }
-  timerManager.value.startFace = setInterval(() => {
-    if (examState.value == 40) {
-      getClearTimer("startFace");
-      startFace();
-    }
-  }, 250);
+const getOpenOneTestAndStartFace = async () => {
+  await openOneTest();
+  await startFace();
 };
 
 /**
  * 停止人脸识别
 */
-const getStopFace = () => {
-  stopFace();
+const getStopFace = async () => {
+  await stopFace();
   if (faceCheckStu.value.student_id) {
     getFaceConfirmOnly();
   }
@@ -173,7 +167,10 @@ const getStopFace = () => {
 /**
  * 确定人脸信息
 */
-const getFaceConfirmOnly = () => {
+const getFaceConfirmOnly = (data?: any) => {
+  if (data) {
+    faceCheckStu.value = data;
+  }
   faceConfirmOnly({
     result_id: resultId.value,
     student_id: faceCheckStu.value.student_id,
@@ -191,18 +188,21 @@ const getFaceConfirmOnly = () => {
  * 重新识别
 */
 const getRetestFace = () => {
-  if (needStart.value == false) {
-    //自动流程项目重新识别直接返回3
-    closeOneTest();
-  } else {
-    //手动流程项目重新识别43返回41,42返回3
-    if (examState.value == 43) {
-      cleanData();
-      startFace();
-    } else {
+  proxy?.$modal.confirm("确定重新识别吗?").then(() => {
+    if (needStart.value == false) {
+      //自动流程项目重新识别直接返回3
       closeOneTest();
+    } else {
+      //手动流程项目重新识别43返回41,42返回3
+      if (examState.value == 43) {
+        cleanData();
+        startFace();
+      } else {
+        closeOneTest();
+      }
     }
-  }
+  }).finally(() => {
+  });
 };
 
 /**
@@ -213,7 +213,11 @@ const getStartOneTest = () => {
     proxy?.$modal.msgError("请选择人员!");
     return false;
   }
-  startOneTest(() => {
+  startOneTest(data == null, () => {
+    //显示再测一次按钮
+    showTestAgain.value = true;
+    //停止播报;
+    speckCancel()
     //计时项目才开
     if (needStart.value == true) {
       //时间为0的为正计时,大于0的为倒计时
@@ -223,7 +227,6 @@ const getStartOneTest = () => {
         getCounting("-");
       }
     } else {
-      speckCancel()//停止播报;
       speckText(faceCheckStu.value.name + ",请开始测试");
     }
   })
@@ -232,35 +235,18 @@ const getStartOneTest = () => {
 /**
  * 再测一次
 */
-const getRetest = () => {
+const getAgain = async () => {
   //预存测试人员
   let student = JSON.parse(JSON.stringify(faceCheckStu.value));
   //测试中
   if (examState.value == 42) {
-    finishOneTest();
-  }
-  //已测完
-  if (examState.value == 3) {
-    openOneTest();
+    await finishOneTest();
   }
-  //获取最新状态逐步发送命令
-  timerManager.value.retest = setInterval(() => {
-    if (examState.value == 3) {
-      openOneTest();
-    }
-    if (examState.value == 40) {
-      startFace();
-    }
-    if (examState.value == 41) {
-      getStopFace();
-    }
-    if (examState.value == 43) {
-      faceCheckStu.value = student;
-      getFaceConfirmOnly();
-      //停止自动执行
-      getClearTimer("retest");
-    }
-  }, 250);
+  //重新走一次流程
+  await openOneTest();
+  await startFace();
+  await stopFace();
+  getFaceConfirmOnly(student);
 };
 
 /**
@@ -306,9 +292,12 @@ const getClearTimer = (data?: any) => {
  * 选择学生
 */
 const getChooseStudent = () => {
-  if (examState.value == 41 || (examState.value == 43 && !faceCheckStu.value.student_id)) {
+  if (examState.value == 41) {
     chooseStudentRef.value.open();
   }
+  if (examState.value == 43) {
+    getRetestFace();
+  }
 };
 
 /**
@@ -325,6 +314,7 @@ const returnStudent = (data: any) => {
 */
 const cleanData = () => {
   time.value.countdownNum = time.value.testTime;
+  showTestAgain.value = false;
   faceCheckStu.value = {};
   currentResultObj.value = {};
   backReason.value = [];
@@ -410,7 +400,7 @@ const getFaceWindow = (data: boolean) => {
  * 成绩
 */
 const getAchievement = (data: any) => {
-  console.log("成绩", data);
+  //console.log("成绩", data);
   let dic: any = dataDictionary;
   let type = parameter.value.project;
   let count =
@@ -530,10 +520,6 @@ onMounted(() => {
   initWs({ parameter: parameter.value, testTime: time.value.testTime }, (data: any) => {
     getMessage(data);
   });
-  //连接状态
-  // getNetWork(() => {
-  //   getExit();
-  // });
   initSpeech();
 })