exam-start.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450
  1. <template>
  2. <ie-page :fix-height="true" :safe-area-inset-bottom="false">
  3. <block v-if="isReady">
  4. <exam-navbar :total-exam-time="totalExamTime" @left-click="handleLeftClick" @right-click="handleRightClick" />
  5. <exam-subtitle />
  6. <exam-swiper @submit="beforeSubmit" />
  7. <exam-toolbar @submit="beforeSubmit" />
  8. </block>
  9. </ie-page>
  10. <fast-guide v-model:show="guideShow" :list="guideList" v-model:index="guideIndex"
  11. @close="handleGuideClose"></fast-guide>
  12. <question-swiper-tip :visible="showSwiperTip" @next="handleSwiperTipNext" />
  13. <exam-mode ref="examModeRef" />
  14. </template>
  15. <script lang="ts" setup>
  16. import ExamNavbar from './components/exam-navbar.vue';
  17. import ExamSubtitle from './components/exam-subtitle.vue';
  18. import ExamSwiper from './components/exam-swiper.vue';
  19. import ExamToolbar from './components/exam-toolbar.vue';
  20. import ExamMode from './components/exam-mode.vue';
  21. import QuestionSwiperTip from './components/question-swiper-tip.vue';
  22. import { useTransferPage } from '@/hooks/useTransferPage';
  23. import { useUserStore } from '@/store/userStore';
  24. import { EnumPaperType, EnumQuestionType } from '@/common/enum';
  25. import { getOpenExaminee, getPaper, commitExamineePaper, beginExaminee, getExamineeResult } from '@/api/modules/study';
  26. import { useExam } from '@/composables/useExam';
  27. import { Study, Transfer } from '@/types';
  28. import {
  29. EXAM_AUTO_SUBMIT,
  30. EXAM_PAGE_OPTIONS,
  31. EXAM_DATA
  32. } from '@/types/injectionSymbols';
  33. const userStore = useUserStore();
  34. // import { Examinee, ExamPaper, ExamPaperSubmit } from '@/types/study';
  35. const { prevData, transferBack, transferTo } = useTransferPage<Transfer.ExamAnalysisPageOptions, {}>();
  36. const examData = useExam();
  37. const { setQuestionList, questionList, flatQuestionList, setSubQuestionIndex,
  38. notDoneCount, isAllDone,
  39. practiceDuration, startTiming, stopTiming, submit, changeIndex, setDuration } = examData;
  40. //
  41. const showSwiperTip = ref(false);
  42. const guideShow = ref(false);
  43. const guideList = ref([
  44. {
  45. target: '#question-calendar-btn',
  46. position: 'top',
  47. msg: '[答题卡]\n查看答题卡,掌握考试进度'
  48. },
  49. {
  50. target: '#question-favorite-btn',
  51. position: 'top',
  52. msg: '[题目收藏]\n收藏的题目可以在收藏夹查看'
  53. },
  54. {
  55. target: '#question-mark-btn',
  56. position: 'top',
  57. msg: '[题目标记]\n标记的题目可以在答题卡中快速找到'
  58. },
  59. {
  60. target: '#question-correct-btn',
  61. position: 'top',
  62. msg: '[试题纠错]\n点击试题纠错,帮助我们改进题目'
  63. }
  64. ]);
  65. const guideIndex = ref(0);
  66. const isReady = ref(false);
  67. // 考试规定时间
  68. const totalExamTime = ref<number>(0);
  69. // 自动提交只提醒1次
  70. const hasShowSubmitConfirm = ref(false);
  71. const examineeId = ref<number | undefined>(undefined);
  72. const paperData = ref<Study.ExamPaper>({} as Study.ExamPaper);
  73. // 是否确认退出
  74. const confirmQuit = ref(false);
  75. const confirmShowing = ref(false);
  76. /**
  77. * 自动提交
  78. */
  79. const autoSubmit = () => {
  80. if (hasShowSubmitConfirm.value) {
  81. return;
  82. }
  83. hasShowSubmitConfirm.value = true;
  84. beforeSubmit();
  85. }
  86. provide(EXAM_PAGE_OPTIONS, prevData.value);
  87. provide(EXAM_DATA, examData);
  88. provide(EXAM_AUTO_SUBMIT, autoSubmit);
  89. const isPracticeExam = computed(() => {
  90. return prevData.value.paperType === EnumPaperType.PRACTICE || prevData.value.paperType === EnumPaperType.COURSE;
  91. });
  92. const isSimulationExam = computed(() => {
  93. // prevData.value
  94. return prevData.value.paperType === EnumPaperType.SIMULATED;
  95. });
  96. const isTestExam = computed(() => {
  97. return prevData.value.paperType === EnumPaperType.TEST;
  98. });
  99. const isReadOnly = computed(() => {
  100. return prevData.value.readonly || false;
  101. });
  102. const handleLeftClick = () => {
  103. if (!isReady.value || isReadOnly.value) {
  104. confirmQuit.value = true;
  105. transferBack();
  106. return;
  107. }
  108. beforeQuit();
  109. };
  110. const examModeRef = ref();
  111. const handleRightClick = () => {
  112. examModeRef.value.open();
  113. }
  114. const beforeQuit = () => {
  115. const { paperType } = prevData.value;
  116. if (!isReady.value || isReadOnly.value) {
  117. return;
  118. }
  119. stopTime();
  120. const msg = paperType === EnumPaperType.PRACTICE ? '当前练习未完成,确认退出?' : '当前考试未完成,确认退出?';
  121. confirmShowing.value = true;
  122. uni.$ie.showModal({
  123. title: '提示',
  124. content: msg,
  125. }).then(confirm => {
  126. if (confirm) {
  127. handleSubmit(true);
  128. } else {
  129. confirmQuit.value = false;
  130. confirmShowing.value = false;
  131. startTime();
  132. }
  133. });
  134. };
  135. const startTime = () => {
  136. startTiming();
  137. }
  138. const stopTime = () => {
  139. stopTiming();
  140. }
  141. const beforeSubmit = async () => {
  142. const text = notDoneCount.value > 0 ? `还有${notDoneCount.value}题未做,确认交卷?` : '是否确认交卷?';
  143. stopTime();
  144. uni.$ie.showModal({
  145. title: '提示',
  146. content: text,
  147. }).then(confirm => {
  148. if (confirm) {
  149. handleSubmit(false);
  150. } else {
  151. startTime();
  152. }
  153. });
  154. }
  155. /**
  156. * 提交试卷
  157. * @param tempSave 是否临时保存
  158. */
  159. const handleSubmit = (tempSave: boolean = false) => {
  160. // 执行完后续逻辑
  161. submit();
  162. const msg = tempSave ? '保存中...' : '提交中...';
  163. uni.$ie.showLoading(msg);
  164. setTimeout(async () => {
  165. const params: Study.ExamPaperSubmit = {
  166. ...paperData.value,
  167. questions: questionList.value.map(item => {
  168. return {
  169. ...item,
  170. title: '',
  171. isDone: tempSave ? item.isDone : true,
  172. subQuestions: item.subQuestions.map(subItem => {
  173. return {
  174. ...subItem,
  175. title: '',
  176. };
  177. })
  178. };
  179. }),
  180. examineeId: examineeId.value,
  181. // examineeId: examineerData.value.examineeId,
  182. isDone: tempSave ? isAllDone.value : true,
  183. duration: practiceDuration.value
  184. };
  185. console.log('提交试卷参数', params)
  186. await commitExamineePaper(params);
  187. if (isSimulationExam.value || isTestExam.value) {
  188. if (!tempSave) {
  189. setTimeout(async () => {
  190. uni.$ie.hideLoading();
  191. await nextTick();
  192. confirmQuit.value = true;
  193. confirmShowing.value = false;
  194. transferTo('/pagesStudy/pages/simulation-analysis/simulation-analysis', {
  195. data: {
  196. examineeId: examineeId.value,
  197. paperType: prevData.value.paperType
  198. } as Transfer.SimulationAnalysisPageOptions,
  199. type: 'redirectTo'
  200. });
  201. }, 2500);
  202. } else {
  203. uni.$ie.hideLoading();
  204. confirmQuit.value = true;
  205. confirmShowing.value = false;
  206. nextTick(() => {
  207. transferBack();
  208. });
  209. }
  210. } else if (isPracticeExam.value) {
  211. if (!tempSave) {
  212. setTimeout(async () => {
  213. uni.$ie.hideLoading();
  214. await nextTick();
  215. confirmQuit.value = true;
  216. confirmShowing.value = false;
  217. transferTo('/pagesStudy/pages/knowledge-practice-detail/knowledge-practice-detail', {
  218. data: {
  219. paperType: prevData.value.paperType,
  220. examineeId: examineeId.value,
  221. name: prevData.value.practiceInfo?.name,
  222. directed: prevData.value.practiceInfo?.directed,
  223. questionType: prevData.value.practiceInfo?.questionType
  224. } as Transfer.PracticeResultPageOptions,
  225. type: 'redirectTo'
  226. });
  227. }, 2500);
  228. } else {
  229. uni.$ie.hideLoading();
  230. confirmQuit.value = true;
  231. confirmShowing.value = false;
  232. nextTick(() => {
  233. transferBack();
  234. });
  235. }
  236. }
  237. }, 300);
  238. }
  239. /**
  240. * 恢复上次做题历史数据
  241. * @param savedQuestion 上次做题历史数据
  242. * @param fullQuestion 当前试卷数据
  243. */
  244. const restoreQuestion = (savedQuestion: Study.ExamineeQuestion[], fullQuestion: Study.ExamineeQuestion[]) => {
  245. if (!savedQuestion) {
  246. return fullQuestion;
  247. }
  248. for (let index = 0; index < fullQuestion.length; index++) {
  249. const item = fullQuestion[index];
  250. const savedQs = savedQuestion[index]
  251. if (savedQs) {
  252. if (savedQs.answers) {
  253. item.answers = savedQs.answers.filter(ans => ans.trim());
  254. }
  255. item.answer1 = savedQs.answer1;
  256. item.answer2 = savedQs.answer2;
  257. item.isMark = savedQs.isMark;
  258. item.isFavorite = savedQs.isFavorite;
  259. item.isNotKnow = savedQs.isNotKnow;
  260. item.parse = savedQs.parse;
  261. item.totalScore = savedQs.totalScore;
  262. if (item.subQuestions) {
  263. restoreQuestion(savedQs.subQuestions, item.subQuestions);
  264. }
  265. }
  266. }
  267. return fullQuestion;
  268. }
  269. // 1、加载知识点练习数据
  270. const loadPracticeData = async () => {
  271. const { paperType, readonly, practiceInfo } = prevData.value;
  272. let data: Study.Examinee | null = null;
  273. if (readonly) {
  274. if (practiceInfo?.examineeId) {
  275. const res = await getExamineeResult(practiceInfo.examineeId);
  276. data = res.data;
  277. }
  278. } else {
  279. const params = {
  280. paperType: paperType,
  281. relateId: practiceInfo?.relateId,
  282. } as Study.OpenExamineeRequestDTO;
  283. if (userStore.isVHS) {
  284. params.questionType = practiceInfo?.questionType;
  285. } else {
  286. params.directed = practiceInfo?.directed || false;
  287. }
  288. const res = await getOpenExaminee(params);
  289. data = res.data || {};
  290. }
  291. if (!data) {
  292. uni.$ie.hideLoading();
  293. transferBack();
  294. return;
  295. }
  296. // 练习没有规定时间,设置为最大值
  297. totalExamTime.value = Number.MAX_SAFE_INTEGER;
  298. combinePaperData(data, paperType);
  299. }
  300. // 2、加载模拟考试数据
  301. const loadExamData = async () => {
  302. const { paperType, readonly, simulationInfo } = prevData.value;
  303. let data: Study.Examinee;
  304. if (simulationInfo?.examineeId) {
  305. if (readonly) {
  306. const res = await getExamineeResult(simulationInfo.examineeId);
  307. data = res.data;
  308. } else {
  309. const res = await beginExaminee(simulationInfo.examineeId);
  310. data = res.data || {};
  311. }
  312. if (!data) {
  313. uni.$ie.hideLoading();
  314. transferBack();
  315. return;
  316. }
  317. totalExamTime.value = data.paperInfo?.time || 0;
  318. combinePaperData(data, paperType);
  319. }
  320. }
  321. // 3、加载对口升学试卷数据
  322. const loadVHSPaperData = async () => {
  323. const { paperType, readonly, simulationInfo } = prevData.value;
  324. let data: Study.Examinee;
  325. if (simulationInfo?.examineeId) {
  326. if (readonly) {
  327. const res = await getExamineeResult(simulationInfo.examineeId);
  328. data = res.data;
  329. } else {
  330. const params = {
  331. paperType: paperType,
  332. relateId: simulationInfo?.examineeId,
  333. } as Study.OpenExamineeRequestDTO;
  334. const res = await getOpenExaminee(params);
  335. data = res.data || {};
  336. }
  337. if (!data) {
  338. uni.$ie.hideLoading();
  339. transferBack();
  340. return;
  341. }
  342. totalExamTime.value = data.paperInfo?.time || Number.MAX_SAFE_INTEGER;
  343. combinePaperData(data, paperType);
  344. }
  345. }
  346. const combinePaperData = async (examinee: Study.Examinee, paperType: EnumPaperType) => {
  347. examineeId.value = examinee.examineeId;
  348. if (examinee.paperId) {
  349. const res = await getPaper({
  350. type: paperType,
  351. id: examinee.paperId
  352. });
  353. paperData.value = res.data;
  354. paperData.value.questions = restoreQuestion(examinee.questions, res.data.questions);
  355. console.log('初始化数据', paperData.value.questions)
  356. setQuestionList(paperData.value.questions);
  357. setDuration(examinee.duration || 0);
  358. await nextTick();
  359. const targetQuestion = flatQuestionList.value.find(item => item.id === prevData.value.questionId);
  360. if (targetQuestion) {
  361. changeIndex(targetQuestion.index);
  362. } else {
  363. changeIndex(0);
  364. }
  365. setTimeout(() => {
  366. if (targetQuestion?.isSubQuestion) {
  367. setSubQuestionIndex(targetQuestion.subIndex || 0);
  368. }
  369. }, 50);
  370. await new Promise(resolve => setTimeout(resolve, 50));
  371. await nextTick();
  372. // 读取用户练习设置
  373. // setPracticeSettings(userStore.practiceSettings);
  374. isReady.value = true;
  375. console.log('试卷信息', res)
  376. if (!userStore.isExamGuideShow) {
  377. setTimeout(() => {
  378. uni.$ie.hideLoading();
  379. setTimeout(() => {
  380. showSwiperTip.value = true;
  381. }, 100);
  382. }, 300);
  383. } else {
  384. uni.$ie.hideLoading();
  385. if (!isReadOnly.value) {
  386. startTime();
  387. }
  388. }
  389. }
  390. }
  391. const handleSwiperTipNext = () => {
  392. showSwiperTip.value = false;
  393. guideShow.value = true;
  394. }
  395. const handleGuideClose = () => {
  396. userStore.isExamGuideShow = true;
  397. startTime();
  398. }
  399. const loadData = async () => {
  400. uni.$ie.showLoading();
  401. const { paperType } = prevData.value;
  402. if (paperType === EnumPaperType.PRACTICE || paperType === EnumPaperType.COURSE) {
  403. loadPracticeData();
  404. } else if (paperType === EnumPaperType.SIMULATED || paperType === EnumPaperType.TEST) {
  405. if (paperType === EnumPaperType.SIMULATED && userStore.isVHS) {
  406. loadVHSPaperData();
  407. } else {
  408. loadExamData();
  409. }
  410. }
  411. };
  412. onLoad(() => {
  413. console.log(prevData.value)
  414. loadData();
  415. uni.addInterceptor('navigateBack', {
  416. invoke: (e) => {
  417. if (confirmShowing.value) {
  418. return false;
  419. }
  420. if (confirmQuit.value) {
  421. return e;
  422. }
  423. handleLeftClick();
  424. return false;
  425. }
  426. })
  427. });
  428. onUnload(() => {
  429. uni.removeInterceptor('navigateBack');
  430. });
  431. </script>
  432. <style lang="scss" scoped></style>