林旭祥 9 ヶ月 前
コミット
82cf81c8fd

+ 1 - 0
package-lock.json

@@ -12,6 +12,7 @@
         "@vueuse/core": "9.5.0",
         "animate.css": "^4.1.1",
         "axios": "^1.7.0",
+        "dayjs": "^1.11.11",
         "echarts": "^5.5.0",
         "element-plus": "^2.7.3",
         "gsap": "^3.12.5",

+ 1 - 0
package.json

@@ -15,6 +15,7 @@
     "@vueuse/core": "9.5.0",
     "animate.css": "^4.1.1",
     "axios": "^1.7.0",
+    "dayjs": "^1.11.11",
     "echarts": "^5.5.0",
     "element-plus": "^2.7.3",
     "gsap": "^3.12.5",

+ 9 - 0
src/api/module/common.ts

@@ -56,5 +56,14 @@ export default {
       method: 'get',
       data: data
     });
+  },
+
+  //获取测试数据
+  reportList: (data: any) => {
+    return req({
+      url: '/report/index',
+      method: 'get',
+      data: data
+    });
   }
 };

BIN
src/assets/images/common/screen.png


BIN
src/assets/images/common/speck.png


BIN
src/assets/images/common/speck1.png


BIN
src/assets/images/common/speck2.png


+ 0 - 0
src/assets/images/erweima.png → src/assets/images/login/erweima.png


+ 0 - 0
src/assets/images/login.png → src/assets/images/login/login.png


BIN
src/assets/images/test/ai.png


BIN
src/assets/images/test/profilePicture.png


BIN
src/assets/images/test/ready.png


BIN
src/assets/images/test/tips.png


BIN
src/assets/images/test/tips/jump.png


BIN
src/assets/images/test/tips/jumprope.png


BIN
src/assets/images/test/yuan.png


+ 11 - 10
src/components/FaceWindow/index.vue

@@ -1,9 +1,9 @@
 <template>
 
   <div>
-    <transition :enter-active-class="proxy?.animate.mask.enter" :leave-active-class="proxy?.animate.mask.leave">
+    <!-- <transition :enter-active-class="proxy?.animate.mask.enter" :leave-active-class="proxy?.animate.mask.leave">
       <div class="mask" v-show="faceState"></div>
-    </transition>
+    </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">
@@ -166,13 +166,14 @@ defineExpose({
     }
   }
 }
+
 @keyframes shineani {
-		0% {
-			top: -19vh;
-		}
-
-		100% {
-			top: 19vh;
-		}
-	}
+  0% {
+    top: -19vh;
+  }
+
+  100% {
+    top: 19vh;
+  }
+}
 </style>

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

