run.vue 31 KB

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