run.vue 28 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069
  1. <template>
  2. <div>
  3. <Header @confirmExit="confirmExit"></Header>
  4. <Transition :enter-active-class="proxy?.animate.dialog.enter" :leave-active-class="proxy?.animate.dialog.leave">
  5. <div class="time" v-show="[42].includes(examState)">{{
  6. countdownNumFormat
  7. }}</div>
  8. </Transition>
  9. <div class="main">
  10. <template v-if="isLongRun">
  11. <!--长跑-->
  12. <swiper :slides-per-view="testListArr.length >= 2 ? 2 : 1" :slides-per-group="testListArr.length >= 2 ? 2 : 1"
  13. :space-between="20">
  14. <swiper-slide v-for="(items, indexs) in testListArr " :key="indexs">
  15. <div class="main-left main-left2">
  16. <div class="trackItem">
  17. <TransitionGroup enter-active-class="animate__animated animate__fadeInRight">
  18. <div v-for="(item, index) in items" :key="indexs + '_' + index" class="li">
  19. <div class="left">
  20. <div class="track">{{ (index + 1) + (8 * indexs) }}</div>
  21. <div class="userInfo" @click="getChooseStudent(item.track)">
  22. <div class="pic pic2" v-if="item.student_id"> <img :src="item.face_pic" /></div>
  23. <div class="pic" v-else>
  24. <img src="@/assets/images/test/profilePicture.png" />
  25. </div>
  26. <div class="nameBox">
  27. <div class="name">{{ item.student_name || "未检录" }}</div>
  28. </div>
  29. </div>
  30. </div>
  31. <div class="scoreBox">
  32. <div v-if="item.timeStr" class="score">
  33. {{ item.timeStr || "-" }}
  34. </div>
  35. <div v-if="isBackRun && item.student_id" class="turns">往返次数:<span><i>{{ item.turns || "0"
  36. }}</i></span>
  37. </div>
  38. </div>
  39. <div class="menuBtn menuBtn2" v-if="examState == 43 && item.student_id">等待开始测试</div>
  40. <div class="menuBtn menuBtn2" v-if="examState == 42 && item.student_id">正在测试</div>
  41. <div class="menuBtn" v-if="examState == 3 && !item.timeStr && item.isfinish && item.student_id">
  42. 异常
  43. </div>
  44. <div class="close" @click="close(item)" v-if="examState == 41"></div>
  45. </div>
  46. </TransitionGroup>
  47. </div>
  48. </div>
  49. </swiper-slide>
  50. </swiper>
  51. </template>
  52. <template v-else>
  53. <!--短跑-->
  54. <div class="main-left">
  55. <div class="trackItem">
  56. <TransitionGroup enter-active-class="animate__animated animate__fadeInRight">
  57. <div v-for="(item, index) in faceStudentList" :key="index" class="li">
  58. <div class="left">
  59. <div class="track">{{ item.track }}</div>
  60. <div class="userInfo" @click="getChooseStudent(item.track)">
  61. <div class="pic pic2" v-if="item.student_id"> <img :src="item.face_pic" /></div>
  62. <div class="pic" v-else>
  63. <img src="@/assets/images/test/profilePicture.png" />
  64. </div>
  65. <div class="nameBox">
  66. <div class="name">{{ item.student_name || "未检录" }}</div>
  67. </div>
  68. </div>
  69. </div>
  70. <div class="scoreBox">
  71. <div v-if="item.timeStr" class="score">
  72. {{ item.timeStr || "-" }}
  73. </div>
  74. <div v-if="isBackRun && item.student_id" class="turns">往返次数:<span><i>{{ item.turns || "0"
  75. }}</i></span>
  76. </div>
  77. </div>
  78. <div class="menuBtn" v-if="examState > 3 && examState < 42" @click="getChooseStudent(item.track)">检录
  79. </div>
  80. <div class="menuBtn menuBtn2" v-if="examState == 43 && item.student_id">等待开始测试</div>
  81. <div class="menuBtn menuBtn2" v-if="examState == 42 && item.student_id">正在测试</div>
  82. <div class="menuBtn" v-if="examState == 3 && !item.timeStr && item.isfinish && item.student_id">
  83. 异常
  84. </div>
  85. </div>
  86. </TransitionGroup>
  87. </div>
  88. </div>
  89. <div class="main-right">
  90. <ReportList ref="reportListRef" :parameter="parameter" :showQRCode="false" />
  91. </div>
  92. </template>
  93. </div>
  94. <!-- <div>当前状态:({{ examState == 3 ? "初始化完成" : examState == 40 ? "创建测试" : examState == 41 ? "正在人脸识别":examState ==43 ? "停止人脸识别" : examState == 42 ? "正在测试" : "请初始化" }})</div> -->
  95. <div class="footerBtn">
  96. <template v-if="needStart">
  97. <div class="btn" @click="getOpenOneTestAndStartFace" v-if="examState == 3 || examState == 40">开始识别</div>
  98. <div class="btn" @click="getChooseStudent()" v-if="examState == 41 && isLongRun">检录</div>
  99. <div class="btn" @click="getStopFace" v-if="examState == 41">停止识别</div>
  100. <div class="btn startBtn" @click="getStartOneTest" v-if="examState == 43">开始测试</div>
  101. </template>
  102. <template v-else> </template>
  103. <div class="btn" @click="getConfirmEnd" v-if="examState == 42">{{ isLongRun ? '结 束' : '下一组' }}</div>
  104. <div class="btn" @click="getAgain" v-if="showTestAgain">再测一次</div>
  105. <div class="btn" @click="getRetestFace" v-if="examState == 43">重新识别</div>
  106. </div>
  107. <ChooseStudent ref="chooseStudentRef" :selectType="isLongRun ? 'multiple' : 'single'" @returnData="returnStudent" />
  108. </div>
  109. </template>
  110. <script setup name="TrainTest" lang="ts">
  111. import { initSpeech, speckText, speckCancel, chineseNumber } from '@/utils/speech'
  112. import { initWs, examEnds, openOneTest, startFace, stopFace, faceConfirmOnly, startOneTest, finishOneTest, closeOneTest, suspendFaceRecognitionChannels, resumeFaceRecognitionChannels } from '@/utils/ws'
  113. import dayjs from 'dayjs'
  114. import dataDictionary from "@/utils/dataDictionary"
  115. import { Swiper, SwiperSlide } from 'swiper/vue';
  116. import { Grid } from 'swiper/modules';
  117. import 'swiper/css';
  118. import 'swiper/css/grid';
  119. const { proxy } = getCurrentInstance() as any;
  120. const router = useRouter();
  121. const route = useRoute();
  122. const chooseStudentRef = ref();
  123. const reportListRef = ref();
  124. const data = reactive<any>({
  125. timerManager: {},//计时器管理
  126. parameter: {},//参数
  127. time: {
  128. testTime: 0,//时长
  129. countdownNum: 0,//计时
  130. },
  131. userInfo: {},//用户信息
  132. examState: 0,//当前状态
  133. resultId: null,//测试ID
  134. unit: "",//单位
  135. needStart: false,//是否需要按钮
  136. faceStudentList: [],//跑道和人信息
  137. currentTrack: null,//当前跑道
  138. showTestAgain: false,//再测一次按钮
  139. isLongRun: false,//是否为长跑项目
  140. isBackRun: false,//是否为折返跑项目
  141. });
  142. const { timerManager, parameter, time, userInfo, examState, resultId, faceStudentList, currentTrack, unit, needStart, showTestAgain, isLongRun, isBackRun } = toRefs(data);
  143. /**
  144. * 接收消息
  145. */
  146. const getMessage = (e: any) => {
  147. //console.log("WS响应:", e)
  148. //实时状态
  149. if (e.cmd === 'exam_status') {
  150. examState.value = e.data;
  151. }
  152. //工作站状态
  153. if (e.cmd === 'init_result') {
  154. // if (isLongRun.value) {
  155. // let num = 80;
  156. // let list: any = [];
  157. // for (let i = 1; i <= num; i++) {
  158. // list.push({ track: i });
  159. // }
  160. // faceStudentList.value = list
  161. // }
  162. }
  163. //获取跑道配置
  164. if (e.cmd === 'exam_config') {
  165. let num = e.data.num_of_tracks;
  166. let list: any = [];
  167. for (let i = 1; i <= num; i++) {
  168. list.push({ track: i });
  169. }
  170. faceStudentList.value = list
  171. }
  172. //测试违规
  173. if (e.cmd === 'warning_result') {
  174. }
  175. //后端播报语音
  176. if (e.cmd === 'return_audio_msg') {
  177. }
  178. //错误提示
  179. if (e.cmd === 'info_result') {
  180. proxy?.$modal.msgError(e.data.message);
  181. }
  182. //错误提示
  183. if (e.cmd === 'error_result') {
  184. proxy?.$modal.msgError(e.data.message);
  185. }
  186. //测试中违规提示
  187. if (e.cmd === 'warning_notify') {
  188. }
  189. //断线状态
  190. if (e.cmd === 'disconnect_request') {
  191. if (e.data.message) {
  192. speckText(e.data.message);
  193. }
  194. getExit();
  195. }
  196. //状态变更
  197. if (e.cmd === 'set_exam_state') {
  198. examState.value = e.data;
  199. if (e.data === 3) {
  200. initProject();
  201. }
  202. if (e.data === 40) {
  203. cleanData();
  204. }
  205. if (e.data == 41) {
  206. }
  207. if (e.data == 43) {
  208. }
  209. if (e.data == 42) {
  210. }
  211. }
  212. //新建测试后返回信息,获取result_id
  213. if (e.cmd === 'open_one_test_ack') {
  214. resultId.value = e.data.result_id;
  215. }
  216. //人脸识别状态
  217. if (e.cmd === 'face_check_result') {
  218. }
  219. //测试结束结果
  220. if (e.cmd === 'oneresult') {
  221. if (e.data.length) {
  222. let data = e.data;
  223. getAchievement(data)
  224. }
  225. }
  226. //结果生成完成(视频图片)
  227. if (e.cmd === 'static_urls_finished') {
  228. }
  229. //选择学生或测试结束后返回的数据
  230. if (e.cmd === 'result_info') {
  231. }
  232. };
  233. /**
  234. * 开始识别
  235. */
  236. const getOpenOneTestAndStartFace = async () => {
  237. if (examState.value > 3) {
  238. await closeOneTest();
  239. }
  240. await openOneTest();
  241. await startFace();
  242. };
  243. /**
  244. * 停止人脸识别
  245. */
  246. const getStopFace = async () => {
  247. if (examState.value != 41) {
  248. return false;
  249. }
  250. let list = faceStudentList.value.filter((item: any) => {
  251. return item.student_id;
  252. })
  253. if (!list.length) {
  254. proxy?.$modal.msgWarning("请选择人员!");
  255. return false;
  256. }
  257. await stopFace();
  258. getFaceConfirmOnly();
  259. };
  260. /**
  261. * 确定人脸信息
  262. */
  263. const getFaceConfirmOnly = (data?: any) => {
  264. let list = [];
  265. if (data) {
  266. faceStudentList.value = data;
  267. list = data.filter((item: any) => {
  268. return item.student_id;
  269. });
  270. } else {
  271. list = faceStudentList.value.filter((item: any) => {
  272. return item.student_id;
  273. });
  274. }
  275. //短跑播报跑道
  276. if (data && !isLongRun.value) {
  277. let speechList = list.map((item: any) => {
  278. return `第${item.track == 2 ? '二' : item.track}道, ${item.student_name}`
  279. }).join();
  280. speckText(speechList);
  281. }
  282. //长跑自动拼接跑道
  283. if (isLongRun.value) {
  284. list = list.map((item: any, index: any) => {
  285. item.track = index + 1;
  286. return item;
  287. })
  288. }
  289. faceConfirmOnly(list, () => {
  290. });
  291. };
  292. /**
  293. * 重新识别
  294. */
  295. const getRetestFace = () => {
  296. proxy?.$modal.confirm("确定重新识别吗?").then(() => {
  297. if (needStart.value == false) {
  298. //自动流程项目重新识别直接返回3
  299. closeOneTest();
  300. } else {
  301. //手动流程项目重新识别43返回41,42返回3
  302. if (examState.value == 43) {
  303. cleanData();
  304. startFace();
  305. } else {
  306. closeOneTest();
  307. }
  308. }
  309. }).finally(() => {
  310. });
  311. };
  312. /**
  313. * 开始测试
  314. */
  315. const getStartOneTest = () => {
  316. if (examState.value != 43) {
  317. return false;
  318. }
  319. let list = faceStudentList.value.filter((item: any) => {
  320. return item.student_id;
  321. })
  322. if (!list.length) {
  323. proxy?.$modal.msgWarning("请选择人员!");
  324. return false;
  325. }
  326. //停止播报;
  327. speckCancel()
  328. // 和工作站搭配时差版
  329. //提前发送开始的时间
  330. let advanceTime = 1000;
  331. //各就位+枪声是7秒左右,5.26秒是播枪声
  332. let myTime = 7010;
  333. //播放音频和遮罩
  334. let myText = "各就位,预备!";
  335. let loading = ElLoading.service({ text: myText, background: 'rgba(0, 0, 0, 0.8)', customClass: `sports ${parameter.value.project}` });
  336. speckText(myText);
  337. setTimeout(() => {
  338. startOneTest(data == null, () => {
  339. });
  340. loading?.close();
  341. }, advanceTime)
  342. setTimeout(() => {
  343. //显示再测一次按钮
  344. showTestAgain.value = true;
  345. //计时项目才开
  346. if (needStart.value == true) {
  347. //时间为0的为正计时,大于0的为倒计时
  348. if (time.value.testTime == 0) {
  349. getCounting("+");
  350. } else {
  351. getCounting("-");
  352. }
  353. } else {
  354. speckText("请开始测试");
  355. }
  356. }, myTime)
  357. // 立即开版本
  358. // startOneTest(data == null, () => {
  359. // //显示再测一次按钮
  360. // showTestAgain.value = true;
  361. // //计时项目才开
  362. // if (needStart.value == true) {
  363. // //时间为0的为正计时,大于0的为倒计时
  364. // if (time.value.testTime == 0) {
  365. // getCounting("+");
  366. // } else {
  367. // getCounting("-");
  368. // }
  369. // } else {
  370. // speckText("请开始测试");
  371. // }
  372. // })
  373. };
  374. /**
  375. * 再测一次
  376. */
  377. const getAgain = async () => {
  378. let loading = ElLoading.service({ text: '请稍等...', background: 'rgba(0, 0, 0, 0.8)', customClass: `sports ${parameter.value.project}` });
  379. //预存测试人员
  380. let student = faceStudentList.value.map((item: any) => {
  381. let obj = {
  382. face_pic: item.face_pic,
  383. student_id: item.student_id,
  384. student_name: item.student_name,
  385. gender: item.gender,
  386. track: item.track,
  387. }
  388. return obj;
  389. });
  390. //测试中
  391. if (examState.value == 42) {
  392. await finishOneTest();
  393. }
  394. //其他状态
  395. if (examState.value > 3) {
  396. await closeOneTest();
  397. }
  398. //重新走一次流程
  399. await openOneTest();
  400. await startFace();
  401. await stopFace();
  402. getFaceConfirmOnly(student);
  403. loading?.close();
  404. let loadingTime = setTimeout(() => {
  405. loading?.close();
  406. clearTimeout(loadingTime);
  407. }, 10000);
  408. };
  409. /**
  410. * 确认退出
  411. */
  412. const getConfirmEnd = async () => {
  413. let txt = isLongRun.value ? '是否结束' : '是否开始下一组?'
  414. await proxy?.$modal.confirm(txt);
  415. let loading = ElLoading.service({ text: '请稍等...', background: 'rgba(0, 0, 0, 0.8)', customClass: `sports ${parameter.value.project}` });
  416. //测试中
  417. if (examState.value == 42) {
  418. await finishOneTest();
  419. }
  420. //其他状态
  421. if (examState.value > 3) {
  422. await closeOneTest();
  423. }
  424. if (!isLongRun.value) {
  425. //重新走一次流程
  426. await openOneTest();
  427. await startFace();
  428. }
  429. loading?.close();
  430. let loadingTime = setTimeout(() => {
  431. loading?.close();
  432. clearTimeout(loadingTime);
  433. }, 10000);
  434. };
  435. /**
  436. * 确认退出
  437. */
  438. const confirmExit = () => {
  439. proxy?.$modal.confirm("确定退出吗?").then(() => {
  440. getExit();
  441. }).finally(() => {
  442. });
  443. };
  444. /**
  445. * 退出
  446. */
  447. const getExit = () => {
  448. getClearTimer();//清除计时器
  449. examEnds();//通知工作站关闭
  450. speckCancel()//停止播报;
  451. router.push({ path: '/' });//跳转
  452. };
  453. /**
  454. * 清空定时任务
  455. */
  456. const getClearTimer = (data?: any) => {
  457. if (data) {
  458. //清除指定
  459. clearInterval(timerManager.value[data])
  460. timerManager.value[data] = null;
  461. } else {
  462. //清除全部
  463. for (let key in timerManager.value) {
  464. if (timerManager.value.hasOwnProperty(key)) {
  465. clearInterval(timerManager.value[key])
  466. timerManager.value[key] = null;
  467. }
  468. }
  469. }
  470. };
  471. /**
  472. * 选择学生
  473. */
  474. const getChooseStudent = (track?: number) => {
  475. if (examState.value < 41) {
  476. if (needStart.value) {
  477. proxy?.$modal.msgWarning("请点击开始识别");
  478. } else {
  479. proxy?.$modal.msgWarning("请稍等...");
  480. }
  481. return false;
  482. }
  483. if (examState.value == 43) {
  484. proxy?.$modal.msgWarning("请点击重新识别");
  485. return false;
  486. }
  487. currentTrack.value = track;
  488. chooseStudentRef.value.open();
  489. };
  490. /**
  491. * 返回被选学生
  492. */
  493. const returnStudent = (data: any) => {
  494. if (isLongRun.value) {
  495. //长跑
  496. longStudent(data)
  497. } else {
  498. //短跑
  499. sprintStudent(data)
  500. }
  501. };
  502. /**
  503. * 处理短跑返回被选学生
  504. */
  505. const sprintStudent = (data: any) => {
  506. let obj = {
  507. result_id: resultId.value,
  508. face_pic: data.face_pic || data.logo_url,
  509. student_id: data.id,
  510. student_name: data.name,
  511. gender: data.gender,
  512. }
  513. //可能已检录的先清除
  514. let oldIndex = faceStudentList.value.findIndex((item: any) => {
  515. return data.student_id == item.student_id;
  516. })
  517. if (oldIndex != -1) {
  518. faceStudentList.value[oldIndex] = { track: faceStudentList.value[oldIndex].track };
  519. }
  520. //赋值
  521. let newIndex = faceStudentList.value.findIndex((item: any) => {
  522. return currentTrack.value == item.track;
  523. })
  524. faceStudentList.value[newIndex] = { ...faceStudentList.value[newIndex], ...obj };
  525. if (!isLongRun.value) {
  526. speckText(`第${currentTrack.value == 2 ? '二' : currentTrack.value}道, ${data.name}`);
  527. }
  528. currentTrack.value = null;
  529. };
  530. /**
  531. * 处理长跑返回被选学生
  532. */
  533. const longStudent = (data: any) => {
  534. let ids = faceStudentList.value.map((item: any, index: any) => {
  535. return item.student_id;
  536. });
  537. //排除掉已经存在的
  538. let newList = data.filter((item: any, index: any) => {
  539. return !ids.includes(item.id)
  540. })
  541. let list = newList.map((item: any, index: any) => {
  542. let obj = {
  543. result_id: resultId.value,
  544. face_pic: item.face_pic || item.logo_url,
  545. student_id: item.id,
  546. student_name: item.name,
  547. gender: item.gender,
  548. }
  549. return obj;
  550. })
  551. faceStudentList.value.push(...list)
  552. };
  553. /**
  554. * 清除历史记录
  555. */
  556. const cleanData = () => {
  557. time.value.countdownNum = time.value.testTime;
  558. showTestAgain.value = false;
  559. if (isLongRun.value) {
  560. faceStudentList.value = [];
  561. } else {
  562. //重置全部
  563. let list = faceStudentList.value.map((item: any) => {
  564. let obj = {
  565. student_id: null,
  566. track: item.track,
  567. isfinish: false,
  568. }
  569. return obj;
  570. })
  571. faceStudentList.value = list;
  572. }
  573. };
  574. /**
  575. * 自动初始化项目
  576. */
  577. const initProject = () => {
  578. //停止计时
  579. getClearTimer("countdownTimer");
  580. };
  581. /**
  582. * 时间转换
  583. */
  584. const countdownNumFormat = computed(() => {
  585. return proxy?.$utils.timeFormat(time.value.countdownNum);
  586. });
  587. /**
  588. * 倒计时
  589. */
  590. const getCounting = (type: string) => {
  591. timerManager.value.countdownTimer = setInterval(() => {
  592. //正计时
  593. if (type == "+") {
  594. time.value.countdownNum++;
  595. }
  596. //倒计时
  597. if (type == "-") {
  598. if (time.value.countdownNum <= 0) {
  599. getClearTimer("countdownTimer");
  600. } else {
  601. time.value.countdownNum--;
  602. }
  603. }
  604. }, 1000);
  605. };
  606. /**
  607. * 成绩
  608. */
  609. const getAchievement = (data: any) => {
  610. if (!data) {
  611. return;
  612. }
  613. let dataList = data.map((item: any) => {
  614. if (item.track && item.times != 0) {
  615. if (typeof item.times === "string") {
  616. item.times = JSON.parse(item.times);
  617. }
  618. if (typeof item.times === "object") {
  619. item.timeStr = proxy?.$utils.runTime(item.times[item.times.length - 1], false, isLongRun.value);
  620. item.turns = Math.floor(item.times.length / 2);
  621. if (item.times.length) {
  622. item.timeTotal = item.times.reduce((total: any, num: any) => {
  623. return total + Number(num);
  624. });
  625. } else {
  626. item.timeTotal = 0;
  627. }
  628. } else {
  629. item.timeStr = proxy?.$utils.runTime(item.times, false, isLongRun.value);
  630. item.turns = 0;
  631. item.timeTotal = 0;
  632. }
  633. }
  634. return item
  635. });
  636. faceStudentList.value.forEach((item: any, index: any) => {
  637. let obj = dataList.find((items: any) => {
  638. return parseInt(item.track) == parseInt(items.track);
  639. })
  640. //加this.result_id == obj.result_id是避免抢跑的时候上一把成绩没返回会被覆盖的可能,要新的ID才能赋值
  641. if (obj != undefined && resultId.value == obj.result_id) {
  642. if (
  643. parseInt(obj.track) === parseInt(item.track) ||
  644. isLongRun.value
  645. ) {
  646. faceStudentList.value[index].timeStr = obj.timeStr
  647. faceStudentList.value[index].times = obj.times
  648. faceStudentList.value[index].timeTotal = obj.timeTotal
  649. faceStudentList.value[index].tid_num = obj.tid_num
  650. faceStudentList.value[index].score = obj.score
  651. faceStudentList.value[index].isfinish = obj.isfinish
  652. faceStudentList.value[index].turns = obj.turns
  653. faceStudentList.value[index].result_id = obj.result_id
  654. } else {
  655. faceStudentList.value[index].trackFalse = true;
  656. faceStudentList.value[index].timeStr = "跑道错误";
  657. }
  658. }
  659. });
  660. };
  661. /**
  662. * 当天成绩列表
  663. */
  664. const close = (data: any) => {
  665. faceStudentList.value = JSON.parse(JSON.stringify(faceStudentList.value)).filter((item: any) => {
  666. return item.student_id != data.student_id;
  667. })
  668. };
  669. /**
  670. * 将测试列表分页
  671. */
  672. const testListArr: any = computed(() => {
  673. let list: any = [];
  674. let num = 8;
  675. let myLength = Math.ceil(faceStudentList.value.length / num);
  676. for (let i = 0; i < myLength; i++) {
  677. list[i] = [];
  678. for (let j = 0; j < faceStudentList.value.length; j++) {
  679. if (j >= i * num && j < (i + 1) * num) {
  680. list[i].push(faceStudentList.value[j])
  681. }
  682. }
  683. }
  684. console.log("111", list)
  685. return list;
  686. });
  687. /**
  688. * 长跑名单
  689. */
  690. const faceStudentListLongRun: any = computed(() => {
  691. if (isLongRun.value) {
  692. return false;
  693. }
  694. console.log("嘻嘻嘻0", faceStudentList.value)
  695. let list = faceStudentList.value.filter((item: any) => {
  696. return item.student_id;
  697. });
  698. //按圈数分组排序
  699. let arr: any = [];
  700. JSON.parse(JSON.stringify(list)).forEach((item: any, index: number) => {
  701. let myIndex = 0;
  702. if (item.times != undefined) {
  703. myIndex = item.times.length;
  704. }
  705. if (arr[myIndex] == undefined) {
  706. arr[myIndex] = [];
  707. }
  708. arr[myIndex].push(item);
  709. if (arr[myIndex]) {
  710. arr[myIndex].sort((a: any, b: any) => {
  711. let val1 = a.timeTotal;
  712. let val2 = b.timeTotal;
  713. return val1 - val2;
  714. });
  715. }
  716. })
  717. console.log("嘻嘻嘻", arr);
  718. if (arr.length) {
  719. console.log("嘻嘻嘻1")
  720. let myList = [];
  721. for (let i = arr.length - 1; i >= 0; --i) {
  722. if (arr.hasOwnProperty(i)) {
  723. myList.push(...arr[i]);
  724. }
  725. }
  726. console.log("嘻嘻嘻3", list)
  727. return myList;
  728. } else {
  729. console.log("嘻嘻嘻2", list)
  730. return list;
  731. }
  732. });
  733. onBeforeMount(() => {
  734. parameter.value = route.query;
  735. let project = parameter.value.project;
  736. let area = parameter.value.area;
  737. parameter.value.examId = `${project}_${area}`; //项目+区
  738. if (parameter.value.time) {
  739. time.value.testTime = parameter.value.time
  740. }
  741. time.value.countdownNum = time.value.testTime;
  742. let myInfo: any = localStorage.getItem("userInfo");
  743. userInfo.value = JSON.parse(myInfo);
  744. let dic: any = dataDictionary;
  745. unit.value = dic.unit[project];
  746. if (parameter.value.gesture == 'true') {
  747. parameter.value.gesture = true
  748. } else {
  749. parameter.value.gesture = false
  750. }
  751. //是否折返跑
  752. if (project.slice(0, 3) === "run" &&
  753. project.includes("x")) {
  754. isBackRun.value = true
  755. }
  756. //是否长跑
  757. if (project.replace('run', '') > 799) {
  758. isLongRun.value = true
  759. }
  760. //需要开始按钮的项目
  761. needStart.value = true;
  762. //加载WS
  763. initWs({ parameter: parameter.value, testTime: time.value.testTime }, (data: any) => {
  764. getMessage(data);
  765. });
  766. initSpeech();
  767. })
  768. onUnmounted(() => {
  769. getExit();
  770. })
  771. </script>
  772. <style scoped lang="scss">
  773. $topPadding: 5.19rem;
  774. $waiPadding: 6.51rem;
  775. .time {
  776. width: 20vh;
  777. height: 20vh;
  778. line-height: 20vh;
  779. border-radius: 50%;
  780. color: #FF9402;
  781. font-size: 7.5vh;
  782. text-align: center;
  783. background-image: url("@/assets/images/test/time.png");
  784. background-position: center;
  785. background-repeat: no-repeat;
  786. background-size: 100% 100%;
  787. position: absolute;
  788. right: 50%;
  789. top: -4.5vh;
  790. margin-right: -10vh;
  791. font-family: 'Saira-BlackItalic';
  792. }
  793. .main {
  794. width: calc(100% - ($waiPadding * 2));
  795. height: 70vh;
  796. padding-top: 10rem;
  797. margin: 0 auto;
  798. display: flex;
  799. justify-content: space-between;
  800. overflow: hidden;
  801. .main-left {
  802. width: 71.5%;
  803. height: 100%;
  804. display: flex;
  805. background: linear-gradient(58deg, #092941 -85%, #2A484B 96%);
  806. box-shadow: inset 0px 1px 0px 2px rgba(255, 255, 255, 0.4);
  807. border-radius: 1.6rem;
  808. .trackItem {
  809. width: 100%;
  810. overflow-y: scroll;
  811. padding: 0px 10px;
  812. &::-webkit-scrollbar {
  813. width: 10px;
  814. }
  815. &::-webkit-scrollbar-thumb {
  816. border-width: 2px;
  817. border-radius: 4px;
  818. border-style: dashed;
  819. border-color: transparent;
  820. background-color: rgba(26, 62, 78, 0.9);
  821. background-clip: padding-box;
  822. }
  823. &::-webkit-scrollbar-button:hover {
  824. border-radius: 6px;
  825. background: rgba(26, 62, 78, 1);
  826. }
  827. .li {
  828. display: flex;
  829. align-items: center;
  830. justify-content: space-between;
  831. border-bottom: 1px solid #475557;
  832. height: calc(100%/8);
  833. box-sizing: border-box;
  834. padding: 0px 20px 0px 50px;
  835. .left {
  836. display: flex;
  837. align-items: center;
  838. .track {
  839. width: 5rem;
  840. color: #13ED84;
  841. font-size: 2rem;
  842. font-family: 'Saira-ExtraBold';
  843. margin-right: 1rem
  844. }
  845. .pic {
  846. width: 6.7vh;
  847. height: 6.7vh;
  848. border-radius: 50%;
  849. display: flex;
  850. justify-content: center;
  851. align-items: center;
  852. overflow: hidden;
  853. margin-right: 15px;
  854. img {
  855. width: 100%;
  856. }
  857. }
  858. .pic2 {
  859. box-sizing: border-box;
  860. border: 1px solid rgba(255, 255, 255, 0.5);
  861. }
  862. .userInfo {
  863. display: flex;
  864. justify-content: flex-start;
  865. align-items: center;
  866. .nameBox {
  867. width: 8rem;
  868. .name {
  869. font-size: 1.5rem;
  870. color: #ffffff;
  871. }
  872. }
  873. }
  874. }
  875. .scoreBox {
  876. display: flex;
  877. align-items: center;
  878. .score {
  879. color: #ffffff;
  880. font-size: 2rem;
  881. font-family: 'Saira-ExtraBold';
  882. }
  883. .turns {
  884. margin-left: 3rem;
  885. color: #ffffff;
  886. display: flex;
  887. align-items: center;
  888. span {
  889. font: 1.5rem;
  890. margin-left: 5px;
  891. }
  892. i {
  893. font-style: normal;
  894. font-size: 1.8rem;
  895. font-family: 'Saira-ExtraBold';
  896. }
  897. }
  898. }
  899. .menuBtn {
  900. width: auto;
  901. padding: 0 1.2rem;
  902. height: 2.8rem;
  903. line-height: 2.8rem;
  904. border-radius: 1.4rem;
  905. color: #ffffff;
  906. font-size: 1.5rem;
  907. background: linear-gradient(180deg, #FFB200 0%, #ED7905 72%);
  908. box-shadow: inset 0px 1px 0px 1px rgba(255, 255, 255, 0.5);
  909. display: flex;
  910. align-items: center;
  911. cursor: pointer;
  912. }
  913. .menuBtn2 {
  914. color: #1A293A;
  915. background: radial-gradient(159% 126% at 5% 93%, #8EFFA9 0%, #07FFE7 100%);
  916. box-shadow: 1px 1px 1px 1px rgba(0, 0, 0, 0.1874), inset 0px 1px 0px 2px rgba(255, 255, 255, 0.5577);
  917. }
  918. .close {
  919. width: 2rem;
  920. height: 2rem;
  921. box-sizing: border-box;
  922. border: 1px solid #979797;
  923. background-image: url("@/assets/images/common/close.png");
  924. background-position: center;
  925. background-repeat: no-repeat;
  926. background-size: 45% 45%;
  927. background-color: rgba(216, 216, 216, 0.8);
  928. border-radius: 50%;
  929. transition: all 0.5s;
  930. cursor: pointer;
  931. &:hover {
  932. transform: rotate(180deg);
  933. background-color: rgba(216, 216, 216, 1);
  934. }
  935. }
  936. }
  937. }
  938. }
  939. .main-left2 {
  940. width: 100%;
  941. .trackItem {
  942. .li {
  943. .scoreBox {
  944. .score {
  945. font-size: 1.8rem;
  946. }
  947. }
  948. }
  949. }
  950. }
  951. .main-right {
  952. width: 27%;
  953. border-radius: 1.6rem;
  954. background: linear-gradient(29deg, #092941 -82%, #2A484B 94%);
  955. box-shadow: inset 0px 1px 0px 2px rgba(255, 255, 255, 0.4);
  956. display: flex;
  957. flex-direction: column;
  958. overflow: hidden;
  959. }
  960. }
  961. .footerBtn {
  962. width: 100%;
  963. padding: 0 calc(13.02rem /2);
  964. box-sizing: border-box;
  965. position: fixed;
  966. bottom: 3vh;
  967. display: flex;
  968. justify-content: end;
  969. .btn {
  970. width: 14.6vw;
  971. height: 6.1vh;
  972. line-height: 6.1vh;
  973. font-size: 3vh;
  974. color: #1A293A;
  975. text-align: center;
  976. border-radius: 1vh;
  977. margin-left: 20px;
  978. cursor: pointer;
  979. background: radial-gradient(159% 126% at 5% 93%, #8EFFA9 0%, #07FFE7 100%);
  980. box-shadow: 1px 1px 1px 1px rgba(0, 0, 0, 0.1874), inset 1px 1px 1px 1px rgba(255, 255, 255, 0.5577);
  981. &:hover {
  982. background: #8EFFA9;
  983. }
  984. }
  985. .startBtn {
  986. color: #ffffff;
  987. background: radial-gradient(159% 126% at 5% 93%, #F99F02 0%, #ED7905 100%);
  988. box-shadow: 1px 1px 1px 1px rgba(0, 0, 0, 0.1874), inset 1px 1px 1px 1px rgba(255, 255, 255, 0.5577);
  989. &:hover {
  990. background: #F99F02;
  991. }
  992. }
  993. }
  994. </style>