ExamingHome.vue 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. <template>
  2. <div v-if="exam && examQuestion()" class="container">
  3. <div class="header">
  4. <RemainTime></RemainTime>
  5. <OverallProgress :exam-question-list="examQuestionList"></OverallProgress>
  6. <QuestionFilters :exam-question-list="examQuestionList"></QuestionFilters>
  7. <Button class="qm-primary-button" @click="submitPaper">交卷</Button>
  8. </div>
  9. <div class="main">
  10. <QuestionView :exam-question="examQuestion()"></QuestionView>
  11. <ArrowNavView :previousQuestionOrder="previousQuestionOrder" :nextQuestionOrder="nextQuestionOrder"></ArrowNavView>
  12. </div>
  13. <div class="side">
  14. <div class="question-nav">
  15. <QuestionNavView :paperStruct="paperStruct" />
  16. </div>
  17. <div class="camera">
  18. <!-- <FaceRecognition width="100%" height="100%" :showRecognizeButton="false" /> -->
  19. </div>
  20. </div>
  21. <Modal v-if="showFaceId" v-model="showFaceId" :mask-closable="false" :closable="false">
  22. <FaceId />
  23. <p slot="footer">
  24. </p>
  25. </Modal>
  26. </div>
  27. </template>
  28. <script>
  29. import RemainTime from "./RemainTime.vue";
  30. import OverallProgress from "./OverallProgress.vue";
  31. import QuestionFilters from "./QuestionFilters.vue";
  32. import QuestionView from "./QuestionView.vue";
  33. import ArrowNavView from "./ArrowNavView.vue";
  34. import QuestionNavView from "./QuestionNavView.vue";
  35. import FaceId from "./FaceId.vue";
  36. import FaceRecognition from "../../../components/FaceRecognition/FaceRecognition";
  37. import { createNamespacedHelpers } from "vuex";
  38. const { mapState, mapMutations } = createNamespacedHelpers("examingHomeModule");
  39. export default {
  40. name: "ExamingHome",
  41. data() {
  42. return { showFaceId: false };
  43. },
  44. created() {
  45. this.initData();
  46. if (!this.$route.params.order) {
  47. // created can access this.$route?
  48. this.$router.push(this.$route.fullPath + "/order/1");
  49. return;
  50. }
  51. setTimeout(() => {
  52. this.toggleSnapNow();
  53. }, 60 * 1000); // 一分钟后抓拍
  54. this.$http
  55. .get(
  56. "/api/ecs_exam_work/exam/examOrgProperty/" +
  57. this.$route.params.examId +
  58. `/SNAPSHOT_INTERVAL`
  59. )
  60. .then(res => {
  61. // console.log(res);
  62. if (res.data) {
  63. // 考务设置抓拍间隔
  64. setInterval(() => {
  65. this.toggleSnapNow();
  66. }, res.data * 60 * 1000);
  67. }
  68. })
  69. .catch(reason => {
  70. this.$Message.error(reason);
  71. });
  72. this.submitInterval = setInterval(
  73. () => this.answerAllQuestions(),
  74. 1 * 60 * 1000
  75. );
  76. // this.$Modal.info({
  77. // // title: "活体检测",
  78. // header: "",
  79. // footerHide: true,
  80. // render: h => {
  81. // return <FaceId />;
  82. // }
  83. // });
  84. },
  85. destroyed() {
  86. clearInterval(this.submitInterval);
  87. },
  88. // beforeRouteUpdate(to, from, next) {
  89. // this.updateQuestion(next);
  90. // },
  91. methods: {
  92. ...mapMutations(["updateExamState", "toggleSnapNow", "updateExamResult"]),
  93. async initData() {
  94. const exam = await this.$http.get(
  95. "/api/ecs_exam_work/exam/" + this.$route.params.examId
  96. );
  97. const paperStruct = await this.$http.get(
  98. "/api/ecs_oe_student/examRecordPaperStruct/getExamRecordPaperStruct?examRecordDataId=" +
  99. this.$route.params.examRecordDataId
  100. );
  101. let examQuestionList = (await this.$http.get(
  102. "/api/ecs_oe_student/examQuestion/findExamQuestionList"
  103. )).data;
  104. // init subNumber
  105. let questionId = null;
  106. let i = 1;
  107. examQuestionList = examQuestionList.map(eq => {
  108. if (questionId == eq.questionId) {
  109. eq.subNumber = i++;
  110. } else {
  111. i = 1;
  112. questionId = eq.questionId;
  113. eq.subNumber = i++;
  114. }
  115. return eq;
  116. });
  117. this.updateExamState({
  118. exam: exam.data,
  119. paperStruct: paperStruct.data,
  120. examQuestionList: examQuestionList
  121. });
  122. },
  123. updateQuestion: async function(next) {
  124. // 初始化套题的答案,为回填部分选项做准备
  125. // for (let q of this.examQuestionList) {
  126. // if (q.subQuestionList.length > 0) {
  127. // q.studentAnswer = [];
  128. // for (let sq of q.subQuestionList) {
  129. // q.studentAnswer.push(sq.studentAnswer);
  130. // }
  131. // }
  132. // }
  133. next && next();
  134. if (!this.exam) return;
  135. },
  136. async answerAllQuestions() {
  137. // TODO: reset dirty
  138. const answers = this.examQuestionList.filter(eq => eq.dirty).map(eq => {
  139. return { order: eq.order, studentAnswer: eq.studentAnswer };
  140. });
  141. await this.$http.post(
  142. "/api/ecs_oe_student/examQuestion/submitQuestionAnswer",
  143. answers
  144. );
  145. // this.updateExamQuestion({
  146. // order: this.$route.params.order,
  147. // studentAnswer
  148. // });
  149. },
  150. async submitPaper() {
  151. //FIXME: submit precondition
  152. await this.answerAllQuestions();
  153. const answered = this.examQuestionList.filter(
  154. q => q.studentAnswer !== null
  155. ).length;
  156. const unanswered = this.examQuestionList.filter(
  157. q => q.studentAnswer === null
  158. ).length;
  159. this.$Modal.confirm({
  160. title: "确认交卷",
  161. content: `<p>已答题目:${answered}</p><p>未答题目:${unanswered}</p>`,
  162. onOk: this.realSubmitPaper
  163. });
  164. },
  165. async realSubmitPaper() {
  166. this.toggleSnapNow();
  167. try {
  168. const res = await this.$http.get(
  169. "/api/ecs_oe_student/examControl/endExam"
  170. );
  171. if (res.status === 200) {
  172. this.updateExamResult({
  173. examRecordDataId: res.data.examRecordDataId,
  174. isWarn: res.data.isWarn,
  175. objectiveScore: res.data.objectiveScore
  176. });
  177. this.$router.push({
  178. path: `/online-exam/exam/${
  179. this.$route.params.examId
  180. }/examRecordData/${this.$route.params.examRecordDataId}/end`
  181. });
  182. }
  183. } catch (e) {
  184. console.log(e);
  185. }
  186. },
  187. examQuestion() {
  188. return (
  189. this.examQuestionList &&
  190. this.examQuestionList.find(
  191. eq => eq.order == this.$route.params.order // number == string
  192. )
  193. );
  194. }
  195. },
  196. computed: {
  197. ...mapState([
  198. "exam",
  199. "paperStruct",
  200. "examQuestionList",
  201. "snapNow",
  202. "shouldSubmitPaper"
  203. ]),
  204. previousQuestionOrder: vm => {
  205. if (vm.examQuestion().order > 1) {
  206. return vm.examQuestion().order - 1;
  207. } else {
  208. return null;
  209. }
  210. },
  211. nextQuestionOrder: vm => {
  212. if (vm.examQuestion().order < vm.examQuestionList.length) {
  213. return vm.examQuestion().order + 1;
  214. } else {
  215. return null;
  216. }
  217. }
  218. },
  219. watch: {
  220. $route: function() {
  221. this.examQuestion();
  222. },
  223. shouldSubmitPaper() {
  224. this.realSubmitPaper();
  225. }
  226. // examQuestionList(val, oldVal) {
  227. // // console.log(val, oldVal);
  228. // }
  229. },
  230. components: {
  231. RemainTime,
  232. OverallProgress,
  233. QuestionFilters,
  234. QuestionView,
  235. ArrowNavView,
  236. QuestionNavView,
  237. FaceRecognition,
  238. FaceId
  239. }
  240. };
  241. </script>
  242. <style scoped>
  243. .container {
  244. display: grid;
  245. grid-template-areas:
  246. "header header"
  247. "main side";
  248. grid-template-rows: 80px 1fr;
  249. grid-template-columns: 1fr 400px;
  250. height: 100vh;
  251. width: 100vw;
  252. }
  253. .header {
  254. display: grid;
  255. place-items: center;
  256. grid-template-columns: 200px 1fr 300px 100px;
  257. grid-area: header;
  258. height: 80px;
  259. background-color: #f5f5f5;
  260. }
  261. .main {
  262. display: grid;
  263. grid-area: main;
  264. grid-template-rows: 1fr 50px;
  265. }
  266. .side {
  267. display: grid;
  268. grid-area: side;
  269. grid-template-rows: 1fr 300px;
  270. background-color: #f5f5f5;
  271. }
  272. .camera {
  273. align-self: flex-end;
  274. justify-self: flex-end;
  275. }
  276. @media screen and (max-height: 768px) {
  277. .container {
  278. grid-template-rows: 50px 1fr;
  279. }
  280. .header {
  281. height: 50px;
  282. }
  283. .side {
  284. grid-template-rows: 1fr 200px;
  285. }
  286. }
  287. </style>