test.vue 30 KB

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