MultipleQuestionView.vue 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. <template>
  2. <div
  3. v-if="isSyncState && examQuestion.questionType === 'MULTIPLE_CHOICE'"
  4. class="question-view"
  5. >
  6. <question-body
  7. :question-body="question.body"
  8. :exam-question="examQuestion"
  9. ></question-body>
  10. <div class="ops">
  11. <div class="stu-answer">{{ oldIndexToABCD }}</div>
  12. <div class="score">({{ examQuestion.questionScore }}分)</div>
  13. </div>
  14. <div
  15. v-for="(option, index) in newQuestionOptions"
  16. :key="index"
  17. class="option"
  18. @click="toggleAnswer(option.oldIndex)"
  19. >
  20. <div
  21. :class="studentAnswer.includes(option.oldIndex) && 'row-selected'"
  22. style="display: flex;"
  23. >
  24. <input
  25. type="checkbox"
  26. name="question"
  27. value="option.oldIndex"
  28. :checked="studentAnswer && studentAnswer.includes(option.oldIndex)"
  29. style="margin-top: 4px"
  30. />
  31. <span style="padding: 0 10px;">{{ optionName[index] }}: </span>
  32. <div v-if="option.value" class="question-options">
  33. <question-body
  34. :question-body="option.value.body"
  35. :exam-question="examQuestion"
  36. ></question-body>
  37. </div>
  38. </div>
  39. </div>
  40. <div class="reset">
  41. <i-button type="warning" size="large" @click="() => answerQuestion(null)">
  42. 重置答案
  43. </i-button>
  44. <span v-if="examShouldShowAnswer()">
  45. &nbsp;&nbsp;&nbsp;<i-button
  46. type="info"
  47. size="large"
  48. @click="showAnswer"
  49. >
  50. 显示答案
  51. </i-button>
  52. </span>
  53. <div v-if="examShouldShowAnswer() && isShowAnswer">
  54. 正确答案:
  55. <div>{{ rightAnswerTransform }}</div>
  56. </div>
  57. </div>
  58. </div>
  59. </template>
  60. <script>
  61. import QuestionBody from "./QuestionBody";
  62. import { createNamespacedHelpers } from "vuex";
  63. const { mapMutations, mapGetters } = createNamespacedHelpers(
  64. "examingHomeModule"
  65. );
  66. const optionName = "ABCDEFGHIJ".split("");
  67. export default {
  68. name: "MultipleQuestionView",
  69. components: {
  70. QuestionBody,
  71. },
  72. props: {
  73. question: {
  74. type: Object,
  75. default() {
  76. return {};
  77. },
  78. },
  79. examQuestion: {
  80. type: Object,
  81. default() {
  82. return {};
  83. },
  84. },
  85. },
  86. data() {
  87. return {
  88. questionBody: this.question.body,
  89. optionName,
  90. studentAnswer: this.examQuestion.studentAnswer || "",
  91. isShowAnswer: false,
  92. };
  93. },
  94. computed: {
  95. isSyncState() {
  96. return this.examQuestion.order == this.$route.params.order;
  97. },
  98. newQuestionOptions() {
  99. return this.question.questionOptionList.map((v, i) => {
  100. return {
  101. value: this.question.questionOptionList[
  102. this.examQuestion.optionPermutation[i]
  103. ],
  104. oldIndex: "" + this.examQuestion.optionPermutation[i],
  105. name: optionName[i],
  106. };
  107. });
  108. },
  109. oldIndexToABCD() {
  110. return (
  111. this.examQuestion.studentAnswer &&
  112. this.newQuestionOptions
  113. .filter(v => this.examQuestion.studentAnswer.includes(v.oldIndex))
  114. .map(v => v.name)
  115. .join("")
  116. );
  117. },
  118. rightAnswerTransform() {
  119. return (
  120. this.question.rightAnswer &&
  121. this.newQuestionOptions
  122. .filter(v => this.question.rightAnswer.includes(v.oldIndex))
  123. .map(v => v.name)
  124. .join("")
  125. );
  126. },
  127. },
  128. watch: {
  129. examQuestion: function() {
  130. this.studentAnswer = this.examQuestion.studentAnswer || "";
  131. },
  132. },
  133. created: function() {
  134. window.addEventListener("keyup", this.keyup);
  135. },
  136. beforeDestroy() {
  137. window.removeEventListener("keyup", this.keyup);
  138. },
  139. beforeUpdate() {
  140. this.answerQuestion(this.studentAnswer);
  141. },
  142. methods: {
  143. ...mapMutations(["updateExamQuestion"]),
  144. ...mapGetters(["examShouldShowAnswer"]),
  145. keyup(e) {
  146. if (
  147. ["BODY", "A", "BUTTON", "DIV"].includes(
  148. document.activeElement.tagName
  149. ) ||
  150. (["INPUT"].includes(document.activeElement.tagName) &&
  151. ["checkbox", "radio"].includes(document.activeElement.type))
  152. ) {
  153. if (
  154. optionName
  155. .map(v => "Key" + v)
  156. .slice(0, this.question.questionOptionList.length)
  157. .includes(e.code)
  158. ) {
  159. window._hmt.push(["_trackEvent", "正在考试页面", "快捷键", "ABCDE"]);
  160. const selectedOldIndex =
  161. "" +
  162. this.newQuestionOptions.find(v => v.name == e.code[3]).oldIndex;
  163. if (this.studentAnswer.includes(selectedOldIndex)) {
  164. this.studentAnswer = this.studentAnswer.replace(
  165. selectedOldIndex,
  166. ""
  167. );
  168. } else {
  169. this.studentAnswer = this.studentAnswer
  170. .concat(selectedOldIndex)
  171. .split("")
  172. .sort()
  173. .join("");
  174. }
  175. }
  176. }
  177. },
  178. toggleAnswer: function(selectedOldIndex) {
  179. if (this.studentAnswer.includes(selectedOldIndex)) {
  180. this.studentAnswer = this.studentAnswer.replace(selectedOldIndex, "");
  181. } else {
  182. this.studentAnswer = this.studentAnswer
  183. .concat(selectedOldIndex)
  184. .split("")
  185. .sort()
  186. .join("");
  187. }
  188. },
  189. async answerQuestion(studentAnswer) {
  190. if (this.isSyncState === false) {
  191. console.log("更新答案时,问题与答案的号码不一致。");
  192. return;
  193. }
  194. let realAnswer = studentAnswer;
  195. if (studentAnswer === "") {
  196. realAnswer = null;
  197. }
  198. if (realAnswer !== this.examQuestion.studentAnswer) {
  199. this.updateExamQuestion({
  200. order: this.examQuestion.order,
  201. studentAnswer: realAnswer,
  202. });
  203. }
  204. },
  205. showAnswer() {
  206. this.isShowAnswer = !this.isShowAnswer;
  207. },
  208. },
  209. };
  210. </script>
  211. <style scoped>
  212. .question-view {
  213. display: grid;
  214. grid-row-gap: 10px;
  215. }
  216. .question-body {
  217. font-size: 18px;
  218. margin-bottom: 10px;
  219. }
  220. .ops {
  221. display: flex;
  222. align-items: flex-end;
  223. }
  224. .stu-answer {
  225. width: 100px;
  226. border-bottom: 1px solid black;
  227. text-align: center;
  228. height: 20px;
  229. }
  230. .option {
  231. display: flex;
  232. cursor: pointer;
  233. padding-top: 10px;
  234. border-radius: 5px;
  235. }
  236. .option:hover {
  237. background-color: aliceblue;
  238. }
  239. .row-selected {
  240. background-color: aliceblue;
  241. width: 100%;
  242. }
  243. .question-options {
  244. text-align: left;
  245. }
  246. .question-options > .question-body {
  247. text-align: left;
  248. font-size: 16px !important;
  249. }
  250. </style>