test.vue 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416
  1. <template>
  2. <div class="test">
  3. <Header @confirmExit="confirmExit"></Header>
  4. <div class="main">
  5. <div class="main-left">
  6. <div class="main-left-top">
  7. <div class="top-left" @click="getChooseStudent">
  8. <div class="top-left-center">
  9. <div class="pic" :class="{ 'pic2': faceCheckStu.student_id }" v-if="faceCheckStu.student_id"> <img
  10. :src="faceCheckStu.face_pic || faceCheckStu.logo_url" /></div>
  11. <div class="pic" v-else>
  12. <img src="@/assets/images/test/profilePicture.png" />
  13. </div>
  14. <div class="name" :class="{ 'name2': faceCheckStu.student_id }">
  15. {{ faceCheckStu.student_id ? faceCheckStu.name : "虚位以待" }}
  16. </div>
  17. </div>
  18. </div>
  19. <div class="top-right">
  20. <Transition :enter-active-class="proxy?.animate.dialog.enter"
  21. :leave-active-class="proxy?.animate.dialog.leave">
  22. <div class="time" v-show="needStart && [42].includes(examState) && !['basketballv1','footballv1'].includes(parameter.project)">{{
  23. time.countdownNum
  24. }}</div>
  25. </Transition>
  26. <div class="tips" v-if="examState == 41">
  27. <img v-if="parameter.gesture" src="@/assets/images/test/ready1.png" />
  28. <img v-if="!parameter.gesture" src="@/assets/images/test/ready2.png" />
  29. </div>
  30. <div class="complete" :class="{ 'complete2': needStart && [42].includes(examState) }"
  31. v-if="faceCheckStu.student_id && time.ready <= 0 && examState != 43 && examState != 41">
  32. <div class="scoreBox" v-if="['basketballv1','footballv1'].includes(parameter.project)">
  33. <div class="score">{{ currentResultObj.count || 0 }}</div>
  34. </div>
  35. <div class="scoreBox" v-else>
  36. <div class="score">{{ currentResultObj.count || 0 }}</div>
  37. <div class="unit" v-if="currentResultObj.count && !needStart">{{ unit }}</div>
  38. </div>
  39. <div class="fractionViolation">
  40. <div class="fraction">
  41. <div class="lable">得分:</div>
  42. <div class="value">{{ currentResultObj.score || "" }}</div>
  43. </div>
  44. <div class="violation">
  45. <div class="lable">{{ ['jumprope'].includes(parameter.project) ? '中断' : '犯规' }}</div>
  46. <div class="value">{{ currentResultObj.back_num || 0 }}</div>
  47. </div>
  48. </div>
  49. </div>
  50. <div class="foulBox" v-if="examState == 42 && backReason.length">
  51. <Transition :enter-active-class="proxy?.animate.mask.enter"
  52. :leave-active-class="proxy?.animate.mask.leave">
  53. <div class="foul" v-show="backReasonStr ? true : false">
  54. <div class="lable">!</div>
  55. <div class="value">{{ backReasonStr }}</div>
  56. </div>
  57. </Transition>
  58. </div>
  59. <div v-show="examState == 43 && time.ready">
  60. <div class="readyBox">
  61. <div class="value" :class="{ 'transparent': time.ready > 5 }">{{ time.ready }}</div>
  62. <div class="lable">倒计时</div>
  63. </div>
  64. </div>
  65. <div v-show="examState == 43 && !time.ready && readyState">
  66. <div class="readyBoxBefore">
  67. <div class="item" v-if="parameter.handcontroller">
  68. <div><img src="@/assets/images/test/jushou.png" /></div>
  69. <div class="lable">
  70. <div>请举左手开始</div>
  71. </div>
  72. </div>
  73. <div class="item" v-else>
  74. <div> <img src="@/assets/images/test/bujushou.png" /></div>
  75. <div class="lable">
  76. <div>请点击开始</div>
  77. </div>
  78. </div>
  79. </div>
  80. </div>
  81. <div class="btn" @click="getReady" v-if="needStart && examState == 43 && !time.ready && readyState">开 始
  82. </div>
  83. <!-- <div v-if="needStart"> -->
  84. <!-- <div class="btn" @click="getOpenOneTestAndStartFace" v-if="examState < 41">开始识别</div> -->
  85. <!-- <div class="btn" @click="getStopFace" v-if="examState == 41 && !parameter.gesture">停止人脸识别</div> -->
  86. <!-- <div class="btn" @click="getStartOneTest" v-if="examState == 43">开始测试</div> -->
  87. <!-- <div @click="getRetestFace" v-if="examState == 43 || examState == 42">重新识别</div> -->
  88. <!-- </div> -->
  89. <!-- <div>当前状态:({{ examState == 3 ? "初始化完成" : examState == 40 ? "创建测试" : examState == 41 ? "正在人脸识别":examState == 43 ? "停止人脸识别" : examState == 42 ? "正在测试" : "请初始化" }})</div> -->
  90. <!-- <div @click="getAgain" v-if="examState == 42 || showTestAgain">再测一次</div> -->
  91. </div>
  92. <i></i>
  93. </div>
  94. <div class="main-left-bottom">
  95. <div class="bottom-left">
  96. <div class="tips"><img src="/src/assets/images/test/tips.png" /></div>
  97. <div class="pic"><img :src="'static/images/tips/' + parameter.project + '.png'" /></div>
  98. </div>
  99. <div class="bottom-right" v-html="dic.projectNote[parameter.project]">
  100. </div>
  101. </div>
  102. </div>
  103. <div class="main-right">
  104. <ReportList ref="reportListRef" :parameter="parameter" :showQRCode="true" />
  105. </div>
  106. </div>
  107. <FaceWindow ref="faceWindowRef" :faceCheckStu="faceCheckStu" :gesture="parameter.gesture" />
  108. <ChooseStudent ref="chooseStudentRef" @returnData="returnStudent" />
  109. </div>
  110. </template>
  111. <script setup name="TrainTest" lang="ts">
  112. import { useWs } from '@/utils/trainWs';
  113. // import { initWs, examEnds, openOneTest, startFace, stopFace, faceConfirmOnly, startOneTest, finishOneTest, closeOneTest, suspendFaceRecognitionChannels, resumeFaceRecognitionChannels } from '@/utils/ws'
  114. import { initSpeech, speckText, playMusic, controlMusic, speckCancel, chineseNumber } from '@/utils/speech'
  115. import { useWebSocket } from '@/utils/handWs';
  116. import dayjs from 'dayjs'
  117. import dataDictionary from "@/utils/dataDictionary"
  118. const { handWs, startDevice, startHand, stateHand } = useWebSocket();
  119. const { initWs, examEnds, openOneTest, startFace, stopFace, faceConfirmOnly, startOneTest, finishOneTest, closeOneTest, suspendFaceRecognitionChannels, resumeFaceRecognitionChannels } = useWs();
  120. const { proxy } = getCurrentInstance() as any;
  121. const router = useRouter();
  122. const route = useRoute();
  123. const faceWindowRef = ref();
  124. const chooseStudentRef = ref();
  125. const reportListRef = ref();
  126. const myInfo: any = localStorage.getItem("userInfo");
  127. const dic: any = dataDictionary;
  128. const data = reactive<any>({
  129. timerManager: {},//计时器管理
  130. parameter: {},//参数
  131. time: {
  132. testTime: 60,//时长
  133. countdownNum: 0,//计时
  134. ready: 0,//预备
  135. exit: 0,//退出倒计时
  136. },
  137. userInfo: {},//用户信息
  138. examState: 0,//当前状态
  139. resultId: null,//测试ID
  140. currentResultObj: {},//成绩
  141. faceCheckStu: {},//人脸信息
  142. unit: "",//单位
  143. backReason: [],//犯规项
  144. backReasonStr: "",//犯规提示
  145. needStart: false,//是否需要按钮
  146. showTestAgain: false,//再测一次按钮
  147. readyState: true,//倒计时按钮状态
  148. exit: 0,//退出响应次数
  149. sid: null,//WS的id
  150. device_info: {},//设备信息
  151. });
  152. const { timerManager, parameter, time, userInfo, examState, resultId, faceCheckStu, currentResultObj, unit, backReason, backReasonStr, needStart, showTestAgain, readyState, exit, sid, device_info } = toRefs(data);
  153. /**
  154. * 接收消息
  155. */
  156. const getMessage = (e: any) => {
  157. //console.log("WS响应:", e)
  158. //获取sid
  159. if (e.cmd === 'mySid') {
  160. console.log("e.data.sid", e.data.sid)
  161. sid.value = e.data.sid;
  162. }
  163. //实时状态
  164. if (e.cmd === 'exam_status') {
  165. examState.value = e.data;
  166. }
  167. //工作站状态
  168. if (e.cmd === 'init_result') {
  169. }
  170. //测试违规
  171. if (e.cmd === 'warning_result') {
  172. console.log("eeeeeeeeeee", e)
  173. if ((e.status + "")[0] === "2") {
  174. proxy?.$modal.msgError(e.data.message);
  175. speckText(e.data.message);
  176. }
  177. }
  178. //后端播报语音
  179. if (e.cmd === 'return_audio_msg') {
  180. if (e.data.message) {
  181. proxy?.$modal.msgError(e.data.message);
  182. speckText(e.data.message);
  183. }
  184. }
  185. //错误提示
  186. if (e.cmd === 'info_result') {
  187. proxy?.$modal.msgError(e.data.message);
  188. }
  189. //错误提示
  190. if (e.cmd === 'error_result') {
  191. proxy?.$modal.msgError(e.data.message);
  192. }
  193. //测试中违规提示
  194. if (e.cmd === 'warning_notify') {
  195. let message = e.data.message;
  196. if (message) {
  197. proxy?.$modal.msgError(message);
  198. speckText(message);
  199. }
  200. if (message == "工作站已断开!") {
  201. getExit();
  202. }
  203. }
  204. //断线状态
  205. if (e.cmd === 'disconnect_request') {
  206. let message = e.data.message;
  207. if (message) {
  208. proxy?.$modal.msgError(`${message}`);
  209. speckText(message);
  210. }
  211. getExit();
  212. }
  213. //状态变更
  214. if (e.cmd === 'set_exam_state') {
  215. examState.value = e.data;
  216. if (e.data === 3) {
  217. initProject();
  218. }
  219. if (e.data === 40) {
  220. cleanData();
  221. }
  222. if (e.data == 41) {
  223. getFaceWindow(true);
  224. }
  225. if (e.data == 43) {
  226. }
  227. if (e.data == 42) {
  228. getClearTimer("readyTimer");
  229. time.value.ready = 0;
  230. }
  231. }
  232. //新建测试后返回信息,获取result_id
  233. if (e.cmd === 'open_one_test_ack') {
  234. resultId.value = e.data.result_id;
  235. }
  236. //人脸识别状态
  237. if (e.cmd === 'face_check_result') {
  238. let myData = e.data[0] || e.data;
  239. returnStudent(myData);
  240. }
  241. //测试结束结果
  242. if (e.cmd === 'oneresult') {
  243. if (e.data.length) {
  244. let data = e.data[0];
  245. getAchievement(data)
  246. }
  247. }
  248. //结果生成完成(视频图片)
  249. if (e.cmd === 'static_urls_finished') {
  250. }
  251. //选择学生或测试结束后返回的数据
  252. if (e.cmd === 'result_info') {
  253. }
  254. };
  255. /**
  256. * 开始识别
  257. */
  258. const getOpenOneTestAndStartFace = async () => {
  259. if (examState.value > 3) {
  260. await closeOneTest();
  261. }
  262. await openOneTest();
  263. await startFace();
  264. };
  265. /**
  266. * 停止人脸识别
  267. */
  268. const getStopFace = async () => {
  269. // 旧版识别成功直接43了这里先屏蔽
  270. // if (examState.value != 41) {
  271. // return false;
  272. // }
  273. getClearTimer("face");
  274. if (needStart.value) {
  275. let txt = parameter.value.handcontroller ? ",请举左手开始测试" : ",请准备";
  276. speckText(faceCheckStu.value.name + txt);
  277. }
  278. if (examState.value == 41) {
  279. await stopFace();
  280. }
  281. if (faceCheckStu.value.student_id) {
  282. getFaceConfirmOnly();
  283. }
  284. };
  285. /**
  286. * 确定人脸信息
  287. */
  288. const getFaceConfirmOnly = (data?: any) => {
  289. if (data) {
  290. faceCheckStu.value = data;
  291. }
  292. faceConfirmOnly({
  293. result_id: resultId.value,
  294. student_id: faceCheckStu.value.student_id,
  295. gender: faceCheckStu.value.gender
  296. }, () => {
  297. faceWindowRef.value?.close();
  298. //不需要按钮的自动进入下一步
  299. if (needStart.value == false) {
  300. getStartOneTest();
  301. }
  302. });
  303. };
  304. /**
  305. * 重新识别
  306. */
  307. const getRetestFace = () => {
  308. proxy?.$modal.confirm("确定重新识别吗?").then(() => {
  309. if (needStart.value == false) {
  310. //自动流程项目重新识别直接返回3
  311. closeOneTest();
  312. } else {
  313. //手动流程项目重新识别43返回41,42返回3
  314. if (examState.value == 43) {
  315. cleanData();
  316. startFace();
  317. } else {
  318. closeOneTest();
  319. }
  320. }
  321. }).finally(() => {
  322. });
  323. };
  324. /**
  325. * 开始测试
  326. */
  327. const getStartOneTest = () => {
  328. if (examState.value != 43 || !faceCheckStu.value.student_id) {
  329. return false;
  330. }
  331. if (!faceCheckStu.value.student_id) {
  332. proxy?.$modal.msgWarning("请选择人员!");
  333. return false;
  334. }
  335. startOneTest(data == null, () => {
  336. //显示再测一次按钮
  337. showTestAgain.value = true;
  338. //停止播报;
  339. speckCancel();
  340. //计时项目才开
  341. if (needStart.value == true) {
  342. speckText("哨声");
  343. if (parameter.value.music) {
  344. playMusic(parameter.value.music);
  345. }
  346. //时间为0的为正计时,大于0的为倒计时
  347. if (time.value.testTime == 0) {
  348. getCounting("+");
  349. } else {
  350. getCounting("-");
  351. }
  352. } else {
  353. speckText(faceCheckStu.value.name + ",请开始测试");
  354. }
  355. })
  356. };
  357. /**
  358. * 再测一次
  359. */
  360. const getAgain = async () => {
  361. //预存测试人员
  362. let student = JSON.parse(JSON.stringify(faceCheckStu.value));
  363. //测试中
  364. if (examState.value == 42) {
  365. await finishOneTest();
  366. }
  367. //其他状态
  368. if (examState.value > 3) {
  369. await closeOneTest();
  370. }
  371. //重新走一次流程
  372. await openOneTest();
  373. await startFace();
  374. await stopFace();
  375. getFaceConfirmOnly(student);
  376. };
  377. /**
  378. * 确认退出
  379. */
  380. const confirmExit = () => {
  381. let handcontroller_id = parameter.value.handcontroller;
  382. proxy?.$modal.confirm(handcontroller_id ? `请举左手确认退出` : `确定退出吗?`).then(() => {
  383. getExit();
  384. }).finally(() => {
  385. });
  386. };
  387. /**
  388. * 退出
  389. */
  390. const getExit = () => {
  391. getClearTimer();//清除计时器
  392. examEnds();//通知工作站关闭
  393. speckCancel();//停止播报
  394. window.onbeforeunload = null;//移除事件处理器
  395. let handcontroller_id = parameter.value.handcontroller;
  396. if (handcontroller_id) {
  397. router.push({ path: '/gesture' });//跳转
  398. } else {
  399. router.push({ path: '/' });//跳转
  400. }
  401. };
  402. /**
  403. * 清空定时任务
  404. */
  405. const getClearTimer = (data?: any) => {
  406. if (data) {
  407. //清除指定
  408. clearInterval(timerManager.value[data])
  409. timerManager.value[data] = null;
  410. } else {
  411. //清除全部
  412. for (let key in timerManager.value) {
  413. if (timerManager.value.hasOwnProperty(key)) {
  414. clearInterval(timerManager.value[key])
  415. timerManager.value[key] = null;
  416. }
  417. }
  418. }
  419. };
  420. /**
  421. * 选择学生
  422. */
  423. const getChooseStudent = () => {
  424. if (examState.value < 41) {
  425. proxy?.$modal.msgWarning("请等待");
  426. }
  427. if (examState.value == 41) {
  428. chooseStudentRef.value.open();
  429. //然后定时自动关闭
  430. setTimeout(() => {
  431. faceWindowRef.value.close();
  432. }, 3000)
  433. }
  434. if (examState.value == 43) {
  435. getRetestFace();
  436. }
  437. };
  438. /**
  439. * 返回被选学生
  440. */
  441. const returnStudent = (data: any) => {
  442. speckCancel();
  443. chooseStudentRef.value.close();
  444. faceCheckStu.value = data;
  445. faceWindowRef.value.open();
  446. //然后定时自动关闭
  447. setTimeout(() => {
  448. faceWindowRef.value.close();
  449. }, 1000)
  450. getStopFace();
  451. };
  452. /**
  453. * 清除历史记录
  454. */
  455. const cleanData = () => {
  456. time.value.countdownNum = time.value.testTime;
  457. showTestAgain.value = false;
  458. faceCheckStu.value = {};
  459. currentResultObj.value = {};
  460. backReason.value = [];
  461. };
  462. /**
  463. * 自动初始化项目
  464. */
  465. const initProject = () => {
  466. //停止计时
  467. getClearTimer("countdownTimer");
  468. //恢复倒计时按钮状态
  469. readyState.value = true;
  470. //自动项目定时进入下一步
  471. let time = 0;
  472. //控制新建测试的时间,第一次快,之后就慢
  473. if (!faceCheckStu.value.student_id) {
  474. time = 1000;
  475. } else {
  476. time = 6000;
  477. }
  478. setTimeout(() => {
  479. //再加一个判断以免和再测一次冲突
  480. if (examState.value == 3) {
  481. getOpenOneTestAndStartFace();
  482. }
  483. }, time)
  484. };
  485. /**
  486. * 时间转换
  487. */
  488. // const countdownNumFormat = computed(() => {
  489. // return time.value.countdownNum;
  490. // //return proxy?.$utils.timeFormat(time.value.countdownNum);
  491. // });
  492. /**
  493. * 倒计时
  494. */
  495. const getCounting = (type: string) => {
  496. timerManager.value.countdownTimer = setInterval(() => {
  497. //正计时
  498. if (type == "+") {
  499. time.value.countdownNum++;
  500. }
  501. //倒计时
  502. if (type == "-") {
  503. if (time.value.countdownNum <= 0) {
  504. getClearTimer("countdownTimer");
  505. } else {
  506. time.value.countdownNum--;
  507. }
  508. }
  509. }, 1000);
  510. };
  511. /**
  512. * 人脸窗口
  513. */
  514. const getFaceWindow = (data: boolean, num: number = 0) => {
  515. let total = num + 1;//叠加三次后不再播放
  516. let txt = parameter.value.gesture === true ? "请举右手看摄像头人脸识别" : "请看摄像头进行人脸识别";
  517. speckText(txt);
  518. //data=true为弹出框,data=false为不要弹出框
  519. if (data) {
  520. faceWindowRef.value.open();
  521. //然后定时自动关闭
  522. setTimeout(() => {
  523. if (examState.value == 41 && faceWindowRef.value?.faceState == true) {
  524. faceWindowRef.value.close();
  525. }
  526. }, 3000)
  527. }
  528. //定时检查如果一直停留在人脸识别就提示
  529. let timeout = 16000;
  530. timerManager.value.face = setInterval(() => {
  531. getClearTimer("face");
  532. if (examState.value == 41 && total < 3) {
  533. getFaceWindow(false, total);
  534. }
  535. }, timeout)
  536. };
  537. /**
  538. * 成绩
  539. */
  540. const getAchievement = (data: any) => {
  541. //console.log("成绩", data);
  542. let type = parameter.value.project;
  543. let count =
  544. data?.[dic.typeResultKey[type]]?.toFixed(0);
  545. if (["trijump", "solidball", "shotput", "longjump"].includes(type)) {
  546. count =
  547. data?.[dic.typeResultKey[type]]?.toFixed(2);
  548. count = Math.round(count) / 100;
  549. }
  550. if (["basketballv1","footballv1"].includes(type)) {
  551. count = proxy?.$utils.runTime(data?.[dic.typeResultKey[type]], true, 1)
  552. }
  553. data.count = count || "0";
  554. data.score = data.score || "0";
  555. currentResultObj.value = data;
  556. //违规处理
  557. let arr = backReason.value;
  558. if (["situp", "pullup", "sidepullup", "jumprope", "jumpingjack", "jump", "longjump", "verticaljump"]
  559. .indexOf(type) > -1) {
  560. if (["pullup", "situp", "jumprope", "jumpingjack"].indexOf(type) > -1) {
  561. currentResultObj.value.back_num = data?.all_failed_num;
  562. }
  563. if (type === "sidepullup") {
  564. currentResultObj.value.back_num = data?.["0"]?.hip_failed_num;
  565. }
  566. if (['jump', 'longjump', 'verticaljump'].includes(type)) {
  567. if (data?.startline_check == 0) {
  568. let txt = "踩线违规";
  569. speckText(txt);
  570. arr.push(txt)
  571. }
  572. if (data?.singleleg_jump_check == 0) {
  573. let txt = "单脚跳违规";
  574. speckText(txt);
  575. arr.push(txt)
  576. }
  577. if (data?.outside_check == 0) {
  578. let txt = "跳出测试区违规";
  579. speckText(txt);
  580. arr.push(txt)
  581. }
  582. }
  583. if (
  584. data?.elbow_check == false
  585. ) {
  586. let txt = "肘部违规";
  587. speckText(txt);
  588. arr.push(txt);
  589. }
  590. if (
  591. ["situp", "pullup"].indexOf(type) > -1 &&
  592. data?.knee_check === false
  593. ) {
  594. let txt = "腿部违规";
  595. speckText(txt);
  596. if (!arr.includes(txt)) { }
  597. arr.push(txt);
  598. }
  599. if (["situp"].indexOf(type) > -1 && data?.hand_check === false) {
  600. let txt = "手部违规";
  601. speckText(txt);
  602. if (!arr.includes(txt)) { }
  603. arr.push(txt);
  604. }
  605. if (
  606. ["pullup"].indexOf(type) > -1 &&
  607. data?.["0"]?.elbow_check === false
  608. ) {
  609. let txt = "手部违规";
  610. speckText(txt);
  611. if (!arr.includes(txt)) { }
  612. arr.push(txt);
  613. }
  614. if (["situp"].indexOf(type) > -1 && data?.["0"]?.back_check === false) {
  615. let txt = "背部违规";
  616. speckText(txt);
  617. if (!arr.includes(txt)) { }
  618. arr.push(txt);
  619. }
  620. if (
  621. ["sidepullup", "situp"].indexOf(type) > -1 &&
  622. data?.["0"]?.hip_check === false
  623. ) {
  624. let txt = "臀部违规";
  625. speckText(txt);
  626. if (!arr.includes(txt)) { }
  627. arr.push(txt);
  628. }
  629. }
  630. backReason.value = arr;
  631. if (data.isfinish) {
  632. if (['jump'].includes(type) && backReason.value.length) {
  633. speckText("请重新测试");
  634. return false;
  635. }
  636. if (["basketballv1","footballv1"].includes(type)) {
  637. speckText(faceCheckStu?.value.name + "成绩为" + (chineseNumber(proxy?.$utils.runTime(data?.[dic.typeResultKey[type]], false, 0,
  638. 1)) || 0) + ",请下一位准备!" || "");
  639. } else {
  640. speckText(faceCheckStu?.value.name + "成绩为" + (chineseNumber(count) || 0) + unit.value + ",请下一位准备!" || "");
  641. }
  642. reportListRef.value.getIniReportList();
  643. faceWindowRef.value.open("right");
  644. //然后定时自动关闭
  645. setTimeout(() => {
  646. faceWindowRef.value.close();
  647. }, 1000)
  648. }
  649. };
  650. /**
  651. * 准备开始
  652. */
  653. const getReady = () => {
  654. if (needStart.value && examState.value == 43 && !time.value.ready && readyState.value) {
  655. if (time.value.ready) {
  656. return false;
  657. }
  658. speckCancel();
  659. readyState.value = false;
  660. time.value.ready = 6;
  661. timerManager.value.readyTimer = setInterval(() => {
  662. time.value.ready--;
  663. speckText(time.value.ready);
  664. if (time.value.ready == 0) {
  665. getClearTimer("readyTimer");
  666. getStartOneTest();
  667. }
  668. }, 1000);
  669. }
  670. };
  671. /**
  672. * 获取设备项目
  673. */
  674. const getDevice = async () => {
  675. let deviceid = localStorage.getItem("deviceid") || '';
  676. if (deviceid) {
  677. startDevice({ deviceid: deviceid })
  678. } else {
  679. proxy?.$modal.msgError(`缺少设备信息请重新登录!`);
  680. await proxy?.$http.common.logout({}).then((res: any) => {
  681. });
  682. proxy?.$modal?.closeLoading()
  683. //清空缓存
  684. localStorage.clear();
  685. //跳转
  686. router.push({ path: '/login/qrcode' });
  687. }
  688. };
  689. /**
  690. * 加载手势WS
  691. */
  692. const initHand = () => {
  693. handWs((e: any) => {
  694. if (router.currentRoute.value.path != '/train/test' || parameter.value.handcontroller == undefined || examState.value == 0) {
  695. return false;
  696. }
  697. console.log("eeeee", e)
  698. if (e?.wksid) {
  699. //获取设备项目
  700. getDevice()
  701. }
  702. //接收设备信息
  703. if (e?.device_info) {
  704. device_info.value = e.device_info;
  705. let handcontroller_id = device_info.value.handcontroller_id;
  706. stateHand(handcontroller_id);
  707. }
  708. //获取手势状态
  709. if (e?.cmd == 'get_handcontroller_state' && e?.state == 0) {
  710. let handcontroller_id = device_info.value.handcontroller_id;
  711. startHand(handcontroller_id);
  712. }
  713. //刷新
  714. if (e?.data?.result == "refresh") {
  715. getExit();
  716. //刷新
  717. window.location.reload()
  718. }
  719. //没初始化完成不监听手势动作
  720. if (examState.value == 0) {
  721. return false;
  722. }
  723. //左滑动
  724. if (e?.data?.result == "next_item") {
  725. }
  726. //举左手
  727. if (e?.data?.result == "left_hand") {
  728. //举左手确认退出
  729. // if (exit.value) {
  730. // exit.value = 0;
  731. // //确认退出
  732. // let keyEvent: any = null;
  733. // keyEvent = new KeyboardEvent('keydown', {
  734. // key: 'Enter', // 键值
  735. // code: 'Enter', // 键盘代码
  736. // keyCode: 13, // 旧的键盘代码
  737. // which: 13, // 新的键盘代码
  738. // shiftKey: false, // 是否按下Shift键
  739. // ctrlKey: false, // 是否按下Ctrl键
  740. // metaKey: false, // 是否按下Meta键(Win键或Command键)
  741. // bubbles: true, // 事件是否冒泡
  742. // cancelable: true // 是否可以取消事件的默认行为
  743. // });
  744. // document.activeElement?.dispatchEvent(keyEvent);
  745. // return false;
  746. // }
  747. //开始识别
  748. if (needStart.value && examState.value < 41) {
  749. getOpenOneTestAndStartFace();
  750. return false;
  751. }
  752. //停止人脸识别
  753. // if (needStart.value && examState.value == 41) {
  754. // getStopFace();
  755. // }
  756. //开始测试
  757. if (examState.value == 43) {
  758. if (needStart.value) {
  759. getReady()
  760. } else {
  761. getStartOneTest();
  762. }
  763. return false;
  764. }
  765. }
  766. //退出
  767. if (e?.data?.result == "exit") {
  768. // console.log("exit.value", exit.value)
  769. if (exit.value == 0) {
  770. speckText("请保持两秒确认退出");
  771. //第一次才弹出
  772. confirmExit();
  773. setTimeout(() => {
  774. let keyEvent: any = null;
  775. let myKey = null;
  776. //如果交叉手两秒后返回超过4次就确认退出
  777. if (exit.value >= 4) {
  778. myKey = 'Enter';
  779. } else {
  780. myKey = 'Esc';
  781. exit.value = 0;
  782. }
  783. if (myKey == 'Esc') {
  784. keyEvent = new KeyboardEvent('keydown', {
  785. key: 'Escape', // 键值
  786. code: 'Escape', // 键盘代码
  787. keyCode: 27, // 旧的键盘代码
  788. which: 27, // 新的键盘代码
  789. shiftKey: false, // 是否按下Shift键
  790. ctrlKey: false, // 是否按下Ctrl键
  791. metaKey: false, // 是否按下Meta键(Win键或Command键)
  792. bubbles: true, // 事件是否冒泡
  793. cancelable: true // 是否可以取消事件的默认行为
  794. });
  795. }
  796. if (myKey == 'Enter') {
  797. keyEvent = new KeyboardEvent('keydown', {
  798. key: 'Enter', // 键值
  799. code: 'Enter', // 键盘代码
  800. keyCode: 13, // 旧的键盘代码
  801. which: 13, // 新的键盘代码
  802. shiftKey: false, // 是否按下Shift键
  803. ctrlKey: false, // 是否按下Ctrl键
  804. metaKey: false, // 是否按下Meta键(Win键或Command键)
  805. bubbles: true, // 事件是否冒泡
  806. cancelable: true // 是否可以取消事件的默认行为
  807. });
  808. }
  809. document.activeElement?.dispatchEvent(keyEvent);
  810. }, 2500)
  811. }
  812. exit.value = exit.value + 1
  813. }
  814. // if (e?.data?.result == "exit") {
  815. // console.log("exit.value", exit.value)
  816. // if (exit.value == 0) {
  817. // exit.value = 1
  818. // speckText("请5秒内举左手确认退出");
  819. // //第一次才弹出
  820. // confirmExit();
  821. // time.value.exit = 6;
  822. // timerManager.value.exitTimer = setInterval(() => {
  823. // time.value.exit--;
  824. // console.log("取消倒计时", time.value.exit)
  825. // proxy?.$modal.msgWarning(`取消倒计时:${time.value.exit}`)
  826. // if (time.value.exit == 0) {
  827. // exit.value = 0;
  828. // getClearTimer("exitTimer");
  829. // let keyEvent: any = null;
  830. // keyEvent = new KeyboardEvent('keydown', {
  831. // key: 'Escape', // 键值
  832. // code: 'Escape', // 键盘代码
  833. // keyCode: 27, // 旧的键盘代码
  834. // which: 27, // 新的键盘代码
  835. // shiftKey: false, // 是否按下Shift键
  836. // ctrlKey: false, // 是否按下Ctrl键
  837. // metaKey: false, // 是否按下Meta键(Win键或Command键)
  838. // bubbles: true, // 事件是否冒泡
  839. // cancelable: true // 是否可以取消事件的默认行为
  840. // });
  841. // document.activeElement?.dispatchEvent(keyEvent);
  842. // }
  843. // }, 1000);
  844. // }
  845. // }
  846. });
  847. };
  848. /**
  849. * 输出犯规
  850. */
  851. watch(() => backReason.value.length, (v) => {
  852. backReasonStr.value = backReason.value[backReason.value.length - 1];
  853. setTimeout(() => {
  854. backReasonStr.value = "";
  855. }, 1500)
  856. }, { immediate: true });
  857. /**
  858. * 播报时间
  859. */
  860. watch(() => time.value.countdownNum, (newData) => {
  861. if (examState.value != 42) {
  862. return false;
  863. }
  864. if (newData >= 30) {
  865. if (newData % 30 == 0) {
  866. speckText(
  867. `还有${newData}秒`
  868. );
  869. }
  870. }
  871. if (newData == 10) {
  872. speckText("还有10秒");
  873. }
  874. if (newData <= 5) {
  875. speckText(newData);
  876. }
  877. if (newData == 0) {
  878. speckText("哨声");
  879. }
  880. }, { immediate: true });
  881. /**
  882. * 成绩整数播报
  883. */
  884. watch(() => currentResultObj.value, (newData: any, oldData: any) => {
  885. if (examState.value != 42 || newData.count <= 0) {
  886. return false;
  887. }
  888. let project = parameter.value.project;
  889. //引体向上比较慢所以都播报
  890. if (
  891. ["pullup"].includes(project) &&
  892. newData.count > 0 &&
  893. oldData.back_num == oldData.back_num
  894. ) {
  895. speckText(newData.count);
  896. }
  897. if (
  898. ["situp", "sidepullup", "jumprope", "jumpingjack"].includes(project) &&
  899. newData.count > 0 &&
  900. newData.count % 10 == 0 &&
  901. oldData.back_num == oldData.back_num
  902. ) {
  903. speckText(newData.count);
  904. }
  905. }, { immediate: true });
  906. onBeforeMount(() => {
  907. parameter.value = route.query;
  908. let project = parameter.value.project;
  909. let area = parameter.value.area;
  910. parameter.value.examId = `${project}_${area}`; //项目+区
  911. if (parameter.value.time) {
  912. time.value.testTime = parameter.value.time
  913. }
  914. time.value.countdownNum = time.value.testTime;
  915. userInfo.value = JSON.parse(myInfo);
  916. unit.value = dic.unit[project];
  917. if (parameter.value.gesture == 'true') {
  918. parameter.value.gesture = true
  919. } else {
  920. parameter.value.gesture = false
  921. }
  922. //需要开始按钮的项目
  923. let myList = ['situp', 'jumprope', 'jumpingjack', 'footballv1', 'basketballv1', 'volleyball', 'pingpong'];
  924. if (myList.includes(project)) {
  925. needStart.value = true;
  926. }
  927. //加载WS
  928. initWs({ parameter: parameter.value, testTime: time.value.testTime }, (data: any) => {
  929. getMessage(data);
  930. });
  931. //初始化语音
  932. initSpeech();
  933. //初始化手势
  934. initHand();
  935. //刷新关闭
  936. window.onbeforeunload = function (e) {
  937. var confirmationMessage = "刷新/关闭页面将会关闭页面,是否确认退出页面?";
  938. (e || window.event).returnValue = confirmationMessage; // 兼容 Gecko + IE
  939. let bUrl = import.meta.env.VITE_APP_BASE_API;
  940. let classId = parameter.value.classes;
  941. let project = parameter.value.project;
  942. let area = parameter.value.area;
  943. let examId = `${project}_${area}`;
  944. let mySid = sid.value;
  945. let token: any = localStorage.getItem("token")
  946. let formData = new FormData();
  947. formData.append("exam_id", examId);
  948. formData.append("class_id", classId);
  949. formData.append("token", token);
  950. formData.append("sid", mySid);
  951. navigator.sendBeacon(bUrl + "/exam/close_exam", formData)
  952. return confirmationMessage; // 兼容 Gecko + Webkit, Safari, Chrome
  953. };
  954. })
  955. onBeforeUnmount(() => {
  956. getExit();
  957. })
  958. </script>
  959. <style lang="scss" scoped>
  960. $topPadding: 5.19rem;
  961. $waiPadding: 6.51rem;
  962. .main {
  963. width: calc(100% - ($waiPadding * 2));
  964. height: 78vh;
  965. padding-top: 10rem;
  966. margin: 0 auto;
  967. display: flex;
  968. justify-content: space-between;
  969. overflow: hidden;
  970. .main-left {
  971. width: 71.5%;
  972. display: flex;
  973. flex-direction: column;
  974. justify-content: space-between;
  975. .main-left-top {
  976. display: flex;
  977. justify-content: space-between;
  978. height: 55.8%;
  979. position: relative;
  980. .top-left {
  981. width: 37.4%;
  982. height: 100%;
  983. border-radius: 1.6rem;
  984. background: radial-gradient(122% 126% at 97% 6%, #35FFC6 0%, #00FFE8 100%);
  985. text-align: center;
  986. display: flex;
  987. align-items: center;
  988. justify-content: center;
  989. cursor: pointer;
  990. .top-left-center {
  991. .pic {
  992. width: 22.3vh;
  993. height: 22.3vh;
  994. border-radius: 50%;
  995. display: flex;
  996. justify-content: center;
  997. align-items: center;
  998. overflow: hidden;
  999. margin: 0 auto 2vh auto;
  1000. img {
  1001. width: 100%;
  1002. }
  1003. }
  1004. .pic2 {
  1005. box-sizing: border-box;
  1006. border: 0.44rem solid rgba(26, 41, 58, 0.6315);
  1007. }
  1008. .name {
  1009. width: 100%;
  1010. color: #1A293A;
  1011. font-size: 2.21rem;
  1012. }
  1013. .name2 {
  1014. padding: 0 0.3rem;
  1015. border-radius: 1.1rem;
  1016. background: radial-gradient(96% 96% at 2% 32%, #FFFFFF 0%, #FCFDFD 54%, #E1E4E7 100%);
  1017. box-shadow: inset 0px 1px 0px 2px rgba(255, 255, 255, 0.9046), inset 0px 3px 6px 0px rgba(0, 0, 0, 0.0851);
  1018. }
  1019. }
  1020. }
  1021. .top-right {
  1022. width: 62%;
  1023. height: 100%;
  1024. border-radius: 1.6rem;
  1025. opacity: 1;
  1026. background: #ffffff;
  1027. box-sizing: border-box;
  1028. border: 0.55rem solid #13ED84;
  1029. display: flex;
  1030. align-items: center;
  1031. justify-content: center;
  1032. flex-direction: column;
  1033. position: relative;
  1034. .time {
  1035. width: 28vh;
  1036. height: 28vh;
  1037. line-height: 28vh;
  1038. border-radius: 50%;
  1039. color: #FF9402;
  1040. font-size: 11vh;
  1041. text-align: center;
  1042. background-image: url("@/assets/images/test/time.png");
  1043. background-position: center;
  1044. background-repeat: no-repeat;
  1045. background-size: 100% 100%;
  1046. position: absolute;
  1047. right: -1.5vh;
  1048. top: -11vh;
  1049. font-family: 'Saira-BlackItalic';
  1050. }
  1051. .tips {
  1052. display: flex;
  1053. justify-content: center;
  1054. align-items: center;
  1055. height: 100%;
  1056. img {
  1057. max-height: 80%;
  1058. max-height: 80%;
  1059. }
  1060. }
  1061. .complete {
  1062. width: 100%;
  1063. display: flex;
  1064. justify-content: center;
  1065. flex-direction: column;
  1066. .scoreBox {
  1067. height: 10vh;
  1068. color: #1A293A;
  1069. display: flex;
  1070. align-items: center;
  1071. justify-content: center;
  1072. margin-bottom: 5vh;
  1073. .score {
  1074. font-size: 8.5rem;
  1075. line-height: 8.5rem;
  1076. font-family: 'Saira-BlackItalic';
  1077. }
  1078. .unit {
  1079. font-size: 2rem;
  1080. margin-left: 10px;
  1081. }
  1082. }
  1083. .fractionViolation {
  1084. height: 10vh;
  1085. display: flex;
  1086. align-items: center;
  1087. justify-content: center;
  1088. .fraction {
  1089. height: 10vh;
  1090. line-height: 10vh;
  1091. border-radius: 5vh;
  1092. display: flex;
  1093. align-items: center;
  1094. padding: 0 6%;
  1095. background: linear-gradient(138deg, #38536C 21%, #1A293A 75%);
  1096. box-shadow: inset 0px 1px 13px 0px rgba(255, 255, 255, 0.9452);
  1097. .lable {
  1098. font-size: 4vh;
  1099. color: #13ED84;
  1100. }
  1101. .value {
  1102. font-size: 7vh;
  1103. color: #00FFE8;
  1104. font-family: 'Saira-BlackItalic';
  1105. min-width: 7vh;
  1106. }
  1107. }
  1108. .violation {
  1109. height: 6.1vh;
  1110. line-height: 6.1vh;
  1111. border-radius: 4vh;
  1112. border: 0.25rem solid #ED7905;
  1113. display: flex;
  1114. align-items: center;
  1115. margin-left: 11px;
  1116. padding: 3px;
  1117. box-sizing: content-box;
  1118. .lable {
  1119. font-size: 1.2rem;
  1120. color: #ffffff;
  1121. width: 6.1vh;
  1122. height: 6.1vh;
  1123. line-height: 6.1vh;
  1124. background: #ED7905;
  1125. border-radius: 50%;
  1126. text-align: center;
  1127. }
  1128. .value {
  1129. margin-left: 1.5vh;
  1130. font-size: 3.2rem;
  1131. color: #ED7905;
  1132. font-family: 'Saira-BlackItalic';
  1133. min-width: 6vh;
  1134. }
  1135. }
  1136. }
  1137. }
  1138. .complete2 {
  1139. padding-left: 20%;
  1140. padding-top: 5vh;
  1141. .scoreBox {
  1142. margin-bottom: 3vh;
  1143. justify-content: left;
  1144. }
  1145. .fractionViolation {
  1146. justify-content: left;
  1147. .fraction {
  1148. padding: 0 8%;
  1149. }
  1150. }
  1151. }
  1152. .foulBox {
  1153. height: calc(4.2vh + 0.5rem + 6px);
  1154. overflow: hidden;
  1155. display: flex;
  1156. align-items: center;
  1157. justify-content: center;
  1158. padding-top: 3vh;
  1159. .foul {
  1160. height: 4.2vh;
  1161. line-height: 4.2vh;
  1162. border-radius: 3vh;
  1163. border: 0.25rem solid #ED7905;
  1164. display: flex;
  1165. align-items: center;
  1166. margin-left: 11px;
  1167. padding: 3px;
  1168. box-sizing: content-box;
  1169. .lable {
  1170. font-size: 2rem;
  1171. color: #ffffff;
  1172. width: 4.2vh;
  1173. height: 4.2vh;
  1174. line-height: 4.2vh;
  1175. background: #ED7905;
  1176. border-radius: 50%;
  1177. text-align: center;
  1178. }
  1179. .value {
  1180. margin-left: 1.5vh;
  1181. font-size: 2rem;
  1182. color: #ED7905;
  1183. font-family: 'Saira-BlackItalic';
  1184. min-width: 6vh;
  1185. padding: 0 10px;
  1186. }
  1187. }
  1188. }
  1189. .readyBoxBefore {
  1190. display: flex;
  1191. justify-content: center;
  1192. font-size: 2.5rem;
  1193. color: #1A293A;
  1194. padding-top: 4vh;
  1195. line-height: 0;
  1196. margin-bottom: 2vh;
  1197. .item {
  1198. display: flex;
  1199. justify-content: center;
  1200. .lable {
  1201. display: flex;
  1202. align-items: center;
  1203. margin-left: 10px;
  1204. }
  1205. }
  1206. img {
  1207. height: 20vh;
  1208. }
  1209. }
  1210. .readyBox {
  1211. text-align: center;
  1212. color: #1A293A;
  1213. .value {
  1214. font-size: 8.5rem;
  1215. line-height: 8.5rem;
  1216. font-family: 'Saira-BlackItalic';
  1217. }
  1218. .lable {
  1219. font-size: 3.5rem;
  1220. }
  1221. .transparent {
  1222. opacity: 0;
  1223. }
  1224. }
  1225. .btn {
  1226. font-size: 2.21rem;
  1227. color: #FFFFFF;
  1228. text-align: center;
  1229. width: 50%;
  1230. line-height: 8vh;
  1231. line-height: 8vh;
  1232. border-radius: 15px;
  1233. opacity: 1;
  1234. background: radial-gradient(159% 126% at 5% 93%, #F99F02 0%, #ED7905 100%);
  1235. box-shadow: 3px 6px 4px 1px rgba(0, 0, 0, 0.1874), inset 0px 1px 0px 2px rgba(255, 255, 255, 0.3);
  1236. cursor: pointer;
  1237. }
  1238. }
  1239. i {
  1240. width: 4vw;
  1241. height: 4vw;
  1242. display: block;
  1243. position: absolute;
  1244. top: 50%;
  1245. left: 37.5%;
  1246. margin-top: calc(4vw * -0.5);
  1247. margin-left: calc(4vw * -0.5);
  1248. background-image: url("@/assets/images/test/yuan.png");
  1249. background-position: center;
  1250. background-repeat: no-repeat;
  1251. background-size: 100% 100%;
  1252. border-radius: 50%;
  1253. flex-shrink: 0;
  1254. transition: all 0.5s;
  1255. }
  1256. }
  1257. .main-left-bottom {
  1258. display: flex;
  1259. justify-content: space-between;
  1260. height: calc(100% - 55.8% - 3vh);
  1261. overflow: hidden;
  1262. .bottom-left {
  1263. width: 58%;
  1264. padding-right: 1rem;
  1265. display: flex;
  1266. flex-direction: column;
  1267. .tips {
  1268. height: 2.8vh;
  1269. img {
  1270. height: 100%;
  1271. }
  1272. }
  1273. .pic {
  1274. text-align: center;
  1275. width: 100%;
  1276. height: 100%;
  1277. display: flex;
  1278. justify-content: center;
  1279. overflow: hidden;
  1280. img {
  1281. max-width: 100%;
  1282. max-height: 100%;
  1283. }
  1284. }
  1285. }
  1286. .bottom-right {
  1287. width: 41%;
  1288. height: 100%;
  1289. overflow-y: scroll;
  1290. color: #F9F9F9;
  1291. font-size: 1.1rem;
  1292. line-height: 1.6rem;
  1293. &::-webkit-scrollbar {
  1294. width: 10px;
  1295. }
  1296. &::-webkit-scrollbar-thumb {
  1297. border-width: 2px;
  1298. border-radius: 4px;
  1299. border-style: dashed;
  1300. border-color: transparent;
  1301. background-color: rgba(26, 62, 78, 0.9);
  1302. background-clip: padding-box;
  1303. }
  1304. &::-webkit-scrollbar-button:hover {
  1305. border-radius: 6px;
  1306. background: rgba(26, 62, 78, 1);
  1307. }
  1308. }
  1309. }
  1310. }
  1311. .main-right {
  1312. width: 27%;
  1313. border-radius: 1.6rem;
  1314. background: linear-gradient(29deg, #092941 -82%, #2A484B 94%);
  1315. box-shadow: inset 0px 1px 0px 2px rgba(255, 255, 255, 0.4);
  1316. display: flex;
  1317. flex-direction: column;
  1318. overflow: hidden;
  1319. }
  1320. }
  1321. </style>