useExam.ts 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. import { EnumQuestionType } from "@/common/enum";
  2. import { getPaper } from '@/api/modules/study';
  3. import { Study } from "@/types";
  4. import { Question } from "@/types/study";
  5. export const useExam = () => {
  6. const questionTypeDesc: Record<EnumQuestionType, string> = {
  7. [EnumQuestionType.SINGLE_CHOICE]: '单选题',
  8. [EnumQuestionType.MULTIPLE_CHOICE]: '多选题',
  9. [EnumQuestionType.JUDGMENT]: '判断题',
  10. [EnumQuestionType.FILL_IN_THE_BLANK]: '填空题',
  11. [EnumQuestionType.SUBJECTIVE]: '主观题',
  12. [EnumQuestionType.SHORT_ANSWER]: '简答题',
  13. [EnumQuestionType.ESSAY]: '问答题',
  14. [EnumQuestionType.ANALYSIS]: '分析题',
  15. [EnumQuestionType.OTHER]: '阅读题'
  16. }
  17. // 题型顺序
  18. const questionTypeOrder = [
  19. EnumQuestionType.SINGLE_CHOICE,
  20. EnumQuestionType.MULTIPLE_CHOICE,
  21. EnumQuestionType.JUDGMENT,
  22. EnumQuestionType.FILL_IN_THE_BLANK,
  23. EnumQuestionType.SUBJECTIVE,
  24. EnumQuestionType.SHORT_ANSWER,
  25. EnumQuestionType.ESSAY,
  26. EnumQuestionType.ANALYSIS,
  27. EnumQuestionType.OTHER
  28. ];
  29. let interval: NodeJS.Timeout | null = null;
  30. const countDownCallback = ref<() => void>(() => { });
  31. // 练习时长
  32. const practiceDuration = ref<number>(0);
  33. const formatPracticeDuration = computed(() => {
  34. const hours = Math.floor(practiceDuration.value / 3600);
  35. const minutes = Math.floor((practiceDuration.value % 3600) / 60);
  36. const seconds = practiceDuration.value % 60;
  37. return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
  38. });
  39. // 考试时长
  40. const examDuration = ref<number>(0);
  41. const formatExamDuration = computed(() => {
  42. const hours = Math.floor(examDuration.value / 3600);
  43. const minutes = Math.floor((examDuration.value % 3600) / 60);
  44. const seconds = examDuration.value % 60;
  45. return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
  46. });
  47. const swiperDuration = ref<number>(300);
  48. const questionList = ref<Study.Question[]>([]);
  49. // 收藏列表
  50. const favoriteList = ref<Study.Question[]>([]);
  51. // 不会列表
  52. const notKnowList = ref<Study.Question[]>([]);
  53. // 重点标记列表
  54. const markList = ref<Study.Question[]>([]);
  55. // 包含状态的问题列表
  56. const stateQuestionList = computed(() => {
  57. return questionList.value.map(item => {
  58. return {
  59. ...item,
  60. isDone: isDone(item)
  61. }
  62. });
  63. });
  64. const groupedQuestionList = computed(() => {
  65. // 状态:已做、未做、是否不会、是否标记,整体按照题型分组
  66. const state = questionTypeOrder.map(type => {
  67. return {
  68. type,
  69. list: [] as {
  70. question: Study.Question;
  71. index: number
  72. }[]
  73. }
  74. });
  75. for (let i = 0; i <= questionList.value.length - 1; i++) {
  76. const qs = questionList.value[i];
  77. state.forEach(item => {
  78. if (qs.typeId === item.type) {
  79. qs.isDone = isDone(qs);
  80. item.list.push({
  81. question: qs,
  82. index: i
  83. });
  84. }
  85. });
  86. }
  87. return state;
  88. });
  89. const isAllDone = computed(() => {
  90. return questionList.value.every(q => isDone(q));
  91. });
  92. // 当前下标
  93. const currentIndex = ref<number>(0);
  94. const totalCount = computed(() => {
  95. return questionList.value.length;
  96. });
  97. const doneCount = computed(() => {
  98. // 有答案的或者不会做的,都认为是做了
  99. // return groupedQuestionList.value.reduce((acc, item) => acc + item.list.filter(q => q.question.isDone || q.question.isNotKnow).length, 0);
  100. return stateQuestionList.value.filter(q => q.isDone || q.isNotKnow).length;
  101. });
  102. const notDoneCount = computed(() => {
  103. // return questionList.value.length - doneCount.value;
  104. return stateQuestionList.value.length - doneCount.value;
  105. });
  106. const notKnowCount = computed(() => {
  107. // return groupedQuestionList.value.reduce((acc, item) => acc + item.list.filter(q => q.question.isNotKnow).length, 0);
  108. return stateQuestionList.value.filter(q => q.isNotKnow).length;
  109. });
  110. const markCount = computed(() => {
  111. // return groupedQuestionList.value.reduce((acc, item) => acc + item.list.filter(q => q.question.isMark).length, 0);
  112. return stateQuestionList.value.filter(q => q.isMark).length;
  113. });
  114. const isDone = (qs: Study.Question): boolean => {
  115. if (qs.subQuestions && qs.subQuestions.length > 0) {
  116. return qs.subQuestions.every(q => isDone(q));
  117. }
  118. return qs.answers && qs.answers.length > 0 || !!qs.isNotKnow;
  119. }
  120. const nextQuestion = () => {
  121. if (currentIndex.value >= questionList.value.length - 1) {
  122. return;
  123. }
  124. currentIndex.value++;
  125. }
  126. const prevQuestion = () => {
  127. if (currentIndex.value <= 0) {
  128. return;
  129. }
  130. currentIndex.value--;
  131. }
  132. const nextQuestionQuickly = () => {
  133. if (currentIndex.value >= questionList.value.length - 1) {
  134. return;
  135. }
  136. swiperDuration.value = 0;
  137. setTimeout(() => {
  138. nextQuestion();
  139. setTimeout(() => {
  140. swiperDuration.value = 300;
  141. }, 0);
  142. }, 0);
  143. }
  144. const prevQuestionQuickly = () => {
  145. if (currentIndex.value <= 0) {
  146. return;
  147. }
  148. swiperDuration.value = 0;
  149. setTimeout(() => {
  150. prevQuestion();
  151. setTimeout(() => {
  152. swiperDuration.value = 300;
  153. }, 0);
  154. }, 0);
  155. }
  156. const changeIndex = (index: number) => {
  157. swiperDuration.value = 0;
  158. setTimeout(() => {
  159. currentIndex.value = index;
  160. setTimeout(() => {
  161. swiperDuration.value = 300;
  162. }, 0);
  163. }, 0);
  164. }
  165. // 开始计时
  166. const startPracticeDuration = () => {
  167. interval = setInterval(() => {
  168. practiceDuration.value += 1;
  169. }, 1000);
  170. }
  171. // 停止计时
  172. const stopPracticeDuration = () => {
  173. interval && clearInterval(interval);
  174. interval = null;
  175. }
  176. // 开始倒计时
  177. const startExamDuration = () => {
  178. interval = setInterval(() => {
  179. if (examDuration.value <= 0) {
  180. console.log('停止倒计时')
  181. stopExamDuration();
  182. return;
  183. }
  184. examDuration.value -= 1;
  185. }, 1000);
  186. }
  187. // 停止倒计时
  188. const stopExamDuration = () => {
  189. interval && clearInterval(interval);
  190. interval = null;
  191. countDownCallback.value && countDownCallback.value();
  192. }
  193. const setExamDuration = (duration: number) => {
  194. examDuration.value = duration;
  195. }
  196. const setPracticeDuration = (duration: number) => {
  197. practiceDuration.value = duration;
  198. }
  199. const setCountDownCallback = (callback: () => void) => {
  200. countDownCallback.value = callback;
  201. }
  202. const setDuration = (duration: number) => {
  203. examDuration.value = duration;
  204. practiceDuration.value = duration;
  205. }
  206. const setQuestionList = (list: Study.ApiQuestion[]) => {
  207. const orders = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];
  208. // 数据预处理
  209. // 1、给每个项目补充额外字段
  210. const parseQuestion = (item: Study.ApiQuestion): Study.Question => {
  211. return {
  212. title: item.title,
  213. // 处理没有题型的大题,统一作为阅读题
  214. typeId: (item.typeId === null || item.typeId === undefined) ? EnumQuestionType.OTHER : item.typeId,
  215. id: item.id,
  216. answers: item.answers || [],
  217. subQuestions: item.subQuestions?.map(parseQuestion) || [],
  218. options: item.options?.map((option, index) => {
  219. return {
  220. name: option,
  221. no: orders[index],
  222. id: index,
  223. isAnswer: false
  224. } as Study.QuestionOption
  225. }) || [],
  226. isDone: false,
  227. isMark: item.isMark,
  228. isNotKnow: item.isNotKnow,
  229. isFavorite: item.isFavorite
  230. } as Study.Question
  231. }
  232. const arr: Study.Question[] = list.map(item => {
  233. return parseQuestion(item);
  234. });
  235. questionList.value = arr;
  236. }
  237. const reset = () => {
  238. questionList.value = questionList.value.map(item => {
  239. return {
  240. ...item,
  241. answers: [],
  242. isMark: false,
  243. isNotKnow: false
  244. } as Study.Question;
  245. });
  246. changeIndex(0);
  247. practiceDuration.value = 0;
  248. examDuration.value = 0;
  249. interval && clearInterval(interval);
  250. interval = null;
  251. }
  252. return {
  253. questionList,
  254. groupedQuestionList,
  255. stateQuestionList,
  256. favoriteList,
  257. notKnowList,
  258. markList,
  259. currentIndex,
  260. isAllDone,
  261. totalCount,
  262. doneCount,
  263. notDoneCount,
  264. notKnowCount,
  265. markCount,
  266. questionTypeDesc,
  267. nextQuestion,
  268. prevQuestion,
  269. nextQuestionQuickly,
  270. prevQuestionQuickly,
  271. swiperDuration,
  272. practiceDuration,
  273. examDuration,
  274. formatExamDuration,
  275. formatPracticeDuration,
  276. startPracticeDuration,
  277. stopPracticeDuration,
  278. setDuration,
  279. startExamDuration,
  280. stopExamDuration,
  281. setExamDuration,
  282. setPracticeDuration,
  283. setCountDownCallback,
  284. setQuestionList,
  285. changeIndex,
  286. reset
  287. }
  288. }