index.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555
  1. <template>
  2. <div class="li" :class="{ 'disable': !area, 'wait': [0].includes(examState), 'startedAlready': examState == 42 }">
  3. <div class="userInfo" @click="getChooseStudent">
  4. <div class="userInfo-center">
  5. <Transition :enter-active-class="proxy?.animate.dialog.enter">
  6. <div class="pic" :class="{ 'pic2': faceCheckStu.student_id }" v-if="faceCheckStu.student_id"> <img
  7. :src="faceCheckStu.face_pic || faceCheckStu.logo_url" /></div>
  8. <div class="pic" v-else>
  9. <div class="area">{{ area }}</div>
  10. <img src="@/assets/images/test/profilePicture2.png" />
  11. </div>
  12. </Transition>
  13. <div class="name" :class="{ 'name2': faceCheckStu.student_id }">
  14. {{ faceCheckStu.student_id ? faceCheckStu.name : area && ![0].includes(examState) ? "虚位以待" : area &&
  15. [0].includes(examState) ? "离线" : "未启用" }}
  16. </div>
  17. <!-- <Transition :enter-active-class="proxy?.animate.hand.enter">
  18. <div class="ctrlBox"
  19. v-if="props.examState == 43 && examState == 43 && handcontroller && ctrl == area && faceCheckStu.student_id">
  20. <img src="@/assets/images/test/jushou.png" />
  21. <div class="lable">
  22. 请举左手开始
  23. </div>
  24. </div>
  25. </Transition> -->
  26. </div>
  27. </div>
  28. <div class="score">
  29. {{ currentResultObj.count
  30. || 0 }}
  31. </div>
  32. <!-- <div>当前状态:({{ examState == 3 ? "初始化完成" : examState == 40 ? "创建测试" : examState == 41 ? "正在人脸识别" :examState == 43 ? "停止人脸识别" : examState == 42 ? "正在测试" : "离线状态" }})</div> -->
  33. </div>
  34. </template>
  35. <script setup lang="ts">
  36. // import { useWs } from '@/utils/ws';
  37. import { initWs, examEnds, openOneTest, startFace, stopFace, faceConfirmOnly, startOneTest, finishOneTest, closeOneTest, suspendFaceRecognitionChannels, resumeFaceRecognitionChannels } from '@/utils/ws'
  38. import dataDictionary from "@/utils/dataDictionary"
  39. // const { initWs, examEnds, openOneTest, startFace, stopFace, faceConfirmOnly, startOneTest, finishOneTest, closeOneTest, suspendFaceRecognitionChannels, resumeFaceRecognitionChannels } = useWs();
  40. const route = useRoute();
  41. const { proxy } = getCurrentInstance() as any;
  42. const emit = defineEmits(['returnData', 'getChooseStudent']);
  43. //父值
  44. const props = defineProps({
  45. parentTrainWsMethod: {
  46. type: Object,
  47. default: {}
  48. },
  49. parentSpeechMethod: {
  50. type: Object,
  51. default: {}
  52. },
  53. area: {
  54. type: String,
  55. default: ""
  56. },
  57. examState: {
  58. type: Number,
  59. default: null
  60. },
  61. needStart: {
  62. type: Boolean,
  63. default: false
  64. },
  65. styleType: {
  66. type: Number,
  67. default: null
  68. },
  69. });
  70. let project: any = route.query.project;
  71. let area: any = props.area;
  72. let examId: string = `${project}_${area}`; //项目+区
  73. const data = reactive<any>({
  74. examState: 0,//当前状态
  75. resultId: null,//测试ID
  76. currentResultObj: {},//成绩
  77. faceCheckStu: {},//人脸信息
  78. unit: "",//单位
  79. backReason: [],//犯规项
  80. handcontroller: null,//是否开启手势控制
  81. ctrl: "",//控制位
  82. });
  83. const { examState, resultId, faceCheckStu, currentResultObj, unit, backReason, handcontroller, ctrl } = toRefs(data);
  84. /**
  85. * 接收消息
  86. */
  87. const getMessage = (e: any) => {
  88. //console.log("WS响应:", e)
  89. //实时状态
  90. if (e.cmd === 'exam_status') {
  91. examState.value = e.data;
  92. }
  93. //工作站状态
  94. if (e.cmd === 'init_result') {
  95. }
  96. //测试违规
  97. if (e.cmd === 'warning_result') {
  98. let message = e.data?.message;
  99. if (message) {
  100. proxy?.$modal.msgError(`【${area}】${message}`);
  101. props.parentSpeechMethod.speckText(`${area}区,${message}`);
  102. }
  103. }
  104. //后端播报语音
  105. if (e.cmd === 'return_audio_msg') {
  106. let message = e.data?.message;
  107. if (message) {
  108. proxy?.$modal.msgError(`【${area}】${message}`);
  109. props.parentSpeechMethod.speckText(`${area}区,${message}`);
  110. }
  111. }
  112. //错误提示
  113. if (e.cmd === 'info_result') {
  114. proxy?.$modal.msgError(`【${area}】${e.data.message}`);
  115. }
  116. //错误提示
  117. if (e.cmd === 'error_result') {
  118. proxy?.$modal.msgError(`【${area}】${e.data.message}`);
  119. }
  120. //测试中违规提示
  121. if (e.cmd === 'warning_notify') {
  122. }
  123. //断线状态
  124. if (e.cmd === 'disconnect_request') {
  125. examState.value = 0;
  126. emit('returnData', { examState: examState.value, area: area });
  127. let message = e.data.message;
  128. if (message) {
  129. proxy?.$modal.msgError(`【${area}】${message}`);
  130. //props.parentSpeechMethod.speckText(e.data.message);
  131. }
  132. }
  133. //状态变更
  134. if (e.cmd === 'set_exam_state') {
  135. examState.value = e.data;
  136. if (e.data === 3) {
  137. initProject();
  138. }
  139. if (e.data === 40) {
  140. cleanData();
  141. }
  142. if (e.data == 41) {
  143. }
  144. if (e.data == 43) {
  145. }
  146. if (e.data == 42) {
  147. }
  148. }
  149. //新建测试后返回信息,获取result_id
  150. if (e.cmd === 'open_one_test_ack') {
  151. resultId.value = e.data.result_id;
  152. }
  153. //人脸识别状态
  154. if (e.cmd === 'face_check_result') {
  155. let myData = e.data[0] || e.data;
  156. returnStudent(myData);
  157. }
  158. //测试结束结果
  159. if (e.cmd === 'oneresult') {
  160. if (e.data.length) {
  161. let data = e.data[0];
  162. getAchievement(data)
  163. }
  164. }
  165. //结果生成完成(视频图片)
  166. if (e.cmd === 'static_urls_finished') {
  167. }
  168. //选择学生或测试结束后返回的数据
  169. if (e.cmd === 'result_info') {
  170. }
  171. };
  172. /**
  173. * 开始识别
  174. */
  175. const getOpenOneTestAndStartFace = async () => {
  176. if (area == null || area == "") {
  177. return false;
  178. }
  179. console.log("examId", examId)
  180. if (examState.value > 3) {
  181. await props.parentTrainWsMethod.closeOneTest(examId);
  182. }
  183. await props.parentTrainWsMethod.openOneTest(examId);
  184. await props.parentTrainWsMethod.startFace(examId);
  185. };
  186. /**
  187. * 停止人脸识别
  188. */
  189. const getStopFace = async () => {
  190. if (area == null || area == "") {
  191. return false;
  192. }
  193. // 旧版识别成功直接43了这里先屏蔽
  194. // if (examState.value != 41) {
  195. // return false;
  196. // }
  197. if (examState.value == 41) {
  198. await props.parentTrainWsMethod.stopFace(examId);
  199. }
  200. if (faceCheckStu.value.student_id) {
  201. getFaceConfirmOnly();
  202. }
  203. };
  204. /**
  205. * 确定人脸信息
  206. */
  207. const getFaceConfirmOnly = (data?: any) => {
  208. if (data) {
  209. faceCheckStu.value = data;
  210. }
  211. props.parentTrainWsMethod.faceConfirmOnly({
  212. exam_id: examId,
  213. result_id: resultId.value,
  214. student_id: faceCheckStu.value.student_id,
  215. gender: faceCheckStu.value.gender
  216. }, () => {
  217. });
  218. };
  219. /**
  220. * 重新识别
  221. */
  222. const getRetestFace = () => {
  223. if (props.examState == 42 || examState.value == 0) {
  224. return false;
  225. }
  226. proxy?.$modal.confirm("确定重新识别吗?").then(() => {
  227. cleanData();
  228. if (props.needStart == false) {
  229. //自动流程项目重新识别直接返回3
  230. props.parentTrainWsMethod.closeOneTest(examId);
  231. } else {
  232. //手动流程项目重新识别43返回41,42返回3
  233. if (examState.value == 43) {
  234. props.parentTrainWsMethod.startFace(examId);
  235. } else {
  236. props.parentTrainWsMethod.closeOneTest(examId);
  237. }
  238. }
  239. }).finally(() => {
  240. });
  241. };
  242. /**
  243. * 开始测试
  244. */
  245. const getStartOneTest = () => {
  246. if (area == null || area == "") {
  247. return false;
  248. }
  249. if (examState.value != 43 || !faceCheckStu.value.student_id) {
  250. return false;
  251. }
  252. props.parentTrainWsMethod.startOneTest(examId, () => { })
  253. };
  254. /**
  255. * 再测一次
  256. */
  257. const getAgain = async () => {
  258. if (area == null || area == "") {
  259. return false;
  260. }
  261. //预存测试人员
  262. let student = JSON.parse(JSON.stringify(faceCheckStu.value));
  263. //测试中
  264. if (examState.value == 42) {
  265. await props.parentTrainWsMethod.finishOneTest(examId);
  266. }
  267. //其他状态
  268. if (examState.value > 3) {
  269. await props.parentTrainWsMethod.closeOneTest(examId);
  270. }
  271. //重新走一次流程
  272. await props.parentTrainWsMethod.openOneTest(examId);
  273. await props.parentTrainWsMethod.startFace(examId);
  274. await props.parentTrainWsMethod.stopFace(examId);
  275. if (student.student_id) {
  276. getFaceConfirmOnly(student);
  277. }
  278. };
  279. /**
  280. * 重新识别
  281. */
  282. const getAllRetestFace = async () => {
  283. if (area == null || area == "") {
  284. return false;
  285. }
  286. //测试中
  287. if (examState.value == 42) {
  288. await props.parentTrainWsMethod.finishOneTest(examId);
  289. }
  290. //其他状态
  291. if (examState.value > 3) {
  292. await props.parentTrainWsMethod.closeOneTest(examId);
  293. }
  294. //重新走一次流程
  295. await getOpenOneTestAndStartFace();
  296. };
  297. /**
  298. * 选择学生
  299. */
  300. const getChooseStudent = () => {
  301. if (!area) {
  302. proxy?.$modal.msgWarning(`未启用`);
  303. return false;
  304. }
  305. console.log("examState.value", examState.value)
  306. if (examState.value == 0) {
  307. proxy?.$modal.msgWarning(`【${area}】当前状态:${examState.value}`);
  308. return false;
  309. }
  310. if (props.examState == 3 && (examState.value == 3 || examState.value == 40)) {
  311. if (props.needStart) {
  312. proxy?.$modal.msgWarning(`请点击右下角的【开始识别】,【${area}】当前状态:${examState.value}`);
  313. } else {
  314. proxy?.$modal.msgWarning(`【${area}】当前状态:${examState.value}`);
  315. }
  316. return false;
  317. }
  318. if (props.examState == 41 && (examState.value == 3 || examState.value == 40)) {
  319. proxy?.$modal.msgWarning(`【${area}】当前状态:${examState.value}`);
  320. return false;
  321. }
  322. if (examState.value == 41) {
  323. chooseStudent();
  324. }
  325. if (examState.value == 43) {
  326. getRetestFace();
  327. }
  328. if (props.examState == 42) {
  329. proxy?.$modal.msgWarning(`【${area}】正在测试请结束后再操作,当前状态:${examState.value}`);
  330. return false;
  331. }
  332. };
  333. /**
  334. * 选择学生
  335. */
  336. const chooseStudent = () => {
  337. props.parentTrainWsMethod.stopFace(examId);
  338. emit('getChooseStudent', { area: area });
  339. };
  340. /**
  341. * 返回被选学生
  342. */
  343. const returnStudent = (data: any) => {
  344. console.log("学生信息", data)
  345. faceCheckStu.value = data;
  346. getStopFace();
  347. };
  348. /**
  349. * 清除历史记录
  350. */
  351. const cleanData = () => {
  352. faceCheckStu.value = {};
  353. currentResultObj.value = {};
  354. backReason.value = [];
  355. };
  356. /**
  357. * 自动初始化项目
  358. */
  359. const initProject = () => {
  360. if (!area) {
  361. return false;
  362. }
  363. //自动项目定时进入下一步
  364. if (props.needStart == false) {
  365. let time = 0;
  366. // 控制开启速度
  367. if (resultId.value) {
  368. time = 10000;
  369. }
  370. setTimeout(() => {
  371. //再加一个判断以免和再测一次冲突
  372. if (examState.value == 3 || examState.value == 43) {
  373. console.log("执行了执行了执行了执行了执行了")
  374. getOpenOneTestAndStartFace();
  375. }
  376. }, time)
  377. }
  378. };
  379. /**
  380. * 成绩
  381. */
  382. const getAchievement = (data: any) => {
  383. //console.log("成绩", data);
  384. let dic: any = dataDictionary;
  385. let type = project;
  386. let count =
  387. data?.[dic.typeResultKey[type]]?.toFixed(0);
  388. if (["trijump", "solidball", "shotput", "longjump"].includes(type)) {
  389. count =
  390. data?.[dic.typeResultKey[type]]?.toFixed(2);
  391. count = Math.round(count) / 100;
  392. }
  393. data.count = count || "0";
  394. data.score = data.score || "0";
  395. currentResultObj.value = data;
  396. //违规处理
  397. let arr = backReason.value;
  398. if (["situp", "pullup", "sidepullup", "jumprope", "jumpingjack", "highknees", "jump", "longjump", "verticaljump"]
  399. .indexOf(type) > -1) {
  400. if (["pullup", "situp", "jumprope", "jumpingjack", "highknees"].indexOf(type) > -1) {
  401. currentResultObj.value.back_num = data?.all_failed_num;
  402. }
  403. if (type === "sidepullup") {
  404. currentResultObj.value.back_num = data?.["0"]?.hip_failed_num;
  405. }
  406. if (['jump', 'longjump', 'verticaljump'].includes(type)) {
  407. if (data?.startline_check == 0) {
  408. let txt = "踩线违规";
  409. arr.push(txt)
  410. }
  411. if (data?.singleleg_jump_check == 0) {
  412. let txt = "单脚跳违规";
  413. arr.push(txt)
  414. }
  415. if (data?.outside_check == 0) {
  416. let txt = "跳出测试区违规";
  417. arr.push(txt)
  418. }
  419. }
  420. if (
  421. data?.elbow_check == false
  422. ) {
  423. let txt = "肘部违规";
  424. arr.push(txt);
  425. }
  426. if (
  427. ["situp", "pullup"].indexOf(type) > -1 &&
  428. data?.knee_check === false
  429. ) {
  430. let txt = "腿部违规";
  431. if (!arr.includes(txt)) {
  432. arr.push(txt);
  433. }
  434. }
  435. if (["situp"].indexOf(type) > -1 && data?.hand_check === false) {
  436. let txt = "手部违规";
  437. if (!arr.includes(txt)) {
  438. arr.push(txt);
  439. }
  440. }
  441. if (
  442. ["pullup"].indexOf(type) > -1 &&
  443. data?.["0"]?.elbow_check === false
  444. ) {
  445. let txt = "手部违规";
  446. if (!arr.includes(txt)) {
  447. arr.push(txt);
  448. }
  449. }
  450. if (["situp"].indexOf(type) > -1 && data?.["0"]?.back_check === false) {
  451. let txt = "背部违规";
  452. if (!arr.includes(txt)) {
  453. arr.push(txt);
  454. }
  455. }
  456. if (
  457. ["sidepullup", "situp"].indexOf(type) > -1 &&
  458. data?.["0"]?.hip_check === false
  459. ) {
  460. let txt = "臀部违规";
  461. if (!arr.includes(txt)) {
  462. arr.push(txt);
  463. }
  464. }
  465. }
  466. backReason.value = arr;
  467. if (data.isfinish) {
  468. // if (['jump'].includes(type) && backReason.value.length) {
  469. // props.parentSpeechMethod.speckText("请重新测试");
  470. // return false;
  471. // }
  472. // props.parentSpeechMethod.speckText(faceCheckStu?.value.name + "成绩为" + (chineseNumber(count) || 0) + unit.value + ",请下一位准备!" || "");
  473. }
  474. };
  475. /**
  476. * 状态抛给父组件
  477. */
  478. watch(() => examState.value, (newVal, oldVal) => {
  479. console.log(area, examState.value)
  480. emit('returnData', { examState: examState.value, area: area });
  481. }, { deep: true });
  482. /**
  483. * 人脸信息抛给父组件
  484. */
  485. watch(() => faceCheckStu.value, (newVal, oldVal) => {
  486. emit('returnData', { faceCheckStu: faceCheckStu.value, area: area });
  487. }, { deep: true });
  488. /**
  489. * 测试ID抛给父组件
  490. */
  491. watch(() => resultId.value, (newVal, oldVal) => {
  492. emit('returnData', { resultId: resultId.value, area: area });
  493. }, { deep: true });
  494. /**
  495. * 如果总流程结束了个别区仍然在43就重新执行一次,用于没有开的区自动复位
  496. */
  497. watch(() => props.examState, (newVal, oldVal) => {
  498. if (newVal == 3 && examState.value == 43) {
  499. setTimeout(() => {
  500. initProject();
  501. }, 10000)
  502. }
  503. //父状态已经进入41了就不要等子组件40清空数据了
  504. if (newVal == 41) {
  505. cleanData();
  506. }
  507. }, { deep: true });
  508. onMounted(() => {
  509. let dic: any = dataDictionary;
  510. unit.value = dic.unit[project];
  511. ctrl.value = route.query.ctrl;
  512. handcontroller.value = route.query.handcontroller;
  513. })
  514. //暴露给父组件用
  515. defineExpose({
  516. getMessage,
  517. getOpenOneTestAndStartFace,
  518. getStopFace,
  519. getStartOneTest,
  520. getAgain,
  521. getAllRetestFace,
  522. returnStudent
  523. })
  524. </script>
  525. <style scoped lang="scss"></style>