useExam.ts 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664
  1. import { EnumQuestionType, EnumReviewMode } from "@/common/enum";
  2. import { getPaper } from '@/api/modules/study';
  3. import { Study } from "@/types";
  4. import { Question } from "@/types/study";
  5. /**
  6. * @description 解码 HTML 实体
  7. * 由于 uv-parse 的 decodeEntity 只支持有限的实体,需要手动解码音标等特殊实体
  8. * 使用手动映射表,兼容所有 uni-app 平台(包括小程序)
  9. */
  10. export const decodeHtmlEntities = (str: string): string => {
  11. if (!str) return str;
  12. // 音标和常用 HTML 实体映射表
  13. const entityMap: Record<string, string> = {
  14. // 音标相关 - 锐音符 (acute)
  15. 'aacute': 'á',
  16. 'eacute': 'é',
  17. 'iacute': 'í',
  18. 'oacute': 'ó',
  19. 'uacute': 'ú',
  20. // 音标相关 - 重音符 (grave)
  21. 'agrave': 'à',
  22. 'egrave': 'è',
  23. 'igrave': 'ì',
  24. 'ograve': 'ò',
  25. 'ugrave': 'ù',
  26. // 音标相关 - 扬抑符 (circumflex)
  27. 'acirc': 'â',
  28. 'ecirc': 'ê',
  29. 'icirc': 'î',
  30. 'ocirc': 'ô',
  31. 'ucirc': 'û',
  32. // 音标相关 - 分音符 (umlaut/diaeresis)
  33. 'auml': 'ä',
  34. 'euml': 'ë',
  35. 'iuml': 'ï',
  36. 'ouml': 'ö',
  37. 'uuml': 'ü',
  38. // 音标相关 - 波浪符 (tilde)
  39. 'ntilde': 'ñ',
  40. 'atilde': 'ã',
  41. 'otilde': 'õ',
  42. // 其他常用实体
  43. 'amp': '&',
  44. 'lt': '<',
  45. 'gt': '>',
  46. 'quot': '"',
  47. 'apos': "'",
  48. 'nbsp': '\u00A0',
  49. 'copy': '©',
  50. 'reg': '®',
  51. 'trade': '™',
  52. 'mdash': '—',
  53. 'ndash': '–',
  54. 'hellip': '…',
  55. // 数学符号
  56. 'times': '×',
  57. 'divide': '÷',
  58. 'plusmn': '±',
  59. };
  60. // 处理命名实体(如 &iacute;)
  61. // 使用 [a-z0-9] 以支持包含数字的实体名称
  62. let result = str.replace(/&([a-z0-9]+);/gi, (match, entity) => {
  63. const lowerEntity = entity.toLowerCase();
  64. if (entityMap[lowerEntity]) {
  65. return entityMap[lowerEntity];
  66. }
  67. return match; // 如果找不到映射,保持原样
  68. });
  69. // 处理数字实体(如 &#237; 或 &#xED;)
  70. result = result.replace(/&#(\d+);/g, (match, num) => {
  71. return String.fromCharCode(parseInt(num, 10));
  72. });
  73. result = result.replace(/&#x([0-9a-f]+);/gi, (match, hex) => {
  74. return String.fromCharCode(parseInt(hex, 16));
  75. });
  76. return result;
  77. }
  78. export const useExam = () => {
  79. const questionTypeDesc: Record<EnumQuestionType, string> = {
  80. [EnumQuestionType.SINGLE_CHOICE]: '单选题',
  81. [EnumQuestionType.MULTIPLE_CHOICE]: '多选题',
  82. [EnumQuestionType.JUDGMENT]: '判断题',
  83. [EnumQuestionType.FILL_IN_THE_BLANK]: '填空题',
  84. [EnumQuestionType.SUBJECTIVE]: '主观题',
  85. [EnumQuestionType.SHORT_ANSWER]: '简答题',
  86. [EnumQuestionType.ESSAY]: '问答题',
  87. [EnumQuestionType.ANALYSIS]: '分析题',
  88. [EnumQuestionType.OTHER]: '阅读题'
  89. }
  90. // 题型顺序
  91. const questionTypeOrder = [
  92. EnumQuestionType.SINGLE_CHOICE,
  93. EnumQuestionType.MULTIPLE_CHOICE,
  94. EnumQuestionType.JUDGMENT,
  95. EnumQuestionType.FILL_IN_THE_BLANK,
  96. EnumQuestionType.SUBJECTIVE,
  97. EnumQuestionType.SHORT_ANSWER,
  98. EnumQuestionType.ESSAY,
  99. EnumQuestionType.ANALYSIS,
  100. EnumQuestionType.OTHER
  101. ];
  102. let interval: NodeJS.Timeout | null = null;
  103. const countDownCallback = ref<() => void>(() => { });
  104. // 练习时长
  105. const practiceDuration = ref<number>(0);
  106. const formatPracticeDuration = computed(() => {
  107. const hours = Math.floor(practiceDuration.value / 3600);
  108. const minutes = Math.floor((practiceDuration.value % 3600) / 60);
  109. const seconds = practiceDuration.value % 60;
  110. if (hours > 0) {
  111. return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
  112. } else {
  113. return `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
  114. }
  115. });
  116. // 考试时长
  117. const examDuration = ref<number>(0);
  118. const formatExamDuration = computed(() => {
  119. const hours = Math.floor(examDuration.value / 3600);
  120. const minutes = Math.floor((examDuration.value % 3600) / 60);
  121. const seconds = examDuration.value % 60;
  122. return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
  123. });
  124. const swiperDuration = ref<number>(300);
  125. const questionList = ref<Study.Question[]>([]);
  126. // 练习设置
  127. const practiceSettings = ref<Study.PracticeSettings>({
  128. reviewMode: EnumReviewMode.AFTER_SUBMIT,
  129. autoNext: false
  130. });
  131. // 收藏列表
  132. const favoriteList = ref<Study.Question[]>([]);
  133. // 不会列表
  134. const notKnowList = ref<Study.Question[]>([]);
  135. // 重点标记列表
  136. const markList = ref<Study.Question[]>([]);
  137. /// 虚拟题目索引,包含子题
  138. const virtualCurrentIndex = ref<number>(0);
  139. /// 虚拟总题量,包含子题
  140. const virtualTotalCount = computed(() => {
  141. return questionList.value.reduce((acc, item) => {
  142. if (item.subQuestions && item.subQuestions.length > 0) {
  143. return acc + item.subQuestions.length;
  144. }
  145. return acc + 1;
  146. }, 0);
  147. });
  148. // 包含状态的问题列表
  149. const stateQuestionList = computed(() => {
  150. const parseQuestion = (qs: Study.Question) => {
  151. if (qs.subQuestions && qs.subQuestions.length > 0) {
  152. qs.isLeaf = false;
  153. qs.subQuestions.forEach((subQs, index) => {
  154. subQs.isDone = isDone(subQs);
  155. subQs.isCorrect = isQuestionCorrect(subQs);
  156. subQs.isNotAnswer = isQuestionNotAnswer(subQs);
  157. subQs.isLeaf = true;
  158. subQs.options.forEach(option => {
  159. option.isCorrect = isOptionCorrect(subQs, option);
  160. option.isSelected = isOptionSelected(subQs, option);
  161. option.isMissed = !option.isSelected && option.isCorrect;
  162. option.isIncorrect = !option.isCorrect && option.isSelected;
  163. });
  164. });
  165. } else {
  166. qs.isSubQuestion = false;
  167. qs.isDone = isDone(qs);
  168. qs.isCorrect = isQuestionCorrect(qs);
  169. qs.isNotAnswer = isQuestionNotAnswer(qs);
  170. qs.isLeaf = true;
  171. qs.options.forEach(option => {
  172. option.isCorrect = isOptionCorrect(qs, option);
  173. option.isSelected = isOptionSelected(qs, option);
  174. option.isMissed = !option.isSelected && option.isCorrect;
  175. option.isIncorrect = !option.isCorrect && option.isSelected;
  176. });
  177. }
  178. return qs;
  179. }
  180. return questionList.value.map((item, index) => {
  181. return parseQuestion(item)
  182. });
  183. });
  184. /// 扁平化题目列表,用于答题卡
  185. const flatQuestionList = computed(() => {
  186. return stateQuestionList.value.flatMap(item => {
  187. if (item.subQuestions && item.subQuestions.length > 0) {
  188. return item.subQuestions.flat();
  189. }
  190. return item;
  191. });
  192. });
  193. /// 按照题型分组,用于答题卡
  194. const groupedQuestionList = computed(() => {
  195. const state = questionTypeOrder.map(type => {
  196. return {
  197. type,
  198. list: [] as {
  199. question: Study.Question;
  200. index: number
  201. }[]
  202. }
  203. });
  204. flatQuestionList.value.forEach((qs, index) => {
  205. let group;
  206. if (qs.isSubQuestion) {
  207. group = state.find(item => item.type === qs.parentTypeId);
  208. } else {
  209. group = state.find(item => item.type === qs.typeId);
  210. }
  211. if (group) {
  212. group.list.push({
  213. question: qs,
  214. index
  215. });
  216. } else {
  217. state.push({
  218. type: qs.typeId,
  219. list: [{
  220. question: qs,
  221. index
  222. }]
  223. });
  224. }
  225. });
  226. return state;
  227. });
  228. /// 是否全部做完
  229. const isAllDone = computed(() => {
  230. return doneCount.value === virtualTotalCount.value;
  231. });
  232. // 当前下标
  233. const currentIndex = ref<number>(0);
  234. // 子题下标
  235. const subQuestionIndex = ref<number>(0);
  236. // 当前题目
  237. const currentQuestion = computed(() => {
  238. return questionList.value[currentIndex.value];
  239. });
  240. // 当前子题
  241. const currentSubQuestion = computed(() => {
  242. if (currentQuestion.value.subQuestions.length === 0) {
  243. return null;
  244. }
  245. if (subQuestionIndex.value >= currentQuestion.value.subQuestions.length) {
  246. return null;
  247. }
  248. return currentQuestion.value.subQuestions[subQuestionIndex.value];
  249. });
  250. /// 总题量,不区分子题,等同于接口返回的题目列表
  251. const totalCount = computed(() => {
  252. return questionList.value.length;
  253. });
  254. /// 已做题的数量 --> stateQuestionList
  255. const doneCount = computed(() => {
  256. // 有答案的或者不会做的,都认为是做了
  257. let count = 0;
  258. for (let i = 0; i <= stateQuestionList.value.length - 1; i++) {
  259. const qs = stateQuestionList.value[i];
  260. if (qs.subQuestions && qs.subQuestions.length > 0) {
  261. qs.subQuestions.forEach(subQs => {
  262. if (subQs.isDone) {
  263. count++;
  264. }
  265. });
  266. } else {
  267. if (qs.isDone) {
  268. count++;
  269. }
  270. }
  271. }
  272. return count;
  273. });
  274. /// 未做题的数量
  275. const notDoneCount = computed(() => {
  276. return virtualTotalCount.value - doneCount.value;
  277. });
  278. /// 包含子题的题目计算整体做题进度
  279. const calcProgress = (qs: Study.Question): number => {
  280. if (qs.subQuestions && qs.subQuestions.length > 0) {
  281. return qs.subQuestions.reduce((acc, q) => acc + calcProgress(q), 0) / qs.subQuestions.length;
  282. }
  283. return qs.isDone ? 100 : 0;
  284. }
  285. /// 题目是否做完
  286. const isDone = (qs: Study.Question): boolean => {
  287. if (qs.subQuestions && qs.subQuestions.length > 0) {
  288. return qs.subQuestions.every(q => isDone(q));
  289. }
  290. return (qs.answers && qs.answers.filter(item => !!item).length > 0) || !!qs.isNotKnow;
  291. }
  292. /// 题目是否正确
  293. const isQuestionCorrect = (qs: Study.Question): boolean => {
  294. let { answers, answer1, answer2, typeId } = qs;
  295. answers = answers?.filter(item => !!item) || [];
  296. answer1 = answer1 || '';
  297. answer2 = answer2 || '';
  298. if ([EnumQuestionType.SINGLE_CHOICE, EnumQuestionType.JUDGMENT].includes(typeId)) {
  299. return answer1.includes(answers[0]);
  300. } else if ([EnumQuestionType.MULTIPLE_CHOICE].includes(typeId)) {
  301. return answers.length === answer1.length && answers.every(item => answer1.includes(item));
  302. } else {
  303. // 主观题 A 对 B 错
  304. return answers.includes('A') && !answers.includes('B');
  305. }
  306. };
  307. /// 题目是否未作答
  308. const isQuestionNotAnswer = (qs: Study.Question): boolean => {
  309. if (qs.subQuestions && qs.subQuestions.length > 0) {
  310. return qs.subQuestions.every(q => isQuestionNotAnswer(q));
  311. }
  312. return !qs.answers || qs.answers.filter(item => !!item).length === 0;
  313. }
  314. /// 选项是否正确
  315. const isOptionCorrect = (question: Study.Question, option: Study.QuestionOption) => {
  316. const { answers, answer1, typeId } = question;
  317. if ([EnumQuestionType.SINGLE_CHOICE, EnumQuestionType.JUDGMENT].includes(typeId)) {
  318. return answer1?.includes(option.no);
  319. } else if ([EnumQuestionType.MULTIPLE_CHOICE].includes(typeId)) {
  320. return answer1?.includes(option.no);
  321. } else {
  322. return answers?.includes(option.no) && option.no === 'A';
  323. }
  324. }
  325. /// 选项是否选中
  326. const isOptionSelected = (question: Study.Question, option: Study.QuestionOption) => {
  327. return question.answers.includes(option.no);
  328. }
  329. // 是否可以切换上一题
  330. const prevEnable = computed(() => {
  331. return virtualCurrentIndex.value > 0;
  332. });
  333. // 是否可以切换下一题
  334. const nextEnable = computed(() => {
  335. if (currentQuestion.value) {
  336. if (currentQuestion.value.isSubQuestion) {
  337. console.log(5, subQuestionIndex.value < currentQuestion.value.subQuestions.length - 1)
  338. return subQuestionIndex.value < currentQuestion.value.subQuestions.length - 1;
  339. } else {
  340. if (currentQuestion.value.subQuestions && currentQuestion.value.subQuestions.length > 0) {
  341. console.log(subQuestionIndex.value, currentQuestion.value.subQuestions.length - 1)
  342. // 子题可以切换
  343. return subQuestionIndex.value < currentQuestion.value.subQuestions.length - 1;
  344. } else {
  345. // 大题可以切换
  346. return currentIndex.value < questionList.value.length - 1;
  347. }
  348. }
  349. }
  350. console.log(7, currentQuestion.value)
  351. return false;
  352. });
  353. // 下一题
  354. const nextQuestion = () => {
  355. if (!nextEnable.value) {
  356. return;
  357. }
  358. // if (currentIndex.value < questionList.value.length - 1) {
  359. // currentIndex.value++;
  360. // setTimeout(() => {
  361. // subQuestionIndex.value = 0;
  362. // }, 300);
  363. // }
  364. if (currentQuestion.value.subQuestions && currentQuestion.value.subQuestions.length > 0) {
  365. if (subQuestionIndex.value < currentQuestion.value.subQuestions.length - 1) {
  366. subQuestionIndex.value++;
  367. } else {
  368. currentIndex.value++;
  369. subQuestionIndex.value = 0;
  370. }
  371. } else {
  372. currentIndex.value++;
  373. }
  374. }
  375. // 上一题
  376. const prevQuestion = () => {
  377. if (!prevEnable.value) {
  378. return;
  379. }
  380. if (currentIndex.value > 0) {
  381. // currentIndex.value--;
  382. // setTimeout(() => {
  383. // subQuestionIndex.value = 0;
  384. // }, 300);
  385. }
  386. if (currentQuestion.value.subQuestions && currentQuestion.value.subQuestions.length > 0) {
  387. if (subQuestionIndex.value > 0) {
  388. subQuestionIndex.value--;
  389. } else {
  390. currentIndex.value--;
  391. }
  392. } else {
  393. if (currentIndex.value > 0) {
  394. currentIndex.value--;
  395. // 如果上一个题是子题,那么,默认选中最后一个子题
  396. const prevQuestion = questionList.value[currentIndex.value - 1];
  397. if (prevQuestion && prevQuestion.subQuestions && prevQuestion.subQuestions.length > 0) {
  398. subQuestionIndex.value = prevQuestion.subQuestions.length - 1;
  399. }
  400. }
  401. }
  402. }
  403. // 快速下一题
  404. const nextQuestionQuickly = () => {
  405. if (!nextEnable) {
  406. return;
  407. }
  408. swiperDuration.value = 0;
  409. setTimeout(() => {
  410. nextQuestion();
  411. setTimeout(() => {
  412. swiperDuration.value = 300;
  413. }, 0);
  414. }, 0);
  415. }
  416. // 快速上一题
  417. const prevQuestionQuickly = () => {
  418. if (!prevEnable.value) {
  419. return;
  420. }
  421. swiperDuration.value = 0;
  422. setTimeout(() => {
  423. prevQuestion();
  424. setTimeout(() => {
  425. swiperDuration.value = 300;
  426. }, 0);
  427. }, 0);
  428. }
  429. // 通过下标切换题目
  430. const changeIndex = (index: number, subIndex?: number) => {
  431. swiperDuration.value = 0;
  432. setTimeout(() => {
  433. currentIndex.value = index;
  434. if (!subIndex !== undefined) {
  435. subQuestionIndex.value = subIndex || 0;
  436. }
  437. setTimeout(() => {
  438. swiperDuration.value = 300;
  439. }, 0);
  440. }, 0);
  441. }
  442. const changeSubIndex = (index: number) => {
  443. subQuestionIndex.value = index;
  444. }
  445. // 开始计时
  446. const startTiming = () => {
  447. interval = setInterval(() => {
  448. practiceDuration.value += 1;
  449. }, 1000);
  450. }
  451. // 停止计时
  452. const stopTiming = () => {
  453. interval && clearInterval(interval);
  454. interval = null;
  455. }
  456. // 开始倒计时
  457. const startCountdown = () => {
  458. interval = setInterval(() => {
  459. if (examDuration.value <= 0) {
  460. console.log('停止倒计时')
  461. stopExamDuration();
  462. return;
  463. }
  464. examDuration.value -= 1;
  465. }, 1000);
  466. }
  467. // 停止倒计时
  468. const stopExamDuration = () => {
  469. interval && clearInterval(interval);
  470. interval = null;
  471. countDownCallback.value && countDownCallback.value();
  472. }
  473. const setExamDuration = (duration: number) => {
  474. examDuration.value = duration;
  475. }
  476. const setPracticeDuration = (duration: number) => {
  477. practiceDuration.value = duration;
  478. }
  479. const setCountDownCallback = (callback: () => void) => {
  480. countDownCallback.value = callback;
  481. }
  482. const setDuration = (duration: number) => {
  483. examDuration.value = duration;
  484. practiceDuration.value = duration;
  485. }
  486. /// 整理题目结构
  487. const transerQuestions = (arr: Study.Question[]) => {
  488. let offset = 0;
  489. return arr.map((item: Study.Question, index: number) => {
  490. const result = {
  491. ...item,
  492. index: index
  493. };
  494. // 如果有子节点,处理子节点并计算subIndex
  495. if (item.subQuestions && Array.isArray(item.subQuestions) && item.subQuestions.length > 0) {
  496. // 为当前节点设置offset
  497. result.offset = offset;
  498. result.subQuestions = item.subQuestions.map((child, childIndex) => ({
  499. ...child,
  500. subIndex: childIndex,
  501. isSubQuestion: true,
  502. parentId: item.id,
  503. parentTypeId: item.typeId,
  504. parentIndex: index,
  505. index: index,
  506. virtualIndex: index + result.offset + childIndex
  507. }));
  508. // 更新offset,累加当前节点的子节点数量
  509. offset += (item.subQuestions.length - 1);
  510. } else {
  511. // 如果没有子节点,设置offset为当前累计值
  512. result.offset = offset;
  513. result.isSubQuestion = false;
  514. result.virtualIndex = result.index + offset;
  515. }
  516. return result;
  517. });
  518. }
  519. // 将ExamineeQuestion转为Question
  520. const setQuestionList = (list: Study.ExamineeQuestion[]) => {
  521. 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'];
  522. // 数据预处理
  523. // 1、给每个项目补充额外字段
  524. const transerQuestion = (item: Study.ExamineeQuestion, index: number): Study.Question => {
  525. return {
  526. ...item,
  527. // 处理没有题型的大题,统一作为阅读题
  528. typeId: (item.typeId === null || item.typeId === undefined) ? EnumQuestionType.OTHER : item.typeId,
  529. answers: item.answers || [],
  530. subQuestions: item.subQuestions?.map(transerQuestion) || [],
  531. options: item.options?.map((option, index) => {
  532. // 移除选项编号(如 A.)并解码 HTML 实体(如 &iacute; → í)
  533. const cleanedOption = option.replace(/[A-Z]\./g, '').replace(/\s/g, ' ');
  534. return {
  535. name: decodeHtmlEntities(cleanedOption),
  536. no: orders[index],
  537. id: index,
  538. isAnswer: false,
  539. isCorrect: false,
  540. isSelected: false
  541. } as Study.QuestionOption
  542. }) || [],
  543. totalScore: item.totalScore || 0,
  544. offset: 0,
  545. index: index,
  546. virtualIndex: 0
  547. } as Study.Question
  548. }
  549. questionList.value = transerQuestions(list.map((item, index) => transerQuestion(item, index)));
  550. }
  551. /// 重置题目状态
  552. const reset = () => {
  553. questionList.value = questionList.value.map(item => {
  554. return {
  555. ...item,
  556. answers: [],
  557. isMark: false,
  558. isNotKnow: false,
  559. hasParsed: false,
  560. showParse: false,
  561. subQuestions: item.subQuestions.map(subItem => {
  562. return {
  563. ...subItem,
  564. answers: [],
  565. isMark: false,
  566. isNotKnow: false,
  567. hasParsed: false,
  568. showParse: false
  569. }
  570. }),
  571. options: item.options.map(option => {
  572. return {
  573. ...option,
  574. isAnswer: false,
  575. isCorrect: false,
  576. isSelected: false,
  577. isMissed: false,
  578. isIncorrect: false
  579. }
  580. })
  581. }
  582. });
  583. console.log(questionList.value)
  584. changeIndex(0);
  585. practiceDuration.value = 0;
  586. examDuration.value = 0;
  587. interval && clearInterval(interval);
  588. interval = null;
  589. }
  590. /// 设置子题下标
  591. const setSubQuestionIndex = (index: number) => {
  592. subQuestionIndex.value = index;
  593. }
  594. // 切换阅卷模式
  595. const setPracticeSettings = (settings: Study.PracticeSettings) => {
  596. practiceSettings.value = settings;
  597. }
  598. // watch(() => currentIndex.value, (val) => {
  599. // subQuestionIndex.value = 0;
  600. // }, {
  601. // immediate: false
  602. // });
  603. watch([() => currentIndex.value, () => subQuestionIndex.value], (val) => {
  604. console.log('currentIndex.value', currentIndex.value)
  605. console.log('subQuestionIndex.value', subQuestionIndex.value)
  606. const qs = questionList.value[val[0]];
  607. virtualCurrentIndex.value = qs.index + qs.offset + val[1];
  608. }, {
  609. immediate: false
  610. });
  611. return {
  612. practiceSettings,
  613. questionList,
  614. groupedQuestionList,
  615. stateQuestionList,
  616. flatQuestionList,
  617. favoriteList,
  618. notKnowList,
  619. markList,
  620. currentIndex,
  621. isAllDone,
  622. totalCount,
  623. virtualCurrentIndex,
  624. virtualTotalCount,
  625. subQuestionIndex,
  626. setSubQuestionIndex,
  627. doneCount,
  628. notDoneCount,
  629. questionTypeDesc,
  630. currentSubQuestion,
  631. prevEnable,
  632. nextEnable,
  633. nextQuestion,
  634. prevQuestion,
  635. nextQuestionQuickly,
  636. prevQuestionQuickly,
  637. swiperDuration,
  638. practiceDuration,
  639. examDuration,
  640. formatExamDuration,
  641. formatPracticeDuration,
  642. startTiming,
  643. stopTiming,
  644. setDuration,
  645. startCountdown,
  646. stopExamDuration,
  647. setExamDuration,
  648. setPracticeDuration,
  649. setCountDownCallback,
  650. setQuestionList,
  651. changeIndex,
  652. reset,
  653. isQuestionCorrect,
  654. isOptionCorrect,
  655. setPracticeSettings,
  656. }
  657. }