useExam.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405
  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. const virtualCurrentIndex = ref<number>(0);
  56. const virtualTotalCount = computed(() => {
  57. return questionList.value.reduce((acc, item) => {
  58. if (item.subQuestions && item.subQuestions.length > 0) {
  59. return acc + item.subQuestions.length;
  60. }
  61. return acc + 1;
  62. }, 0);
  63. });
  64. const subQuestionIndex = ref<number>(0);
  65. // 包含状态的问题列表
  66. const stateQuestionList = computed(() => {
  67. function parseQuestion(qs: Study.Question, parentIndex: number) {
  68. if (qs.subQuestions && qs.subQuestions.length > 0) {
  69. qs.subQuestions.forEach((item, index) => {
  70. item.isSubQuestion = true;
  71. item.parentId = qs.id;
  72. item.parentTypeId = qs.typeId;
  73. item.subIndex = index;
  74. item.parentIndex = parentIndex;
  75. item.isDone = isDone(item);
  76. });
  77. } else {
  78. qs.isSubQuestion = false;
  79. }
  80. return {
  81. ...qs,
  82. isDone: isDone(qs)
  83. }
  84. }
  85. console.log('重新计算')
  86. return questionList.value.map((item, index) => {
  87. return parseQuestion(item, index)
  88. });
  89. });
  90. const groupedQuestionList = computed(() => {
  91. // 状态:已做、未做、是否不会、是否标记,整体按照题型分组
  92. const state = questionTypeOrder.map(type => {
  93. return {
  94. type,
  95. list: [] as {
  96. question: Study.Question;
  97. index: number
  98. }[]
  99. }
  100. });
  101. let offset = 0;
  102. function addQuestion(qs: Study.Question, index: number) {
  103. if (qs.subQuestions && qs.subQuestions.length > 0) {
  104. qs.subQuestions.forEach((subQs, subIndex) => {
  105. offset++;
  106. addQuestion(subQs, index + subIndex);
  107. });
  108. } else {
  109. let group;
  110. if (qs.isSubQuestion) {
  111. group = state.find(item => item.type === qs.parentTypeId);
  112. } else {
  113. group = state.find(item => item.type === qs.typeId);
  114. }
  115. if (group) {
  116. group.list.push({
  117. question: qs,
  118. index
  119. });
  120. } else {
  121. state.push({
  122. type: qs.typeId,
  123. list: [{
  124. question: qs,
  125. index
  126. }]
  127. });
  128. }
  129. }
  130. }
  131. console.log(stateQuestionList.value.length)
  132. for (let i = 0; i <= stateQuestionList.value.length - 1; i++) {
  133. const qs = stateQuestionList.value[i];
  134. addQuestion(qs, i + offset);
  135. }
  136. return state;
  137. });
  138. const isAllDone = computed(() => {
  139. return questionList.value.every(q => isDone(q));
  140. });
  141. // 当前下标
  142. const currentIndex = ref<number>(0);
  143. const totalCount = computed(() => {
  144. return questionList.value.length;
  145. });
  146. const doneCount = computed(() => {
  147. // 有答案的或者不会做的,都认为是做了
  148. // return groupedQuestionList.value.reduce((acc, item) => acc + item.list.filter(q => q.question.isDone || q.question.isNotKnow).length, 0);
  149. // return stateQuestionList.value.filter(q => q.isDone || q.isNotKnow).length;
  150. let count = 0;
  151. for (let i = 0; i <= stateQuestionList.value.length - 1; i++) {
  152. const qs = stateQuestionList.value[i];
  153. if (qs.isDone || qs.isNotKnow) {
  154. count++;
  155. }
  156. if (qs.subQuestions && qs.subQuestions.length > 0) {
  157. qs.subQuestions.forEach(subQs => {
  158. if (subQs.isDone || subQs.isNotKnow) {
  159. count++;
  160. }
  161. });
  162. }
  163. }
  164. return count;
  165. });
  166. const notDoneCount = computed(() => {
  167. return virtualTotalCount.value - doneCount.value;
  168. });
  169. // 包含子题的题目计算整体做题进度
  170. const calcProgress = (qs: Study.Question): number => {
  171. if (qs.subQuestions && qs.subQuestions.length > 0) {
  172. return qs.subQuestions.reduce((acc, q) => acc + calcProgress(q), 0) / qs.subQuestions.length;
  173. }
  174. return qs.isDone ? 100 : 0;
  175. }
  176. const isDone = (qs: Study.Question): boolean => {
  177. // if (qs.subQuestions && qs.subQuestions.length > 0) {
  178. // return qs.subQuestions.every(q => isDone(q));
  179. // }
  180. return qs.answers && qs.answers.filter(item => !!item).length > 0 || !!qs.isNotKnow;
  181. }
  182. const isCorrect = (qs: Study.ExamineeQuestion): boolean => {
  183. const { answers, answer1, answer2, typeId } = qs;
  184. if (!answer1) {
  185. return false;
  186. }
  187. if ([EnumQuestionType.SINGLE_CHOICE, EnumQuestionType.JUDGMENT].includes(typeId)) {
  188. return answer1.includes(answers[0]);
  189. } else if ([EnumQuestionType.MULTIPLE_CHOICE].includes(typeId)) {
  190. return answers.length === answer1.length && answers.every(item => answer1.includes(item));
  191. }
  192. return false;
  193. };
  194. const nextQuestion = () => {
  195. if (currentIndex.value >= questionList.value.length - 1) {
  196. return;
  197. }
  198. currentIndex.value++;
  199. }
  200. const prevQuestion = () => {
  201. if (currentIndex.value <= 0) {
  202. return;
  203. }
  204. currentIndex.value--;
  205. }
  206. const nextQuestionQuickly = () => {
  207. if (currentIndex.value >= questionList.value.length - 1) {
  208. return;
  209. }
  210. swiperDuration.value = 0;
  211. setTimeout(() => {
  212. nextQuestion();
  213. setTimeout(() => {
  214. swiperDuration.value = 300;
  215. }, 0);
  216. }, 0);
  217. }
  218. const prevQuestionQuickly = () => {
  219. if (currentIndex.value <= 0) {
  220. return;
  221. }
  222. swiperDuration.value = 0;
  223. setTimeout(() => {
  224. prevQuestion();
  225. setTimeout(() => {
  226. swiperDuration.value = 300;
  227. }, 0);
  228. }, 0);
  229. }
  230. const changeIndex = (index: number) => {
  231. swiperDuration.value = 0;
  232. setTimeout(() => {
  233. currentIndex.value = index;
  234. setTimeout(() => {
  235. swiperDuration.value = 300;
  236. }, 0);
  237. }, 0);
  238. }
  239. // 开始计时
  240. const startPracticeDuration = () => {
  241. interval = setInterval(() => {
  242. practiceDuration.value += 1;
  243. }, 1000);
  244. }
  245. // 停止计时
  246. const stopPracticeDuration = () => {
  247. interval && clearInterval(interval);
  248. interval = null;
  249. }
  250. // 开始倒计时
  251. const startExamDuration = () => {
  252. interval = setInterval(() => {
  253. if (examDuration.value <= 0) {
  254. console.log('停止倒计时')
  255. stopExamDuration();
  256. return;
  257. }
  258. examDuration.value -= 1;
  259. }, 1000);
  260. }
  261. // 停止倒计时
  262. const stopExamDuration = () => {
  263. interval && clearInterval(interval);
  264. interval = null;
  265. countDownCallback.value && countDownCallback.value();
  266. }
  267. const setExamDuration = (duration: number) => {
  268. examDuration.value = duration;
  269. }
  270. const setPracticeDuration = (duration: number) => {
  271. practiceDuration.value = duration;
  272. }
  273. const setCountDownCallback = (callback: () => void) => {
  274. countDownCallback.value = callback;
  275. }
  276. const setDuration = (duration: number) => {
  277. examDuration.value = duration;
  278. practiceDuration.value = duration;
  279. }
  280. const setQuestionList = (list: Study.ExamineeQuestion[]) => {
  281. 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'];
  282. // 数据预处理
  283. // 1、给每个项目补充额外字段
  284. const transerQuestion = (item: Study.ExamineeQuestion): Study.Question => {
  285. return {
  286. ...item,
  287. // 处理没有题型的大题,统一作为阅读题
  288. typeId: (item.typeId === null || item.typeId === undefined) ? EnumQuestionType.OTHER : item.typeId,
  289. answers: item.answers || [],
  290. subQuestions: item.subQuestions?.map(transerQuestion) || [],
  291. options: item.options?.map((option, index) => {
  292. return {
  293. name: option,
  294. no: orders[index],
  295. id: index,
  296. isAnswer: false
  297. } as Study.QuestionOption
  298. }) || [],
  299. isDone: false,
  300. isCorrect: isCorrect(item)
  301. } as Study.Question
  302. }
  303. const arr: Study.Question[] = list.map(item => {
  304. return transerQuestion(item);
  305. });
  306. const qsList: Study.Question[] = [];
  307. const parseQuestion = (qs: Study.Question) => {
  308. if (qs.subQuestions && qs.subQuestions.length > 0) {
  309. qs.isSubQuestion = true;
  310. qs.isDone = isDone(qs);
  311. qs.subQuestions.forEach((item, index) => {
  312. item.parentId = qs.id;
  313. item.parentTypeId = qs.typeId;
  314. item.subIndex = index;
  315. // parseQuestion(item);
  316. });
  317. } else {
  318. qsList.push({
  319. ...qs,
  320. isSubQuestion: false,
  321. isDone: isDone(qs)
  322. });
  323. }
  324. };
  325. // arr.forEach(parseQuestion);
  326. // return qsList;
  327. questionList.value = arr;
  328. }
  329. const reset = () => {
  330. questionList.value = questionList.value.map(item => {
  331. return {
  332. ...item,
  333. answers: [],
  334. isMark: false,
  335. isNotKnow: false,
  336. subQuestions: item.subQuestions.map(subItem => {
  337. return {
  338. ...subItem,
  339. answers: [],
  340. isMark: false,
  341. isNotKnow: false
  342. } as Study.Question;
  343. })
  344. } as Study.Question;
  345. });
  346. console.log(questionList.value)
  347. changeIndex(0);
  348. practiceDuration.value = 0;
  349. examDuration.value = 0;
  350. interval && clearInterval(interval);
  351. interval = null;
  352. }
  353. const setSubQuestionIndex = (index: number) => {
  354. subQuestionIndex.value = index;
  355. }
  356. watch(() => currentIndex.value, (val) => {
  357. subQuestionIndex.value = 0;
  358. }, {
  359. immediate: false
  360. });
  361. watch([() => currentIndex.value, () => subQuestionIndex.value], (val) => {
  362. virtualCurrentIndex.value = val[0] + val[1];
  363. }, {
  364. immediate: false
  365. });
  366. return {
  367. questionList,
  368. groupedQuestionList,
  369. stateQuestionList,
  370. favoriteList,
  371. notKnowList,
  372. markList,
  373. currentIndex,
  374. isAllDone,
  375. totalCount,
  376. virtualCurrentIndex,
  377. virtualTotalCount,
  378. subQuestionIndex,
  379. setSubQuestionIndex,
  380. doneCount,
  381. notDoneCount,
  382. questionTypeDesc,
  383. nextQuestion,
  384. prevQuestion,
  385. nextQuestionQuickly,
  386. prevQuestionQuickly,
  387. swiperDuration,
  388. practiceDuration,
  389. examDuration,
  390. formatExamDuration,
  391. formatPracticeDuration,
  392. startPracticeDuration,
  393. stopPracticeDuration,
  394. setDuration,
  395. startExamDuration,
  396. stopExamDuration,
  397. setExamDuration,
  398. setPracticeDuration,
  399. setCountDownCallback,
  400. setQuestionList,
  401. changeIndex,
  402. reset
  403. }
  404. }