multiple.vue 50 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958
  1. <template>
  2. <div>
  3. <Header @confirmExit="confirmExit" @setMusic="setMusic"></Header>
  4. <Transition :enter-active-class="proxy?.animate.dialog.enter" :leave-active-class="proxy?.animate.dialog.leave">
  5. <div
  6. class="time"
  7. :class="{ 'time1': styleType == 1, 'time2': styleType == 2, 'time3': styleType == 3, 'time4': styleType == 4, }"
  8. v-show="(needStart && [42].includes(examState)) || (['jumprope', 'jumpingjack', 'highknees', 'situp'].includes(parameter.project) && [42].includes(examState))"
  9. >
  10. {{
  11. time.countdownNum
  12. }}
  13. </div>
  14. </Transition>
  15. <div class="main">
  16. <div
  17. class="testBox"
  18. :class="{ 'testBox1': styleType == 1, 'testBox2': styleType == 2, 'testBox3': styleType == 3, 'testBox4': styleType == 4 }"
  19. >
  20. <div
  21. class="ul"
  22. :class="{ 'overlap': (examState == 43 && time.ready) || [42].includes(examState) || (showTestAgain && ![41].includes(examState)), 'ready': [41].includes(examState), 'hands': parameter.gesture }"
  23. v-for="(items, indexs) in testListArr "
  24. :key="indexs"
  25. >
  26. <MultipleItem
  27. :ref="(el: any) => { multipleItemRef(el, item.itemNumber, item.area) }"
  28. v-for="(item, index) in items"
  29. :query="parameter"
  30. :area="item.area"
  31. :key="index"
  32. @returnData="returnData"
  33. @getChooseStudent="getChooseStudent"
  34. :examState="examState"
  35. :needStart="needStart"
  36. :styleType="styleType"
  37. :parentTrainWsMethod="{ initWs, examEnds, openOneTest, startFace, stopFace, faceConfirmOnly, startOneTest, finishOneTest, closeOneTest, suspendFaceRecognitionChannels, resumeFaceRecognitionChannels }"
  38. :parentSpeechMethod="{ initSpeech, speckText, playMusic, controlMusic, speckCancel, chineseNumber }"
  39. />
  40. </div>
  41. </div>
  42. </div>
  43. <div class="footerBtn">
  44. <template v-if="needStart">
  45. <div class="btn" @click="getAgain" v-if="examState == 42 || showTestAgain">再测一次</div>
  46. <div class="btn" @click="getOpenOneTestAndStartFace" v-if="examState < 41">开始识别</div>
  47. <div class="btn" @click="getStopFace" v-if="examState == 41">停止人脸识别</div>
  48. <div class="btn startBtn" @click="getReady" v-if="examState == 43">开始测试</div>
  49. <div class="btn" @click="getAllRetestFace" v-if="examState == 43 || examState == 42">重新识别</div>
  50. </template>
  51. <template v-else>
  52. <template v-if="examState == 43">
  53. <div class="btn startBtn" @click="getReady" v-if="['jumprope', 'jumpingjack', 'highknees', 'situp'].includes(parameter.project)">
  54. 开始测试
  55. </div>
  56. <div class="btn startBtn" @click="getStartOneTest" v-else>开始测试</div>
  57. </template>
  58. </template>
  59. <div class="btn" :class="{ testRecordBtn: showReportList }" @click="getTestRecord" v-if="![43, 42].includes(examState)">
  60. {{ showReportList ? '关闭记录' : '测试记录' }}
  61. </div>
  62. <div class="btn exitBtn" @click="confirmExit">退 出</div>
  63. <!--测试记录开始-->
  64. <div class="mask mask2" v-show="showReportList" @click="getTestRecord"></div>
  65. <Transition :enter-active-class="proxy?.animate.report.enter" :leave-active-class="proxy?.animate.report.leave">
  66. <div class="main-right" v-show="showReportList">
  67. <ReportList ref="reportListRef" :parameter="parameter" :showQRCode="true" />
  68. </div>
  69. </Transition>
  70. <!--测试记录结束-->
  71. </div>
  72. <!--倒计时开始-->
  73. <div>
  74. <Transition :enter-active-class="proxy?.animate.mask.enter" :leave-active-class="proxy?.animate.mask.leave">
  75. <div class="mask" v-show="examState == 43 && time.ready"></div>
  76. </Transition>
  77. <Transition :enter-active-class="proxy?.animate.face.enter" :leave-active-class="proxy?.animate.face.leave">
  78. <div class="confirmDiaBg" v-show="examState == 43 && time.ready">
  79. <div class="confirmDiaWindow">
  80. <div class="readyBox">
  81. <div class="lable">倒计时</div>
  82. <div class="value" :class="{ 'transparent': time.ready > 5 }">{{ time.ready }}</div>
  83. </div>
  84. </div>
  85. </div>
  86. </Transition>
  87. </div>
  88. <!--倒计时结束-->
  89. <ChooseStudent ref="chooseStudentRef" @returnData="returnStudent" />
  90. </div>
  91. </template>
  92. <script setup name="Multiple" lang="ts">
  93. import useAppStore from '@/store/modules/app';
  94. import { useWs } from '@/utils/trainWs';
  95. // import { initWs, examEnds, openOneTest, startFace, stopFace, faceConfirmOnly, startOneTest, finishOneTest, closeOneTest, suspendFaceRecognitionChannels, resumeFaceRecognitionChannels } from '@/utils/ws'
  96. import { initSpeech, speckText, playMusic, controlMusic, speckCancel, chineseNumber } from '@/utils/speech';
  97. import { useWebSocket } from '@/utils/handWs';
  98. const { handWs, startDevice, startHand, stateHand } = useWebSocket();
  99. const {
  100. initWs,
  101. examEnds,
  102. openOneTest,
  103. startFace,
  104. stopFace,
  105. faceConfirmOnly,
  106. startOneTest,
  107. finishOneTest,
  108. closeOneTest,
  109. suspendFaceRecognitionChannels,
  110. resumeFaceRecognitionChannels
  111. } = useWs();
  112. const { proxy } = getCurrentInstance() as any;
  113. const router = useRouter();
  114. const route = useRoute();
  115. const reportListRef = ref();
  116. const chooseStudentRef = ref();
  117. const data = reactive<any>({
  118. timerManager: {}, //计时器管理
  119. parameter: {}, //参数
  120. time: {
  121. testTime: 60, //时长
  122. countdownNum: 0, //计时
  123. ready: 0, //预备
  124. exit: 0 //退出倒计时
  125. },
  126. userInfo: {}, //用户信息
  127. examState: 0, //当前状态
  128. needStart: false, //是否需要按钮
  129. showTestAgain: false, //再测一次按钮
  130. testList: [], //获取区列表
  131. multipleItemRefList: [], //获取区列表
  132. styleType: null, //展示样式1:1-5个,2:6-10个,3:10个以上,4:1-4个,
  133. showReportList: false, //显示隐藏测试记录
  134. exitStatus: 0, //退出响应次数
  135. sid: null, //WS的id
  136. chooseStudentArea: {}, //弹出手动选择对应的区号
  137. deviceInfo: {}, //设备信息
  138. musicList: [] //音乐列表
  139. });
  140. const {
  141. timerManager,
  142. parameter,
  143. time,
  144. userInfo,
  145. examState,
  146. needStart,
  147. showTestAgain,
  148. testList,
  149. multipleItemRefList,
  150. styleType,
  151. showReportList,
  152. exitStatus,
  153. sid,
  154. chooseStudentArea,
  155. deviceInfo,
  156. musicList
  157. } = toRefs(data);
  158. /**
  159. * 创建组件实例
  160. */
  161. const multipleItemRef = (el: any, index: number, area: any) => {
  162. multipleItemRefList.value[index - 1] = el;
  163. };
  164. /**
  165. * 开始识别
  166. */
  167. const getOpenOneTestAndStartFace = () => {
  168. cleanData();
  169. examState.value = 41;
  170. for (let i = 0; i < multipleItemRefList.value.length; i++) {
  171. multipleItemRefList.value[i].getOpenOneTestAndStartFace();
  172. }
  173. };
  174. /**
  175. * 停止人脸识别
  176. */
  177. const getStopFace = async () => {
  178. let flag = false;
  179. for (let i = 0; i < testList.value.length; i++) {
  180. if (testList.value[i] && testList.value[i].faceCheckStu?.student_id) {
  181. flag = true;
  182. }
  183. }
  184. if (!flag) {
  185. proxy?.$modal.msgWarning('请选择人员!');
  186. return false;
  187. }
  188. examState.value = 43;
  189. for (let i = 0; i < multipleItemRefList.value.length; i++) {
  190. multipleItemRefList.value[i].getStopFace();
  191. }
  192. };
  193. /**
  194. * 开始测试
  195. */
  196. const getStartOneTest = () => {
  197. if (examState.value != 43) {
  198. return false;
  199. }
  200. let flag = false;
  201. for (let i = 0; i < testList.value.length; i++) {
  202. if (testList.value[i] && testList.value[i].faceCheckStu?.student_id) {
  203. flag = true;
  204. }
  205. }
  206. if (!flag) {
  207. proxy?.$modal.msgWarning('请选择人员!');
  208. return false;
  209. }
  210. getClearTimer('readyTimer');
  211. time.value.ready = 0;
  212. examState.value = 42;
  213. for (let i = 0; i < multipleItemRefList.value.length; i++) {
  214. if (testList.value[i].examState == 41) {
  215. //正在识别的停止识别
  216. multipleItemRefList.value[i].getStopFace();
  217. }
  218. if (testList.value[i].examState == 43) {
  219. multipleItemRefList.value[i].getStartOneTest();
  220. }
  221. }
  222. speckText('哨声');
  223. if (parameter.value.music && musicList.value.length) {
  224. let obj = musicList.value.find((item: any) => {
  225. return item.id == parameter.value.music;
  226. });
  227. if (obj != undefined) {
  228. playMusic(obj.url);
  229. }
  230. }
  231. //显示再测一次按钮
  232. showTestAgain.value = true;
  233. //时间为0的为正计时,大于0的为倒计时
  234. if (time.value.testTime == 0) {
  235. getCounting('+');
  236. } else {
  237. getCounting('-');
  238. }
  239. };
  240. /**
  241. * 再测一次
  242. */
  243. const getAgain = async () => {
  244. let txt = '是否再测一次?';
  245. await proxy?.$modal.confirm(txt);
  246. getClearTimer();
  247. let loading = ElLoading.service({ text: '请稍等...', background: 'rgba(0, 0, 0, 0.8)', customClass: `sports ${parameter.value.project}` });
  248. cleanData();
  249. examState.value = 41;
  250. for (let i = 0; i < multipleItemRefList.value.length; i++) {
  251. multipleItemRefList.value[i].getAgain();
  252. }
  253. timerManager.value.againTimer = setInterval(() => {
  254. if (examState.value == 43) {
  255. getClearTimer('againTimer');
  256. loading?.close();
  257. }
  258. }, 300);
  259. let loadingTime = setTimeout(() => {
  260. if (examState.value <= 41) {
  261. loading?.close();
  262. clearTimeout(loadingTime);
  263. getClearTimer('againTimer');
  264. }
  265. }, 10000);
  266. };
  267. /**
  268. * 重新识别
  269. */
  270. const getAllRetestFace = async () => {
  271. let loading = ElLoading.service({ text: '请稍等...', background: 'rgba(0, 0, 0, 0.8)', customClass: `sports ${parameter.value.project}` });
  272. showTestAgain.value = false;
  273. examState.value = 3;
  274. for (let i = 0; i < multipleItemRefList.value.length; i++) {
  275. multipleItemRefList.value[i].getAllRetestFace();
  276. }
  277. timerManager.value.allRetestFaceTimer = setInterval(() => {
  278. if (examState.value == 41) {
  279. getClearTimer('allRetestFaceTimer');
  280. loading?.close();
  281. }
  282. }, 300);
  283. let loadingTime = setTimeout(() => {
  284. if (examState.value <= 3) {
  285. loading?.close();
  286. clearTimeout(loadingTime);
  287. getClearTimer('allRetestFaceTimer');
  288. }
  289. }, 10000);
  290. };
  291. /**
  292. * 确认退出
  293. */
  294. const confirmExit = () => {
  295. let handcontroller_id = parameter.value.handcontroller;
  296. proxy?.$modal
  297. .confirm(handcontroller_id ? `请保持两秒确认退出` : `确定退出吗?`)
  298. .then(() => {
  299. getExit();
  300. })
  301. .finally(() => {});
  302. };
  303. /**
  304. * 退出
  305. */
  306. const getExit = () => {
  307. getClearTimer(); //清除计时器
  308. examEnds(); //通知工作站关闭
  309. speckCancel(); //停止播报
  310. window.onbeforeunload = null; //移除事件处理器
  311. let handcontroller_id = parameter.value.handcontroller;
  312. if (handcontroller_id) {
  313. router.push({ path: '/gesture' });
  314. } else {
  315. if (parameter.value.taskId) {
  316. router.push({ path: '/test' });
  317. } else {
  318. router.push({ path: '/train' });
  319. }
  320. }
  321. };
  322. /**
  323. * 清空定时任务
  324. */
  325. const getClearTimer = (data?: any) => {
  326. if (data) {
  327. //清除指定
  328. clearInterval(timerManager.value[data]);
  329. timerManager.value[data] = null;
  330. } else {
  331. //清除全部
  332. for (let key in timerManager.value) {
  333. if (timerManager.value.hasOwnProperty(key)) {
  334. clearInterval(timerManager.value[key]);
  335. timerManager.value[key] = null;
  336. }
  337. }
  338. }
  339. };
  340. /**
  341. * 清除历史记录
  342. */
  343. const cleanData = () => {
  344. getClearTimer('countdownTimer');
  345. time.value.countdownNum = time.value.testTime;
  346. showTestAgain.value = false;
  347. };
  348. /**
  349. * 倒计时
  350. */
  351. const getCounting = (type: string) => {
  352. timerManager.value.countdownTimer = setInterval(() => {
  353. //正计时
  354. if (type == '+') {
  355. time.value.countdownNum++;
  356. }
  357. //倒计时
  358. if (type == '-') {
  359. if (time.value.countdownNum <= 0) {
  360. getClearTimer('countdownTimer');
  361. } else {
  362. time.value.countdownNum--;
  363. }
  364. }
  365. }, 1000);
  366. };
  367. /**
  368. * 子组件选择学生
  369. */
  370. const getChooseStudent = (data: any) => {
  371. console.log('data', data);
  372. chooseStudentArea.value = data;
  373. chooseStudentRef.value.open();
  374. };
  375. /**
  376. * 返回被选学生
  377. */
  378. const returnStudent = (data: any) => {
  379. console.log('data', data);
  380. let area = chooseStudentArea.value.area;
  381. let index = testList.value.findIndex((item: any) => {
  382. return area == item.area;
  383. });
  384. console.log('index', index);
  385. multipleItemRefList.value[index]?.returnStudent(data);
  386. };
  387. /**
  388. * 子组件返回数据
  389. */
  390. const returnData = (data: any) => {
  391. let index = testList.value.findIndex((item: any) => {
  392. return item.area == data.area;
  393. });
  394. let obj = Object.assign(testList.value[index], data);
  395. testList.value[index] = JSON.parse(JSON.stringify(obj));
  396. console.log('testList.value', testList.value);
  397. if (examState.value == 0) {
  398. let flag = false;
  399. //只监听人脸识别的区
  400. let newList = testList.value;
  401. for (let i = 0; i < newList.length; i++) {
  402. if (newList[i] && newList[i].examState == 3) {
  403. flag = true;
  404. }
  405. }
  406. if (flag) {
  407. console.log('变更状态:', 3);
  408. examState.value = 3;
  409. if (needStart.value) {
  410. let handcontroller = parameter.value.handcontroller;
  411. let ctrl = parameter.value.ctrl;
  412. if (handcontroller && ctrl) {
  413. speckText(`请${ctrl}区举左手开始识别`);
  414. } else {
  415. proxy?.$modal.msgWarning(`请点击右下角的【开始识别】`);
  416. }
  417. }
  418. }
  419. }
  420. if (examState.value == 3) {
  421. let flag = false;
  422. //只监听人脸识别的区
  423. let newList = testList.value;
  424. for (let i = 0; i < newList.length; i++) {
  425. if (newList[i] && newList[i].examState == 40) {
  426. flag = true;
  427. }
  428. }
  429. if (flag) {
  430. console.log('变更状态:', 40);
  431. examState.value = 40;
  432. }
  433. }
  434. if (examState.value == 40) {
  435. let flag = false;
  436. //只监听人脸识别的区
  437. let newList = testList.value;
  438. for (let i = 0; i < newList.length; i++) {
  439. if (newList[i] && newList[i].examState == 41) {
  440. flag = true;
  441. }
  442. }
  443. if (flag) {
  444. console.log('变更状态:', 41);
  445. examState.value = 41;
  446. let txt = parameter.value.gesture ? '请举右手看摄像头人脸识别' : '请看摄像头进行人脸识别';
  447. speckText(txt);
  448. //如果过段时候后仍在识别中就提示停止识别
  449. // setTimeout(() => {
  450. // if (examState.value == 41) {
  451. // let handcontroller = parameter.value.handcontroller;
  452. // let ctrl = parameter.value.ctrl;
  453. // if (handcontroller && ctrl) {
  454. // speckText(`各就位后请${ctrl}区举左手停止识别`);
  455. // } else {
  456. // proxy?.$modal.msgWarning(`各就位后请点击右下角的【停止人脸识别】`);
  457. // }
  458. // }
  459. // }, 15000)
  460. }
  461. }
  462. if (examState.value == 41) {
  463. let flag = false;
  464. //只监听人脸识别的区
  465. let newList = testList.value.filter((item: any) => {
  466. return item?.faceCheckStu?.student_id;
  467. });
  468. for (let i = 0; i < newList.length; i++) {
  469. if (newList[i] && newList[i].faceCheckStu?.student_id && newList[i].examState == 43) {
  470. flag = true;
  471. }
  472. }
  473. if (flag) {
  474. console.log('变更状态:', 43);
  475. examState.value = 43;
  476. cleanData();
  477. //如果过段时候后仍在识别中就提示停止识别
  478. // setTimeout(() => {
  479. // if (examState.value == 43) {
  480. // let handcontroller = parameter.value.handcontroller;
  481. // let ctrl = parameter.value.ctrl;
  482. // if (handcontroller && ctrl) {
  483. // speckText(`请${ctrl}区举左手开始测试`);
  484. // } else {
  485. // proxy?.$modal.msgWarning(`请点击右下角的【开始测试】`);
  486. // }
  487. // }
  488. // }, 10000)
  489. }
  490. }
  491. //测试完成后回退状态
  492. if (examState.value == 42) {
  493. let flag = false;
  494. //只监听人脸识别的区
  495. let newList = testList.value.filter((item: any) => {
  496. return item?.faceCheckStu?.student_id;
  497. });
  498. for (let i = 0; i < newList.length; i++) {
  499. if (newList[i] && newList[i].faceCheckStu?.student_id && newList[i].examState == 3) {
  500. flag = true;
  501. } else {
  502. return false;
  503. }
  504. }
  505. if (flag) {
  506. console.log('变更状态:', 3);
  507. examState.value = 3;
  508. }
  509. }
  510. //如果全部状态为0就退出
  511. if (examState.value >= 0) {
  512. let flag = false;
  513. //只监听人脸识别的区
  514. let newList = testList.value;
  515. let myList = newList.filter((item: any) => {
  516. return item.examState == 0;
  517. });
  518. if (myList.length == newList.length) {
  519. examState.value = 0;
  520. getExit();
  521. }
  522. }
  523. };
  524. /**
  525. * 准备开始
  526. */
  527. const getReady = () => {
  528. if (examState.value != 43) {
  529. return false;
  530. }
  531. if (time.value.ready) {
  532. return false;
  533. }
  534. let flag = false;
  535. for (let i = 0; i < testList.value.length; i++) {
  536. if (testList.value[i] && testList.value[i].faceCheckStu?.student_id) {
  537. flag = true;
  538. }
  539. }
  540. if (!flag) {
  541. proxy?.$modal.msgWarning('请选择人员!');
  542. return false;
  543. }
  544. //停止播报;
  545. speckCancel();
  546. //正在识别的停止识别
  547. for (let i = 0; i < multipleItemRefList.value.length; i++) {
  548. if (testList.value[i].examState == 41) {
  549. multipleItemRefList.value[i].getStopFace();
  550. }
  551. }
  552. time.value.ready = 6;
  553. timerManager.value.readyTimer = setInterval(() => {
  554. time.value.ready--;
  555. if (time.value.ready <= 0) {
  556. getClearTimer('readyTimer');
  557. getStartOneTest();
  558. } else {
  559. speckText(time.value.ready);
  560. }
  561. }, 1000);
  562. };
  563. /**
  564. * 成绩列表
  565. */
  566. const getTestRecord = () => {
  567. if (showReportList.value) {
  568. showReportList.value = false;
  569. } else {
  570. reportListRef.value.getIniReportList();
  571. showReportList.value = true;
  572. }
  573. };
  574. /**
  575. * 自动填补空缺
  576. */
  577. const getAddTestList = (num: number) => {
  578. let list = parameter.value.area.split(',');
  579. let myLength = num - list.length;
  580. for (let i = 0; i < myLength; i++) {
  581. let obj = {
  582. area: '',
  583. itemNumber: list.length + (i + 1)
  584. };
  585. testList.value.push(obj);
  586. }
  587. };
  588. /**
  589. * 获取设备项目
  590. */
  591. const getDevice = async () => {
  592. let deviceid = localStorage.getItem('deviceid') || '';
  593. if (deviceid) {
  594. startDevice({ deviceid: deviceid });
  595. } else {
  596. proxy?.$modal.msgError(`缺少设备信息请重新登录!`);
  597. await proxy?.$http.common.logout({}).then((res: any) => {});
  598. proxy?.$modal?.closeLoading();
  599. //清空缓存
  600. // localStorage.clear();
  601. localStorage.removeItem('token');
  602. localStorage.removeItem('userInfo');
  603. //跳转
  604. router.push({ path: '/login/qrcode' });
  605. }
  606. };
  607. /**
  608. * 获取音乐
  609. */
  610. const getMusic = async () => {
  611. const list: any = useAppStore().getMusic();
  612. if (list.length) {
  613. musicList.value = list;
  614. } else {
  615. await proxy?.$http.train.musicList().then((res: any) => {
  616. if (res.data.length > 0) {
  617. let myList: any = res.data;
  618. musicList.value = myList;
  619. useAppStore().setMusic(myList);
  620. }
  621. });
  622. }
  623. };
  624. /**
  625. * 设置音乐
  626. */
  627. const setMusic = async (data: any) => {
  628. //console.log("data",data)
  629. parameter.value.music = data;
  630. };
  631. /**
  632. * 加载手势WS
  633. */
  634. const initHand = () => {
  635. handWs((e: any) => {
  636. if (router.currentRoute.value.path != '/train/multiple' || parameter.value.handcontroller == undefined) {
  637. return false;
  638. }
  639. console.log('eeeee', e);
  640. if (e?.wksid) {
  641. //获取设备项目
  642. getDevice();
  643. }
  644. //接收设备信息
  645. if (e?.device_info) {
  646. deviceInfo.value = e.device_info;
  647. let handcontroller_id = deviceInfo.value.handcontroller_id;
  648. stateHand(handcontroller_id);
  649. }
  650. //获取手势状态
  651. if (e?.cmd == 'get_handcontroller_state' && e?.state == 0) {
  652. let handcontroller_id = deviceInfo.value.handcontroller_id;
  653. startHand(handcontroller_id);
  654. }
  655. //刷新
  656. if (e?.data?.result == 'refresh') {
  657. getExit();
  658. //刷新
  659. window.location.reload();
  660. }
  661. //没初始化完成不监听手势动作
  662. if (examState.value == 0) {
  663. return false;
  664. }
  665. //左滑动
  666. if (e?.data?.result == 'next_item') {
  667. // proxy?.$modal.msgSuccess('手势指令:左滑动');
  668. // if(examState.value == 43 && time.value.ready){
  669. // return false;
  670. // }
  671. // if (examState.value == 43 || examState.value == 42) {
  672. // speckCancel();//停止播报
  673. // getAllRetestFace();
  674. // }
  675. }
  676. //举左手
  677. if (e?.data?.result == 'left_hand') {
  678. //proxy?.$modal.msgSuccess('手势指令:举左手');
  679. //举左手确认退出
  680. // if (exitStatus.value) {
  681. // exitStatus.value = 0;
  682. // //确认退出
  683. // let keyEvent: any = null;
  684. // keyEvent = new KeyboardEvent('keydown', {
  685. // key: 'Enter', // 键值
  686. // code: 'Enter', // 键盘代码
  687. // keyCode: 13, // 旧的键盘代码
  688. // which: 13, // 新的键盘代码
  689. // shiftKey: false, // 是否按下Shift键
  690. // ctrlKey: false, // 是否按下Ctrl键
  691. // metaKey: false, // 是否按下Meta键(Win键或Command键)
  692. // bubbles: true, // 事件是否冒泡
  693. // cancelable: true // 是否可以取消事件的默认行为
  694. // });
  695. // document.activeElement?.dispatchEvent(keyEvent);
  696. // return false;
  697. // }
  698. //开始识别
  699. if (needStart.value && examState.value < 41) {
  700. getOpenOneTestAndStartFace();
  701. return false;
  702. }
  703. //停止人脸识别
  704. if (needStart.value && examState.value == 41) {
  705. getStopFace();
  706. return false;
  707. }
  708. //开始测试
  709. if (examState.value == 43) {
  710. getReady();
  711. return false;
  712. }
  713. }
  714. //退出
  715. if (e?.data?.result == 'exit') {
  716. //proxy?.$modal.msgSuccess('手势指令:交叉手');
  717. // console.log("exitStatus.value", exitStatus.value)
  718. // if (exitStatus.value == 0) {
  719. // speckText("请保持两秒确认退出");
  720. // //第一次才弹出
  721. // confirmExit();
  722. // setTimeout(() => {
  723. // let keyEvent: any = null;
  724. // let myKey = null;
  725. // //如果交叉手两秒后返回超过4次就确认退出
  726. // if (exitStatus.value >= 4) {
  727. // myKey = 'Enter';
  728. // } else {
  729. // myKey = 'Esc';
  730. // }
  731. // if (myKey == 'Esc') {
  732. // keyEvent = new KeyboardEvent('keydown', {
  733. // key: 'Escape', // 键值
  734. // code: 'Escape', // 键盘代码
  735. // keyCode: 27, // 旧的键盘代码
  736. // which: 27, // 新的键盘代码
  737. // shiftKey: false, // 是否按下Shift键
  738. // ctrlKey: false, // 是否按下Ctrl键
  739. // metaKey: false, // 是否按下Meta键(Win键或Command键)
  740. // bubbles: true, // 事件是否冒泡
  741. // cancelable: true // 是否可以取消事件的默认行为
  742. // });
  743. // exitStatus.value = 0;
  744. // }
  745. // if (myKey == 'Enter') {
  746. // keyEvent = new KeyboardEvent('keydown', {
  747. // key: 'Enter', // 键值
  748. // code: 'Enter', // 键盘代码
  749. // keyCode: 13, // 旧的键盘代码
  750. // which: 13, // 新的键盘代码
  751. // shiftKey: false, // 是否按下Shift键
  752. // ctrlKey: false, // 是否按下Ctrl键
  753. // metaKey: false, // 是否按下Meta键(Win键或Command键)
  754. // bubbles: true, // 事件是否冒泡
  755. // cancelable: true // 是否可以取消事件的默认行为
  756. // });
  757. // }
  758. // document.activeElement?.dispatchEvent(keyEvent);
  759. // }, 2500)
  760. // }
  761. // exitStatus.value = exitStatus.value + 1
  762. }
  763. // if (e?.data?.result == "exit") {
  764. // console.log("exitStatus.value", exitStatus.value)
  765. // if (exitStatus.value == 0) {
  766. // exitStatus.value = 1
  767. // speckText("请5秒内举左手确认退出");
  768. // //第一次才弹出
  769. // confirmExit();
  770. // time.value.exit = 6;
  771. // timerManager.value.exitTimer = setInterval(() => {
  772. // time.value.exit--;
  773. // console.log("取消倒计时", time.value.exit)
  774. // proxy?.$modal.msgWarning(`取消倒计时:${time.value.exit}`)
  775. // if (time.value.exit == 0) {
  776. // exitStatus.value = 0;
  777. // getClearTimer("exitTimer");
  778. // let keyEvent: any = null;
  779. // keyEvent = new KeyboardEvent('keydown', {
  780. // key: 'Escape', // 键值
  781. // code: 'Escape', // 键盘代码
  782. // keyCode: 27, // 旧的键盘代码
  783. // which: 27, // 新的键盘代码
  784. // shiftKey: false, // 是否按下Shift键
  785. // ctrlKey: false, // 是否按下Ctrl键
  786. // metaKey: false, // 是否按下Meta键(Win键或Command键)
  787. // bubbles: true, // 事件是否冒泡
  788. // cancelable: true // 是否可以取消事件的默认行为
  789. // });
  790. // document.activeElement?.dispatchEvent(keyEvent);
  791. // }
  792. // }, 1000);
  793. // }
  794. // }
  795. });
  796. };
  797. /**
  798. * 播报时间
  799. */
  800. watch(
  801. () => time.value.countdownNum,
  802. (newData) => {
  803. if (examState.value != 42 || !needStart.value) {
  804. return false;
  805. }
  806. if (newData >= 30 && newData < time.value.testTime) {
  807. if (newData % 30 == 0) {
  808. speckText(`还有${newData}秒`);
  809. }
  810. }
  811. if (newData == 10) {
  812. speckText('还有10秒');
  813. }
  814. if (newData <= 5) {
  815. speckText(newData);
  816. }
  817. if (newData == 0) {
  818. speckText('哨声');
  819. }
  820. },
  821. { immediate: true }
  822. );
  823. /**
  824. * 将测试列表转为多行
  825. */
  826. const testListArr = computed(() => {
  827. let list: any = [];
  828. let num = 0;
  829. if (styleType.value == 1) {
  830. num = 3;
  831. }
  832. if (styleType.value == 2) {
  833. num = 5;
  834. }
  835. if (styleType.value == 3) {
  836. num = 10;
  837. }
  838. if (styleType.value == 4) {
  839. num = 4;
  840. }
  841. let myLength = Math.ceil(testList.value.length / num);
  842. for (let i = 0; i < myLength; i++) {
  843. list[i] = [];
  844. for (let j = 0; j < testList.value.length; j++) {
  845. if (j >= i * num && j < (i + 1) * num) {
  846. list[i].push(testList.value[j]);
  847. }
  848. }
  849. }
  850. console.log('list', list);
  851. return list;
  852. });
  853. /**
  854. * 时间转换
  855. */
  856. // const countdownNumFormat = computed(() => {
  857. // return time.value.countdownNum;
  858. // //return proxy?.$utils.timeFormat(time.value.countdownNum);
  859. // });
  860. onBeforeMount(() => {
  861. parameter.value = route.query;
  862. let project = parameter.value.project;
  863. let area = parameter.value.area;
  864. parameter.value.examId = `${project}_${area}`; //项目+区
  865. if (parameter.value.time) {
  866. time.value.testTime = parameter.value.time;
  867. }
  868. time.value.countdownNum = time.value.testTime;
  869. let myInfo: any = localStorage.getItem('userInfo');
  870. userInfo.value = JSON.parse(myInfo);
  871. if (parameter.value.gesture == 'true') {
  872. parameter.value.gesture = true;
  873. } else {
  874. parameter.value.gesture = false;
  875. }
  876. let list = parameter.value.area.split(',');
  877. testList.value = list.map((item: any, index: number) => {
  878. let obj = {
  879. area: item,
  880. itemNumber: index + 1
  881. };
  882. return obj;
  883. });
  884. //展示样式控制
  885. if (testList.value.length == 5) {
  886. styleType.value = 1;
  887. //填补空缺
  888. getAddTestList(5);
  889. }
  890. if (testList.value.length > 5 && testList.value.length <= 10) {
  891. styleType.value = 2;
  892. //填补空缺
  893. getAddTestList(10);
  894. }
  895. if (testList.value.length > 10) {
  896. styleType.value = 3;
  897. }
  898. if (testList.value.length < 5) {
  899. styleType.value = 4;
  900. }
  901. //需要开始按钮的项目
  902. let myList = ['jumprope', 'jumpingjack', 'highknees', 'situp'];
  903. if (myList.includes(project) && styleType.value == 3) {
  904. needStart.value = true;
  905. }
  906. //加载音乐
  907. getMusic();
  908. });
  909. onMounted(() => {
  910. //加载WS
  911. let project = parameter.value.project;
  912. initWs({ parameter: parameter.value, testTime: time.value.testTime, version: 'v2' }, (data: any) => {
  913. //获取sid
  914. if (data.cmd === 'mySid') {
  915. console.log('data.data.sid', data.data.sid);
  916. sid.value = data.data.sid;
  917. }
  918. let index = testList.value.findIndex((item: any) => {
  919. let examId = `${project}_${item.area}`;
  920. return examId == data.exam_id;
  921. });
  922. multipleItemRefList.value[index]?.getMessage(data);
  923. });
  924. //初始化语音
  925. initSpeech();
  926. //初始化手势
  927. initHand();
  928. //刷新关闭
  929. window.onbeforeunload = function (e) {
  930. var confirmationMessage = '刷新/关闭页面将会关闭页面,是否确认退出页面?';
  931. (e || window.event).returnValue = confirmationMessage; // 兼容 Gecko + IE
  932. testList.value.forEach((item: any) => {
  933. let examId = item.exam_id;
  934. let bUrl = import.meta.env.VITE_APP_BASE_API;
  935. let classId = parameter.value.classes;
  936. let mySid = sid.value;
  937. let token: any = localStorage.getItem('token');
  938. let formData = new FormData();
  939. formData.append('exam_id', examId);
  940. formData.append('class_id', classId);
  941. formData.append('token', token);
  942. formData.append('sid', mySid);
  943. navigator.sendBeacon(bUrl + '/exam/close_exam', formData);
  944. });
  945. return confirmationMessage; // 兼容 Gecko + Webkit, Safari, Chrome
  946. };
  947. });
  948. onBeforeUnmount(() => {
  949. getExit();
  950. });
  951. </script>
  952. <style scoped lang="scss">
  953. $waiPadding: 6.51rem;
  954. .time {
  955. width: 20vh;
  956. height: 20vh;
  957. line-height: 20vh;
  958. border-radius: 50%;
  959. color: #ff9402;
  960. font-size: 8vh;
  961. text-align: center;
  962. background-image: url('@/assets/images/test/time.png');
  963. background-position: center;
  964. background-repeat: no-repeat;
  965. background-size: 100% 100%;
  966. position: absolute;
  967. right: 50%;
  968. top: -4vh;
  969. margin-right: -10vh;
  970. font-family: 'Saira-BlackItalic';
  971. }
  972. .time1,
  973. .time4 {
  974. width: 26vh;
  975. height: 26vh;
  976. line-height: 26vh;
  977. font-size: 10.3vh;
  978. right: auto;
  979. left: 50%;
  980. top: 0vh;
  981. margin-left: 8vw;
  982. margin-right: auto;
  983. }
  984. .main {
  985. width: calc(100% - ($waiPadding * 2));
  986. height: 78vh;
  987. padding-top: 10rem;
  988. margin: 0 auto;
  989. display: flex;
  990. overflow: hidden;
  991. flex-direction: column;
  992. }
  993. .mask {
  994. position: fixed;
  995. height: 100vh;
  996. width: 100vw;
  997. top: 0;
  998. left: 0;
  999. background: rgba(0, 0, 0, 0.3);
  1000. z-index: 998;
  1001. }
  1002. .mask2 {
  1003. background: rgba(0, 0, 0, 0.68);
  1004. }
  1005. .confirmDiaBg {
  1006. width: 100%;
  1007. height: 100vh;
  1008. position: fixed;
  1009. left: 0;
  1010. top: 0;
  1011. display: flex;
  1012. align-items: center;
  1013. justify-content: center;
  1014. z-index: 999;
  1015. .confirmDiaWindow {
  1016. width: 40%;
  1017. height: 43.4%;
  1018. border-radius: 1.6rem;
  1019. opacity: 1;
  1020. box-sizing: border-box;
  1021. border: 0.55rem solid #13ed84;
  1022. text-align: center;
  1023. display: flex;
  1024. align-items: center;
  1025. justify-content: center;
  1026. position: fixed;
  1027. background: radial-gradient(96% 96% at 2% 32%, #ffffff 0%, #fcfdfd 54%, #e1e4e7 100%);
  1028. .readyBox {
  1029. text-align: center;
  1030. color: #1a293a;
  1031. .lable {
  1032. font-size: 4.3rem;
  1033. line-height: 1;
  1034. margin-bottom: 1.5rem;
  1035. }
  1036. .value {
  1037. font-size: 10rem;
  1038. line-height: 1;
  1039. font-family: 'Saira-BlackItalic';
  1040. }
  1041. .transparent {
  1042. opacity: 0;
  1043. }
  1044. }
  1045. }
  1046. }
  1047. .footerBtn {
  1048. width: 100%;
  1049. padding: 0 calc(13.02rem / 2);
  1050. box-sizing: border-box;
  1051. position: fixed;
  1052. bottom: 3vh;
  1053. display: flex;
  1054. justify-content: end;
  1055. .btn {
  1056. width: 13vw;
  1057. height: 6.1vh;
  1058. line-height: 6.1vh;
  1059. font-size: 3vh;
  1060. color: #1a293a;
  1061. text-align: center;
  1062. border-radius: 1vh;
  1063. cursor: pointer;
  1064. margin-left: 10px;
  1065. background: radial-gradient(159% 126% at 5% 93%, #8effa9 0%, #07ffe7 100%);
  1066. box-shadow: 1px 1px 1px 1px rgba(0, 0, 0, 0.1874), inset 0px 1px 0px 1px rgba(255, 255, 255, 0.3);
  1067. &:hover {
  1068. background: #8effa9;
  1069. }
  1070. }
  1071. .startBtn {
  1072. color: #ffffff;
  1073. background: radial-gradient(159% 126% at 5% 93%, #f99f02 0%, #ed7905 100%);
  1074. box-shadow: 3px 6px 4px 1px rgba(0, 0, 0, 0.1874), inset 0px 1px 0px 2px rgba(255, 255, 255, 0.3);
  1075. &:hover {
  1076. background: #f99f02;
  1077. }
  1078. }
  1079. .exitBtn {
  1080. color: #ffffff;
  1081. background: radial-gradient(159% 126% at 5% 93%, #931b1b 0%, #ec5624 0%, #ff6860 76%);
  1082. box-shadow: 3px 6px 4px 1px rgba(0, 0, 0, 0.1874), inset 0px 1px 0px 2px rgba(255, 255, 255, 0.5577);
  1083. &:hover {
  1084. background: #ec5624;
  1085. }
  1086. }
  1087. .testRecordBtn {
  1088. position: relative;
  1089. z-index: 999;
  1090. }
  1091. }
  1092. .main-right {
  1093. width: 22.7%;
  1094. height: 80vh;
  1095. border-radius: 1.6rem;
  1096. // background: linear-gradient(29deg, #092941 -82%, #2A484B 94%);
  1097. background-color: rgba(9, 41, 65, 0.8);
  1098. box-shadow: inset 0px 1px 0px 2px rgba(255, 255, 255, 0.4);
  1099. display: flex;
  1100. flex-direction: column;
  1101. overflow: hidden;
  1102. position: absolute;
  1103. z-index: 999;
  1104. bottom: 6.5vh;
  1105. right: calc(13.02rem / 2);
  1106. }
  1107. //列表样式写在此父组件里,子组件尽量减少个性化class和style,多样性以父组件控制类型
  1108. ::v-deep(.testBox1) {
  1109. $listMargin: 10vw;
  1110. $listWidth: calc((100vw - ($waiPadding * 2) - $listMargin) / 5);
  1111. display: flex;
  1112. flex-wrap: wrap;
  1113. align-items: center;
  1114. .ul {
  1115. width: 100%;
  1116. height: 36vh;
  1117. display: flex;
  1118. .li {
  1119. width: $listWidth;
  1120. position: relative;
  1121. .ready {
  1122. position: absolute;
  1123. height: 100%;
  1124. width: 100%;
  1125. display: flex;
  1126. align-items: center;
  1127. justify-items: center;
  1128. img {
  1129. width: 100%;
  1130. }
  1131. }
  1132. .userInfo {
  1133. width: 100%;
  1134. height: 100%;
  1135. border-radius: 1.6rem;
  1136. background: radial-gradient(122% 126% at 97% 6%, #35ffc6 0%, #00ffe8 100%);
  1137. text-align: center;
  1138. display: flex;
  1139. align-items: center;
  1140. justify-content: center;
  1141. cursor: pointer;
  1142. margin-bottom: 3px;
  1143. .userInfo-center {
  1144. .pic {
  1145. width: calc(36vh * 0.62);
  1146. height: calc(36vh * 0.62);
  1147. border-radius: 50%;
  1148. display: flex;
  1149. justify-content: center;
  1150. align-items: center;
  1151. overflow: hidden;
  1152. margin: 0 auto 2vh auto;
  1153. position: relative;
  1154. .area {
  1155. position: absolute;
  1156. top: calc(36vh * 0.18);
  1157. color: #203c52;
  1158. font-size: 5vh;
  1159. line-height: 1;
  1160. font-family: 'Saira-ExtraBold';
  1161. text-align: center;
  1162. }
  1163. img {
  1164. width: 100%;
  1165. }
  1166. }
  1167. .pic2 {
  1168. box-sizing: border-box;
  1169. border: 0.44rem solid rgba(26, 41, 58, 0.6315);
  1170. }
  1171. .name {
  1172. width: 100%;
  1173. color: #1a293a;
  1174. font-size: 2.21rem;
  1175. }
  1176. .name2 {
  1177. box-sizing: border-box;
  1178. padding: 0.1rem 0.4rem;
  1179. border-radius: 1.1rem;
  1180. background: radial-gradient(96% 96% at 2% 32%, #ffffff 0%, #fcfdfd 54%, #e1e4e7 100%);
  1181. box-shadow: inset 0px 1px 0px 2px rgba(255, 255, 255, 0.9046), inset 0px 3px 6px 0px rgba(0, 0, 0, 0.0851);
  1182. }
  1183. .ctrlBox {
  1184. position: absolute;
  1185. left: 0;
  1186. top: 0;
  1187. border: 5px solid #ffffff;
  1188. display: flex;
  1189. flex-direction: column;
  1190. align-items: center;
  1191. background: rgba(0, 0, 0, 0.8);
  1192. z-index: 2;
  1193. width: 100%;
  1194. height: 100%;
  1195. box-sizing: border-box;
  1196. border-radius: 1.6rem;
  1197. img {
  1198. max-width: 100%;
  1199. max-height: 70%;
  1200. margin: 10% 0 5% 0;
  1201. }
  1202. .lable {
  1203. text-align: center;
  1204. font-size: 2rem;
  1205. color: #ffffff;
  1206. }
  1207. }
  1208. }
  1209. }
  1210. .score {
  1211. height: 18vh;
  1212. line-height: 18vh;
  1213. font-size: 12vh;
  1214. font-family: 'Saira-BlackItalic';
  1215. border: 0.55rem solid #13ed84;
  1216. text-align: center;
  1217. border-radius: 1.6rem;
  1218. box-sizing: content-box;
  1219. color: #1a293a;
  1220. position: relative;
  1221. background: radial-gradient(96% 96% at 2% 32%, #ffffff 0%, #fcfdfd 54%, #e1e4e7 100%);
  1222. display: none;
  1223. &::before {
  1224. content: '';
  1225. width: 2vw;
  1226. height: 2vw;
  1227. display: block;
  1228. position: absolute;
  1229. top: -1.5vw;
  1230. left: 50%;
  1231. margin-left: -1vw;
  1232. background-image: url('@/assets/images/test/yuan.png');
  1233. background-position: center;
  1234. background-repeat: no-repeat;
  1235. background-size: 100% 100%;
  1236. border-radius: 50%;
  1237. flex-shrink: 0;
  1238. transition: all 0.5s;
  1239. }
  1240. }
  1241. }
  1242. .disable {
  1243. .userInfo,
  1244. .score {
  1245. opacity: 0.5;
  1246. }
  1247. }
  1248. .wait {
  1249. .userInfo,
  1250. .score {
  1251. opacity: 0.5;
  1252. }
  1253. }
  1254. &:nth-child(1) {
  1255. justify-content: space-between;
  1256. }
  1257. &:nth-child(2) {
  1258. justify-content: space-evenly;
  1259. .li {
  1260. margin-left: calc($listMargin/4);
  1261. margin-right: calc($listMargin/4);
  1262. }
  1263. }
  1264. }
  1265. .ready.ul {
  1266. &:nth-child(1) {
  1267. .li:nth-child(2) {
  1268. &::after {
  1269. content: '';
  1270. position: absolute;
  1271. height: 100%;
  1272. width: 100%;
  1273. display: block;
  1274. background-image: url('@/assets/images/test/ready3.png');
  1275. background-position: center center;
  1276. background-repeat: no-repeat;
  1277. background-size: 100%;
  1278. }
  1279. }
  1280. }
  1281. &:nth-child(2) {
  1282. .li {
  1283. &::after {
  1284. content: '';
  1285. position: absolute;
  1286. height: 100%;
  1287. width: 100%;
  1288. display: block;
  1289. background-image: url('@/assets/images/test/ready3.png');
  1290. background-position: center center;
  1291. background-repeat: no-repeat;
  1292. background-size: 100%;
  1293. top: -100%;
  1294. }
  1295. }
  1296. }
  1297. }
  1298. .hands.ul {
  1299. &:nth-child(1) {
  1300. .li:nth-child(2) {
  1301. &::after {
  1302. background-image: url('@/assets/images/test/ready4.png');
  1303. }
  1304. }
  1305. }
  1306. &:nth-child(2) {
  1307. .li {
  1308. &::after {
  1309. background-image: url('@/assets/images/test/ready4.png');
  1310. }
  1311. }
  1312. }
  1313. }
  1314. .overlap.ul {
  1315. transition: all 0.5s;
  1316. .li {
  1317. .score {
  1318. display: block;
  1319. }
  1320. }
  1321. &:nth-child(1) {
  1322. margin-top: 5vh;
  1323. .li {
  1324. &::before {
  1325. display: none;
  1326. }
  1327. &::after {
  1328. display: none;
  1329. }
  1330. }
  1331. }
  1332. &:nth-child(2) {
  1333. margin-top: -20vh;
  1334. .li {
  1335. &::before {
  1336. display: none;
  1337. }
  1338. &::after {
  1339. display: none;
  1340. }
  1341. }
  1342. }
  1343. }
  1344. }
  1345. ::v-deep(.testBox2) {
  1346. $listMargin: 10vw;
  1347. $listWidth: calc((100% - $listMargin) / 5);
  1348. .ul {
  1349. width: 100%;
  1350. height: 34vh;
  1351. display: flex;
  1352. justify-content: space-between;
  1353. margin-bottom: 4vh;
  1354. .li {
  1355. width: $listWidth;
  1356. position: relative;
  1357. height: 100%;
  1358. border-radius: 3vh;
  1359. padding: 0.6vw;
  1360. background: radial-gradient(122% 126% at 97% 6%, #35ffc6 0%, #00ffe8 100%);
  1361. display: flex;
  1362. flex-direction: column;
  1363. justify-content: space-between;
  1364. box-sizing: border-box;
  1365. .userInfo {
  1366. width: 100%;
  1367. height: 100%;
  1368. border-radius: 1.6rem;
  1369. background: radial-gradient(122% 126% at 97% 6%, #35ffc6 0%, #00ffe8 100%);
  1370. text-align: center;
  1371. display: flex;
  1372. align-items: center;
  1373. justify-content: center;
  1374. cursor: pointer;
  1375. position: relative;
  1376. .userInfo-center {
  1377. .pic {
  1378. width: calc(36vh * 0.62);
  1379. height: calc(36vh * 0.62);
  1380. border-radius: 50%;
  1381. display: flex;
  1382. justify-content: center;
  1383. align-items: center;
  1384. overflow: hidden;
  1385. margin: 0 auto 2vh auto;
  1386. position: relative;
  1387. .area {
  1388. position: absolute;
  1389. top: calc(36vh * 0.18);
  1390. color: #203c52;
  1391. font-size: 5vh;
  1392. line-height: 1;
  1393. font-family: 'Saira-ExtraBold';
  1394. text-align: center;
  1395. }
  1396. img {
  1397. width: 100%;
  1398. }
  1399. }
  1400. .pic2 {
  1401. box-sizing: border-box;
  1402. border: 0.44rem solid rgba(26, 41, 58, 0.6315);
  1403. }
  1404. .name {
  1405. width: 100%;
  1406. color: #1a293a;
  1407. font-size: 2.21rem;
  1408. }
  1409. .ctrlBox {
  1410. position: absolute;
  1411. left: 0;
  1412. top: 0;
  1413. border: 5px solid #ffffff;
  1414. display: flex;
  1415. flex-direction: column;
  1416. align-items: center;
  1417. background: rgba(0, 0, 0, 0.8);
  1418. z-index: 2;
  1419. width: 100%;
  1420. height: 100%;
  1421. box-sizing: border-box;
  1422. border-radius: 1.6rem;
  1423. img {
  1424. max-width: 100%;
  1425. max-height: 70%;
  1426. margin: 10% 0 5% 0;
  1427. }
  1428. .lable {
  1429. text-align: center;
  1430. font-size: 2rem;
  1431. color: #ffffff;
  1432. }
  1433. }
  1434. }
  1435. }
  1436. .score {
  1437. height: 14vh;
  1438. line-height: 14vh;
  1439. font-size: 9vh;
  1440. font-family: 'Saira-BlackItalic';
  1441. background: #ffffff;
  1442. box-sizing: border-box;
  1443. border: 0.55rem solid #13ed84;
  1444. text-align: center;
  1445. border-radius: 1.6rem;
  1446. box-sizing: content-box;
  1447. color: #1a293a;
  1448. position: relative;
  1449. display: none;
  1450. &::before {
  1451. content: '';
  1452. width: 2vw;
  1453. height: 2vw;
  1454. display: block;
  1455. position: absolute;
  1456. top: -1.3vw;
  1457. left: 50%;
  1458. margin-left: -1vw;
  1459. background-image: url('@/assets/images/test/yuan.png');
  1460. background-position: center;
  1461. background-repeat: no-repeat;
  1462. background-size: 100% 100%;
  1463. border-radius: 50%;
  1464. flex-shrink: 0;
  1465. transition: all 0.5s;
  1466. }
  1467. }
  1468. }
  1469. .disable {
  1470. opacity: 0.5;
  1471. }
  1472. .wait {
  1473. opacity: 0.5;
  1474. }
  1475. }
  1476. .overlap {
  1477. .li {
  1478. .userInfo {
  1479. height: 6.2vw;
  1480. .userInfo-center {
  1481. width: 100%;
  1482. text-align: center;
  1483. display: flex;
  1484. align-items: center;
  1485. cursor: pointer;
  1486. .pic {
  1487. width: 6.2vw;
  1488. height: 6.2vw;
  1489. border-radius: 50%;
  1490. display: flex;
  1491. justify-content: center;
  1492. align-items: center;
  1493. overflow: hidden;
  1494. position: relative;
  1495. flex-shrink: 0;
  1496. margin: 0;
  1497. .area {
  1498. position: absolute;
  1499. top: 0;
  1500. left: 0;
  1501. width: 100%;
  1502. height: 100%;
  1503. line-height: 6.2vw;
  1504. color: #ffffff;
  1505. font-size: 2.5vw;
  1506. font-family: 'Saira-ExtraBold';
  1507. text-align: center;
  1508. background: rgba(#000000, 0.4);
  1509. }
  1510. img {
  1511. width: 100%;
  1512. }
  1513. }
  1514. .pic2 {
  1515. box-sizing: border-box;
  1516. border: 2px solid rgba(26, 41, 58, 0.6315);
  1517. }
  1518. .name {
  1519. width: 100%;
  1520. color: #1a293a;
  1521. font-size: 1.8rem;
  1522. border-radius: 2vw;
  1523. padding: 0.5rem 0;
  1524. background: radial-gradient(96% 96% at 2% 32%, #ffffff 0%, #fcfdfd 54%, #e1e4e7 100%);
  1525. box-shadow: inset 0px 1px 0px 2px rgba(255, 255, 255, 0.9046), inset 0px 3px 6px 0px rgba(0, 0, 0, 0.0851);
  1526. }
  1527. }
  1528. }
  1529. .score {
  1530. display: block;
  1531. }
  1532. }
  1533. }
  1534. }
  1535. ::v-deep(.testBox3) {
  1536. $listMargin: 5vw;
  1537. $listWidth: calc((100% - $listMargin) / 10);
  1538. .ul {
  1539. width: 100%;
  1540. height: 14vh;
  1541. display: flex;
  1542. justify-content: space-around;
  1543. margin-bottom: calc($listMargin / 10);
  1544. .li {
  1545. width: $listWidth;
  1546. position: relative;
  1547. height: 100%;
  1548. border-radius: 5vh;
  1549. background: radial-gradient(122% 126% at 97% 6%, #35ffc6 0%, #00ffe8 100%);
  1550. .userInfo {
  1551. padding: 2vh 0.6vw 0 0.6vw;
  1552. margin-bottom: 0.8vh;
  1553. .userInfo-center {
  1554. text-align: center;
  1555. display: flex;
  1556. align-items: center;
  1557. cursor: pointer;
  1558. border-radius: 1.3vw;
  1559. height: 2.6vw;
  1560. background: linear-gradient(180deg, #ffffff 0%, rgba(255, 255, 255, 0.6571) 100%);
  1561. border: 1px solid #ffffff;
  1562. .pic {
  1563. width: 2.6vw;
  1564. height: 2.6vw;
  1565. border-radius: 50%;
  1566. display: flex;
  1567. justify-content: center;
  1568. align-items: center;
  1569. overflow: hidden;
  1570. position: relative;
  1571. flex-shrink: 0;
  1572. .area {
  1573. position: absolute;
  1574. top: 0;
  1575. left: 0;
  1576. width: 100%;
  1577. height: 100%;
  1578. line-height: 2.6vw;
  1579. color: #ffffff;
  1580. font-size: 1.4vw;
  1581. font-family: 'Saira-ExtraBold';
  1582. text-align: center;
  1583. background: rgba(#000000, 0.4);
  1584. }
  1585. img {
  1586. width: 100%;
  1587. }
  1588. }
  1589. .pic2 {
  1590. box-sizing: border-box;
  1591. border: 2px solid rgba(26, 41, 58, 0.6315);
  1592. }
  1593. .name {
  1594. color: #1a293a;
  1595. font-size: 1rem;
  1596. margin-left: 0.5vw;
  1597. }
  1598. .ctrlBox {
  1599. position: absolute;
  1600. left: 0;
  1601. top: 0;
  1602. border: 5px solid #ffffff;
  1603. display: flex;
  1604. flex-direction: column;
  1605. align-items: center;
  1606. background: rgba(0, 0, 0, 0.8);
  1607. z-index: 2;
  1608. width: 100%;
  1609. height: 100%;
  1610. box-sizing: border-box;
  1611. border-radius: 5vh;
  1612. img {
  1613. max-width: 100%;
  1614. max-height: 60%;
  1615. margin: 5% 0 5% 0;
  1616. }
  1617. .lable {
  1618. text-align: center;
  1619. font-size: 1rem;
  1620. color: #ffffff;
  1621. }
  1622. }
  1623. }
  1624. }
  1625. .score {
  1626. font-size: 3rem;
  1627. font-family: 'Saira-BlackItalic';
  1628. text-align: center;
  1629. color: #1a293a;
  1630. line-height: 1;
  1631. }
  1632. }
  1633. .disable {
  1634. opacity: 0.5;
  1635. }
  1636. .wait {
  1637. opacity: 0.75;
  1638. }
  1639. .startedAlready {
  1640. background: #15ef96;
  1641. }
  1642. }
  1643. }
  1644. ::v-deep(.testBox4) {
  1645. $listMargin: 10vw;
  1646. $listWidth: calc((100vw - ($waiPadding * 2) - $listMargin) / 5);
  1647. display: flex;
  1648. flex-wrap: wrap;
  1649. align-items: center;
  1650. height: 100%;
  1651. .ul {
  1652. width: 100%;
  1653. display: flex;
  1654. justify-content: space-evenly;
  1655. align-items: center;
  1656. height: 36vh;
  1657. .li {
  1658. width: $listWidth;
  1659. height: 100%;
  1660. position: relative;
  1661. .ready {
  1662. position: absolute;
  1663. height: 100%;
  1664. width: 100%;
  1665. display: flex;
  1666. align-items: center;
  1667. justify-items: center;
  1668. img {
  1669. width: 100%;
  1670. }
  1671. }
  1672. .userInfo {
  1673. width: 100%;
  1674. height: 100%;
  1675. border-radius: 1.6rem;
  1676. background: radial-gradient(122% 126% at 97% 6%, #35ffc6 0%, #00ffe8 100%);
  1677. text-align: center;
  1678. display: flex;
  1679. align-items: center;
  1680. justify-content: center;
  1681. cursor: pointer;
  1682. margin-bottom: 3px;
  1683. .userInfo-center {
  1684. .pic {
  1685. width: calc(36vh * 0.62);
  1686. height: calc(36vh * 0.62);
  1687. border-radius: 50%;
  1688. display: flex;
  1689. justify-content: center;
  1690. align-items: center;
  1691. overflow: hidden;
  1692. margin: 0 auto 2vh auto;
  1693. position: relative;
  1694. .area {
  1695. position: absolute;
  1696. top: calc(36vh * 0.18);
  1697. color: #203c52;
  1698. font-size: 5vh;
  1699. line-height: 1;
  1700. font-family: 'Saira-ExtraBold';
  1701. text-align: center;
  1702. }
  1703. img {
  1704. width: 100%;
  1705. }
  1706. }
  1707. .pic2 {
  1708. box-sizing: border-box;
  1709. border: 0.44rem solid rgba(26, 41, 58, 0.6315);
  1710. }
  1711. .name {
  1712. width: 100%;
  1713. color: #1a293a;
  1714. font-size: 2.21rem;
  1715. }
  1716. .name2 {
  1717. box-sizing: border-box;
  1718. padding: 0.1rem 0.4rem;
  1719. border-radius: 1.1rem;
  1720. background: radial-gradient(96% 96% at 2% 32%, #ffffff 0%, #fcfdfd 54%, #e1e4e7 100%);
  1721. box-shadow: inset 0px 1px 0px 2px rgba(255, 255, 255, 0.9046), inset 0px 3px 6px 0px rgba(0, 0, 0, 0.0851);
  1722. }
  1723. .ctrlBox {
  1724. position: absolute;
  1725. left: 0;
  1726. top: 0;
  1727. border: 5px solid #ffffff;
  1728. display: flex;
  1729. flex-direction: column;
  1730. align-items: center;
  1731. background: rgba(0, 0, 0, 0.8);
  1732. z-index: 2;
  1733. width: 100%;
  1734. height: 100%;
  1735. box-sizing: border-box;
  1736. border-radius: 1.6rem;
  1737. img {
  1738. max-width: 100%;
  1739. max-height: 70%;
  1740. margin: 10% 0 5% 0;
  1741. }
  1742. .lable {
  1743. text-align: center;
  1744. font-size: 2rem;
  1745. color: #ffffff;
  1746. }
  1747. }
  1748. }
  1749. }
  1750. .score {
  1751. height: 18vh;
  1752. line-height: 18vh;
  1753. font-size: 12vh;
  1754. font-family: 'Saira-BlackItalic';
  1755. border: 0.55rem solid #13ed84;
  1756. text-align: center;
  1757. border-radius: 1.6rem;
  1758. box-sizing: content-box;
  1759. color: #1a293a;
  1760. position: relative;
  1761. background: radial-gradient(96% 96% at 2% 32%, #ffffff 0%, #fcfdfd 54%, #e1e4e7 100%);
  1762. display: none;
  1763. &::before {
  1764. content: '';
  1765. width: 2vw;
  1766. height: 2vw;
  1767. display: block;
  1768. position: absolute;
  1769. top: -1.5vw;
  1770. left: 50%;
  1771. margin-left: -1vw;
  1772. background-image: url('@/assets/images/test/yuan.png');
  1773. background-position: center;
  1774. background-repeat: no-repeat;
  1775. background-size: 100% 100%;
  1776. border-radius: 50%;
  1777. flex-shrink: 0;
  1778. transition: all 0.5s;
  1779. }
  1780. }
  1781. }
  1782. .disable {
  1783. .userInfo,
  1784. .score {
  1785. opacity: 0.5;
  1786. }
  1787. }
  1788. .wait {
  1789. .userInfo,
  1790. .score {
  1791. opacity: 0.5;
  1792. }
  1793. }
  1794. }
  1795. .overlap.ul {
  1796. transition: all 0.5s;
  1797. margin-top: -15vh;
  1798. .li {
  1799. .score {
  1800. display: block;
  1801. }
  1802. }
  1803. }
  1804. }
  1805. </style>