test.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531
  1. <template>
  2. <div>
  3. <div v-if="needStart">{{
  4. countdownNumFormat
  5. }}</div>
  6. <div><el-avatar :src="faceCheckStu.logo_url || faceCheckStu.face_pic" @click="getChooseStudent" /></div>
  7. <div>{{ faceCheckStu.name }}</div>
  8. <div>成绩:{{ currentResultObj.count }} {{ unit }}</div>
  9. <div>得分:{{ currentResultObj.score }}</div>
  10. <div>
  11. <div v-if="['jumprope'].includes(parameter.project)">中断</div>
  12. <div v-else>犯规</div>
  13. <div>{{ currentResultObj.back_num }}</div>
  14. </div>
  15. <div v-if="backReason.length">
  16. <div>违规项</div>
  17. <div v-for="(item, index) in backReason" :key="index">{{ item }}</div>
  18. </div>
  19. <div>当前状态:({{ examState == 3 ? "初始化完成" : examState == 40 ? "创建测试" : examState == 41 ? "正在人脸识别"
  20. :
  21. examState ==
  22. 43 ? "停止人脸识别" : examState == 42 ? "正在测试" : "请初始化" }})</div>
  23. <div v-if="needStart">
  24. <div @click="getOpenOneTestAndStartFace" v-if="examState == 3 || examState == 40">1、开始识别</div>
  25. <div @click="getStopFace" v-if="examState == 41">2、停止人脸识别</div>
  26. <div @click="getStartOneTest" v-if="examState == 43">3、开始测试</div>
  27. </div>
  28. <div @click="getChooseStudent" v-if="examState == 41 || (examState == 43 && !faceCheckStu.student_id)">选择学生</div>
  29. <div @click="getRetestFace" v-if="examState == 43 || examState == 42">重新识别</div>
  30. <div @click="getAgain" v-if="examState == 42 || showTestAgain">再测一次</div>
  31. <div @click="confirmExit">退出</div>
  32. <FaceWindow ref="faceWindowRef" :faceCheckStu="faceCheckStu" />
  33. <ChooseStudent ref="chooseStudentRef" @returnData="returnStudent" />
  34. </div>
  35. </template>
  36. <script setup name="TrainTest" lang="ts">
  37. import { initSpeech, speckText, speckCancel, chineseNumber } from '@/utils/speech'
  38. import { initWs, examEnds, openOneTest, startFace, stopFace, faceConfirmOnly, startOneTest, finishOneTest, closeOneTest, suspendFaceRecognitionChannels, resumeFaceRecognitionChannels } from '@/utils/ws'
  39. import dataDictionary from "@/utils/dataDictionary"
  40. const { proxy } = getCurrentInstance() as any;
  41. const router = useRouter();
  42. const route = useRoute();
  43. const faceWindowRef = ref();
  44. const chooseStudentRef = ref();
  45. const data = reactive<any>({
  46. timerManager: {},//计时器管理
  47. parameter: {},//参数
  48. time: {
  49. testTime: 60,//时长
  50. countdownNum: 0,//计时
  51. },
  52. userInfo: {},//用户信息
  53. examState: 0,//当前状态
  54. resultId: null,//测试ID
  55. currentResultObj: {},//成绩
  56. faceCheckStu: {},//人脸信息
  57. unit: "",//单位
  58. backReason: [],//犯规项
  59. needStart: false,//是否需要按钮
  60. showTestAgain: false,//再测一次按钮
  61. });
  62. const { timerManager, parameter, time, userInfo, examState, resultId, faceCheckStu, currentResultObj, unit, backReason, needStart, showTestAgain } = toRefs(data);
  63. /**
  64. * 接收消息
  65. */
  66. const getMessage = (e: any) => {
  67. //console.log("WS响应:", e)
  68. //实时状态
  69. if (e.cmd === 'exam_status') {
  70. examState.value = e.data;
  71. }
  72. //工作站状态
  73. if (e.cmd === 'init_result') {
  74. }
  75. //测试违规
  76. if (e.cmd === 'warning_result') {
  77. }
  78. //后端播报语音
  79. if (e.cmd === 'return_audio_msg') {
  80. }
  81. //错误提示
  82. if (e.cmd === 'info_result') {
  83. proxy?.$modal.msgError(e.data.message);
  84. }
  85. //错误提示
  86. if (e.cmd === 'error_result') {
  87. proxy?.$modal.msgError(e.data.message);
  88. }
  89. //测试中违规提示
  90. if (e.cmd === 'warning_notify') {
  91. }
  92. //断线状态
  93. if (e.cmd === 'disconnect_request') {
  94. if (e.data.message) {
  95. speckText(e.data.message);
  96. }
  97. getExit();
  98. }
  99. //状态变更
  100. if (e.cmd === 'set_exam_state') {
  101. examState.value = e.data;
  102. if (e.data === 3) {
  103. initProject();
  104. }
  105. if (e.data === 40) {
  106. cleanData();
  107. }
  108. if (e.data == 41) {
  109. getFaceWindow(true);
  110. }
  111. if (e.data == 43) {
  112. }
  113. if (e.data == 42) {
  114. }
  115. }
  116. //新建测试后返回信息,获取result_id
  117. if (e.cmd === 'open_one_test_ack') {
  118. resultId.value = e.data.result_id;
  119. }
  120. //人脸识别状态
  121. if (e.cmd === 'face_check_result') {
  122. faceCheckStu.value = e.data[0] || e.data;
  123. //工作站识别成功后停止识别并确定人脸
  124. getStopFace();
  125. }
  126. //测试结束结果
  127. if (e.cmd === 'oneresult') {
  128. if (e.data.length) {
  129. let data = e.data[0];
  130. getAchievement(data)
  131. }
  132. }
  133. //结果生成完成(视频图片)
  134. if (e.cmd === 'static_urls_finished') {
  135. }
  136. //选择学生或测试结束后返回的数据
  137. if (e.cmd === 'result_info') {
  138. }
  139. };
  140. /**
  141. * 开始识别
  142. */
  143. const getOpenOneTestAndStartFace = async () => {
  144. await openOneTest();
  145. await startFace();
  146. };
  147. /**
  148. * 停止人脸识别
  149. */
  150. const getStopFace = async () => {
  151. await stopFace();
  152. if (faceCheckStu.value.student_id) {
  153. getFaceConfirmOnly();
  154. }
  155. };
  156. /**
  157. * 确定人脸信息
  158. */
  159. const getFaceConfirmOnly = (data?: any) => {
  160. if (data) {
  161. faceCheckStu.value = data;
  162. }
  163. faceConfirmOnly({
  164. result_id: resultId.value,
  165. student_id: faceCheckStu.value.student_id,
  166. gender: faceCheckStu.value.gender
  167. }, () => {
  168. faceWindowRef.value?.close();
  169. //不需要按钮的自动进入下一步
  170. if (needStart.value == false) {
  171. getStartOneTest();
  172. }
  173. });
  174. };
  175. /**
  176. * 重新识别
  177. */
  178. const getRetestFace = () => {
  179. proxy?.$modal.confirm("确定重新识别吗?").then(() => {
  180. if (needStart.value == false) {
  181. //自动流程项目重新识别直接返回3
  182. closeOneTest();
  183. } else {
  184. //手动流程项目重新识别43返回41,42返回3
  185. if (examState.value == 43) {
  186. cleanData();
  187. startFace();
  188. } else {
  189. closeOneTest();
  190. }
  191. }
  192. }).finally(() => {
  193. });
  194. };
  195. /**
  196. * 开始测试
  197. */
  198. const getStartOneTest = () => {
  199. if (!faceCheckStu.value.student_id) {
  200. proxy?.$modal.msgError("请选择人员!");
  201. return false;
  202. }
  203. startOneTest(data == null, () => {
  204. //显示再测一次按钮
  205. showTestAgain.value = true;
  206. //停止播报;
  207. speckCancel()
  208. //计时项目才开
  209. if (needStart.value == true) {
  210. //时间为0的为正计时,大于0的为倒计时
  211. if (time.value.testTime == 0) {
  212. getCounting("+");
  213. } else {
  214. getCounting("-");
  215. }
  216. } else {
  217. speckText(faceCheckStu.value.name + ",请开始测试");
  218. }
  219. })
  220. };
  221. /**
  222. * 再测一次
  223. */
  224. const getAgain = async () => {
  225. //预存测试人员
  226. let student = JSON.parse(JSON.stringify(faceCheckStu.value));
  227. //测试中
  228. if (examState.value == 42) {
  229. await finishOneTest();
  230. }
  231. //重新走一次流程
  232. await openOneTest();
  233. await startFace();
  234. await stopFace();
  235. getFaceConfirmOnly(student);
  236. };
  237. /**
  238. * 确认退出
  239. */
  240. const confirmExit = () => {
  241. proxy?.$modal.confirm("确定退出吗?").then(() => {
  242. getExit();
  243. }).finally(() => {
  244. });
  245. };
  246. /**
  247. * 退出
  248. */
  249. const getExit = () => {
  250. getClearTimer();//清除计时器
  251. examEnds();//通知工作站关闭
  252. speckCancel()//停止播报;
  253. router.push({ path: '/' });//跳转
  254. };
  255. /**
  256. * 清空定时任务
  257. */
  258. const getClearTimer = (data?: any) => {
  259. if (data) {
  260. //清除指定
  261. clearInterval(timerManager.value[data])
  262. timerManager.value[data] = null;
  263. } else {
  264. //清除全部
  265. for (let key in timerManager.value) {
  266. if (timerManager.value.hasOwnProperty(key)) {
  267. clearInterval(timerManager.value[key])
  268. timerManager.value[key] = null;
  269. }
  270. }
  271. }
  272. };
  273. /**
  274. * 选择学生
  275. */
  276. const getChooseStudent = () => {
  277. if (examState.value == 41) {
  278. chooseStudentRef.value.open();
  279. }
  280. if (examState.value == 43) {
  281. getRetestFace();
  282. }
  283. };
  284. /**
  285. * 返回被选学生
  286. */
  287. const returnStudent = (data: any) => {
  288. faceCheckStu.value = data;
  289. faceWindowRef.value.open();
  290. getStopFace();
  291. };
  292. /**
  293. * 清除历史记录
  294. */
  295. const cleanData = () => {
  296. time.value.countdownNum = time.value.testTime;
  297. showTestAgain.value = false;
  298. faceCheckStu.value = {};
  299. currentResultObj.value = {};
  300. backReason.value = [];
  301. };
  302. /**
  303. * 自动初始化项目
  304. */
  305. const initProject = () => {
  306. //停止计时
  307. getClearTimer("countdownTimer");
  308. //自动项目定时进入下一步
  309. if (needStart.value == false) {
  310. let time = 0;
  311. //控制新建测试的时间,第一次快,之后就慢
  312. if (!faceCheckStu.value.student_id) {
  313. time = 1000;
  314. } else {
  315. time = 6000;
  316. }
  317. setTimeout(() => {
  318. //再加一个判断以免和再测一次冲突
  319. if (examState.value == 3) {
  320. getOpenOneTestAndStartFace();
  321. }
  322. }, time)
  323. }
  324. };
  325. /**
  326. * 时间转换
  327. */
  328. const countdownNumFormat = computed(() => {
  329. return proxy?.$utils.timeFormat(time.value.countdownNum);
  330. });
  331. /**
  332. * 倒计时
  333. */
  334. const getCounting = (type: string) => {
  335. timerManager.value.countdownTimer = setInterval(() => {
  336. //正计时
  337. if (type == "+") {
  338. time.value.countdownNum++;
  339. }
  340. //倒计时
  341. if (type == "-") {
  342. if (time.value.countdownNum <= 0) {
  343. getClearTimer("countdownTimer");
  344. } else {
  345. time.value.countdownNum--;
  346. }
  347. }
  348. }, 1000);
  349. };
  350. /**
  351. * 人脸窗口
  352. */
  353. const getFaceWindow = (data: boolean) => {
  354. let txt = parameter.value.gesture ? "请举手看摄像头人脸识别" : "请看摄像头进行人脸识别";
  355. speckText(txt);
  356. //data=true为弹出框,data=false为不要弹出框
  357. if (data) {
  358. faceWindowRef.value.open();
  359. //然后定时自动关闭
  360. setTimeout(() => {
  361. if (examState.value == 41 && faceWindowRef.value.faceState == true) {
  362. faceWindowRef.value?.close();
  363. }
  364. }, 3000)
  365. }
  366. //定时检查如果一直停留在人脸识别就提示
  367. timerManager.value.face = setInterval(() => {
  368. getClearTimer("face");
  369. if (examState.value == 41) {
  370. getFaceWindow(false);
  371. }
  372. }, 15000)
  373. };
  374. /**
  375. * 成绩
  376. */
  377. const getAchievement = (data: any) => {
  378. //console.log("成绩", data);
  379. let dic: any = dataDictionary;
  380. let type = parameter.value.project;
  381. let count =
  382. data?.[dic.typeResultKey[type]]?.toFixed(0);
  383. if (["trijump", "solidball", "shotput", "longjump"].includes(type)) {
  384. count =
  385. data?.[dic.typeResultKey[type]]?.toFixed(2);
  386. count = Math.round(count) / 100;
  387. }
  388. data.count = count || "0";
  389. data.score = data.score || "0";
  390. currentResultObj.value = data;
  391. //违规处理
  392. let arr = backReason.value;
  393. if (["situp", "pullup", "sidepullup", "jumprope", "jumpingjack", "jump", "longjump", "verticaljump"]
  394. .indexOf(type) > -1) {
  395. if (["pullup", "situp", "jumprope", "jumpingjack"].indexOf(type) > -1) {
  396. currentResultObj.value.back_num = data?.all_failed_num;
  397. }
  398. if (type === "sidepullup") {
  399. currentResultObj.value.back_num = data?.["0"]?.hip_failed_num;
  400. }
  401. if (['jump', 'longjump', 'verticaljump'].includes(type)) {
  402. if (data?.startline_check == 0) {
  403. let txt = "踩线违规";
  404. arr.push(txt)
  405. }
  406. if (data?.singleleg_jump_check == 0) {
  407. let txt = "单脚跳违规";
  408. arr.push(txt)
  409. }
  410. if (data?.outside_check == 0) {
  411. let txt = "跳出测试区违规";
  412. arr.push(txt)
  413. }
  414. }
  415. if (
  416. data?.elbow_check == false
  417. ) {
  418. let txt = "肘部违规";
  419. arr.push(txt);
  420. }
  421. if (
  422. ["situp", "pullup"].indexOf(type) > -1 &&
  423. data?.knee_check === false
  424. ) {
  425. let txt = "腿部违规";
  426. if (!arr.includes(txt)) {
  427. arr.push(txt);
  428. }
  429. }
  430. if (["situp"].indexOf(type) > -1 && data?.hand_check === false) {
  431. let txt = "手部违规";
  432. if (!arr.includes(txt)) {
  433. arr.push(txt);
  434. }
  435. }
  436. if (
  437. ["pullup"].indexOf(type) > -1 &&
  438. data?.["0"]?.elbow_check === false
  439. ) {
  440. let txt = "手部违规";
  441. if (!arr.includes(txt)) {
  442. arr.push(txt);
  443. }
  444. }
  445. if (["situp"].indexOf(type) > -1 && data?.["0"]?.back_check === false) {
  446. let txt = "背部违规";
  447. if (!arr.includes(txt)) {
  448. arr.push(txt);
  449. }
  450. }
  451. if (
  452. ["sidepullup", "situp"].indexOf(type) > -1 &&
  453. data?.["0"]?.hip_check === false
  454. ) {
  455. let txt = "臀部违规";
  456. if (!arr.includes(txt)) {
  457. arr.push(txt);
  458. }
  459. }
  460. }
  461. backReason.value = arr;
  462. if (data.isfinish) {
  463. if (['jump'].includes(type) && backReason.value.length) {
  464. speckText("请重新测试");
  465. return false;
  466. }
  467. speckText(faceCheckStu?.value.name + "成绩为" + (chineseNumber(count) || 0) + unit.value + ",请下一位准备!" || "");
  468. }
  469. };
  470. onMounted(() => {
  471. parameter.value = route.query;
  472. let project = parameter.value.project;
  473. let area = parameter.value.area;
  474. parameter.value.examId = `${project}_${area}`; //项目+区
  475. if (parameter.value.time) {
  476. time.value.testTime = parameter.value.time
  477. }
  478. time.value.countdownNum = time.value.testTime;
  479. let myInfo: any = localStorage.getItem("userInfo");
  480. userInfo.value = JSON.parse(myInfo);
  481. let dic: any = dataDictionary;
  482. unit.value = dic.unit[project];
  483. if (parameter.value.gesture == 'true') {
  484. parameter.value.gesture = true
  485. } else {
  486. parameter.value.gesture = false
  487. }
  488. //需要开始按钮的项目
  489. if (["jumprope", "skiprope", "jumpingjack", "situp"].includes(project)) {
  490. needStart.value = true;
  491. }
  492. //加载WS
  493. initWs({ parameter: parameter.value, testTime: time.value.testTime }, (data: any) => {
  494. getMessage(data);
  495. });
  496. initSpeech();
  497. })
  498. onUnmounted(() => {
  499. getExit();
  500. })
  501. </script>
  502. <style scoped lang="scss"></style>