diff --git a/server/routes/vocabulary.ts b/server/routes/vocabulary.ts index f4e76b8..4bdc5b0 100644 --- a/server/routes/vocabulary.ts +++ b/server/routes/vocabulary.ts @@ -879,7 +879,6 @@ router.post('/test-record', authMiddleware, async (req, res) => { message: '测试记录已保存', attemptId: attempt._id.toString(), stats: verifiedStats, - results: evaluatedResults, submittedAt: new Date(lastSubmittedAtMs) }); } catch (error) { diff --git a/src/components/VocabularyStudy.tsx b/src/components/VocabularyStudy.tsx index 1b7bddc..767ec2d 100644 --- a/src/components/VocabularyStudy.tsx +++ b/src/components/VocabularyStudy.tsx @@ -93,7 +93,6 @@ interface SavedVocabularyTestRecordResponse { endTime: string | Date; duration: number; }; - results: TestResult[]; } interface TestAttemptResponse { @@ -125,6 +124,7 @@ const VocabularyStudy: React.FC = () => { const [testStarted, setTestStarted] = useState(false); const [testFinished, setTestFinished] = useState(false); const [testResults, setTestResults] = useState([]); + const [answerFeedback, setAnswerFeedback] = useState(null); const [testAttemptId, setTestAttemptId] = useState(''); const [pendingAnswers, setPendingAnswers] = useState([]); const [testAttemptLoading, setTestAttemptLoading] = useState(false); @@ -429,6 +429,7 @@ const VocabularyStudy: React.FC = () => { setTestAttemptId(''); setPendingAnswers([]); setTestResults([]); + setAnswerFeedback(null); const wordIds = studyWords.map(getWordId).filter(Boolean); if (wordIds.length === 0) { message.error('单词数据不完整,请重新加载后再试'); @@ -506,6 +507,30 @@ const VocabularyStudy: React.FC = () => { .toLowerCase(); } + const getQuestionWord = (question: TestQuestion): Word | undefined => { + return studyWords.find(word => getWordId(word) === question.wordId); + }; + + const buildLocalTestResult = (question: TestQuestion, rawUserAnswer: string): TestResult => { + const matchedWord = getQuestionWord(question); + const userAnswer = rawUserAnswer.trim(); + const correctAnswer = testType === 'multiple-choice' + ? String(matchedWord?.translation ?? question.translation ?? '').trim() + : String(matchedWord?.word ?? question.word ?? '').trim(); + const isCorrect = testType === 'multiple-choice' + ? userAnswer === correctAnswer + : normalizeAnswer(userAnswer) === normalizeAnswer(correctAnswer); + + return { + questionToken: question.questionToken, + wordId: question.wordId, + word: String(matchedWord?.word ?? question.word ?? question.translation ?? '').trim(), + userAnswer, + correctAnswer, + isCorrect + }; + }; + // 提交答案 const submitAnswer = async () => { if (!testStarted || testWords.length === 0 || !testAttemptId || answerSubmittingRef.current) return; @@ -518,6 +543,7 @@ const VocabularyStudy: React.FC = () => { } const submittedAt = Date.now(); const normalizedUserAnswer = userAnswer.trim(); + const localResult = buildLocalTestResult(currentQuestion, normalizedUserAnswer); const answerProof = buildAnswerProof( testAttemptId, currentQuestion.questionToken, @@ -534,34 +560,57 @@ const VocabularyStudy: React.FC = () => { answerProof } ]; + const updatedTestResults = [ + ...testResults, + localResult + ]; setPendingAnswers(updatedPendingAnswers); + setTestResults(updatedTestResults); + setAnswerFeedback(localResult); setShowAnswer(true); setUserAnswer(''); setTimeout(() => { if (testIndex < testWords.length - 1) { - setTestIndex(testIndex + 1); + const nextIndex = testIndex + 1; + setTestIndex(nextIndex); setShowAnswer(false); + setAnswerFeedback(null); answerSubmittingRef.current = false; if (testType === 'audio-to-english') { setTimeout(() => { - playWordSound(testWords[testIndex + 1]?.word || ''); + playWordSound(testWords[nextIndex]?.word || ''); }, 500); } if (testType === 'multiple-choice') { - setOptions(testWords[testIndex + 1]?.options || []); + setOptions(testWords[nextIndex]?.options || []); } } else { answerSubmittingRef.current = false; - finishTestWithResults(updatedPendingAnswers); + finishTestWithResults(updatedPendingAnswers, updatedTestResults); } }, 1500); }; // 使用指定结果完成测试 - const finishTestWithResults = async (finalAnswers: PendingAnswer[]) => { + const finishTestWithResults = async (finalAnswers: PendingAnswer[], finalResults: TestResult[]) => { try { + const lastSubmittedAtMs = finalAnswers.length > 0 + ? Math.max(...finalAnswers.map(item => item.submittedAt)) + : Date.now(); + const localCorrectWords = finalResults.filter(result => result.isCorrect).length; + const localStats = { + totalWords: finalResults.length, + correctWords: localCorrectWords, + accuracy: finalResults.length > 0 ? (localCorrectWords / finalResults.length) * 100 : 0, + startTime: studyStats.startTime, + endTime: new Date(lastSubmittedAtMs), + duration: Math.max(0, (lastSubmittedAtMs - studyStats.startTime.getTime()) / 1000) + }; + + setTestResults(finalResults); + setStudyStats(localStats); setTestFinished(true); // 添加调试信息 @@ -591,7 +640,6 @@ const VocabularyStudy: React.FC = () => { endTime: new Date(savedRecord.stats.endTime), duration: savedRecord.stats.duration }); - setTestResults(savedRecord.results); setPendingAnswers([]); setTestAttemptId(''); @@ -609,7 +657,7 @@ const VocabularyStudy: React.FC = () => { // 完成测试 - 兼容原来的调用方式 const finishTest = async () => { // 使用当前的测试结果完成测试 - await finishTestWithResults([...pendingAnswers]); + await finishTestWithResults([...pendingAnswers], [...testResults]); }; // 重新开始测试 @@ -622,6 +670,7 @@ const VocabularyStudy: React.FC = () => { setUserAnswer(''); setShowAnswer(false); setTestResults([]); + setAnswerFeedback(null); setPendingAnswers([]); setOptions([]); answerSubmittingRef.current = false; @@ -1222,18 +1271,24 @@ const VocabularyStudy: React.FC = () => { {showAnswer && (
- 本题已提交 + {answerFeedback?.isCorrect ? '回答正确' : '回答错误'}
+ {answerFeedback && ( + <> +
正确答案: {answerFeedback.correctAnswer}
+
你的答案: {answerFeedback.userAnswer || '(空)'}
+ + )}
正在进入下一题...
)}