import { EnumQuestionType } from "@/common/enum"; import { getPaper } from '@/api/modules/study'; import { Study } from "@/types"; import { Question } from "@/types/study"; export const useExam = () => { const questionTypeDesc: Record = { [EnumQuestionType.SINGLE_CHOICE]: '单选题', [EnumQuestionType.MULTIPLE_CHOICE]: '多选题', [EnumQuestionType.JUDGMENT]: '判断题', [EnumQuestionType.FILL_IN_THE_BLANK]: '填空题', [EnumQuestionType.SUBJECTIVE]: '主观题', [EnumQuestionType.SHORT_ANSWER]: '简答题', [EnumQuestionType.ESSAY]: '问答题', [EnumQuestionType.ANALYSIS]: '分析题', [EnumQuestionType.OTHER]: '阅读题' } // 题型顺序 const questionTypeOrder = [ EnumQuestionType.SINGLE_CHOICE, EnumQuestionType.MULTIPLE_CHOICE, EnumQuestionType.JUDGMENT, EnumQuestionType.FILL_IN_THE_BLANK, EnumQuestionType.SUBJECTIVE, EnumQuestionType.SHORT_ANSWER, EnumQuestionType.ESSAY, EnumQuestionType.ANALYSIS, EnumQuestionType.OTHER ]; let interval: NodeJS.Timeout | null = null; const countDownCallback = ref<() => void>(() => { }); // 练习时长 const practiceDuration = ref(0); const formatPracticeDuration = computed(() => { const hours = Math.floor(practiceDuration.value / 3600); const minutes = Math.floor((practiceDuration.value % 3600) / 60); const seconds = practiceDuration.value % 60; return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`; }); // 考试时长 const examDuration = ref(0); const formatExamDuration = computed(() => { const hours = Math.floor(examDuration.value / 3600); const minutes = Math.floor((examDuration.value % 3600) / 60); const seconds = examDuration.value % 60; return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`; }); const swiperDuration = ref(300); const questionList = ref([]); // 收藏列表 const favoriteList = ref([]); // 不会列表 const notKnowList = ref([]); // 重点标记列表 const markList = ref([]); const virtualCurrentIndex = ref(0); const virtualTotalCount = computed(() => { return questionList.value.reduce((acc, item) => { if (item.subQuestions && item.subQuestions.length > 0) { return acc + item.subQuestions.length; } return acc + 1; }, 0); }); const subQuestionIndex = ref(0); // 包含状态的问题列表 const stateQuestionList = computed(() => { function parseQuestion(qs: Study.Question, parentIndex: number) { if (qs.subQuestions && qs.subQuestions.length > 0) { qs.subQuestions.forEach((item, index) => { item.isSubQuestion = true; item.parentId = qs.id; item.parentTypeId = qs.typeId; item.subIndex = index; item.parentIndex = parentIndex; item.isDone = isDone(item); }); } else { qs.isSubQuestion = false; } return { ...qs, isDone: isDone(qs) } } console.log('重新计算') return questionList.value.map((item, index) => { return parseQuestion(item, index) }); }); const groupedQuestionList = computed(() => { // 状态:已做、未做、是否不会、是否标记,整体按照题型分组 const state = questionTypeOrder.map(type => { return { type, list: [] as { question: Study.Question; index: number }[] } }); let offset = 0; function addQuestion(qs: Study.Question, index: number) { if (qs.subQuestions && qs.subQuestions.length > 0) { qs.subQuestions.forEach((subQs, subIndex) => { offset++; addQuestion(subQs, index + subIndex); }); } else { let group; if (qs.isSubQuestion) { group = state.find(item => item.type === qs.parentTypeId); } else { group = state.find(item => item.type === qs.typeId); } if (group) { group.list.push({ question: qs, index }); } else { state.push({ type: qs.typeId, list: [{ question: qs, index }] }); } } } console.log(stateQuestionList.value.length) for (let i = 0; i <= stateQuestionList.value.length - 1; i++) { const qs = stateQuestionList.value[i]; addQuestion(qs, i + offset); } return state; }); const isAllDone = computed(() => { return questionList.value.every(q => isDone(q)); }); // 当前下标 const currentIndex = ref(0); const totalCount = computed(() => { return questionList.value.length; }); const doneCount = computed(() => { // 有答案的或者不会做的,都认为是做了 // return groupedQuestionList.value.reduce((acc, item) => acc + item.list.filter(q => q.question.isDone || q.question.isNotKnow).length, 0); // return stateQuestionList.value.filter(q => q.isDone || q.isNotKnow).length; let count = 0; for (let i = 0; i <= stateQuestionList.value.length - 1; i++) { const qs = stateQuestionList.value[i]; if (qs.isDone || qs.isNotKnow) { count++; } if (qs.subQuestions && qs.subQuestions.length > 0) { qs.subQuestions.forEach(subQs => { if (subQs.isDone || subQs.isNotKnow) { count++; } }); } } return count; }); const notDoneCount = computed(() => { return virtualTotalCount.value - doneCount.value; }); // 包含子题的题目计算整体做题进度 const calcProgress = (qs: Study.Question): number => { if (qs.subQuestions && qs.subQuestions.length > 0) { return qs.subQuestions.reduce((acc, q) => acc + calcProgress(q), 0) / qs.subQuestions.length; } return qs.isDone ? 100 : 0; } const isDone = (qs: Study.Question): boolean => { // if (qs.subQuestions && qs.subQuestions.length > 0) { // return qs.subQuestions.every(q => isDone(q)); // } return qs.answers && qs.answers.filter(item => !!item).length > 0 || !!qs.isNotKnow; } const isCorrect = (qs: Study.ExamineeQuestion): boolean => { const { answers, answer1, answer2, typeId } = qs; if (!answer1) { return false; } if ([EnumQuestionType.SINGLE_CHOICE, EnumQuestionType.JUDGMENT].includes(typeId)) { return answer1.includes(answers[0]); } else if ([EnumQuestionType.MULTIPLE_CHOICE].includes(typeId)) { return answers.length === answer1.length && answers.every(item => answer1.includes(item)); } return false; }; const nextQuestion = () => { if (currentIndex.value >= questionList.value.length - 1) { return; } currentIndex.value++; } const prevQuestion = () => { if (currentIndex.value <= 0) { return; } currentIndex.value--; } const nextQuestionQuickly = () => { if (currentIndex.value >= questionList.value.length - 1) { return; } swiperDuration.value = 0; setTimeout(() => { nextQuestion(); setTimeout(() => { swiperDuration.value = 300; }, 0); }, 0); } const prevQuestionQuickly = () => { if (currentIndex.value <= 0) { return; } swiperDuration.value = 0; setTimeout(() => { prevQuestion(); setTimeout(() => { swiperDuration.value = 300; }, 0); }, 0); } const changeIndex = (index: number) => { swiperDuration.value = 0; setTimeout(() => { currentIndex.value = index; setTimeout(() => { swiperDuration.value = 300; }, 0); }, 0); } // 开始计时 const startPracticeDuration = () => { interval = setInterval(() => { practiceDuration.value += 1; }, 1000); } // 停止计时 const stopPracticeDuration = () => { interval && clearInterval(interval); interval = null; } // 开始倒计时 const startExamDuration = () => { interval = setInterval(() => { if (examDuration.value <= 0) { console.log('停止倒计时') stopExamDuration(); return; } examDuration.value -= 1; }, 1000); } // 停止倒计时 const stopExamDuration = () => { interval && clearInterval(interval); interval = null; countDownCallback.value && countDownCallback.value(); } const setExamDuration = (duration: number) => { examDuration.value = duration; } const setPracticeDuration = (duration: number) => { practiceDuration.value = duration; } const setCountDownCallback = (callback: () => void) => { countDownCallback.value = callback; } const setDuration = (duration: number) => { examDuration.value = duration; practiceDuration.value = duration; } const setQuestionList = (list: Study.ExamineeQuestion[]) => { 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']; // 数据预处理 // 1、给每个项目补充额外字段 const transerQuestion = (item: Study.ExamineeQuestion): Study.Question => { return { ...item, // 处理没有题型的大题,统一作为阅读题 typeId: (item.typeId === null || item.typeId === undefined) ? EnumQuestionType.OTHER : item.typeId, answers: item.answers || [], subQuestions: item.subQuestions?.map(transerQuestion) || [], options: item.options?.map((option, index) => { return { name: option, no: orders[index], id: index, isAnswer: false } as Study.QuestionOption }) || [], isDone: false, isCorrect: isCorrect(item) } as Study.Question } const arr: Study.Question[] = list.map(item => { return transerQuestion(item); }); const qsList: Study.Question[] = []; const parseQuestion = (qs: Study.Question) => { if (qs.subQuestions && qs.subQuestions.length > 0) { qs.isSubQuestion = true; qs.isDone = isDone(qs); qs.subQuestions.forEach((item, index) => { item.parentId = qs.id; item.parentTypeId = qs.typeId; item.subIndex = index; // parseQuestion(item); }); } else { qsList.push({ ...qs, isSubQuestion: false, isDone: isDone(qs) }); } }; // arr.forEach(parseQuestion); // return qsList; questionList.value = arr; } const reset = () => { questionList.value = questionList.value.map(item => { return { ...item, answers: [], isMark: false, isNotKnow: false, subQuestions: item.subQuestions.map(subItem => { return { ...subItem, answers: [], isMark: false, isNotKnow: false } as Study.Question; }) } as Study.Question; }); console.log(questionList.value) changeIndex(0); practiceDuration.value = 0; examDuration.value = 0; interval && clearInterval(interval); interval = null; } const setSubQuestionIndex = (index: number) => { subQuestionIndex.value = index; } watch(() => currentIndex.value, (val) => { subQuestionIndex.value = 0; }, { immediate: false }); watch([() => currentIndex.value, () => subQuestionIndex.value], (val) => { virtualCurrentIndex.value = val[0] + val[1]; }, { immediate: false }); return { questionList, groupedQuestionList, stateQuestionList, favoriteList, notKnowList, markList, currentIndex, isAllDone, totalCount, virtualCurrentIndex, virtualTotalCount, subQuestionIndex, setSubQuestionIndex, doneCount, notDoneCount, questionTypeDesc, nextQuestion, prevQuestion, nextQuestionQuickly, prevQuestionQuickly, swiperDuration, practiceDuration, examDuration, formatExamDuration, formatPracticeDuration, startPracticeDuration, stopPracticeDuration, setDuration, startExamDuration, stopExamDuration, setExamDuration, setPracticeDuration, setCountDownCallback, setQuestionList, changeIndex, reset } }