test.vue 29 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178
  1. <template>
  2. <div class="test">
  3. <div class="close" @click="confirmExit"></div>
  4. <div class="toolList">
  5. <div class="li">{{ date }}</div>
  6. <div class="li btn speck" @click="speckCancel"></div>
  7. <div class="li btn screen" @click="getFullScreen"></div>
  8. </div>
  9. <div class="logo"> <img src="@/assets/images/logo.png">
  10. </img>
  11. <div class="title"><i></i><span>{{ dic.project[parameter.project] || "" }}</span></div>
  12. </div>
  13. <div class="main">
  14. <div class="main-left">
  15. <div class="main-left-top">
  16. <div class="top-left" @click="getChooseStudent">
  17. <div class="top-left-center">
  18. <div class="pic" :class="{ 'pic2': faceCheckStu.student_id }" v-if="faceCheckStu.student_id"> <img
  19. :src="faceCheckStu.logo_url || faceCheckStu.face_pic" /></div>
  20. <div class="pic" v-else>
  21. <img src="@/assets/images/test/profilePicture.png" />
  22. </div>
  23. <div class="name" :class="{ 'name2': faceCheckStu.student_id }">
  24. {{ faceCheckStu.student_id ? faceCheckStu.name : "虚伪以待" }}
  25. </div>
  26. </div>
  27. </div>
  28. <div class="top-right">
  29. <div v-if="needStart && [42, 43].includes(examState)">{{
  30. countdownNumFormat
  31. }}</div>
  32. <div class="tips" v-if="examState == 41">
  33. <img v-if="parameter.gesture" src="@/assets/images/test/ready1.png" />
  34. <img v-if="!parameter.gesture" src="@/assets/images/test/ready2.png" />
  35. </div>
  36. <div class="complete" v-if="faceCheckStu.student_id">
  37. <div class="scoreBox">
  38. <div class="score">{{ currentResultObj.count }}</div>
  39. <!-- <div class="unit" v-if="currentResultObj.count">{{ unit }}</div> -->
  40. </div>
  41. <div class="fractionViolation">
  42. <div class="fraction">
  43. <div class="lable">得分:</div>
  44. <div class="value">{{ currentResultObj.score }}</div>
  45. </div>
  46. <div class="violation">
  47. <div class="lable">{{ ['jumprope'].includes(parameter.project) ? '中断' : '犯规' }}</div>
  48. <div class="value">{{ currentResultObj.back_num }}</div>
  49. </div>
  50. </div>
  51. </div>
  52. <div v-if="examState == 42 && backReason.length">
  53. <div>违规项</div>
  54. <div v-for="(item, index) in backReason" :key="index">{{ item }}</div>
  55. </div>
  56. <div v-if="needStart">
  57. <!-- <div class="btn" @click="getOpenOneTestAndStartFace" v-if="examState == 3 || examState == 40">开始识别</div> -->
  58. <!-- <div class="btn" @click="getStopFace" v-if="examState == 41 && !parameter.gesture">停止人脸识别</div> -->
  59. <div class="btn" @click="getStartOneTest" v-if="examState == 43">开始测试</div>
  60. <!-- <div @click="getRetestFace" v-if="examState == 43 || examState == 42">重新识别</div> -->
  61. </div>
  62. <!-- <div>当前状态:({{ examState == 3 ? "初始化完成" : examState == 40 ? "创建测试" : examState == 41 ? "正在人脸识别":examState == 43 ? "停止人脸识别" : examState == 42 ? "正在测试" : "请初始化" }})</div> -->
  63. <!-- <div @click="getAgain" v-if="examState == 42 || showTestAgain">再测一次</div> -->
  64. </div>
  65. <i></i>
  66. </div>
  67. <div class="main-left-bottom">
  68. <div class="bottom-left">
  69. <div class="tips"><img src="/src/assets/images/test/tips.png" /></div>
  70. <div class="pic"><img :src="'/src/assets/images/test/tips/' + parameter.project + '.png'" /></div>
  71. </div>
  72. <div class="bottom-right" v-html="dic.projectNote[parameter.project]">
  73. </div>
  74. </div>
  75. </div>
  76. <div class="main-right">
  77. <div class="title">测试记录</div>
  78. <ul>
  79. <li v-for="(item, index) in reportList " :key="index">
  80. <div class="left">
  81. <div class="pic"><img :src="item.face_pic || item.logo_url" /></div>
  82. <div class="txt">
  83. <div>
  84. <div class="name">{{ item.student_name }}</div>
  85. <div class="className"></div>
  86. </div>
  87. </div>
  88. </div>
  89. <div class="right">
  90. <div class="score">{{ item.result }}
  91. </div>
  92. <div class="unit">{{ unit }}</div>
  93. </div>
  94. </li>
  95. </ul>
  96. <div class="erweima"> <img src="@/assets/images/login/erweima.png" />
  97. <span>扫码查看详情</span>
  98. </div>
  99. </div>
  100. </div>
  101. <FaceWindow ref="faceWindowRef" :faceCheckStu="faceCheckStu" />
  102. <ChooseStudent ref="chooseStudentRef" @returnData="returnStudent" />
  103. </div>
  104. </template>
  105. <script setup name="TrainTest" lang="ts">
  106. import { initSpeech, speckText, speckCancel, chineseNumber } from '@/utils/speech'
  107. import { initWs, examEnds, openOneTest, startFace, stopFace, faceConfirmOnly, startOneTest, finishOneTest, closeOneTest, suspendFaceRecognitionChannels, resumeFaceRecognitionChannels } from '@/utils/ws'
  108. import dayjs from 'dayjs'
  109. import dataDictionary from "@/utils/dataDictionary"
  110. const { proxy } = getCurrentInstance() as any;
  111. const router = useRouter();
  112. const route = useRoute();
  113. const faceWindowRef = ref();
  114. const chooseStudentRef = ref();
  115. const myInfo: any = localStorage.getItem("userInfo");
  116. const dic: any = dataDictionary;
  117. const data = reactive<any>({
  118. timerManager: {},//计时器管理
  119. parameter: {},//参数
  120. time: {
  121. testTime: 60,//时长
  122. countdownNum: 0,//计时
  123. },
  124. userInfo: {},//用户信息
  125. examState: 0,//当前状态
  126. resultId: null,//测试ID
  127. currentResultObj: {},//成绩
  128. faceCheckStu: {},//人脸信息
  129. unit: "",//单位
  130. backReason: [],//犯规项
  131. needStart: false,//是否需要按钮
  132. showTestAgain: false,//再测一次按钮
  133. reportList: [],//测试列表
  134. date: "",//当前时间
  135. });
  136. const { timerManager, parameter, time, userInfo, examState, resultId, faceCheckStu, currentResultObj, unit, backReason, needStart, showTestAgain, reportList, date } = toRefs(data);
  137. /**
  138. * 接收消息
  139. */
  140. const getMessage = (e: any) => {
  141. //console.log("WS响应:", e)
  142. //实时状态
  143. if (e.cmd === 'exam_status') {
  144. examState.value = e.data;
  145. }
  146. //工作站状态
  147. if (e.cmd === 'init_result') {
  148. }
  149. //测试违规
  150. if (e.cmd === 'warning_result') {
  151. }
  152. //后端播报语音
  153. if (e.cmd === 'return_audio_msg') {
  154. }
  155. //错误提示
  156. if (e.cmd === 'info_result') {
  157. proxy?.$modal.msgError(e.data.message);
  158. }
  159. //错误提示
  160. if (e.cmd === 'error_result') {
  161. proxy?.$modal.msgError(e.data.message);
  162. }
  163. //测试中违规提示
  164. if (e.cmd === 'warning_notify') {
  165. }
  166. //断线状态
  167. if (e.cmd === 'disconnect_request') {
  168. if (e.data.message) {
  169. speckText(e.data.message);
  170. }
  171. getExit();
  172. }
  173. //状态变更
  174. if (e.cmd === 'set_exam_state') {
  175. examState.value = e.data;
  176. if (e.data === 3) {
  177. initProject();
  178. }
  179. if (e.data === 40) {
  180. cleanData();
  181. }
  182. if (e.data == 41) {
  183. getFaceWindow(true);
  184. }
  185. if (e.data == 43) {
  186. }
  187. if (e.data == 42) {
  188. }
  189. }
  190. //新建测试后返回信息,获取result_id
  191. if (e.cmd === 'open_one_test_ack') {
  192. resultId.value = e.data.result_id;
  193. }
  194. //人脸识别状态
  195. if (e.cmd === 'face_check_result') {
  196. let myData = e.data[0] || e.data;
  197. returnStudent(myData);
  198. }
  199. //测试结束结果
  200. if (e.cmd === 'oneresult') {
  201. if (e.data.length) {
  202. let data = e.data[0];
  203. getAchievement(data)
  204. }
  205. }
  206. //结果生成完成(视频图片)
  207. if (e.cmd === 'static_urls_finished') {
  208. }
  209. //选择学生或测试结束后返回的数据
  210. if (e.cmd === 'result_info') {
  211. }
  212. };
  213. /**
  214. * 开始识别
  215. */
  216. const getOpenOneTestAndStartFace = async () => {
  217. if (examState.value > 3) {
  218. await closeOneTest();
  219. }
  220. await openOneTest();
  221. await startFace();
  222. };
  223. /**
  224. * 停止人脸识别
  225. */
  226. const getStopFace = async () => {
  227. // 旧版识别成功直接43了这里先屏蔽
  228. // if (examState.value != 41) {
  229. // return false;
  230. // }
  231. getClearTimer("face");
  232. speckText(faceCheckStu.value.name + ",请准备");
  233. await stopFace();
  234. if (faceCheckStu.value.student_id) {
  235. getFaceConfirmOnly();
  236. }
  237. };
  238. /**
  239. * 确定人脸信息
  240. */
  241. const getFaceConfirmOnly = (data?: any) => {
  242. if (data) {
  243. faceCheckStu.value = data;
  244. }
  245. faceConfirmOnly({
  246. result_id: resultId.value,
  247. student_id: faceCheckStu.value.student_id,
  248. gender: faceCheckStu.value.gender
  249. }, () => {
  250. faceWindowRef.value?.close();
  251. //不需要按钮的自动进入下一步
  252. if (needStart.value == false) {
  253. getStartOneTest();
  254. }
  255. });
  256. };
  257. /**
  258. * 重新识别
  259. */
  260. const getRetestFace = () => {
  261. proxy?.$modal.confirm("确定重新识别吗?").then(() => {
  262. if (needStart.value == false) {
  263. //自动流程项目重新识别直接返回3
  264. closeOneTest();
  265. } else {
  266. //手动流程项目重新识别43返回41,42返回3
  267. if (examState.value == 43) {
  268. cleanData();
  269. startFace();
  270. } else {
  271. closeOneTest();
  272. }
  273. }
  274. }).finally(() => {
  275. });
  276. };
  277. /**
  278. * 开始测试
  279. */
  280. const getStartOneTest = () => {
  281. if (examState.value != 43 || !faceCheckStu.value.student_id) {
  282. return false;
  283. }
  284. if (!faceCheckStu.value.student_id) {
  285. proxy?.$modal.msgError("请选择人员!");
  286. return false;
  287. }
  288. startOneTest(data == null, () => {
  289. //显示再测一次按钮
  290. showTestAgain.value = true;
  291. //停止播报;
  292. speckCancel()
  293. //计时项目才开
  294. if (needStart.value == true) {
  295. //时间为0的为正计时,大于0的为倒计时
  296. if (time.value.testTime == 0) {
  297. getCounting("+");
  298. } else {
  299. getCounting("-");
  300. }
  301. speckText("哨声");
  302. } else {
  303. speckText(faceCheckStu.value.name + ",请开始测试");
  304. }
  305. })
  306. };
  307. /**
  308. * 再测一次
  309. */
  310. const getAgain = async () => {
  311. //预存测试人员
  312. let student = JSON.parse(JSON.stringify(faceCheckStu.value));
  313. //测试中
  314. if (examState.value == 42) {
  315. await finishOneTest();
  316. }
  317. //其他状态
  318. if (examState.value > 3) {
  319. await closeOneTest();
  320. }
  321. //重新走一次流程
  322. await openOneTest();
  323. await startFace();
  324. await stopFace();
  325. getFaceConfirmOnly(student);
  326. };
  327. /**
  328. * 确认退出
  329. */
  330. const confirmExit = () => {
  331. proxy?.$modal.confirm("确定退出吗?").then(() => {
  332. getExit();
  333. }).finally(() => {
  334. });
  335. };
  336. /**
  337. * 退出
  338. */
  339. const getExit = () => {
  340. getClearTimer();//清除计时器
  341. examEnds();//通知工作站关闭
  342. speckCancel()//停止播报;
  343. router.push({ path: '/' });//跳转
  344. };
  345. /**
  346. * 清空定时任务
  347. */
  348. const getClearTimer = (data?: any) => {
  349. if (data) {
  350. //清除指定
  351. clearInterval(timerManager.value[data])
  352. timerManager.value[data] = null;
  353. } else {
  354. //清除全部
  355. for (let key in timerManager.value) {
  356. if (timerManager.value.hasOwnProperty(key)) {
  357. clearInterval(timerManager.value[key])
  358. timerManager.value[key] = null;
  359. }
  360. }
  361. }
  362. };
  363. /**
  364. * 选择学生
  365. */
  366. const getChooseStudent = () => {
  367. if (examState.value < 41) {
  368. if (needStart.value) {
  369. proxy?.$modal.msgError("请点击开始识别");
  370. } else {
  371. proxy?.$modal.msgError("请等待");
  372. }
  373. }
  374. if (examState.value == 41) {
  375. chooseStudentRef.value.open();
  376. //然后定时自动关闭
  377. setTimeout(() => {
  378. faceWindowRef.value.close();
  379. }, 3000)
  380. }
  381. if (examState.value == 43) {
  382. getRetestFace();
  383. }
  384. };
  385. /**
  386. * 返回被选学生
  387. */
  388. const returnStudent = (data: any) => {
  389. faceCheckStu.value = data;
  390. faceWindowRef.value.open();
  391. //然后定时自动关闭
  392. setTimeout(() => {
  393. faceWindowRef.value.close();
  394. }, 1000)
  395. getStopFace();
  396. };
  397. /**
  398. * 清除历史记录
  399. */
  400. const cleanData = () => {
  401. time.value.countdownNum = time.value.testTime;
  402. showTestAgain.value = false;
  403. faceCheckStu.value = {};
  404. currentResultObj.value = {};
  405. backReason.value = [];
  406. };
  407. /**
  408. * 自动初始化项目
  409. */
  410. const initProject = () => {
  411. //停止计时
  412. getClearTimer("countdownTimer");
  413. //自动项目定时进入下一步
  414. let time = 0;
  415. //控制新建测试的时间,第一次快,之后就慢
  416. if (!faceCheckStu.value.student_id) {
  417. time = 1000;
  418. } else {
  419. time = 6000;
  420. }
  421. setTimeout(() => {
  422. //再加一个判断以免和再测一次冲突
  423. if (examState.value == 3) {
  424. getOpenOneTestAndStartFace();
  425. }
  426. }, time)
  427. };
  428. /**
  429. * 时间转换
  430. */
  431. const countdownNumFormat = computed(() => {
  432. return time.value.countdownNum;
  433. //return proxy?.$utils.timeFormat(time.value.countdownNum);
  434. });
  435. /**
  436. * 倒计时
  437. */
  438. const getCounting = (type: string) => {
  439. timerManager.value.countdownTimer = setInterval(() => {
  440. //正计时
  441. if (type == "+") {
  442. time.value.countdownNum++;
  443. }
  444. //倒计时
  445. if (type == "-") {
  446. if (time.value.countdownNum <= 0) {
  447. getClearTimer("countdownTimer");
  448. } else {
  449. time.value.countdownNum--;
  450. }
  451. }
  452. }, 1000);
  453. };
  454. /**
  455. * 人脸窗口
  456. */
  457. const getFaceWindow = (data: boolean) => {
  458. let txt = parameter.value.gesture ? "请举手看摄像头人脸识别" : "请看摄像头进行人脸识别";
  459. speckText(txt);
  460. //data=true为弹出框,data=false为不要弹出框
  461. if (data) {
  462. faceWindowRef.value.open();
  463. //然后定时自动关闭
  464. setTimeout(() => {
  465. if (examState.value == 41 && faceWindowRef.value.faceState == true) {
  466. faceWindowRef.value.close();
  467. }
  468. }, 3000)
  469. }
  470. //定时检查如果一直停留在人脸识别就提示
  471. timerManager.value.face = setInterval(() => {
  472. getClearTimer("face");
  473. if (examState.value == 41) {
  474. getFaceWindow(false);
  475. }
  476. }, 15000)
  477. };
  478. /**
  479. * 成绩
  480. */
  481. const getAchievement = (data: any) => {
  482. //console.log("成绩", data);
  483. let type = parameter.value.project;
  484. let count =
  485. data?.[dic.typeResultKey[type]]?.toFixed(0);
  486. if (["trijump", "solidball", "shotput", "longjump"].includes(type)) {
  487. count =
  488. data?.[dic.typeResultKey[type]]?.toFixed(2);
  489. count = Math.round(count) / 100;
  490. }
  491. data.count = count || "0";
  492. data.score = data.score || "0";
  493. currentResultObj.value = data;
  494. //违规处理
  495. let arr = backReason.value;
  496. if (["situp", "pullup", "sidepullup", "jumprope", "jumpingjack", "jump", "longjump", "verticaljump"]
  497. .indexOf(type) > -1) {
  498. if (["pullup", "situp", "jumprope", "jumpingjack"].indexOf(type) > -1) {
  499. currentResultObj.value.back_num = data?.all_failed_num;
  500. }
  501. if (type === "sidepullup") {
  502. currentResultObj.value.back_num = data?.["0"]?.hip_failed_num;
  503. }
  504. if (['jump', 'longjump', 'verticaljump'].includes(type)) {
  505. if (data?.startline_check == 0) {
  506. let txt = "踩线违规";
  507. arr.push(txt)
  508. }
  509. if (data?.singleleg_jump_check == 0) {
  510. let txt = "单脚跳违规";
  511. arr.push(txt)
  512. }
  513. if (data?.outside_check == 0) {
  514. let txt = "跳出测试区违规";
  515. arr.push(txt)
  516. }
  517. }
  518. if (
  519. data?.elbow_check == false
  520. ) {
  521. let txt = "肘部违规";
  522. arr.push(txt);
  523. }
  524. if (
  525. ["situp", "pullup"].indexOf(type) > -1 &&
  526. data?.knee_check === false
  527. ) {
  528. let txt = "腿部违规";
  529. if (!arr.includes(txt)) {
  530. arr.push(txt);
  531. }
  532. }
  533. if (["situp"].indexOf(type) > -1 && data?.hand_check === false) {
  534. let txt = "手部违规";
  535. if (!arr.includes(txt)) {
  536. arr.push(txt);
  537. }
  538. }
  539. if (
  540. ["pullup"].indexOf(type) > -1 &&
  541. data?.["0"]?.elbow_check === false
  542. ) {
  543. let txt = "手部违规";
  544. if (!arr.includes(txt)) {
  545. arr.push(txt);
  546. }
  547. }
  548. if (["situp"].indexOf(type) > -1 && data?.["0"]?.back_check === false) {
  549. let txt = "背部违规";
  550. if (!arr.includes(txt)) {
  551. arr.push(txt);
  552. }
  553. }
  554. if (
  555. ["sidepullup", "situp"].indexOf(type) > -1 &&
  556. data?.["0"]?.hip_check === false
  557. ) {
  558. let txt = "臀部违规";
  559. if (!arr.includes(txt)) {
  560. arr.push(txt);
  561. }
  562. }
  563. }
  564. backReason.value = arr;
  565. if (data.isfinish) {
  566. if (['jump'].includes(type) && backReason.value.length) {
  567. speckText("请重新测试");
  568. return false;
  569. }
  570. speckText(faceCheckStu?.value.name + "成绩为" + (chineseNumber(count) || 0) + unit.value + ",请下一位准备!" || "");
  571. getReportList();
  572. }
  573. };
  574. /**
  575. * 当天成绩列表
  576. */
  577. const getReportList = () => {
  578. let params: any = {
  579. start_date: dayjs().format("YYYY-MM-DD"),
  580. end_date: dayjs().format("YYYY-MM-DD"),
  581. exam_name: parameter.value.project,
  582. page: 1,
  583. per_page: 9000
  584. };
  585. proxy?.$http.common.reportList(params).then((res: any) => {
  586. if (res.data.length > 0) {
  587. reportList.value = res.data;
  588. }
  589. });
  590. };
  591. /**
  592. * 更新时间
  593. */
  594. const setDate = () => {
  595. date.value = proxy?.$utils.getDate();
  596. setTimeout(setDate, 1000);
  597. };
  598. /**
  599. * 更新时间
  600. */
  601. const getFullScreen = () => {
  602. date.value = proxy?.$utils.fullScreen();
  603. };
  604. onMounted(() => {
  605. setDate();
  606. parameter.value = route.query;
  607. let project = parameter.value.project;
  608. let area = parameter.value.area;
  609. parameter.value.examId = `${project}_${area}`; //项目+区
  610. if (parameter.value.time) {
  611. time.value.testTime = parameter.value.time
  612. }
  613. time.value.countdownNum = time.value.testTime;
  614. userInfo.value = JSON.parse(myInfo);
  615. unit.value = dic.unit[project];
  616. if (parameter.value.gesture == 'true') {
  617. parameter.value.gesture = true
  618. } else {
  619. parameter.value.gesture = false
  620. }
  621. //需要开始按钮的项目
  622. if (["jumprope", "skiprope", "jumpingjack", "situp"].includes(project)) {
  623. needStart.value = true;
  624. }
  625. //加载WS
  626. initWs({ parameter: parameter.value, testTime: time.value.testTime }, (data: any) => {
  627. getMessage(data);
  628. });
  629. initSpeech();
  630. getReportList();
  631. })
  632. onUnmounted(() => {
  633. getExit();
  634. })
  635. </script>
  636. <style lang="scss" scoped>
  637. $topPadding: 5.19rem;
  638. $waiPadding: 6.51rem;
  639. .logo {
  640. position: absolute;
  641. left: $waiPadding;
  642. top: $topPadding;
  643. display: flex;
  644. align-items: center;
  645. img {
  646. width: 14.563rem;
  647. }
  648. .title {
  649. width: auto;
  650. padding: 0 1.2rem 0 0.7rem;
  651. height: 3rem;
  652. line-height: 3rem;
  653. border-radius: 1.5rem;
  654. color: #ffffff;
  655. font-size: 1.7rem;
  656. margin-left: 1rem;
  657. background: linear-gradient(180deg, #FFB200 0%, #ED7905 72%);
  658. box-shadow: inset 0px 1px 0px 1px rgba(255, 255, 255, 0.5);
  659. display: flex;
  660. align-items: center;
  661. i {
  662. width: 2.5rem;
  663. height: 2.1rem;
  664. margin-right: 0.8rem;
  665. background-image: url("@/assets/images/test/ai.png");
  666. background-position: center;
  667. background-repeat: no-repeat;
  668. background-size: 100% 100%;
  669. }
  670. }
  671. }
  672. .close {
  673. width: 3.2rem;
  674. height: 3.2rem;
  675. position: absolute;
  676. right: calc($waiPadding - 3.2rem);
  677. top: calc($topPadding / 2 - 3.2rem/4);
  678. box-sizing: border-box;
  679. border: 1px solid #979797;
  680. background-image: url("@/assets/images/common/close.png");
  681. background-position: center;
  682. background-repeat: no-repeat;
  683. background-size: 45% 45%;
  684. background-color: rgba(216, 216, 216, 0.8);
  685. border-radius: 50%;
  686. transition: all 0.5s;
  687. cursor: pointer;
  688. &:hover {
  689. transform: rotate(180deg);
  690. background-color: rgba(216, 216, 216, 1);
  691. }
  692. }
  693. .toolList {
  694. position: absolute;
  695. right: $waiPadding;
  696. top: calc($topPadding / 2);
  697. color: #00FFE8;
  698. font-size: 1.1rem;
  699. display: flex;
  700. .li {
  701. margin-right: 3rem;
  702. }
  703. .btn {
  704. width: 1.1rem;
  705. height: 1.1rem;
  706. display: block;
  707. cursor: pointer;
  708. }
  709. .speck {
  710. background-image: url("@/assets/images/common/speck.png");
  711. background-position: center;
  712. background-repeat: no-repeat;
  713. background-size: 100% auto;
  714. }
  715. .screen {
  716. background-image: url("@/assets/images/common/screen.png");
  717. background-position: center;
  718. background-repeat: no-repeat;
  719. background-size: 100% auto;
  720. }
  721. }
  722. .main {
  723. width: calc(100% - ($waiPadding * 2));
  724. height: 78vh;
  725. padding-top: 10rem;
  726. margin: 0 auto;
  727. display: flex;
  728. justify-content: space-between;
  729. overflow: hidden;
  730. .main-left {
  731. width: 71.5%;
  732. display: flex;
  733. flex-direction: column;
  734. justify-content: space-between;
  735. .main-left-top {
  736. display: flex;
  737. justify-content: space-between;
  738. height: 55.8%;
  739. overflow: hidden;
  740. position: relative;
  741. .top-left {
  742. width: 37.4%;
  743. height: 100%;
  744. border-radius: 29px;
  745. opacity: 1;
  746. background: radial-gradient(122% 126% at 97% 6%, #35FFC6 0%, #00FFE8 100%);
  747. text-align: center;
  748. display: flex;
  749. align-items: center;
  750. justify-content: center;
  751. cursor: pointer;
  752. .pic {
  753. width: 22.3vh;
  754. height: 22.3vh;
  755. margin-bottom: 2vh;
  756. border-radius: 50%;
  757. display: flex;
  758. justify-content: center;
  759. align-items: center;
  760. overflow: hidden;
  761. img {
  762. width: 100%;
  763. }
  764. }
  765. .pic2 {
  766. box-sizing: border-box;
  767. border: 0.44rem solid rgba(26, 41, 58, 0.6315);
  768. }
  769. .name {
  770. width: 100%;
  771. color: #1A293A;
  772. font-size: 2.21rem;
  773. }
  774. .name2 {
  775. padding: 0 0.3rem;
  776. border-radius: 1.1rem;
  777. background: radial-gradient(96% 96% at 2% 32%, #FFFFFF 0%, #FCFDFD 54%, #E1E4E7 100%);
  778. box-shadow: inset 0px 1px 0px 2px rgba(255, 255, 255, 0.9046), inset 0px 3px 6px 0px rgba(0, 0, 0, 0.0851);
  779. }
  780. }
  781. .top-right {
  782. width: 62%;
  783. height: 100%;
  784. border-radius: 29px;
  785. opacity: 1;
  786. background: #ffffff;
  787. box-sizing: border-box;
  788. border: 0.55rem solid #13ED84;
  789. .tips {
  790. display: flex;
  791. justify-content: center;
  792. align-items: center;
  793. height: 100%;
  794. img {
  795. max-height: 80%;
  796. max-height: 80%;
  797. }
  798. }
  799. .scoreBox {
  800. color: #1A293A;
  801. display: flex;
  802. align-items: center;
  803. .score {
  804. font-family: 'Saira-ExtraBold';
  805. font-size: 7.178rem;
  806. font-variation-settings: "opsz" auto;
  807. }
  808. .unit {
  809. font-size: 2rem;
  810. margin-left: 10px;
  811. }
  812. }
  813. .fractionViolation {
  814. display: flex;
  815. align-items: center;
  816. .fraction {
  817. width: 40%;
  818. height: 11vh;
  819. line-height: 11vh;
  820. border-radius: 5.5vh;
  821. display: flex;
  822. align-items: center;
  823. justify-content: center;
  824. background: linear-gradient(138deg, #38536C 21%, #1A293A 75%);
  825. box-shadow: inset 0px 1px 13px 0px rgba(255, 255, 255, 0.9452);
  826. .lable {
  827. font-size: 1.93rem;
  828. color: #13ED84;
  829. }
  830. .value {
  831. color: #00FFE8;
  832. font-family: 'Saira-ExtraBold';
  833. font-size: 6.62rem;
  834. font-variation-settings: "opsz" auto;
  835. }
  836. }
  837. .violation {
  838. height: 8vh;
  839. line-height: 8vh;
  840. border-radius: 4vh;
  841. box-sizing: border-box;
  842. border: 0.25rem solid #ED7905;
  843. display: flex;
  844. align-items: center;
  845. margin-left: 11px;
  846. .lable {
  847. font-size: 1.2rem;
  848. color: #ffffff;
  849. width: 6vh;
  850. height: 6vh;
  851. line-height: 6vh;
  852. background: #ED7905;
  853. border-radius: 50%;
  854. margin: 0 1vh;
  855. text-align: center;
  856. }
  857. .value {
  858. color: #ED7905;
  859. font-family: 'Saira-ExtraBold';
  860. font-size: 3.2rem;
  861. font-variation-settings: "opsz" auto;
  862. }
  863. }
  864. }
  865. .btn {
  866. font-size: 2.21rem;
  867. color: #FFFFFF;
  868. text-align: center;
  869. width: 50%;
  870. line-height: 8vh;
  871. line-height: 8vh;
  872. border-radius: 15px;
  873. opacity: 1;
  874. background: radial-gradient(159% 126% at 5% 93%, #F99F02 0%, #ED7905 100%);
  875. box-shadow: 3px 6px 4px 1px rgba(0, 0, 0, 0.1874), inset 0px 1px 0px 2px rgba(255, 255, 255, 0.5577);
  876. cursor: pointer;
  877. }
  878. }
  879. i {
  880. width: 4vw;
  881. height: 4vw;
  882. display: block;
  883. position: absolute;
  884. top: 50%;
  885. left: 37.5%;
  886. margin-top: calc(4vw * -0.5);
  887. margin-left: calc(4vw * -0.5);
  888. background-image: url("@/assets/images/test/yuan.png");
  889. background-position: center;
  890. background-repeat: no-repeat;
  891. background-size: 100% 100%;
  892. border-radius: 50%;
  893. flex-shrink: 0;
  894. transition: all 0.5s;
  895. }
  896. }
  897. .main-left-bottom {
  898. display: flex;
  899. justify-content: space-between;
  900. height: calc(100% - 55.8% - 3vh);
  901. overflow: hidden;
  902. .bottom-left {
  903. width: 58%;
  904. padding-right: 1rem;
  905. display: flex;
  906. flex-direction: column;
  907. .tips {
  908. height: 2.8vh;
  909. img {
  910. height: 100%;
  911. }
  912. }
  913. .pic {
  914. text-align: center;
  915. width: 100%;
  916. height: 100%;
  917. display: flex;
  918. justify-content: center;
  919. overflow: hidden;
  920. img {
  921. max-width: 100%;
  922. max-height: 100%;
  923. }
  924. }
  925. }
  926. .bottom-right {
  927. width: 41%;
  928. height: 100%;
  929. overflow-y: scroll;
  930. color: #F9F9F9;
  931. font-size: 1.1rem;
  932. line-height: 1.6rem;
  933. &::-webkit-scrollbar {
  934. width: 10px;
  935. }
  936. &::-webkit-scrollbar-thumb {
  937. border-width: 2px;
  938. border-radius: 4px;
  939. border-style: dashed;
  940. border-color: transparent;
  941. background-color: rgba(26, 62, 78, 0.9);
  942. background-clip: padding-box;
  943. }
  944. &::-webkit-scrollbar-button:hover {
  945. border-radius: 6px;
  946. background: rgba(26, 62, 78, 1);
  947. }
  948. }
  949. }
  950. }
  951. .main-right {
  952. width: 27%;
  953. border-radius: 1.6rem;
  954. background: linear-gradient(29deg, #092941 -82%, #2A484B 94%);
  955. box-shadow: inset 0px 1px 0px 2px rgba(255, 255, 255, 0.4);
  956. display: flex;
  957. flex-direction: column;
  958. overflow: hidden;
  959. .title {
  960. height: 7.05vh;
  961. line-height: 7.05vh;
  962. width: 100%;
  963. text-align: center;
  964. color: #1A293A;
  965. font-size: 1.65rem;
  966. background: radial-gradient(120% 126% at 5% 93%, #8EFFA9 0%, #07FFE7 100%);
  967. }
  968. ul {
  969. height: 100%;
  970. overflow-y: scroll;
  971. li {
  972. border-bottom: 1px solid #48677E;
  973. padding: 8px 30px;
  974. display: flex;
  975. justify-content: space-between;
  976. align-items: center;
  977. cursor: pointer;
  978. &:hover {
  979. background: rgba(255, 255, 255, 0.4);
  980. }
  981. .left {
  982. display: flex;
  983. .pic {
  984. width: 7.5vh;
  985. height: 7.5vh;
  986. border-radius: 50%;
  987. display: flex;
  988. justify-content: center;
  989. align-items: center;
  990. overflow: hidden;
  991. box-sizing: border-box;
  992. border: 1px solid rgba(255, 255, 255, 0.5);
  993. margin-right: 13px;
  994. img {
  995. width: 100%;
  996. }
  997. }
  998. .txt {
  999. display: flex;
  1000. align-items: center;
  1001. .name {
  1002. color: #F9F9F9;
  1003. font-size: 1.38rem;
  1004. }
  1005. .className {
  1006. color: #13ED84;
  1007. font-size: 1.1rem;
  1008. }
  1009. }
  1010. }
  1011. .right {
  1012. display: flex;
  1013. font-weight: bold;
  1014. align-items: center;
  1015. .score {
  1016. color: #ffffff;
  1017. font-size: 1.1rem;
  1018. font-family: 'Saira-ExtraBold';
  1019. }
  1020. .unit {
  1021. color: #ffffff;
  1022. font-size: 0.8rem;
  1023. margin-left: 2px;
  1024. }
  1025. }
  1026. }
  1027. &::-webkit-scrollbar {
  1028. width: 0px;
  1029. }
  1030. &::-webkit-scrollbar-thumb {
  1031. border-width: 2px;
  1032. border-radius: 4px;
  1033. border-style: dashed;
  1034. border-color: transparent;
  1035. background-color: rgba(216, 216, 216, 0.8);
  1036. background-clip: padding-box;
  1037. }
  1038. &::-webkit-scrollbar-button:hover {
  1039. border-radius: 6px;
  1040. background: rgba(216, 216, 216, 0.8);
  1041. }
  1042. }
  1043. .erweima {
  1044. text-align: center;
  1045. padding: 1vh 0;
  1046. img {
  1047. width: 5rem;
  1048. }
  1049. span {
  1050. display: block;
  1051. color: #FFFFFF;
  1052. font-size: 1.1rem;
  1053. padding-top: 3px;
  1054. }
  1055. }
  1056. }
  1057. }
  1058. .chengji {
  1059. font-family: "Saira Black";
  1060. }
  1061. </style>