ExamingHome.vue 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693
  1. <template>
  2. <div v-if="exam && examQuestion()" class="container" :key="exam.id">
  3. <div class="header">
  4. <RemainTime></RemainTime>
  5. <OverallProgress :exam-question-list="examQuestionList"></OverallProgress>
  6. <QuestionFilters :exam-question-list="examQuestionList"></QuestionFilters>
  7. <i-button class="qm-primary-button" @click="submitPaper">交卷</i-button>
  8. </div>
  9. <div class="main" id="examing-home-question">
  10. <QuestionView :exam-question="examQuestion()"></QuestionView>
  11. <ArrowNavView
  12. :previousQuestionOrder="previousQuestionOrder"
  13. :nextQuestionOrder="nextQuestionOrder"
  14. ></ArrowNavView>
  15. </div>
  16. <div :class="['side', 'side-row-size']">
  17. <div :class="['question-nav', !faceEnable && 'question-nav-long']">
  18. <QuestionNavView :paperStruct="paperStruct" />
  19. </div>
  20. <div v-if="faceEnable" class="camera">
  21. <FaceRecognition
  22. v-if="faceEnable"
  23. width="100%"
  24. height="100%"
  25. :showRecognizeButton="false"
  26. />
  27. </div>
  28. </div>
  29. <Modal
  30. v-model="showFaceId"
  31. :mask-closable="false"
  32. :closable="false"
  33. width="800"
  34. :styles="{ top: '10px' }"
  35. >
  36. <FaceId v-if="showFaceId" @closeFaceId="closeFaceId" />
  37. <p slot="footer"></p>
  38. </Modal>
  39. <FaceTracking v-if="faceEnable && PRODUCTION" />
  40. </div>
  41. <div v-else>
  42. 正在等待数据返回...
  43. <i-button class="qm-primary-button" v-if="timeouted" @click="reloadPage">
  44. 重试
  45. </i-button>
  46. </div>
  47. </template>
  48. <script>
  49. import RemainTime from "./RemainTime.vue";
  50. import OverallProgress from "./OverallProgress.vue";
  51. import QuestionFilters from "./QuestionFilters.vue";
  52. import QuestionView from "./QuestionView.vue";
  53. import ArrowNavView from "./ArrowNavView.vue";
  54. import QuestionNavView from "./QuestionNavView.vue";
  55. import FaceTracking from "./FaceTracking.vue";
  56. import FaceId from "./FaceId.vue";
  57. import FaceRecognition from "../../../components/FaceRecognition/FaceRecognition";
  58. import { openWS, closeWsWithoutReconnect } from "./ws.js";
  59. import { createNamespacedHelpers } from "vuex";
  60. const { mapState, mapMutations } = createNamespacedHelpers("examingHomeModule");
  61. export default {
  62. name: "ExamingHome",
  63. data() {
  64. return {
  65. showFaceId: false,
  66. faceEnable: false,
  67. timeouted: false,
  68. PRODUCTION: process.env.NODE_ENV === "production"
  69. };
  70. },
  71. async created() {
  72. this.timeoutTimeout = setTimeout(() => (this.timeouted = true), 30 * 1000);
  73. // 仅在线上使用活体检测
  74. if (
  75. process.env.NODE_ENV === "production" &&
  76. /^\d+$/.test(this.$route.query.faceVerifyMinute)
  77. ) {
  78. const enoughTimeForFaceId = this.remainTime // 如果remainTime取到了的话
  79. ? this.remainTime / (60 * 1000) - 1 > this.$route.query.faceVerifyMinute
  80. : true;
  81. if (!enoughTimeForFaceId) return;
  82. this.faceIdMsgTimeout = setTimeout(() => {
  83. this.serverLog("debug/S-002001", "活体检测前抓拍");
  84. this.toggleSnapNow();
  85. this.$Message.info("30秒后开始活体检测");
  86. }, this.$route.query.faceVerifyMinute * 60 * 1000 - 30 * 1000); // 活体检测提醒
  87. this.faceIdDivTimeout = setTimeout(() => {
  88. this.serverLog("debug/S-003001", "准备弹出活体检测框");
  89. this.showFaceId = true;
  90. }, this.$route.query.faceVerifyMinute * 60 * 1000); // 定时做活体检测
  91. // }, 1 * 1000); // 定时做活体检测
  92. }
  93. // for test
  94. // setTimeout(() => {
  95. // this.showFaceId = true;
  96. // // this.$Modal.remove();
  97. // // }, this.$route.query.faceVerifyMinute * 60 * 1000); // 定时做活体检测
  98. // }, 5 * 1000); // 定时做活体检测
  99. let faceEnable;
  100. try {
  101. faceEnable = await this.$http.get(
  102. "/api/ecs_exam_work/exam/examOrgProperty/" +
  103. this.$route.params.examId +
  104. `/IS_FACE_ENABLE`
  105. );
  106. } catch (error) {
  107. this.$Message.error("获取人脸检测设置失败");
  108. this.logout("?LogoutReason=获取人脸检测设置失败");
  109. return;
  110. }
  111. if (faceEnable.data) {
  112. this.faceEnable = true;
  113. // setTimeout(() => {
  114. // this.serverLog("debug/S-002001", "进入考试后60秒内抓拍");
  115. // this.toggleSnapNow(); // 开启抓拍才在进入考试时抓拍一张
  116. // }, 60 * 1000); // 60内秒钟后抓拍
  117. let initSnapshotTrialTimes = 0;
  118. this.initSnapInterval = setInterval(() => {
  119. if (this.exam || initSnapshotTrialTimes > 12) {
  120. clearInterval(this.initSnapInterval);
  121. this.serverLog(
  122. "debug/S-002001",
  123. "进入考试后60秒内抓拍" + `(${(initSnapshotTrialTimes + 1) * 5}秒)`
  124. );
  125. this.toggleSnapNow(); // 开启抓拍才在进入考试时抓拍一张
  126. } else {
  127. initSnapshotTrialTimes++;
  128. }
  129. }, 5 * 1000);
  130. // let initSnapshotTrialTimes = 0;
  131. // const initSnapshot = setTimeout(() => {
  132. // if (this.exam || initSnapshotTrialTimes < 6) {
  133. // this.serverLog("debug/S-002001", "进入考试后60秒内抓拍");
  134. // this.toggleSnapNow(); // 开启抓拍才在进入考试时抓拍一张
  135. // } else {
  136. // setTimeout(() => initSnapshot(), 5 * 1000);
  137. // }
  138. // }, 5 * 1000); // 60内秒钟后抓拍
  139. try {
  140. const snapshotInterval = await this.$http.get(
  141. "/api/ecs_exam_work/exam/examOrgProperty/" +
  142. this.$route.params.examId +
  143. `/SNAPSHOT_INTERVAL`
  144. );
  145. if (snapshotInterval.data) {
  146. // 考务设置抓拍间隔
  147. this.snapInterval = setInterval(() => {
  148. this.serverLog(
  149. "debug/S-002001",
  150. "根据抓拍间隔抓拍:抓拍间隔=" + snapshotInterval.data + "分钟"
  151. );
  152. this.toggleSnapNow();
  153. }, snapshotInterval.data * 60 * 1000);
  154. }
  155. } catch (error) {
  156. this.$Message.error("获取人脸抓拍间隔设置失败");
  157. this.logout("?LogoutReason=获取人脸抓拍间隔设置失败");
  158. return;
  159. }
  160. }
  161. try {
  162. await this.initData();
  163. } catch (error) {
  164. this.$Message.error("获取试卷信息失败,退出登录");
  165. this.logout("?LogoutReason=获取试卷信息失败");
  166. return;
  167. }
  168. this.submitInterval = setInterval(
  169. () => this.answerAllQuestions(),
  170. 5 * 1000 // 10秒检查是否有更改需要提交答案
  171. );
  172. },
  173. async mounted() {
  174. // iview bug: https://github.com/iview/iview/issues/4061
  175. // document.body.style = "";
  176. window._hmt.push(["_trackEvent", "正在考试页面", "进入页面"]);
  177. if (typeof nodeRequire != "undefined") {
  178. try {
  179. var fs = window.nodeRequire("fs");
  180. if (fs.existsSync("multiCamera.exe")) {
  181. await new Promise((resolve, reject) => {
  182. window.nodeRequire("node-cmd").get("multiCamera.exe", () => {
  183. try {
  184. let cameraInfos = fs.readFileSync("CameraInfo.txt", "utf-8");
  185. if (cameraInfos && cameraInfos.trim()) {
  186. cameraInfos = cameraInfos.trim();
  187. cameraInfos = cameraInfos.replace(/\r\n/g, "");
  188. cameraInfos = cameraInfos.replace(/\n/g, "");
  189. console.log(cameraInfos);
  190. this.serverLog("debug/S-001001", cameraInfos);
  191. }
  192. resolve();
  193. } catch (error) {
  194. reject("读取摄像头列表失败");
  195. }
  196. });
  197. });
  198. }
  199. } catch (error) {
  200. console.log(error);
  201. }
  202. }
  203. },
  204. beforeRouteUpdate(from, to, next) {
  205. window._hmt.push(["_trackEvent", "正在考试页面", "题目切换"]);
  206. if (process.env.NODE_ENV === "development") {
  207. console.log("beforeRouteUpdate from: " + this.$route.fullPath);
  208. }
  209. this.answerAllQuestions();
  210. next();
  211. },
  212. beforeDestroy() {
  213. clearTimeout(this.timeoutTimeout);
  214. clearInterval(this.submitInterval);
  215. clearInterval(this.initSnapInterval);
  216. clearInterval(this.snapInterval);
  217. clearTimeout(this.faceIdMsgTimeout);
  218. clearTimeout(this.faceIdDivTimeout);
  219. closeWsWithoutReconnect();
  220. this.updateExamState({
  221. exam: null,
  222. paperStruct: null,
  223. examQuestionList: null,
  224. questionAudioFileUrl: []
  225. });
  226. this.$Modal.remove();
  227. },
  228. // beforeRouteUpdate(to, from, next) {
  229. // this.updateQuestion(next);
  230. // },
  231. methods: {
  232. ...mapMutations([
  233. "updateExamState",
  234. "updateExamQuestion",
  235. "toggleSnapNow",
  236. "updateExamResult",
  237. "resetExamQuestionDirty"
  238. ]),
  239. async initData() {
  240. const [
  241. examData,
  242. paperStructData,
  243. examQuestionListData
  244. ] = await Promise.all([
  245. this.$http.get("/api/ecs_exam_work/exam/" + this.$route.params.examId),
  246. this.$http.get(
  247. "/api/ecs_oe_student/examRecordPaperStruct/getExamRecordPaperStruct?examRecordDataId=" +
  248. this.$route.params.examRecordDataId
  249. ),
  250. this.$http.get("/api/ecs_oe_student/examQuestion/findExamQuestionList")
  251. ]);
  252. const [exam, paperStruct] = [examData.data, paperStructData.data];
  253. let examQuestionList = examQuestionListData.data;
  254. if (
  255. exam === undefined ||
  256. paperStruct === undefined ||
  257. examQuestionListData === undefined
  258. ) {
  259. this.$Message.error("获取试卷信息失败");
  260. this.logout("?LogoutReason=获取试卷信息失败");
  261. return;
  262. }
  263. if (exam.examType === "PRACTICE") {
  264. const practiceType = (await this.$http.get(
  265. "/api/ecs_exam_work/exam/examOrgProperty/" +
  266. this.$route.params.examId +
  267. `/PRACTICE_TYPE`
  268. )).data;
  269. this.practiceType = practiceType; // IN_PRACTICE NO_ANSWER
  270. exam.practiceType = practiceType;
  271. }
  272. try {
  273. let freezeTimeData = await this.$http.get(
  274. "/api/ecs_exam_work/exam/examOrgProperty/" +
  275. this.$route.params.examId +
  276. `/FREEZE_TIME`
  277. );
  278. exam.freezeTime = freezeTimeData.data;
  279. } catch (error) {
  280. console.log("获取考试冻结时间失败--忽略");
  281. }
  282. // init subNumber
  283. let questionId = null;
  284. let i = 1;
  285. examQuestionList = examQuestionList.map(eq => {
  286. if (questionId == eq.questionId) {
  287. eq.subNumber = i++;
  288. } else {
  289. i = 1;
  290. questionId = eq.questionId;
  291. eq.subNumber = i++;
  292. }
  293. return eq;
  294. });
  295. let groupOrder = 1;
  296. let mainNumber = 0;
  297. examQuestionList = examQuestionList.map(eq => {
  298. if (mainNumber == eq.mainNumber) {
  299. eq.groupOrder = groupOrder++;
  300. } else {
  301. mainNumber = eq.mainNumber;
  302. groupOrder = 1;
  303. eq.groupOrder = groupOrder++;
  304. }
  305. const questionWrapperList =
  306. paperStruct.defaultPaper.questionGroupList[eq.mainNumber - 1]
  307. .questionWrapperList;
  308. const groupName =
  309. paperStruct.defaultPaper.questionGroupList[eq.mainNumber - 1]
  310. .groupName;
  311. const groupTotal = questionWrapperList.reduce(
  312. (accumulator, questionWrapper) =>
  313. accumulator + questionWrapper.questionUnitWrapperList.length,
  314. 0
  315. );
  316. eq.groupName = groupName;
  317. eq.groupTotal = groupTotal;
  318. return eq;
  319. });
  320. examQuestionList = examQuestionList.map(eq => {
  321. const paperStructQuestion = paperStruct.defaultPaper.questionGroupList[
  322. eq.mainNumber - 1
  323. ].questionWrapperList.find(q => q.questionId === eq.questionId);
  324. return Object.assign(eq, {
  325. limitedPlayTimes: paperStructQuestion.limitedPlayTimes
  326. });
  327. });
  328. this.updateExamState({
  329. exam: exam,
  330. paperStruct: paperStruct,
  331. examQuestionList: examQuestionList,
  332. allAudioPlayTimes: JSON.parse(examQuestionList[0].audioPlayTimes) || [],
  333. questionAudioFileUrl: []
  334. });
  335. // console.log(examQuestionList);
  336. // console.log(examQuestionList.find(v => v.answerType === "SINGLE_AUDIO"));
  337. if (examQuestionList.find(v => v.answerType === "SINGLE_AUDIO")) {
  338. // console.log("have single");
  339. const examRecordDataId = this.$route.params.examRecordDataId;
  340. openWS({ examRecordDataId });
  341. }
  342. },
  343. updateQuestion: async function(next) {
  344. // 初始化套题的答案,为回填部分选项做准备
  345. // for (let q of this.examQuestionList) {
  346. // if (q.subQuestionList.length > 0) {
  347. // q.studentAnswer = [];
  348. // for (let sq of q.subQuestionList) {
  349. // q.studentAnswer.push(sq.studentAnswer);
  350. // }
  351. // }
  352. // }
  353. next && next();
  354. if (!this.exam) return;
  355. },
  356. closeFaceId() {
  357. this.showFaceId = false;
  358. },
  359. async answerAllQuestions(ignoreDirty) {
  360. const answers = this.examQuestionList
  361. .filter(eq => !ignoreDirty && eq.dirty)
  362. .filter(eq => eq.getQuestionContent)
  363. .map(eq => {
  364. return Object.assign(
  365. {
  366. order: eq.order,
  367. studentAnswer: eq.studentAnswer
  368. },
  369. eq.audioPlayTimes && { audioPlayTimes: eq.audioPlayTimes },
  370. eq.isSign && { isSign: eq.isSign }
  371. );
  372. });
  373. if (answers.length > 0) {
  374. try {
  375. await this.$http.post(
  376. "/api/ecs_oe_student/examQuestion/submitQuestionAnswer",
  377. answers
  378. );
  379. this.resetExamQuestionDirty();
  380. } catch (error) {
  381. console.log(error);
  382. this.$Message.error("提交答案失败");
  383. window._hmt.push([
  384. "_trackEvent",
  385. "正在考试页面",
  386. "提交答案失败",
  387. error.message +
  388. " |||| " +
  389. (((error.response || {}).data || {}).desc || "")
  390. ]);
  391. this.serverLog(
  392. "debug/S-008001",
  393. `提交答案失败 => 考试剩余时间:${this.remainTime / 1000}`
  394. );
  395. }
  396. }
  397. },
  398. async submitPaper() {
  399. try {
  400. // 交卷前强制提交所有答案
  401. await this.answerAllQuestions(true);
  402. } catch (error) {
  403. return;
  404. }
  405. if (
  406. this.exam.freezeTime &&
  407. this.remainTime >
  408. (this.exam.duration - this.exam.freezeTime) * 60 * 1000
  409. ) {
  410. this.$Message.info({
  411. content: `考试开始${this.exam.freezeTime}分钟后才允许交卷。`,
  412. duration: 5,
  413. closable: true
  414. });
  415. return;
  416. }
  417. const answered = this.examQuestionList.filter(
  418. q => q.studentAnswer !== null
  419. ).length;
  420. const unanswered = this.examQuestionList.filter(
  421. q => q.studentAnswer === null
  422. ).length;
  423. const signed = this.examQuestionList.filter(q => q.isSign).length;
  424. this.$Modal.confirm({
  425. title: "确认交卷",
  426. content: `<p>已答题目:${answered}</p><p>未答题目:${unanswered}</p><p>标记题目:${signed}</p>`,
  427. onOk: this.realSubmitPaper
  428. });
  429. },
  430. async realSubmitPaper() {
  431. this.$Spin.show({
  432. render: () => {
  433. return <div style="font-size: 44px">正在交卷,请耐心等待...</div>;
  434. }
  435. });
  436. if (this.faceEnable) {
  437. this.serverLog("debug/S-002001", "交卷前抓拍");
  438. this.toggleSnapNow();
  439. }
  440. // 确保抓拍指令在交卷前执行
  441. setTimeout(() => this.realSubmitPaperStep2(), 1000);
  442. this.submitCount = 1;
  443. },
  444. async realSubmitPaperStep2() {
  445. if (this.snapProcessingCount > 0) {
  446. this.submitCount++;
  447. if (this.submitCount < 200) {
  448. // 一分钟后,强制交卷
  449. setTimeout(() => this.realSubmitPaperStep2(), 300);
  450. return;
  451. }
  452. }
  453. if (this.submitLock) {
  454. return;
  455. } else {
  456. this.submitLock = true;
  457. }
  458. if (this.$route.name !== "OnlineExamingHome") {
  459. // 非考试页,不在交卷
  460. this.$Spin.hide();
  461. return;
  462. }
  463. try {
  464. const examId = this.$route.params.examId;
  465. const examRecordDataId = this.$route.params.examRecordDataId;
  466. const res = await this.$http.get(
  467. "/api/ecs_oe_student/examControl/endExam"
  468. );
  469. if (res.status === 200) {
  470. this.$router.replace({
  471. path: `/online-exam/exam/${examId}/examRecordData/${examRecordDataId}/end`
  472. });
  473. // 确保交卷成功后,不会再次交卷
  474. this.submitLock = true;
  475. this.$Spin.hide();
  476. return;
  477. } else {
  478. this.$Message.error("交卷失败");
  479. }
  480. this.submitLock = false;
  481. } catch (e) {
  482. this.$Message.error("交卷失败");
  483. console.log(e);
  484. }
  485. this.submitLock = false;
  486. this.$Spin.hide();
  487. closeWsWithoutReconnect();
  488. },
  489. examQuestion() {
  490. return (
  491. this.examQuestionList &&
  492. this.examQuestionList.find(
  493. eq => eq.order == this.$route.params.order // number == string
  494. )
  495. );
  496. },
  497. reloadPage() {
  498. window._hmt.push([
  499. "_trackEvent",
  500. "正在考试页面",
  501. "页面加载失败",
  502. "reload"
  503. ]);
  504. window.location.reload();
  505. }
  506. },
  507. computed: {
  508. ...mapState([
  509. "exam",
  510. "paperStruct",
  511. "examQuestionList",
  512. "snapNow",
  513. "snapProcessingCount",
  514. "shouldSubmitPaper",
  515. "remainTime",
  516. "questionAudioFileUrl"
  517. ]),
  518. previousQuestionOrder: vm => {
  519. if (vm.examQuestion().order > 1) {
  520. return vm.examQuestion().order - 1;
  521. } else {
  522. return null;
  523. }
  524. },
  525. nextQuestionOrder: vm => {
  526. if (vm.examQuestion().order < vm.examQuestionList.length) {
  527. return vm.examQuestion().order + 1;
  528. } else {
  529. return null;
  530. }
  531. }
  532. },
  533. watch: {
  534. $route: function() {
  535. this.examQuestion();
  536. },
  537. shouldSubmitPaper() {
  538. this.realSubmitPaper();
  539. },
  540. questionAudioFileUrl(value) {
  541. // console.log(this.examQuestion.studentAnswer);
  542. // console.log("watch", value);
  543. const examRecordDataId = this.$route.params.examRecordDataId;
  544. const that = this;
  545. for (const q of value) {
  546. if (!q.saved) {
  547. this.$http
  548. .post(
  549. "/api/ecs_oe_student/examControl/confirmAudioUploadSaveStatus",
  550. {
  551. examRecordDataId,
  552. filePath: q.audioFileUrl,
  553. order: q.order
  554. }
  555. )
  556. .then(() => {
  557. that.updateExamQuestion({
  558. order: q.order,
  559. studentAnswer: q.audioFileUrl
  560. });
  561. q.saved = true;
  562. this.$Message.info("音频题作答已更新");
  563. })
  564. .catch(() => {
  565. this.$Message.error({
  566. content: "更新音频题答案失败!",
  567. duration: 10,
  568. closable: true
  569. });
  570. });
  571. }
  572. }
  573. }
  574. // examQuestionList(val, oldVal) {
  575. // // console.log(val, oldVal);
  576. // }
  577. },
  578. components: {
  579. RemainTime,
  580. OverallProgress,
  581. QuestionFilters,
  582. QuestionView,
  583. ArrowNavView,
  584. QuestionNavView,
  585. FaceRecognition,
  586. FaceId,
  587. FaceTracking
  588. }
  589. };
  590. </script>
  591. <style scoped>
  592. .container {
  593. display: grid;
  594. grid-template-areas:
  595. "header header"
  596. "main side";
  597. grid-template-rows: 80px minmax(0, 1fr);
  598. grid-template-columns: 1fr 400px;
  599. height: 100vh;
  600. width: 100vw;
  601. }
  602. .header {
  603. display: grid;
  604. align-items: center;
  605. justify-items: center;
  606. grid-template-columns: 200px 1fr 300px 100px;
  607. grid-area: header;
  608. height: 80px;
  609. background-color: #f5f5f5;
  610. }
  611. .main {
  612. display: grid;
  613. grid-area: main;
  614. grid-template-rows: 1fr 50px;
  615. }
  616. .side {
  617. display: grid;
  618. grid-area: side;
  619. grid-template-rows: 1fr 250px;
  620. background-color: #f5f5f5;
  621. }
  622. .side-row-size {
  623. grid-template-rows: 1fr;
  624. }
  625. .question-nav {
  626. overflow-y: scroll;
  627. max-height: calc(100vh - 300px);
  628. }
  629. .question-nav-long {
  630. max-height: calc(100vh - 100px);
  631. }
  632. .camera {
  633. align-self: flex-end;
  634. justify-self: flex-end;
  635. }
  636. @media screen and (max-height: 768px) {
  637. .container {
  638. grid-template-rows: 50px minmax(0, 1fr);
  639. }
  640. .header {
  641. height: 50px;
  642. }
  643. .side {
  644. grid-template-rows: minmax(0, 1fr) 200px;
  645. }
  646. }
  647. </style>
  648. <style>
  649. #examing-home-question img {
  650. max-width: 100%;
  651. height: auto !important;
  652. }
  653. </style>