exam-start.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376
  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. * 自动提交
  75. */
  76. const autoSubmit = () => {
  77. if (hasShowSubmitConfirm.value) {
  78. return;
  79. }
  80. hasShowSubmitConfirm.value = true;
  81. beforeSubmit();
  82. }
  83. provide(EXAM_PAGE_OPTIONS, prevData.value);
  84. provide(EXAM_DATA, examData);
  85. provide(EXAM_AUTO_SUBMIT, autoSubmit);
  86. const isExam = computed(() => {
  87. // prevData.value
  88. return prevData.value.paperType === EnumPaperType.SIMULATED || prevData.value.paperType === EnumPaperType.TEST;
  89. });
  90. const isReadOnly = computed(() => {
  91. return prevData.value.readonly || false;
  92. });
  93. const handleLeftClick = () => {
  94. if (!isReady.value || isReadOnly.value) {
  95. transferBack();
  96. return;
  97. }
  98. beforeQuit();
  99. };
  100. const examModeRef = ref();
  101. const handleRightClick = () => {
  102. examModeRef.value.open();
  103. }
  104. const beforeQuit = () => {
  105. const { paperType } = prevData.value;
  106. if (!isReady.value || isReadOnly.value) {
  107. return;
  108. }
  109. stopTime();
  110. const msg = paperType === EnumPaperType.PRACTICE ? '当前练习未完成,确认退出?' : '当前考试未完成,确认退出?';
  111. uni.$ie.showModal({
  112. title: '提示',
  113. content: msg,
  114. }).then(confirm => {
  115. if (confirm) {
  116. handleSubmit(true);
  117. } else {
  118. startTime();
  119. }
  120. });
  121. };
  122. const startTime = () => {
  123. startTiming();
  124. }
  125. const stopTime = () => {
  126. stopTiming();
  127. }
  128. const beforeSubmit = () => {
  129. const text = notDoneCount.value > 0 ? `还有${notDoneCount.value}题未做,确认交卷?` : '是否确认交卷?';
  130. stopTime();
  131. uni.$ie.showModal({
  132. title: '提示',
  133. content: text,
  134. }).then(confirm => {
  135. if (confirm) {
  136. handleSubmit(false);
  137. } else {
  138. startTime();
  139. }
  140. });
  141. }
  142. /**
  143. * 提交试卷
  144. * @param tempSave 是否临时保存
  145. */
  146. const handleSubmit = (tempSave: boolean = false) => {
  147. // 执行完后续逻辑
  148. submit();
  149. const msg = tempSave ? '保存中...' : '提交中...';
  150. uni.$ie.showLoading(msg);
  151. setTimeout(() => {
  152. const params = {
  153. ...paperData.value,
  154. questions: questionList.value.map(item => {
  155. return {
  156. ...item,
  157. title: '',
  158. isDone: tempSave ? item.isDone : true,
  159. subQuestions: item.subQuestions.map(subItem => {
  160. return {
  161. ...subItem,
  162. title: '',
  163. };
  164. })
  165. };
  166. }),
  167. examineeId: examineeId.value,
  168. // examineeId: examineerData.value.examineeId,
  169. isDone: tempSave ? isAllDone.value : true,
  170. duration: practiceDuration.value
  171. } as Study.ExamPaperSubmit;
  172. console.log('提交试卷参数', params)
  173. commitExamineePaper(params);
  174. if (isExam.value) {
  175. if (!tempSave) {
  176. setTimeout(async () => {
  177. uni.$ie.hideLoading();
  178. await nextTick();
  179. transferTo('/pagesStudy/pages/simulation-analysis/simulation-analysis', {
  180. data: {
  181. examineeId: examineeId.value
  182. },
  183. type: 'redirectTo'
  184. });
  185. }, 2500);
  186. } else {
  187. uni.$ie.hideLoading();
  188. nextTick(() => {
  189. transferBack();
  190. });
  191. }
  192. } else {
  193. if (!tempSave) {
  194. setTimeout(async () => {
  195. uni.$ie.hideLoading();
  196. await nextTick();
  197. transferTo('/pagesStudy/pages/knowledge-practice-detail/knowledge-practice-detail', {
  198. data: {
  199. paperType: prevData.value.paperType,
  200. examineeId: examineeId.value,
  201. name: prevData.value.practiceInfo?.name,
  202. directed: prevData.value.practiceInfo?.directed
  203. } as Transfer.PracticeResultPageOptions,
  204. type: 'redirectTo'
  205. });
  206. }, 2500);
  207. } else {
  208. uni.$ie.hideLoading();
  209. nextTick(() => {
  210. transferBack();
  211. });
  212. }
  213. }
  214. }, 1000);
  215. }
  216. /**
  217. * 恢复上次做题历史数据
  218. * @param savedQuestion 上次做题历史数据
  219. * @param fullQuestion 当前试卷数据
  220. */
  221. const restoreQuestion = (savedQuestion: Study.ExamineeQuestion[], fullQuestion: Study.ExamineeQuestion[]) => {
  222. if (!savedQuestion) {
  223. return fullQuestion;
  224. }
  225. for (let index = 0; index < fullQuestion.length; index++) {
  226. const item = fullQuestion[index];
  227. const savedQs = savedQuestion[index]
  228. if (savedQs) {
  229. if (savedQs.answers) {
  230. item.answers = savedQs.answers.filter(ans => ans.trim());
  231. }
  232. item.answer1 = savedQs.answer1;
  233. item.answer2 = savedQs.answer2;
  234. item.isMark = savedQs.isMark;
  235. item.isFavorite = savedQs.isFavorite;
  236. item.isNotKnow = savedQs.isNotKnow;
  237. item.parse = savedQs.parse;
  238. item.totalScore = savedQs.totalScore;
  239. if (item.subQuestions) {
  240. restoreQuestion(savedQs.subQuestions, item.subQuestions);
  241. }
  242. }
  243. }
  244. return fullQuestion;
  245. }
  246. const loadPracticeData = async () => {
  247. const { paperType, readonly, practiceInfo } = prevData.value;
  248. let data: Study.Examinee | null = null;
  249. if (readonly) {
  250. if (practiceInfo?.examineeId) {
  251. const res = await getExamineeResult(practiceInfo.examineeId);
  252. data = res.data;
  253. }
  254. } else {
  255. const res = await getOpenExaminee({
  256. paperType: paperType,
  257. relateId: practiceInfo?.relateId,
  258. directed: practiceInfo?.directed || false
  259. });
  260. data = res.data || {};
  261. }
  262. if (!data) {
  263. uni.$ie.hideLoading();
  264. transferBack();
  265. return;
  266. }
  267. // 练习没有规定时间,设置为最大值
  268. totalExamTime.value = Number.MAX_SAFE_INTEGER;
  269. combinePaperData(data, paperType);
  270. }
  271. const loadSimulationData = async () => {
  272. const { paperType, readonly, simulationInfo } = prevData.value;
  273. let data: Study.Examinee;
  274. if (simulationInfo?.examineeId) {
  275. if (readonly) {
  276. const res = await getExamineeResult(simulationInfo.examineeId);
  277. data = res.data;
  278. } else {
  279. const res = await beginExaminee(simulationInfo.examineeId);
  280. data = res.data || {};
  281. }
  282. if (!data) {
  283. uni.$ie.hideLoading();
  284. transferBack();
  285. return;
  286. }
  287. totalExamTime.value = data.paperInfo?.time || 0;
  288. combinePaperData(data, paperType);
  289. }
  290. }
  291. const combinePaperData = async (examinee: Study.Examinee, paperType: EnumPaperType) => {
  292. examineeId.value = examinee.examineeId;
  293. if (examinee.paperId) {
  294. const res = await getPaper({
  295. type: paperType,
  296. id: examinee.paperId
  297. });
  298. paperData.value = res.data;
  299. paperData.value.questions = restoreQuestion(examinee.questions, res.data.questions);
  300. console.log('初始化数据', paperData.value.questions)
  301. setQuestionList(paperData.value.questions);
  302. setDuration(examinee.duration || 0);
  303. await nextTick();
  304. const targetQuestion = flatQuestionList.value.find(item => item.id === prevData.value.questionId);
  305. if (targetQuestion) {
  306. changeIndex(targetQuestion.index);
  307. } else {
  308. changeIndex(0);
  309. }
  310. setTimeout(() => {
  311. if (targetQuestion?.isSubQuestion) {
  312. setSubQuestionIndex(targetQuestion.subIndex || 0);
  313. }
  314. }, 50);
  315. await new Promise(resolve => setTimeout(resolve, 50));
  316. await nextTick();
  317. // 读取用户练习设置
  318. // setPracticeSettings(userStore.practiceSettings);
  319. isReady.value = true;
  320. console.log('试卷信息', res)
  321. if (!userStore.isExamGuideShow) {
  322. setTimeout(() => {
  323. uni.$ie.hideLoading();
  324. setTimeout(() => {
  325. showSwiperTip.value = true;
  326. }, 100);
  327. }, 300);
  328. } else {
  329. uni.$ie.hideLoading();
  330. if (!isReadOnly.value) {
  331. startTime();
  332. }
  333. }
  334. }
  335. }
  336. const handleSwiperTipNext = () => {
  337. showSwiperTip.value = false;
  338. guideShow.value = true;
  339. }
  340. const handleGuideClose = () => {
  341. userStore.isExamGuideShow = true;
  342. startTime();
  343. }
  344. const loadData = async () => {
  345. uni.$ie.showLoading();
  346. const { paperType } = prevData.value;
  347. if (paperType === EnumPaperType.PRACTICE || paperType === EnumPaperType.COURSE || paperType === EnumPaperType.TEST) {
  348. loadPracticeData();
  349. } else if (paperType === EnumPaperType.SIMULATED) {
  350. loadSimulationData();
  351. }
  352. };
  353. onLoad(() => {
  354. console.log(prevData.value)
  355. loadData();
  356. });
  357. </script>
  358. <style lang="scss" scoped></style>