run.vue 31 KB

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