multiple.vue 39 KB

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