@@ -357,7 +357,7 @@ defineExpose({
           display: flex;
           flex-wrap: wrap;
           justify-content: center;
-          padding: 0 20px;
+          padding: 0 20px 0 30px;
           box-sizing: border-box;
           margin-bottom: 5px;
 
@@ -439,6 +439,7 @@ defineExpose({
 
           .select {
             border-radius: 1.8rem;
+            box-shadow: inset 0px 1px 0px 2px rgba(255, 255, 255, 0.5577);
             background: radial-gradient(50% 181% at 163% 0%, #35FFC6 0%, #00FFE8 100%);
           }
         }

+ 24 - 0
src/utils/index.ts

@@ -166,6 +166,30 @@ let utils = {
           .slice(0, -1);
       }
     }
+  },
+
+  getDate() {
+    let now = new Date();
+    let year = now.getFullYear();
+    let month = now.getMonth();
+    let day = now.getDate();
+    let hours = now.getHours();
+    let minutes = now.getMinutes() < 10 ? '0' + now.getMinutes() : now.getMinutes();
+    let seconds = now.getSeconds() < 10 ? '0' + now.getSeconds() : now.getSeconds();
+    month = month + 1;
+    //let week;
+    //let arr_week = new Array("星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六");
+    //week = arr_week[now.getDay()];
+    let time = year + '-' + month + '-' + day + ' ' + ' ' + hours + ':' + minutes + ':' + seconds;
+    return time;
+  },
+
+  // 全屏
+  fullScreen() {
+    if (document.fullscreenElement) {
+      document.exitFullscreen();
+    }
+    document.body.requestFullscreen();
   }
 };
 export default utils;

+ 2 - 2
src/views/login/index.vue

@@ -5,7 +5,7 @@
     <transition :enter-active-class="proxy?.animate.error.enter">
       <div class="login-content" :key="key">
         <div class="login-content-center">
-          <div class="login-title"> <img src="@/assets/images/login.png" /></div>
+          <div class="login-title"> <img src="@/assets/images/login/login.png" /></div>
           <div class="login-input-box">
             <div class="login-item">
               <input class="login-input login-input-username" type="text" placeholder="请输入帐号"
@@ -20,7 +20,7 @@
         </div>
       </div>
     </transition>
-    <div class="erweima"> <img src="@/assets/images/erweima.png" />
+    <div class="erweima"> <img src="@/assets/images/login/erweima.png" />
       <span>扫码登录</span>
     </div>
   </div>

+ 420 - 36
src/views/train/test.vue

@@ -1,34 +1,88 @@
 <template>
-  <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>
-      <div v-if="['jumprope'].includes(parameter.project)">中断</div>
-      <div v-else>犯规</div>
-      <div>{{ currentResultObj.back_num }}</div>
+  <div class="test">
+    <div class="close" @click="confirmExit"></div>
+
+    <div class="toolList">
+      <div class="li">{{ date }}</div>
+      <div class="li btn speck" @click="speckCancel"></div>
+      <div class="li btn screen" @click="getFullScreen"></div>
     </div>
-    <div v-if="backReason.length">
-      <div>违规项</div>
-      <div v-for="(item, index) in backReason" :key="index">{{ item }}</div>
+    <div class="logo"> <img src="@/assets/images/logo.png">
+      </img>
+      <div class="title"><i></i><span>{{ dic.project[parameter.project] || "" }}</span></div>
+
     </div>
-    <div>当前状态:({{ examState == 3 ? "初始化完成" : examState == 40 ? "创建测试" : examState == 41 ? "正在人脸识别"
-      :
-      examState ==
-        43 ? "停止人脸识别" : examState == 42 ? "正在测试" : "请初始化" }})</div>
-    <div v-if="needStart">
-      <div @click="getOpenOneTestAndStartFace" v-if="examState == 3 || examState == 40">1、开始识别</div>
-      <div @click="getStopFace" v-if="examState == 41">2、停止人脸识别</div>
-      <div @click="getStartOneTest" v-if="examState == 43">3、开始测试</div>
+    <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.logo_url || faceCheckStu.face_pic" /></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">
+            <div v-if="needStart && [42, 43].includes(examState)">{{
+      countdownNumFormat
+    }}</div>
+            <div class="tips" v-if="examState == 41">
+              <img v-if="parameter.gesture" src="@/assets/images/test/ready.png" />
+            </div>
+            <div v-if="examState == 43">
+              <div @click="getRetestFace" v-if="examState == 43 || examState == 42">重新识别</div>
+            </div>
+            <div v-if="faceCheckStu.student_id">
+              <div>成绩:{{ currentResultObj.count }} {{ unit }}</div>
+              <div>得分:{{ currentResultObj.score }}</div>
+              <div>
+                <div v-if="['jumprope'].includes(parameter.project)">中断</div>
+                <div v-else>犯规</div>
+                <div>{{ currentResultObj.back_num }}</div>
+              </div>
+            </div>
+            <div v-if="examState == 42 && backReason.length">
+              <div>违规项</div>
+              <div v-for="(item, index) in backReason" :key="index">{{ item }}</div>
+            </div>
+            <div v-if="needStart">
+              <div @click="getOpenOneTestAndStartFace" v-if="examState == 3 || examState == 40">开始识别</div>
+              <div @click="getStopFace" v-if="examState == 41">停止人脸识别</div>
+              <div @click="getStartOneTest" v-if="examState == 43">开始测试</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="'/src/assets/images/test/tips/' + parameter.project + '.png'"></div>
+          </div>
+          <div class="bottom-right">
+            {{ currentResultObj?._comments?.join() }}
+          </div>
+        </div>
+      </div>
+      <div class="main-right">
+        <ul>
+          <li v-for="(item, index) in  reportList " :key="index">
+            <el-avatar :src="item.face_pic || item.logo_url" />
+            {{ item.student_name }}
+            {{ item.result }}
+          </li>
+        </ul>
+        <div class="erweima"> <img src="@/assets/images/login/erweima.png" />
+          <span>扫码查看详情</span>
+        </div>
+      </div>
     </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" />
   </div>
@@ -37,12 +91,15 @@
 <script setup name="TrainTest" 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'
+import dayjs from 'dayjs'
 import dataDictionary from "@/utils/dataDictionary"
 const { proxy } = getCurrentInstance() as any;
 const router = useRouter();
 const route = useRoute();
 const faceWindowRef = ref();
 const chooseStudentRef = ref();
+const myInfo: any = localStorage.getItem("userInfo");
+const dic: any = dataDictionary;
 const data = reactive<any>({
   timerManager: {},//计时器管理
   parameter: {},//参数
@@ -59,8 +116,10 @@ const data = reactive<any>({
   backReason: [],//犯规项
   needStart: false,//是否需要按钮
   showTestAgain: false,//再测一次按钮
+  reportList: [],//测试列表
+  date: "",//当前时间
 });
-const { timerManager, parameter, time, userInfo, examState, resultId, faceCheckStu, currentResultObj, unit, backReason, needStart, showTestAgain } = toRefs(data);
+const { timerManager, parameter, time, userInfo, examState, resultId, faceCheckStu, currentResultObj, unit, backReason, needStart, showTestAgain, reportList, date } = toRefs(data);
 
 /**
  * 接收消息
@@ -160,9 +219,10 @@ const getOpenOneTestAndStartFace = async () => {
  * 停止人脸识别
 */
 const getStopFace = async () => {
-  if (examState.value != 41) {
-    return false;
-  }
+  // 旧版识别成功直接43了这里先屏蔽
+  // if (examState.value != 41) {
+  //   return false;
+  // }
   await stopFace();
   if (faceCheckStu.value.student_id) {
     getFaceConfirmOnly();
@@ -306,6 +366,10 @@ const getClearTimer = (data?: any) => {
 const getChooseStudent = () => {
   if (examState.value == 41) {
     chooseStudentRef.value.open();
+    //然后定时自动关闭
+    setTimeout(() => {
+      faceWindowRef.value.close();
+    }, 3000)
   }
   if (examState.value == 43) {
     getRetestFace();
@@ -318,6 +382,10 @@ const getChooseStudent = () => {
 const returnStudent = (data: any) => {
   faceCheckStu.value = data;
   faceWindowRef.value.open();
+  //然后定时自动关闭
+  setTimeout(() => {
+    faceWindowRef.value.close();
+  }, 1000)
   getStopFace();
 };
 
@@ -395,7 +463,7 @@ const getFaceWindow = (data: boolean) => {
     //然后定时自动关闭
     setTimeout(() => {
       if (examState.value == 41 && faceWindowRef.value.faceState == true) {
-        faceWindowRef.value?.close();
+        faceWindowRef.value.close();
       }
     }, 3000)
   }
@@ -413,7 +481,6 @@ const getFaceWindow = (data: boolean) => {
 */
 const getAchievement = (data: any) => {
   //console.log("成绩", data);
-  let dic: any = dataDictionary;
   let type = parameter.value.project;
   let count =
     data?.[dic.typeResultKey[type]]?.toFixed(0);
@@ -502,11 +569,47 @@ const getAchievement = (data: any) => {
       return false;
     }
     speckText(faceCheckStu?.value.name + "成绩为" + (chineseNumber(count) || 0) + unit.value + ",请下一位准备!" || "");
+    getReportList();
   }
 };
 
 
+/**
+ * 当天成绩列表
+*/
+const getReportList = () => {
+  let params: any = {
+    start_date: dayjs().format("YYYY-MM-DD"),
+    end_date: dayjs().format("YYYY-MM-DD"),
+    exam_name: parameter.value.project,
+
+    page: 1,
+    per_page: 9000
+  };
+  proxy?.$http.common.reportList(params).then((res: any) => {
+    if (res.data.length > 0) {
+      reportList.value = res.data;
+    }
+  });
+};
+
+/**
+ * 更新时间
+*/
+const setDate = () => {
+  date.value = proxy?.$utils.getDate();
+  setTimeout(setDate, 1000);
+};
+
+/**
+ * 更新时间
+*/
+const getFullScreen = () => {
+  date.value = proxy?.$utils.fullScreen();
+};
+
 onMounted(() => {
+  setDate();
   parameter.value = route.query;
   let project = parameter.value.project;
   let area = parameter.value.area;
@@ -515,9 +618,7 @@ onMounted(() => {
     time.value.testTime = parameter.value.time
   }
   time.value.countdownNum = time.value.testTime;
-  let myInfo: any = localStorage.getItem("userInfo");
   userInfo.value = JSON.parse(myInfo);
-  let dic: any = dataDictionary;
   unit.value = dic.unit[project];
   if (parameter.value.gesture == 'true') {
     parameter.value.gesture = true
@@ -533,6 +634,7 @@ onMounted(() => {
     getMessage(data);
   });
   initSpeech();
+  getReportList();
 })
 
 onUnmounted(() => {
@@ -540,4 +642,286 @@ onUnmounted(() => {
 })
 </script>
 
-<style scoped lang="scss"></style>
+<style lang="scss" scoped>
+$topPadding: 5.19rem;
+$waiPadding: 6.51rem;
+
+.logo {
+
+
+  position: absolute;
+  left: $waiPadding;
+  top: $topPadding;
+  display: flex;
+  align-items: center;
+
+  img {
+    width: 14.563rem;
+  }
+
+  .title {
+    width: auto;
+    padding: 0 1.2rem 0 0.7rem;
+    height: 3rem;
+    line-height: 3rem;
+    border-radius: 1.5rem;
+    color: #ffffff;
+    font-size: 1.7rem;
+    margin-left: 1rem;
+    background: linear-gradient(180deg, #FFB200 0%, #ED7905 72%);
+    box-shadow: inset 0px 1px 0px 1px rgba(255, 255, 255, 0.5);
+    display: flex;
+    align-items: center;
+
+    i {
+      width: 2.5rem;
+      height: 2.1rem;
+      margin-right: 0.8rem;
+      background-image: url("@/assets/images/test/ai.png");
+      background-position: center;
+      background-repeat: no-repeat;
+      background-size: 100% 100%;
+    }
+  }
+}
+
+.close {
+  width: 3.2rem;
+  height: 3.2rem;
+  position: absolute;
+  right: calc($waiPadding - 3.2rem);
+  top: calc($topPadding / 2 - 3.2rem/4);
+  box-sizing: border-box;
+  border: 1px solid #979797;
+  background-image: url("@/assets/images/common/close.png");
+  background-position: center;
+  background-repeat: no-repeat;
+  background-size: 45% 45%;
+  background-color: rgba(216, 216, 216, 0.8);
+  border-radius: 50%;
+  transition: all 0.5s;
+  cursor: pointer;
+
+  &:hover {
+    transform: rotate(180deg);
+    background-color: rgba(216, 216, 216, 1);
+  }
+}
+
+.toolList {
+
+  position: absolute;
+  right: $waiPadding;
+  top: calc($topPadding / 2);
+  color: #00FFE8;
+  font-size: 1.1rem;
+  display: flex;
+
+  .li {
+    margin-right: 3rem;
+
+
+
+  }
+
+  .btn {
+    width: 1.1rem;
+    height: 1.1rem;
+
+    display: block;
+    cursor: pointer;
+  }
+
+  .speck {
+    background-image: url("@/assets/images/common/speck.png");
+    background-position: center;
+    background-repeat: no-repeat;
+    background-size: 100% auto;
+  }
+
+  .screen {
+    background-image: url("@/assets/images/common/screen.png");
+    background-position: center;
+    background-repeat: no-repeat;
+    background-size: 100% auto;
+  }
+}
+
+.main {
+  width: calc(100% - ($waiPadding * 2));
+  height: 72vh;
+  padding-top: 10rem;
+  margin: 0 auto;
+  display: flex;
+  justify-content: space-between;
+
+  .main-left {
+    width: 71.5%;
+
+    .main-left-top {
+      display: flex;
+      justify-content: space-between;
+      height: 43.5vh;
+      margin-bottom: 3vh;
+      position: relative;
+
+      .top-left {
+        width: 37.5%;
+        height: 100%;
+        border-radius: 29px;
+        opacity: 1;
+        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;
+
+        .pic {
+          width: 22.3vh;
+          height: 22.3vh;
+          margin-bottom: 2vh;
+          border-radius: 50%;
+          display: flex;
+          justify-content: center;
+          align-items: center;
+          overflow: hidden;
+
+          img {
+            height: 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: 29px;
+        opacity: 1;
+        background: #ffffff;
+        box-sizing: border-box;
+        border: 0.55rem solid #13ED84;
+
+        .tips {
+          display: flex;
+          justify-content: center;
+          align-items: center;
+          height: 100%;
+
+          img {
+            max-height: 80%;
+            max-height: 80%;
+          }
+        }
+      }
+
+      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: 29.5vh;
+
+      .bottom-left {
+        width: 60%;
+        padding-right: 1rem;
+        display: flex;
+        flex-wrap: wrap;
+
+        .tips {
+          img {
+            height: 2.8vh;
+          }
+        }
+
+        .pic {
+          text-align: center;
+          width: 100%;
+          height: calc(100% - 2.8vh);
+          display: flex;
+          justify-content: center;
+
+          img {
+            max-width: 100%;
+            max-height: 100%;
+          }
+
+        }
+      }
+
+      .bottom-right {
+        width: 40%;
+        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: 29px;
+    opacity: 1;
+    background: linear-gradient(29deg, #092941 -82%, #2A484B 94%);
+    box-shadow: inset 0px 1px 0px 2px rgba(255, 255, 255, 0.5577);
+  }
+}
+</style>