|
@@ -0,0 +1,426 @@
|
|
|
+<template>
|
|
|
+ <div>
|
|
|
+ <div v-if="needStart">{{
|
|
|
+ countdownNumFormat
|
|
|
+ }}</div>
|
|
|
+ <div class="trackItem">
|
|
|
+ <div v-for="(item, index) in faceStudentList" :key="index" class="li">
|
|
|
+ <div>{{ item.track }}</div>
|
|
|
+ <div><el-avatar :src="item.face_pic" @click="getChooseStudent(item.track)" />
|
|
|
+ </div>
|
|
|
+ <div>{{ item.student_name || "未检录" }}</div>
|
|
|
+ <div class="menuBtn" v-if="examState <= 42" @click="getChooseStudent(item.track)">检录
|
|
|
+ </div>
|
|
|
+
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div>当前状态:({{ examState == 3 ? "初始化完成" : examState == 40 ? "创建测试" : examState == 41 ? "正在人脸识别"
|
|
|
+ :
|
|
|
+ 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>
|
|
|
+ <div @click="getRetest" v-if="examState == 42">再测一次</div>
|
|
|
+ <div @click="confirmExit">退出</div>
|
|
|
+ <ChooseStudent ref="chooseStudentRef" @returnData="returnStudent" />
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup name="TrainTest" lang="ts">
|
|
|
+import { initSpeech, speckText, speckCancel, chineseNumber } from '@/utils/speech'
|
|
|
+import { initWs, examEnds, openOneTest, startFace, stopFace, faceConfirmOnly, startOneTest, finishOneTest, closeOneTest, getNetWork, suspendFaceRecognitionChannels, resumeFaceRecognitionChannels } from '@/utils/ws'
|
|
|
+import dataDictionary from "@/utils/dataDictionary"
|
|
|
+const { proxy } = getCurrentInstance() as any;
|
|
|
+const router = useRouter();
|
|
|
+const route = useRoute();
|
|
|
+const chooseStudentRef = ref();
|
|
|
+const data = reactive<any>({
|
|
|
+ timerManager: {},//计时器管理
|
|
|
+ parameter: {},//参数
|
|
|
+ time: {
|
|
|
+ testTime: 0,//时长
|
|
|
+ countdownNum: 0,//计时
|
|
|
+ },
|
|
|
+ userInfo: {},//用户信息
|
|
|
+ examState: 0,//当前状态
|
|
|
+ resultId: null,//测试ID
|
|
|
+ currentResultObj: {},//成绩
|
|
|
+ unit: "",//单位
|
|
|
+ backReason: [],//犯规项
|
|
|
+ needStart: false,//是否需要按钮
|
|
|
+ faceStudentList: [],//跑道和人信息
|
|
|
+ currentTrack: null,//当前跑道
|
|
|
+});
|
|
|
+const { timerManager, parameter, time, userInfo, examState, resultId, currentResultObj, unit, backReason, needStart, faceStudentList, currentTrack } = 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 === 'exam_config') {
|
|
|
+ let list: any = [];
|
|
|
+ for (let i = 1; i <= e.data.num_of_tracks; i++) {
|
|
|
+ list.push({ track: i });
|
|
|
+ }
|
|
|
+ faceStudentList.value = list
|
|
|
+ }
|
|
|
+
|
|
|
+ //测试违规
|
|
|
+ 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') {
|
|
|
+ speckText(e.data.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) {
|
|
|
+ }
|
|
|
+ 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') {
|
|
|
+ }
|
|
|
+ //测试结束结果
|
|
|
+ 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 = () => {
|
|
|
+ if (examState.value == 3) {
|
|
|
+ openOneTest();
|
|
|
+ }
|
|
|
+ timerManager.value.startFace = setInterval(() => {
|
|
|
+ if (examState.value == 40) {
|
|
|
+ getClearTimer("startFace");
|
|
|
+ startFace();
|
|
|
+ }
|
|
|
+ }, 250);
|
|
|
+};
|
|
|
+
|
|
|
+/**
|
|
|
+ * 停止人脸识别
|
|
|
+*/
|
|
|
+const getStopFace = () => {
|
|
|
+ stopFace();
|
|
|
+ getFaceConfirmOnly();
|
|
|
+};
|
|
|
+
|
|
|
+/**
|
|
|
+ * 确定人脸信息
|
|
|
+*/
|
|
|
+const getFaceConfirmOnly = () => {
|
|
|
+ faceConfirmOnly(faceStudentList.value, () => {
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+/**
|
|
|
+ * 重新识别
|
|
|
+*/
|
|
|
+const getRetestFace = () => {
|
|
|
+ if (needStart.value == false) {
|
|
|
+ //自动流程项目重新识别直接返回3
|
|
|
+ closeOneTest();
|
|
|
+ } else {
|
|
|
+ //手动流程项目重新识别43返回41,42返回3
|
|
|
+ if (examState.value == 43) {
|
|
|
+ cleanData();
|
|
|
+ startFace();
|
|
|
+ } else {
|
|
|
+ closeOneTest();
|
|
|
+ }
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+/**
|
|
|
+ * 开始测试
|
|
|
+*/
|
|
|
+const getStartOneTest = () => {
|
|
|
+ // if (!faceCheckStu.value.student_id) {
|
|
|
+ // proxy?.$modal.msgError("请选择人员!");
|
|
|
+ // return false;
|
|
|
+ // }
|
|
|
+ startOneTest(() => {
|
|
|
+ //计时项目才开
|
|
|
+ if (needStart.value == true) {
|
|
|
+ //时间为0的为正计时,大于0的为倒计时
|
|
|
+ if (time.value.testTime == 0) {
|
|
|
+ getCounting("+");
|
|
|
+ } else {
|
|
|
+ getCounting("-");
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ speckCancel()//停止播报;
|
|
|
+ speckText("请开始测试");
|
|
|
+ }
|
|
|
+ })
|
|
|
+};
|
|
|
+
|
|
|
+/**
|
|
|
+ * 再测一次
|
|
|
+*/
|
|
|
+const getRetest = () => {
|
|
|
+ //预存测试人员
|
|
|
+ let student = JSON.parse(JSON.stringify(faceStudentList.value));
|
|
|
+ //测试中
|
|
|
+ if (examState.value == 42) {
|
|
|
+ finishOneTest();
|
|
|
+ }
|
|
|
+ //已测完
|
|
|
+ if (examState.value == 3) {
|
|
|
+ openOneTest();
|
|
|
+ }
|
|
|
+ //获取最新状态逐步发送命令
|
|
|
+ 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);
|
|
|
+};
|
|
|
+
|
|
|
+/**
|
|
|
+ * 确认退出
|
|
|
+*/
|
|
|
+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 getChooseStudent = (track: number) => {
|
|
|
+ if (examState.value < 41) {
|
|
|
+ proxy?.$modal.msgError("请点击开始人脸识别");
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ currentTrack.value = track;
|
|
|
+ chooseStudentRef.value.open();
|
|
|
+};
|
|
|
+
|
|
|
+/**
|
|
|
+ * 返回被选学生
|
|
|
+*/
|
|
|
+const returnStudent = (data: any) => {
|
|
|
+ let obj = {
|
|
|
+ result_id: resultId.value,
|
|
|
+ face_pic: data.face_pic || data.logo_url,
|
|
|
+ student_id: data.id,
|
|
|
+ student_name: data.name,
|
|
|
+ gender: data.gender,
|
|
|
+ }
|
|
|
+ //可能已检录的先清除
|
|
|
+ let oldIndex = faceStudentList.value.findIndex((item: any) => {
|
|
|
+ return data.student_id == item.student_id;
|
|
|
+ })
|
|
|
+ if (oldIndex != -1) {
|
|
|
+ faceStudentList.value[oldIndex] = { track: faceStudentList.value[oldIndex].track };
|
|
|
+ }
|
|
|
+ //赋值
|
|
|
+ let newIndex = faceStudentList.value.findIndex((item: any) => {
|
|
|
+ return currentTrack.value == item.track;
|
|
|
+ })
|
|
|
+ faceStudentList.value[newIndex] = { ...faceStudentList.value[newIndex], ...obj };
|
|
|
+ speckText(`第${currentTrack.value == 2 ? '二' : currentTrack.value}道, ${data.name}`);
|
|
|
+ currentTrack.value = null;
|
|
|
+};
|
|
|
+
|
|
|
+/**
|
|
|
+ * 清除历史记录
|
|
|
+*/
|
|
|
+const cleanData = () => {
|
|
|
+ time.value.countdownNum = time.value.testTime;
|
|
|
+};
|
|
|
+
|
|
|
+/**
|
|
|
+ * 自动初始化项目
|
|
|
+*/
|
|
|
+const initProject = () => {
|
|
|
+ //停止计时
|
|
|
+ getClearTimer("countdownTimer");
|
|
|
+};
|
|
|
+
|
|
|
+/**
|
|
|
+ * 时间转换
|
|
|
+*/
|
|
|
+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 getAchievement = (data: any) => {
|
|
|
+};
|
|
|
+
|
|
|
+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);
|
|
|
+ let dic: any = dataDictionary;
|
|
|
+ unit.value = dic.unit[project];
|
|
|
+ if (parameter.value.gesture == 'true') {
|
|
|
+ parameter.value.gesture = true
|
|
|
+ } else {
|
|
|
+ parameter.value.gesture = false
|
|
|
+ }
|
|
|
+ //需要开始按钮的项目
|
|
|
+ needStart.value = true;
|
|
|
+ //加载WS
|
|
|
+ initWs({ parameter: parameter.value, testTime: time.value.testTime }, (data: any) => {
|
|
|
+ getMessage(data);
|
|
|
+ });
|
|
|
+ getNetWork(() => {
|
|
|
+ getExit();
|
|
|
+ });
|
|
|
+ initSpeech();
|
|
|
+})
|
|
|
+
|
|
|
+onUnmounted(() => {
|
|
|
+ getExit();
|
|
|
+})
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped lang="scss">
|
|
|
+.trackItem {
|
|
|
+ .li {
|
|
|
+ display: flex;
|
|
|
+
|
|
|
+ .menuBtn {
|
|
|
+ margin-left: 20px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|