ExamPaper.vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  1. <template>
  2. <div v-if="exam" class="container" :key="exam.id">
  3. <div class="main">
  4. <div v-for="(qG, index) in questionGroupList" :key="index">
  5. <div>
  6. {{ toChineseNumber(index + 1) }}、{{ qG.groupName }} ({{
  7. qG.groupScore
  8. }}分)
  9. </div>
  10. <div
  11. v-for="questionWrapper in qG.questionWrapperList"
  12. :key="questionWrapper.questionId"
  13. class="question-wrapper"
  14. >
  15. <div v-html="restoreAudio(questionWrapper.body)"></div>
  16. <div
  17. v-for="(questionUnit, index) in questionWrapper.questionUnitList"
  18. :key="index"
  19. >
  20. <div class="flex">
  21. <div>{{ questionWrapper.eqs[index].order }}、</div>
  22. <div v-html="restoreAudio(questionUnit.body)"></div>
  23. </div>
  24. <div
  25. v-for="(optionOrder, index) in questionWrapper.eqs[index]
  26. .optionPermutation"
  27. :key="index"
  28. >
  29. <div class="flex">
  30. <div>{{ indexToABCD(index) }}、</div>
  31. <div
  32. v-html="
  33. restoreAudio(
  34. questionUnit.questionOptionList[optionOrder - 0].body
  35. )
  36. "
  37. ></div>
  38. </div>
  39. </div>
  40. <div class="flex" v-if="practiceType !== 'NO_ANSWER'">
  41. 正确答案:
  42. <div
  43. v-html="
  44. rightAnswerToABCD(
  45. questionUnit.questionType,
  46. questionUnit.rightAnswer,
  47. questionWrapper.eqs[index].optionPermutation
  48. )
  49. "
  50. ></div>
  51. </div>
  52. <div class="flex">
  53. 学生答案:
  54. <div
  55. v-html="
  56. rightAnswerToABCD(
  57. questionUnit.questionType,
  58. questionWrapper.eqs[index].studentAnswer,
  59. questionWrapper.eqs[index].optionPermutation,
  60. questionUnit.rightAnswer
  61. )
  62. "
  63. ></div>
  64. </div>
  65. </div>
  66. </div>
  67. </div>
  68. </div>
  69. </div>
  70. <div v-else>正在等待数据返回...</div>
  71. </template>
  72. <script>
  73. const optionName = "ABCDEFGHIJ".split("");
  74. export default {
  75. name: "ExamPaper",
  76. data() {
  77. return {
  78. exam: null,
  79. questionGroupList: null,
  80. examQuestionList: null,
  81. practiceType: null
  82. };
  83. },
  84. props: {
  85. examId: Number,
  86. examRecordDataId: Number
  87. },
  88. async created() {
  89. await this.initData();
  90. },
  91. methods: {
  92. async initData() {
  93. const practiceType = (await this.$http.get(
  94. "/api/ecs_exam_work/exam/examOrgProperty/" +
  95. this.examId +
  96. `/PRACTICE_TYPE`
  97. )).data;
  98. this.practiceType = practiceType; // IN_PRACTICE NO_ANSWER
  99. const [
  100. examData,
  101. paperStructData,
  102. examQuestionListData,
  103. examRecordDataData,
  104. courseInfoData
  105. ] = await Promise.all([
  106. this.$http.get("/api/ecs_exam_work/exam/" + this.examId),
  107. this.$http.get(
  108. "/api/ecs_oe_student/examRecordPaperStruct/getExamRecordPaperStruct?examRecordDataId=" +
  109. this.examRecordDataId
  110. ),
  111. this.$http.get(
  112. "/api/ecs_oe_admin/examRecordQuestions/getExamRecordQuestions?examRecordDataId=" +
  113. this.examRecordDataId
  114. ),
  115. this.$http.get(
  116. "/api/ecs_oe_admin/exam/record/data/findExamRecordDataEntity?examRecordDataId=" +
  117. this.examRecordDataId
  118. ),
  119. this.$http.get(
  120. "/api/ecs_oe_student/practice/getPracticeDetailInfo?examRecordDataId=" +
  121. this.examRecordDataId
  122. )
  123. ]);
  124. const [exam, paperStruct, examRecordData, courseInfo] = [
  125. examData.data,
  126. paperStructData.data,
  127. examRecordDataData.data,
  128. courseInfoData.data
  129. ];
  130. let examQuestionList = examQuestionListData.data.examQuestionEntities;
  131. if (
  132. exam === undefined ||
  133. paperStruct === undefined ||
  134. examQuestionListData === undefined
  135. ) {
  136. this.$Message.error({
  137. content: "获取试卷信息失败",
  138. duration: 15,
  139. closable: true
  140. });
  141. return;
  142. }
  143. // init subNumber
  144. let questionId = null;
  145. let i = 1;
  146. examQuestionList = examQuestionList.map(eq => {
  147. if (questionId == eq.questionId) {
  148. eq.subNumber = i++;
  149. } else {
  150. i = 1;
  151. questionId = eq.questionId;
  152. eq.subNumber = i++;
  153. }
  154. return eq;
  155. });
  156. let groupOrder = 1;
  157. let mainNumber = 0;
  158. examQuestionList = examQuestionList.map(eq => {
  159. if (mainNumber == eq.mainNumber) {
  160. eq.groupOrder = groupOrder++;
  161. } else {
  162. mainNumber = eq.mainNumber;
  163. groupOrder = 1;
  164. eq.groupOrder = groupOrder++;
  165. }
  166. const questionWrapperList =
  167. paperStruct.defaultPaper.questionGroupList[eq.mainNumber - 1]
  168. .questionWrapperList;
  169. const groupName =
  170. paperStruct.defaultPaper.questionGroupList[eq.mainNumber - 1]
  171. .groupName;
  172. const groupTotal = questionWrapperList.reduce(
  173. (accumulator, questionWrapper) =>
  174. accumulator + questionWrapper.questionUnitWrapperList.length,
  175. 0
  176. );
  177. eq.groupName = groupName;
  178. eq.groupTotal = groupTotal;
  179. return eq;
  180. });
  181. examQuestionList = examQuestionList.map(eq => {
  182. const paperStructQuestion = paperStruct.defaultPaper.questionGroupList[
  183. eq.mainNumber - 1
  184. ].questionWrapperList.find(q => q.questionId === eq.questionId);
  185. return Object.assign(eq, {
  186. limitedPlayTimes: paperStructQuestion.limitedPlayTimes
  187. });
  188. });
  189. this.exam = examData.data;
  190. this.examQuestionList = examQuestionList;
  191. // 子题乱序只在纯选择题中出现。选项乱序出现在任何选择题中。
  192. const questionGroupList =
  193. paperStructData.data.defaultPaper.questionGroupList;
  194. for (const qG of questionGroupList) {
  195. for (const question of qG.questionWrapperList) {
  196. const qs = this.examQuestionList.filter(
  197. eq => eq.questionId === question.questionId
  198. );
  199. // if (qs.length === 1) {
  200. // const q = qs[0];
  201. // Object.assign(question, {
  202. // correctAnswer: q.correctAnswer,
  203. // groupOrder: q.groupOrder,
  204. // groupTotal: q.groupTotal,
  205. // isAnswer: q.isAnswer,
  206. // isSign: q.isSign,
  207. // mainNumber: q.mainNumber,
  208. // optionPermutation: q.optionPermutation,
  209. // order: q.order,
  210. // questionScore: q.questionScore,
  211. // questionType: q.questionType,
  212. // studentAnswer: q.studentAnswer,
  213. // subNumber: q.subNumber
  214. // });
  215. // } else {
  216. Object.assign(question, {
  217. eqs: qs
  218. });
  219. // }
  220. }
  221. }
  222. for (const qG of questionGroupList) {
  223. for (const question of qG.questionWrapperList) {
  224. const q = await this.getQuestionContent(
  225. question.questionId,
  226. this.exam,
  227. courseInfo,
  228. examRecordData
  229. );
  230. question.body = q.body;
  231. question.hasAudios = q.hasAudios;
  232. question.questionUnitList = q.questionUnitList;
  233. }
  234. }
  235. this.questionGroupList = questionGroupList;
  236. },
  237. async getQuestionContent(questionId, exam, courseInfo, examRecordData) {
  238. const qContentRes = await this.$http.post(
  239. "/api/ecs_ques/default_question/question",
  240. {
  241. questionId: questionId,
  242. examId: this.exam.id,
  243. courseCode: courseInfo.courseCode,
  244. groupCode: examRecordData.examRecord.paperType
  245. }
  246. );
  247. return qContentRes.data.masterVersion;
  248. },
  249. restoreAudio(str) {
  250. return (str || "")
  251. .replace(/<a/g, "<audio controls ")
  252. .replace(/url=/g, "src=")
  253. .replace(/a>/g, "audio>");
  254. },
  255. toChineseNumber(num) {
  256. return num.toLocaleString("zh-u-nu-hanidec");
  257. },
  258. indexToABCD(index) {
  259. return optionName[index];
  260. },
  261. rightAnswerToABCD(
  262. questionType,
  263. studentAnswer,
  264. optionPermutation,
  265. rightAnswer
  266. ) {
  267. if (studentAnswer === null) studentAnswer = [];
  268. if (["SINGLE_CHOICE", "MULTIPLE_CHOICE"].includes(questionType)) {
  269. // 学生的答案是字符串
  270. if (typeof studentAnswer === "string") {
  271. studentAnswer = studentAnswer.split("");
  272. }
  273. const t = studentAnswer
  274. .map(v => optionPermutation.indexOf(v - 0))
  275. .sort();
  276. let result = t.map(v => this.indexToABCD(v + "")).join("");
  277. if (rightAnswer && rightAnswer.join("") == studentAnswer.join(""))
  278. result +=
  279. '<i class="ivu-icon ivu-icon-md-checkmark" style="color: red"></i>';
  280. else if (rightAnswer && rightAnswer.join("") != studentAnswer.join(""))
  281. result +=
  282. '<i class="ivu-icon ivu-icon-md-close" style="color: red"></i>';
  283. return result;
  284. } else if (["TRUE_OR_FALSE"].includes(questionType)) {
  285. if (typeof studentAnswer === "string") {
  286. studentAnswer = [studentAnswer];
  287. }
  288. let result = { true: "正确", false: "错误" }[studentAnswer.join("")];
  289. result = result || "";
  290. if (rightAnswer && rightAnswer.join("") == studentAnswer.join(""))
  291. result +=
  292. '<i class="ivu-icon ivu-icon-md-checkmark" style="color: red"></i>';
  293. else if (rightAnswer && rightAnswer.join("") != studentAnswer.join(""))
  294. result +=
  295. '<i class="ivu-icon ivu-icon-md-close" style="color: red"></i>';
  296. return result;
  297. }
  298. let result =
  299. typeof studentAnswer === "string"
  300. ? studentAnswer
  301. : studentAnswer.join("");
  302. return result;
  303. }
  304. }
  305. };
  306. </script>
  307. <style scoped>
  308. .flex {
  309. display: flex;
  310. }
  311. .container {
  312. text-align: left;
  313. padding: 20px;
  314. }
  315. .question-wrapper {
  316. padding-bottom: 20px;
  317. }
  318. </style>