|
|
@@ -107,7 +107,13 @@ export const useExam = () => {
|
|
|
EnumQuestionType.OTHER
|
|
|
];
|
|
|
let interval: NodeJS.Timeout | null = null;
|
|
|
+ let animationFrameId: number | null = null; // requestAnimationFrame 的 ID
|
|
|
+ let countStart: number = 0;
|
|
|
+ let countTime: number = 0;
|
|
|
const countDownCallback = ref<() => void>(() => { });
|
|
|
+ // 练习计时相关变量
|
|
|
+ let practiceStartTime: number = 0; // 练习开始时间戳(毫秒)
|
|
|
+ let practiceAccumulatedTime: number = 0; // 累计的练习时间(秒)
|
|
|
// 练习时长
|
|
|
const practiceDuration = ref<number>(0);
|
|
|
const formatPracticeDuration = computed(() => {
|
|
|
@@ -252,7 +258,8 @@ export const useExam = () => {
|
|
|
if (subQuestionIndex.value >= currentQuestion.value.subQuestions.length) {
|
|
|
return null;
|
|
|
}
|
|
|
- return currentQuestion.value.subQuestions[subQuestionIndex.value];
|
|
|
+ // return currentQuestion.value.subQuestions[subQuestionIndex.value];
|
|
|
+ return currentQuestion.value.subQuestions[currentQuestion.value.activeSubIndex];
|
|
|
});
|
|
|
/// 总题量,不区分子题,等同于接口返回的题目列表
|
|
|
const totalCount = computed(() => {
|
|
|
@@ -282,6 +289,20 @@ export const useExam = () => {
|
|
|
const notDoneCount = computed(() => {
|
|
|
return virtualTotalCount.value - doneCount.value;
|
|
|
});
|
|
|
+ watch(() => virtualCurrentIndex.value, (newVal, oldVal) => {
|
|
|
+ updateQuestionDuration(oldVal);
|
|
|
+ });
|
|
|
+ // 更新单个题目做题时间
|
|
|
+ const updateQuestionDuration = (index: number, continueCount = true) => {
|
|
|
+ const question = flatQuestionList.value[index];
|
|
|
+ const time = stopCount();
|
|
|
+ question.duration += time;
|
|
|
+ // 每次结算后都清空累计时长,避免多次提交或多次 stop 导致重复累加
|
|
|
+ clearCount();
|
|
|
+ if (continueCount) {
|
|
|
+ startCount();
|
|
|
+ }
|
|
|
+ }
|
|
|
/// 包含子题的题目计算整体做题进度
|
|
|
const calcProgress = (qs: Study.Question): number => {
|
|
|
if (qs.subQuestions && qs.subQuestions.length > 0) {
|
|
|
@@ -335,46 +356,19 @@ export const useExam = () => {
|
|
|
}
|
|
|
// 是否可以切换上一题
|
|
|
const prevEnable = computed(() => {
|
|
|
- return virtualCurrentIndex.value > 0;
|
|
|
+ return currentIndex.value > 0 || currentQuestion.value.activeSubIndex > 0;
|
|
|
});
|
|
|
// 是否可以切换下一题
|
|
|
const nextEnable = computed(() => {
|
|
|
- if (currentQuestion.value) {
|
|
|
- if (currentQuestion.value.isSubQuestion) {
|
|
|
- console.log(5, subQuestionIndex.value < currentQuestion.value.subQuestions.length - 1)
|
|
|
- return subQuestionIndex.value < currentQuestion.value.subQuestions.length - 1;
|
|
|
- } else {
|
|
|
- if (currentQuestion.value.subQuestions && currentQuestion.value.subQuestions.length > 0) {
|
|
|
- console.log(subQuestionIndex.value, currentQuestion.value.subQuestions.length - 1)
|
|
|
- // 子题可以切换
|
|
|
- return subQuestionIndex.value < currentQuestion.value.subQuestions.length - 1;
|
|
|
- } else {
|
|
|
- // 大题可以切换
|
|
|
- return currentIndex.value < questionList.value.length - 1;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- console.log(7, currentQuestion.value)
|
|
|
- return false;
|
|
|
+ return currentIndex.value < questionList.value.length - 1 || currentQuestion.value.activeSubIndex < currentQuestion.value.subQuestions.length - 1;
|
|
|
});
|
|
|
// 下一题
|
|
|
const nextQuestion = () => {
|
|
|
if (!nextEnable.value) {
|
|
|
return;
|
|
|
}
|
|
|
- // if (currentIndex.value < questionList.value.length - 1) {
|
|
|
- // currentIndex.value++;
|
|
|
- // setTimeout(() => {
|
|
|
- // subQuestionIndex.value = 0;
|
|
|
- // }, 300);
|
|
|
- // }
|
|
|
- if (currentQuestion.value.subQuestions && currentQuestion.value.subQuestions.length > 0) {
|
|
|
- if (subQuestionIndex.value < currentQuestion.value.subQuestions.length - 1) {
|
|
|
- subQuestionIndex.value++;
|
|
|
- } else {
|
|
|
- currentIndex.value++;
|
|
|
- subQuestionIndex.value = 0;
|
|
|
- }
|
|
|
+ if (currentQuestion.value.activeSubIndex < currentQuestion.value.subQuestions.length - 1) {
|
|
|
+ currentQuestion.value.activeSubIndex++;
|
|
|
} else {
|
|
|
currentIndex.value++;
|
|
|
}
|
|
|
@@ -384,26 +378,15 @@ export const useExam = () => {
|
|
|
if (!prevEnable.value) {
|
|
|
return;
|
|
|
}
|
|
|
- if (currentIndex.value > 0) {
|
|
|
- // currentIndex.value--;
|
|
|
- // setTimeout(() => {
|
|
|
- // subQuestionIndex.value = 0;
|
|
|
- // }, 300);
|
|
|
- }
|
|
|
if (currentQuestion.value.subQuestions && currentQuestion.value.subQuestions.length > 0) {
|
|
|
- if (subQuestionIndex.value > 0) {
|
|
|
- subQuestionIndex.value--;
|
|
|
+ if (currentQuestion.value.activeSubIndex > 0) {
|
|
|
+ currentQuestion.value.activeSubIndex--;
|
|
|
} else {
|
|
|
currentIndex.value--;
|
|
|
}
|
|
|
} else {
|
|
|
if (currentIndex.value > 0) {
|
|
|
currentIndex.value--;
|
|
|
- // 如果上一个题是子题,那么,默认选中最后一个子题
|
|
|
- const prevQuestion = questionList.value[currentIndex.value - 1];
|
|
|
- if (prevQuestion && prevQuestion.subQuestions && prevQuestion.subQuestions.length > 0) {
|
|
|
- subQuestionIndex.value = prevQuestion.subQuestions.length - 1;
|
|
|
- }
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -439,26 +422,72 @@ export const useExam = () => {
|
|
|
setTimeout(() => {
|
|
|
currentIndex.value = index;
|
|
|
if (!subIndex !== undefined) {
|
|
|
- subQuestionIndex.value = subIndex || 0;
|
|
|
+ if (subIndex !== undefined) {
|
|
|
+ questionList.value[index].activeSubIndex = subIndex || 0;
|
|
|
+ }
|
|
|
}
|
|
|
setTimeout(() => {
|
|
|
swiperDuration.value = 300;
|
|
|
}, 0);
|
|
|
}, 0);
|
|
|
}
|
|
|
- const changeSubIndex = (index: number) => {
|
|
|
- subQuestionIndex.value = index;
|
|
|
- }
|
|
|
// 开始计时
|
|
|
const startTiming = () => {
|
|
|
- interval = setInterval(() => {
|
|
|
- practiceDuration.value += 1;
|
|
|
- }, 1000);
|
|
|
+ startCount();
|
|
|
+
|
|
|
+ // 记录开始时间戳(毫秒)
|
|
|
+ if (practiceStartTime === 0) {
|
|
|
+ practiceStartTime = performance.now();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 使用 requestAnimationFrame 更新显示,更流畅且性能更好
|
|
|
+ const updatePracticeDuration = () => {
|
|
|
+ if (practiceStartTime > 0) {
|
|
|
+ // 计算实际经过的时间(秒)
|
|
|
+ const elapsed = (performance.now() - practiceStartTime) / 1000;
|
|
|
+ practiceDuration.value = Math.floor(practiceAccumulatedTime + elapsed);
|
|
|
+ // 继续下一帧更新
|
|
|
+ animationFrameId = requestAnimationFrame(updatePracticeDuration);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 开始动画帧循环
|
|
|
+ animationFrameId = requestAnimationFrame(updatePracticeDuration);
|
|
|
}
|
|
|
// 停止计时
|
|
|
const stopTiming = () => {
|
|
|
- interval && clearInterval(interval);
|
|
|
- interval = null;
|
|
|
+ stopCount();
|
|
|
+
|
|
|
+ // 取消动画帧
|
|
|
+ if (animationFrameId !== null) {
|
|
|
+ cancelAnimationFrame(animationFrameId);
|
|
|
+ animationFrameId = null;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果正在计时,累加经过的时间
|
|
|
+ if (practiceStartTime > 0) {
|
|
|
+ const elapsed = (performance.now() - practiceStartTime) / 1000;
|
|
|
+ practiceAccumulatedTime += elapsed;
|
|
|
+ practiceDuration.value = Math.floor(practiceAccumulatedTime);
|
|
|
+ practiceStartTime = 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ const startCount = () => {
|
|
|
+ if (countStart === 0) {
|
|
|
+ countStart = performance.now();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ const stopCount = () => {
|
|
|
+ // 如果当前没有在计时(countStart 为 0),说明已经 stop 过了,直接返回累计时长,避免重复累加
|
|
|
+ if (countStart === 0) {
|
|
|
+ return countTime;
|
|
|
+ }
|
|
|
+ countTime += (performance.now() - countStart);
|
|
|
+ countStart = 0;
|
|
|
+ return countTime;
|
|
|
+ }
|
|
|
+ const clearCount = () => {
|
|
|
+ countTime = 0;
|
|
|
}
|
|
|
// 开始倒计时
|
|
|
const startCountdown = () => {
|
|
|
@@ -482,6 +511,8 @@ export const useExam = () => {
|
|
|
}
|
|
|
const setPracticeDuration = (duration: number) => {
|
|
|
practiceDuration.value = duration;
|
|
|
+ practiceAccumulatedTime = duration;
|
|
|
+ practiceStartTime = 0;
|
|
|
}
|
|
|
const setCountDownCallback = (callback: () => void) => {
|
|
|
countDownCallback.value = callback;
|
|
|
@@ -489,6 +520,8 @@ export const useExam = () => {
|
|
|
const setDuration = (duration: number) => {
|
|
|
examDuration.value = duration;
|
|
|
practiceDuration.value = duration;
|
|
|
+ practiceAccumulatedTime = duration;
|
|
|
+ practiceStartTime = 0;
|
|
|
}
|
|
|
/// 整理题目结构
|
|
|
const transerQuestions = (arr: Study.Question[]) => {
|
|
|
@@ -550,7 +583,10 @@ export const useExam = () => {
|
|
|
totalScore: item.totalScore || 0,
|
|
|
offset: 0,
|
|
|
index: index,
|
|
|
- virtualIndex: 0
|
|
|
+ virtualIndex: 0,
|
|
|
+ duration: 0,
|
|
|
+ activeSubIndex: 0,
|
|
|
+ hasSubQuestions: item.subQuestions?.length > 0
|
|
|
} as Study.Question
|
|
|
}
|
|
|
questionList.value = transerQuestions(list.map((item, index) => transerQuestion(item, index)));
|
|
|
@@ -587,31 +623,33 @@ export const useExam = () => {
|
|
|
})
|
|
|
}
|
|
|
});
|
|
|
- console.log(questionList.value)
|
|
|
changeIndex(0);
|
|
|
practiceDuration.value = 0;
|
|
|
+ practiceAccumulatedTime = 0;
|
|
|
+ practiceStartTime = 0;
|
|
|
examDuration.value = 0;
|
|
|
interval && clearInterval(interval);
|
|
|
interval = null;
|
|
|
+ if (animationFrameId !== null) {
|
|
|
+ cancelAnimationFrame(animationFrameId);
|
|
|
+ animationFrameId = null;
|
|
|
+ }
|
|
|
}
|
|
|
/// 设置子题下标
|
|
|
const setSubQuestionIndex = (index: number) => {
|
|
|
- subQuestionIndex.value = index;
|
|
|
+ currentQuestion.value.activeSubIndex = index;
|
|
|
}
|
|
|
// 切换阅卷模式
|
|
|
const setPracticeSettings = (settings: Study.PracticeSettings) => {
|
|
|
practiceSettings.value = settings;
|
|
|
}
|
|
|
- // watch(() => currentIndex.value, (val) => {
|
|
|
- // subQuestionIndex.value = 0;
|
|
|
- // }, {
|
|
|
- // immediate: false
|
|
|
- // });
|
|
|
- watch([() => currentIndex.value, () => subQuestionIndex.value], (val) => {
|
|
|
- console.log('currentIndex.value', currentIndex.value)
|
|
|
- console.log('subQuestionIndex.value', subQuestionIndex.value)
|
|
|
+ const submit = () => {
|
|
|
+ updateQuestionDuration(virtualCurrentIndex.value, false);
|
|
|
+ }
|
|
|
+ watch([() => currentIndex.value, () => currentQuestion.value?.activeSubIndex], (val) => {
|
|
|
const qs = questionList.value[val[0]];
|
|
|
virtualCurrentIndex.value = qs.index + qs.offset + val[1];
|
|
|
+ console.log(virtualCurrentIndex.value, 777)
|
|
|
}, {
|
|
|
immediate: false
|
|
|
});
|
|
|
@@ -657,6 +695,7 @@ export const useExam = () => {
|
|
|
setQuestionList,
|
|
|
changeIndex,
|
|
|
reset,
|
|
|
+ submit,
|
|
|
isQuestionCorrect,
|
|
|
isOptionCorrect,
|
|
|
setPracticeSettings,
|