question_detail.html 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404
  1. <!-- +---------------------------------------------------------------------- -->
  2. <!-- | CRMEB [ CRMEB赋能开发者,助力企业发展 ] -->
  3. <!-- +---------------------------------------------------------------------- -->
  4. <!-- | Copyright (c) 2016~2022 https://www.crmeb.com All rights reserved. -->
  5. <!-- +---------------------------------------------------------------------- -->
  6. <!-- | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权 -->
  7. <!-- +---------------------------------------------------------------------- -->
  8. <!-- | Author: CRMEB Team <admin@crmeb.com> -->
  9. <!-- +---------------------------------------------------------------------- -->
  10. {extend name="public/container"}
  11. {block name="title"}考试答题{/block}
  12. {block name="head_top"}
  13. <style>
  14. body {
  15. background-color: #f5f5f5;
  16. }
  17. </style>
  18. {/block}
  19. {block name="content"}
  20. <div v-cloak id="app" class="question-paper">
  21. <div v-show="questions.length" class="header">
  22. <div class="message">温馨提示:请点击“答题卡”前去提交考试哦!</div>
  23. <div class="header-bd">
  24. <a id="some-element"
  25. :href="'{:url('topic/question_sheet')}?test_id=' + test_id + '&record_id=' + e_id + '&index=' + activeIndex + '&is_analysis=' + is_analysis + '&txamination_time=' + txamination_time + '&exam_time=' + duration">答题卡</a>
  26. <div v-if="!is_analysis">{{ duration | formatTime }}</div>
  27. </div>
  28. </div>
  29. <div class="swiper-container swiper-no-swiping">
  30. <div class="swiper-wrapper">
  31. <div v-for="(item, index) in virtualData.slides" :key="item.id" :style="{ left: virtualData.offset + 'px' }" class="swiper-slide">
  32. <div class="type">{{ item.questionType }}</div>
  33. <div class="question">
  34. <div>{{ item.stem }}</div>
  35. <img v-if="item.image" :src="item.image">
  36. <div :class="{ image: item.is_img }" class="label-group">
  37. <template v-for="option in item.options">
  38. <label v-if="option.value" :key="option.code">
  39. <input v-if="item.question_type === 2" v-model="item.user_answer" :value="option.code"
  40. :disabled="!!is_analysis || !!questions[questionSwiper.activeIndex].is_correct" type="checkbox" hidden>
  41. <input v-else v-model="item.user_answer" :value="option.code" :disabled="!!is_analysis || !!questions[questionSwiper.activeIndex].is_correct"
  42. type="radio" hidden>
  43. <div :class="{
  44. ok: option.right && item.user_answer.includes(option.code) && is_analysis,
  45. ok: !option.right && item.user_answer.includes(option.code) && is_analysis
  46. }">
  47. <img v-if="item.is_img" :src="option.value">
  48. <template v-else>{{ option.value }}</template>
  49. </div>
  50. </label>
  51. </template>
  52. </div>
  53. </div>
  54. <div v-if="is_analysis" class="analysis">
  55. <div :class="{ no: item.is_correct !== 2 }">回答{{ item.is_correct === 2 ? '正确' : '错误' }}</div>
  56. <div>
  57. <div>正确答案:<div>{{ item.answer }}</div>
  58. </div>
  59. <div>您的答案:<div>{{ item.user_answer.toString() || '未作答' }}</div>
  60. </div>
  61. </div>
  62. <div>试题难度:<span v-for="star in 5" :key="star" :class="{ on: item.difficulty >= star }" class="iconfont iconxing"></span></div>
  63. <div>答案解析:</div>
  64. <div v-html="item.analysis"></div>
  65. <div v-if="item.special.length">关联知识点:</div>
  66. <a v-for="special in item.special" :key="special.id"
  67. :href="(special.is_light ? '{:url('special/single_details')}' : '{:url('special/details')}') + '?id=' + special.id">{{ special.title }}</a>
  68. </div>
  69. </div>
  70. </div>
  71. <div class="swiper-pagination"></div>
  72. </div>
  73. <div v-if="questionSwiper" class="footer">
  74. <button :disabled="!questionSwiper.activeIndex" @click="slidePrev">
  75. <i class="iconfont iconshangyige"></i>
  76. <div>上一题</div>
  77. </button>
  78. <button v-if="!is_analysis" :disabled="!!questions[questionSwiper.activeIndex].is_correct" class="submit" @click="submitQuestion">
  79. <i class="iconfont icontijiao"></i>
  80. <div>确认提交</div>
  81. </button>
  82. <button :disabled="questionSwiper.activeIndex === questionSwiper.virtual.slides.length - 1" @click="slideNext">
  83. <i class="iconfont iconxiayige"></i>
  84. <div>下一题</div>
  85. </button>
  86. </div>
  87. <quick-menu v-if="is_analysis"></quick-menu>
  88. <question-guide :visible.sync="guideVisible"></question-guide>
  89. </div>
  90. {/block}
  91. {block name="foot"}
  92. <script>
  93. var uid = '{$uid}';
  94. var titles = '{$titles}';
  95. var txamination_time = "{$txamination_time}";
  96. var wechat_share =<?php echo isset($overallShareWechat) ? $overallShareWechat : '{}'; ?>;
  97. window.overallShare = false;
  98. require(['vue', 'helper', 'axios', 'swiper', 'layer', 'components/question-guide/index', 'quick'], function (Vue, $h, axios, Swiper, layer, questionGuide) {
  99. var isWechat = '{$isWechat}';
  100. var app = new Vue({
  101. el: '#app',
  102. components: {
  103. 'question-guide': questionGuide
  104. },
  105. filters: {
  106. formatTime: function (time) {
  107. var hour = Math.floor(time / 3600000);
  108. var minute = Math.floor((time - hour * 3600000) / 60000);
  109. var second = Math.floor((time - hour * 3600000 - minute * 60000) / 1000);
  110. if (hour < 10) {
  111. hour = '0' + hour;
  112. }
  113. if (minute < 10) {
  114. minute = '0' + minute;
  115. }
  116. if (second < 10) {
  117. second = '0' + second;
  118. }
  119. return hour + ':' + minute + ':' + second;
  120. }
  121. },
  122. data: {
  123. questions: [], // 考题列表
  124. test_id: '',
  125. e_id: '',
  126. isWechat: isWechat,
  127. is_analysis: 0,
  128. activeIndex: 0,
  129. virtualData: {
  130. slides: []
  131. },
  132. questionSwiper: null,
  133. startTime: new Date(),
  134. duration: 0,
  135. txamination_time: 0,
  136. guideVisible: false,
  137. submitProblem: {}
  138. },
  139. watch: {
  140. guideVisible: function (value) {
  141. if (!value) {
  142. localStorage.setItem('question-guide', new Date());
  143. }
  144. }
  145. },
  146. created: function () {
  147. var vm = this;
  148. var search = window.location.search.slice(1);
  149. var query = {};
  150. search.split('&').forEach(function (item) {
  151. item = item.split('=');
  152. query[item[0]] = item[1];
  153. });
  154. this.test_id = query.test_id;
  155. this.e_id = query.e_id;
  156. this.is_analysis = query.is_analysis || 0;
  157. this.activeIndex = Number(query.index) || 0;
  158. this.getQuestions();
  159. this.txamination_time = parseInt(txamination_time);
  160. if ($h.getCookie('exam_time')) {
  161. if ($h.getCookie('exam_time') === 'NaN') {
  162. this.exam_time = 0;
  163. } else {
  164. this.exam_time = parseInt($h.getCookie('exam_time'));
  165. }
  166. } else {
  167. this.exam_time = 0;
  168. }
  169. if (!this.is_analysis) {
  170. this.setTimer();
  171. }
  172. if (this.isWechat) {
  173. mapleWx($jssdk(), function () {
  174. this.onMenuShareAll({
  175. title: titles,
  176. desc: titles,
  177. imgUrl: wechat_share.wechat_share_img,
  178. link: window.location.origin + "{:url('special/question_index')}?spread_uid=" + uid + "&id=" + vm.test_id
  179. });
  180. });
  181. }
  182. },
  183. methods: {
  184. // 获取考试题
  185. getQuestions: function () {
  186. var vm = this;
  187. var load = layer.load(1);
  188. axios.get('/wap/topic/testPaperQuestions', {
  189. params: {
  190. test_id: this.test_id,
  191. record_id: this.e_id,
  192. type: 2
  193. }
  194. }).then(function (res) {
  195. if (res.data.code === 200) {
  196. var questions = res.data.data;
  197. questions.forEach(function (question) {
  198. question.options = [];
  199. if (Array.isArray(question.option)) {
  200. question.option.forEach(function (option, index) {
  201. var code = String.fromCharCode(index + 65);
  202. question.options.push({
  203. code: code,
  204. value: option,
  205. right: question.answer.includes(code)
  206. });
  207. });
  208. } else {
  209. for (var key in question.option) {
  210. if (Object.hasOwnProperty.call(question.option, key)) {
  211. question.options.push({
  212. code: key,
  213. value: question.option[key],
  214. right: question.answer.includes(key)
  215. });
  216. }
  217. }
  218. }
  219. if (!Array.isArray(question.userAnswer)) {
  220. Object.assign(question, question.userAnswer);
  221. }
  222. if (!('is_correct' in question)) {
  223. question.is_correct = 0;
  224. }
  225. if (!('user_answer' in question)) {
  226. question.user_answer = '';
  227. }
  228. if (question.question_type === 2) {
  229. question.user_answer = question.user_answer ? question.user_answer.split(',') : [];
  230. }
  231. switch (question.question_type) {
  232. case 1:
  233. question.questionType = '单选题';
  234. break;
  235. case 2:
  236. question.questionType = '多选题';
  237. break;
  238. case 3:
  239. question.questionType = '判断题';
  240. break;
  241. }
  242. });
  243. vm.questions = questions;
  244. vm.questionSwiper = new Swiper('.swiper-container', {
  245. initialSlide: vm.activeIndex,
  246. pagination: {
  247. el: '.swiper-pagination',
  248. type: 'fraction'
  249. },
  250. virtual: {
  251. slides: questions,
  252. renderExternal: function (data) {
  253. vm.virtualData = data;
  254. }
  255. },
  256. on: {
  257. init: function () {
  258. vm.guideVisible = !localStorage.getItem('question-guide');
  259. },
  260. slideChange: function () {
  261. if (!vm.is_analysis) {
  262. vm.activeIndex = this.activeIndex;
  263. if (vm.activeIndex === vm.questions.length - 1) {
  264. layer.msg('答完全部考题后<br>点击左上角“答题卡”前去提交考试');
  265. }
  266. }
  267. }
  268. }
  269. });
  270. } else {
  271. layer.msg(res.data.msg, function () {
  272. window.location.replace($h.U({
  273. c: 'topic',
  274. a: 'question_user'
  275. }));
  276. });
  277. }
  278. }).catch(function () {
  279. }).then(function () {
  280. layer.close(load);
  281. });
  282. },
  283. // 计时器
  284. setTimer: function () {
  285. var vm = this;
  286. window.addEventListener('pagehide', function () {
  287. $h.setCookie('exam_time', vm.duration);
  288. });
  289. var timer = setInterval(function () {
  290. vm.duration = new Date() - vm.startTime + vm.exam_time;
  291. if (vm.txamination_time * 60000 <= vm.duration) {
  292. clearInterval(timer);
  293. layer.msg('考试时间已到', function () {
  294. vm.submit();
  295. });
  296. }
  297. }, 1000);
  298. },
  299. // 提交考试
  300. submit: function () {
  301. var vm = this;
  302. var submitProblem = this.questions[this.activeIndex];
  303. if (submitProblem.user_answer.length) {
  304. var question = submitProblem;
  305. var data = {
  306. e_id: this.e_id,
  307. questions_id: question.questions_id,
  308. answer: question.answer,
  309. score: question.score,
  310. type: 2
  311. };
  312. if (typeof question.user_answer === 'string') {
  313. data.user_answer = question.user_answer;
  314. data.is_correct = question.answer === question.user_answer ? 2 : 1;
  315. } else {
  316. var answer = question.answer.split(',');
  317. if (answer.length === question.user_answer.length) {
  318. if (answer.toString() === question.user_answer.toString()) {
  319. data.is_correct = 2;
  320. } else {
  321. data.is_correct = answer.sort().toString() === question.user_answer.sort().toString() ? 2 : 1;
  322. }
  323. } else {
  324. data.is_correct = 1;
  325. }
  326. data.user_answer = question.user_answer.sort().toString();
  327. }
  328. var index = layer.load(1);
  329. axios.post('/wap/topic/submitQuestions', data).then(function (res) {
  330. var resData = res.data;
  331. if (resData.code === 200) {
  332. question.is_correct = data.is_correct;
  333. window.location = "{:url('topic/question_sheet')}?test_id=" + vm.test_id + '&record_id=' + vm.e_id + '&index=' + vm.activeIndex + '&is_analysis=' + vm.is_analysis + '&txamination_time=' + vm.txamination_time;
  334. } else {
  335. layer.msg(resData.msg);
  336. }
  337. }).catch(function (err) {
  338. }).then(function () {
  339. layer.close(index);
  340. });
  341. } else {
  342. window.location = "{:url('topic/question_sheet')}?test_id=" + vm.test_id + '&record_id=' + vm.e_id + '&index=' + vm.activeIndex + '&is_analysis=' + vm.is_analysis + '&txamination_time=' + vm.txamination_time;
  343. }
  344. },
  345. // 提交本题
  346. submitQuestion: function () {
  347. var vm = this;
  348. var question = this.questions[this.activeIndex];
  349. var data = {
  350. e_id: this.e_id,
  351. questions_id: question.questions_id,
  352. answer: question.answer,
  353. score: question.score,
  354. type: 2
  355. };
  356. if (!question.user_answer.length) {
  357. return layer.msg('请作答后提交本题');
  358. }
  359. if (question.question_type === 2) {
  360. question.user_answer.sort();
  361. data.user_answer = question.user_answer.toString();
  362. } else {
  363. data.user_answer = question.user_answer;
  364. }
  365. data.is_correct = data.user_answer === question.answer ? 2 : 1;
  366. var load = layer.load(1);
  367. axios.post('/wap/topic/submitQuestions', data).then(function (res) {
  368. if (res.data.code === 200) {
  369. question.is_correct = data.is_correct;
  370. } else {
  371. layer.msg(res.data.msg);
  372. }
  373. }).catch(function () {
  374. }).then(function () {
  375. layer.close(load);
  376. })
  377. },
  378. // 上一题
  379. slidePrev: function () {
  380. this.questionSwiper.slidePrev();
  381. },
  382. // 下一题
  383. slideNext: function () {
  384. this.questionSwiper.slideNext();
  385. }
  386. }
  387. });
  388. });
  389. </script>
  390. {/block}