multiple.vue 48 KB

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