林旭祥 пре 11 месеци
родитељ
комит
e481c0cc59

+ 11 - 0
package-lock.json

@@ -14,6 +14,7 @@
         "axios": "^1.7.0",
         "echarts": "^5.5.0",
         "element-plus": "^2.7.3",
+        "gsap": "^3.12.5",
         "pinia": "^2.1.7",
         "socket.io-client": "^2.5.0",
         "vue": "^3.4.21",
@@ -3628,6 +3629,11 @@
       "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==",
       "dev": true
     },
+    "node_modules/gsap": {
+      "version": "3.12.5",
+      "resolved": "https://registry.npmmirror.com/gsap/-/gsap-3.12.5.tgz",
+      "integrity": "sha512-srBfnk4n+Oe/ZnMIOXt3gT605BX9x5+rh/prT2F1SsNJsU1XuMiP0E2aptW481OnonOGACZWBqseH5Z7csHxhQ=="
+    },
     "node_modules/has-ansi": {
       "version": "2.0.0",
       "resolved": "https://registry.npmmirror.com/has-ansi/-/has-ansi-2.0.0.tgz",
@@ -10126,6 +10132,11 @@
       "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==",
       "dev": true
     },
+    "gsap": {
+      "version": "3.12.5",
+      "resolved": "https://registry.npmmirror.com/gsap/-/gsap-3.12.5.tgz",
+      "integrity": "sha512-srBfnk4n+Oe/ZnMIOXt3gT605BX9x5+rh/prT2F1SsNJsU1XuMiP0E2aptW481OnonOGACZWBqseH5Z7csHxhQ=="
+    },
     "has-ansi": {
       "version": "2.0.0",
       "resolved": "https://registry.npmmirror.com/has-ansi/-/has-ansi-2.0.0.tgz",

+ 1 - 0
package.json

@@ -17,6 +17,7 @@
     "axios": "^1.7.0",
     "echarts": "^5.5.0",
     "element-plus": "^2.7.3",
+    "gsap": "^3.12.5",
     "pinia": "^2.1.7",
     "socket.io-client": "^2.5.0",
     "vue": "^3.4.21",

BIN
src/assets/images/close.png


BIN
src/assets/images/noImg.png


+ 0 - 0
src/assets/index.scss → src/assets/styles/index.scss


+ 0 - 0
src/assets/transition.scss → src/assets/styles/transition.scss


+ 4 - 6
src/components/ChooseStudent/index.vue

@@ -1,11 +1,9 @@
 <template>
   <div>
-    <transition :enter-active-class="proxy?.animate.searchAnimate2.enter"
-      :leave-active-class="proxy?.animate.searchAnimate2.leave">
+    <transition :enter-active-class="proxy?.animate.mask.enter" :leave-active-class="proxy?.animate.mask.leave">
       <div class="mask" v-if="optionWindow.show"></div>
     </transition>
-    <transition :enter-active-class="proxy?.animate.searchAnimate.enter"
-      :leave-active-class="proxy?.animate.searchAnimate.leave">
+    <transition :enter-active-class="proxy?.animate.dialog.enter" :leave-active-class="proxy?.animate.dialog.leave">
       <div class="optionWindow" v-if="optionWindow.show">
         <div class="box">
           <div class="top">
@@ -55,7 +53,7 @@
 import useAppStore from '@/store/modules/app';
 import dataDictionary from "@/utils/dataDictionary"
 const { proxy } = getCurrentInstance() as any;
-const emit = defineEmits(['back']);
+const emit = defineEmits(['returnData']);
 
 //筛选班别
 const classData = computed(() => {
@@ -176,8 +174,8 @@ const confirm = () => {
     proxy?.$modal.msgError(`请选择!`);
     return false;
   }
-  emit('back', selectValue.value)
   close();
+  emit('returnData', selectValue.value);
 };
 
 onMounted(() => {

+ 169 - 0
src/components/FaceWindow/index.vue

@@ -0,0 +1,169 @@
+<template>
+
+  <div>
+    <transition :enter-active-class="proxy?.animate.mask.enter" :leave-active-class="proxy?.animate.mask.leave">
+      <div class="mask" v-show="faceState"></div>
+    </transition>
+    <transition :enter-active-class="proxy?.animate.face.enter"
+      :leave-active-class="faceCheckStu.student_id ? proxy?.animate.face.leave : proxy?.animate.face.leave2">
+      <div class="confirmDiaBg" v-show="faceState">
+        <div class="confirmDiaWindow">
+          <div class="confirmDiaWindow-title">
+            提示
+            <div class="cancelBtn" @click="close"></div>
+          </div>
+          <div class="confirmDiaWindow-prompt">
+            {{
+      faceCheckStu.student_id
+        ? "识别成功"
+        : "请进行人脸识别"
+    }}
+          </div>
+          <div class="confirmDiaWindow-con">
+            <div class="pic">
+              <div class="shine">
+                <img mode="aspectFill" class="image" src="../../static/images/common/shine.png">
+                </img>
+              </div>
+              <el-avatar :src="faceCheckStu.logo_url || faceCheckStu.face_pic" />
+            </div>
+          </div>
+        </div>
+      </div>
+    </transition>
+  </div>
+
+</template>
+<script setup lang="ts">
+
+const { proxy } = getCurrentInstance() as any;
+
+
+//父值
+const props = defineProps({
+  faceCheckStu: {
+    type: Object,
+    default: {}
+  },
+});
+
+
+const data = reactive<any>({
+  faceState: false,//人脸识别窗口状态
+});
+
+const { faceState } = toRefs(data);
+
+//打开
+const open = (data: any) => {
+  faceState.value = true;
+};
+
+//关闭
+const close = () => {
+  faceState.value = false;
+};
+
+onMounted(() => {
+})
+
+//暴露给父组件用
+defineExpose({
+  faceState,
+  open,
+  close
+})
+</script>
+<style lang="scss" scoped>
+.mask {
+  position: fixed;
+  height: 100vh;
+  width: 100vw;
+  top: 0;
+  left: 0;
+  background: rgba(0, 0, 0, 0.3);
+  z-index: 998;
+}
+
+.confirmDiaBg {
+  width: 100%;
+  height: 100vh;
+  position: fixed;
+  left: 0;
+  top: 0;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  z-index: 999;
+
+  .confirmDiaWindow {
+    width: 90%;
+    position: fixed;
+    box-sizing: border-box;
+    background: #ffffff;
+    border-radius: 30rpx;
+    overflow: hidden;
+
+    .confirmDiaWindow-title {
+      width: 100%;
+      height: 12vh;
+      line-height: 12vh;
+      color: #009699;
+      font-size: 5vh;
+      text-align: center;
+      position: relative;
+      margin-bottom: 2vh;
+
+      .cancelBtn {
+        width: 12vh;
+        height: 12vh;
+        background: url('@/assets/images/close.png') center center no-repeat;
+        background-size: 50% 50%;
+        position: absolute;
+        right: 0;
+        top: 0;
+        transform: rotate(45deg);
+      }
+    }
+
+    .confirmDiaWindow-prompt {
+      text-align: center;
+      font-size: 4vh;
+      line-height: 1;
+    }
+
+    .confirmDiaWindow-con {
+      padding: 0;
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      padding: 5vh 0 10vh 0;
+
+      .pic {
+        width: 19vh;
+        height: 19vh;
+        overflow: hidden;
+        border-radius: 50%;
+        border: 4rpx solid #ffffff;
+        position: relative;
+
+        .shine {
+          position: absolute;
+          left: 0;
+          top: -19vh;
+          width: 19vh;
+          height: 19vh;
+          animation: shineani 3s infinite;
+          -webkit-animation: shineani 3s infinite;
+          z-index: 1;
+        }
+
+        .image {
+          width: 100%;
+          height: 100%;
+        }
+      }
+    }
+  }
+}
+</style>

+ 3 - 4
src/components/OptionWindow/index.vue

@@ -1,11 +1,10 @@
 <template>
   <div>
-    <transition :enter-active-class="proxy?.animate.searchAnimate2.enter"
-      :leave-active-class="proxy?.animate.searchAnimate2.leave">
+    <transition :enter-active-class="proxy?.animate.mask.enter"
+      :leave-active-class="proxy?.animate.mask.leave">
       <div class="mask" v-if="optionWindow.show"></div>
     </transition>
-    <transition :enter-active-class="proxy?.animate.searchAnimate.enter"
-      :leave-active-class="proxy?.animate.searchAnimate.leave">
+    <transition :enter-active-class="proxy?.animate.dialog.enter" :leave-active-class="proxy?.animate.dialog.leave">
       <div class="optionWindow" v-if="optionWindow.show">
         <div class="box">
           <div class="top">

+ 1 - 1
src/main.ts

@@ -1,5 +1,5 @@
 import { createApp } from 'vue';
-import '@/assets/index.scss';
+import '@/assets/styles/index.scss';
 import App from './App.vue';
 import router from './router';
 import store from './store';

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

@@ -18,6 +18,7 @@ declare module 'vue' {
     ElSwitch: typeof import('element-plus/es')['ElSwitch']
     ElTable: typeof import('element-plus/es')['ElTable']
     ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
+    FaceWindow: typeof import('./../components/FaceWindow/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']

+ 15 - 8
src/utils/animate.ts

@@ -1,18 +1,25 @@
 //预览地址 https://animate.style/
-// 前缀
-const animatePrefix = 'animate__animated ';
 
-const searchAnimate = {
+const animatePrefix = 'animate__animated '; //前缀
+
+const mask = {
+  enter: animatePrefix + 'animate__fadeIn',
+  leave: animatePrefix + 'animate__fadeOut'
+};
+
+const dialog = {
   enter: animatePrefix + 'animate__fadeInDown',
   leave: animatePrefix + 'animate__fadeOutUp'
 };
 
-const searchAnimate2 = {
-  enter: animatePrefix + 'animate__fadeIn',
-  leave: animatePrefix + 'animate__fadeOut'
+const face = {
+  enter: animatePrefix + 'animate__zoomInLeft',
+  leave: animatePrefix + 'animate__zoomOutLeft',
+  leave2: animatePrefix + 'animate__fadeOut'
 };
 
 export default {
-  searchAnimate,
-  searchAnimate2
+  mask,
+  dialog,
+  face
 };

+ 8 - 4
src/views/train/index.vue

@@ -1,11 +1,15 @@
 <template>
   <div>
     <div class="menu">
-      <div v-for="(item, index) in projectList" :key="index" class="li" hover-class="li-hover" @click="getOption(item)">
-        <div class="name">
-          <div>{{ item.name }}</div>
+      <transition-group :enter-active-class="proxy?.animate.dialog.enter"
+        :leave-active-class="proxy?.animate.dialog.leave">
+        <div v-for="(item, index) in projectList" :key="index" :data-index="index" class="li" hover-class="li-hover"
+          @click="getOption(item)">
+          <div class="name">
+            <div>{{ item.name }}</div>
+          </div>
         </div>
-      </div>
+      </transition-group>
     </div>
     <OptionWindow ref="optionWindowRef" :projectList="projectList" />
   </div>

+ 69 - 24
src/views/train/test.vue

@@ -1,21 +1,25 @@
 <template>
   <div>
-    <div>{{
+    <div v-if="needStart">{{
       countdownNumFormat
     }}</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>中断/犯规:{{ currentResultObj.back_num }}</div>
+    <div>
+      <div v-if="['jumprope'].includes(parameter.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 ? "停止人脸识别"
+    <div>当前状态:({{ examState == 3 ? "初始化完成" : examState == 40 ? "创建测试" : examState == 41 ? "开始人脸识别"
       :
       examState ==
-        43 ? "开始测试" : examState == 42 ? "正在测试" : "请初始化" }})</div>
+        43 ? "停止人脸识别" : examState == 42 ? "正在测试" : "请初始化" }})</div>
     <!-- <div @click="getProcess">走一套流程</div> -->
 
     <div v-if="needStart">
@@ -27,7 +31,9 @@
     <div @click="getRetestFace" v-if="examState == 43 || examState == 42">4、重新识别</div>
     <div @click="getRetest" v-if="(examState == 3 && faceCheckStu.student_id) || examState == 42">再测一次</div>
     <div @click="confirmExit">退出</div>
-    <ChooseStudent ref="chooseStudentRef" @back="backStudent" />
+
+    <FaceWindow ref="faceWindowRef" :faceCheckStu="faceCheckStu" />
+    <ChooseStudent ref="chooseStudentRef" @returnData="backStudent" />
   </div>
 </template>
 
@@ -37,6 +43,7 @@ import dataDictionary from "@/utils/dataDictionary"
 const { proxy } = getCurrentInstance() as any;
 const router = useRouter();
 const route = useRoute();
+const faceWindowRef = ref();
 const chooseStudentRef = ref();
 const data = reactive<any>({
   timerManager: {},//计时器管理
@@ -95,23 +102,13 @@ const getMessage = (e: any) => {
   if (e.cmd === 'set_exam_state') {
     examState.value = e.data;
     if (e.data === 3) {
-      //停止计时
-      getStopCountdown();
-      //不需要按钮的自动进入下一步
-      if (needStart.value == false) {
-        setTimeout(() => {
-          //再加一个判断以免和再测一次冲突
-          if (examState.value == 3) {
-            getOpenOneTestAndStartFace();
-          }
-        }, 6000)
-      }
+      initProject();
     }
     if (e.data === 40) {
       cleanData();
     }
     if (e.data == 41) {
-
+      getFaceWindow();
     }
     if (e.data == 43) {
 
@@ -127,13 +124,13 @@ const getMessage = (e: any) => {
   //人脸识别状态
   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];
-      console.log("成绩", data);
       getAchievement(data)
     }
   }
@@ -199,6 +196,7 @@ const getFaceConfirmOnly = () => {
     student_id: faceCheckStu.value.student_id,
     gender: faceCheckStu.value.gender
   }, () => {
+    faceWindowRef.value.close();
     //不需要按钮的自动进入下一步
     if (needStart.value == false) {
       getStartOneTest();
@@ -211,11 +209,16 @@ const getFaceConfirmOnly = () => {
 */
 const getRetestFace = () => {
   if (needStart.value == false) {
-    //不需要手动操作的项目重新识别直接返回3
+    //自动流程项目重新识别直接返回3
     closeOneTest();
   } else {
-    //需要手动操作的项目重新识别返回41
-    startFace();
+    //手动流程项目重新识别43返回41,42返回3
+    if (examState.value == 43) {
+      cleanData();
+      startFace();
+    } else {
+      closeOneTest();
+    }
   }
 };
 
@@ -228,7 +231,10 @@ const getStartOneTest = () => {
     return false;
   }
   startOneTest(() => {
-    getCountdown();
+    //计时项目才开
+    if (needStart.value == true) {
+      getCountdown();
+    }
   })
 };
 
@@ -312,11 +318,12 @@ const getChooseStudent = () => {
 */
 const backStudent = (data: any) => {
   faceCheckStu.value = data;
+  faceWindowRef.value.open();
   getStopFace();
 };
 
 /**
- * 返回被选学生
+ * 清除历史记录
 */
 const cleanData = () => {
   countdownNum.value = testTime.value;
@@ -325,6 +332,29 @@ const cleanData = () => {
   backReason.value = [];
 };
 
+/**
+ * 自动初始化项目
+*/
+const initProject = () => {
+  //停止计时
+  getStopCountdown();
+  //自动项目定时进入下一步
+  if (needStart.value == false) {
+    let time = 0;
+    if (!faceCheckStu.value.student_id) {
+      time = 1000;
+    } else {
+      time = 6000;
+    }
+    setTimeout(() => {
+      //再加一个判断以免和再测一次冲突
+      if (examState.value == 3) {
+        getOpenOneTestAndStartFace();
+      }
+    }, time)
+  }
+};
+
 /**
  * 时间转换
 */
@@ -353,11 +383,25 @@ const getStopCountdown = () => {
   timerManager.value.countdownTimer = null;
 };
 
+/**
+ * 停止人脸识别
+*/
+const getFaceWindow = () => {
+  faceWindowRef.value.open();
+  //然后定时自动关闭
+  setTimeout(() => {
+    if (examState.value == 41 && faceWindowRef.value.faceState == true) {
+      faceWindowRef.value.close();
+    }
+  }, 3000)
+
+};
 
 /**
  * 成绩
 */
 const getAchievement = (data: any) => {
+  console.log("成绩", data);
   let dic: any = dataDictionary;
   let type = parameter.value.project;
   let count =
@@ -452,6 +496,7 @@ onMounted(() => {
   if (parameter.value.time) {
     testTime.value = parameter.value.time
   }
+  countdownNum.value = testTime.value;
   let myInfo: any = localStorage.getItem("userInfo");
   userInfo.value = JSON.parse(myInfo);
   let dic: any = dataDictionary;
@@ -471,4 +516,4 @@ onUnmounted(() => {
 })
 </script>
 
-<style scoped></style>
+<style scoped lang="scss"></style>