fixed some bugs
This commit is contained in:
@@ -879,7 +879,6 @@ router.post('/test-record', authMiddleware, async (req, res) => {
|
|||||||
message: '测试记录已保存',
|
message: '测试记录已保存',
|
||||||
attemptId: attempt._id.toString(),
|
attemptId: attempt._id.toString(),
|
||||||
stats: verifiedStats,
|
stats: verifiedStats,
|
||||||
results: evaluatedResults,
|
|
||||||
submittedAt: new Date(lastSubmittedAtMs)
|
submittedAt: new Date(lastSubmittedAtMs)
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -93,7 +93,6 @@ interface SavedVocabularyTestRecordResponse {
|
|||||||
endTime: string | Date;
|
endTime: string | Date;
|
||||||
duration: number;
|
duration: number;
|
||||||
};
|
};
|
||||||
results: TestResult[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TestAttemptResponse {
|
interface TestAttemptResponse {
|
||||||
@@ -125,6 +124,7 @@ const VocabularyStudy: React.FC = () => {
|
|||||||
const [testStarted, setTestStarted] = useState(false);
|
const [testStarted, setTestStarted] = useState(false);
|
||||||
const [testFinished, setTestFinished] = useState(false);
|
const [testFinished, setTestFinished] = useState(false);
|
||||||
const [testResults, setTestResults] = useState<TestResult[]>([]);
|
const [testResults, setTestResults] = useState<TestResult[]>([]);
|
||||||
|
const [answerFeedback, setAnswerFeedback] = useState<TestResult | null>(null);
|
||||||
const [testAttemptId, setTestAttemptId] = useState<string>('');
|
const [testAttemptId, setTestAttemptId] = useState<string>('');
|
||||||
const [pendingAnswers, setPendingAnswers] = useState<PendingAnswer[]>([]);
|
const [pendingAnswers, setPendingAnswers] = useState<PendingAnswer[]>([]);
|
||||||
const [testAttemptLoading, setTestAttemptLoading] = useState(false);
|
const [testAttemptLoading, setTestAttemptLoading] = useState(false);
|
||||||
@@ -429,6 +429,7 @@ const VocabularyStudy: React.FC = () => {
|
|||||||
setTestAttemptId('');
|
setTestAttemptId('');
|
||||||
setPendingAnswers([]);
|
setPendingAnswers([]);
|
||||||
setTestResults([]);
|
setTestResults([]);
|
||||||
|
setAnswerFeedback(null);
|
||||||
const wordIds = studyWords.map(getWordId).filter(Boolean);
|
const wordIds = studyWords.map(getWordId).filter(Boolean);
|
||||||
if (wordIds.length === 0) {
|
if (wordIds.length === 0) {
|
||||||
message.error('单词数据不完整,请重新加载后再试');
|
message.error('单词数据不完整,请重新加载后再试');
|
||||||
@@ -506,6 +507,30 @@ const VocabularyStudy: React.FC = () => {
|
|||||||
.toLowerCase();
|
.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 () => {
|
const submitAnswer = async () => {
|
||||||
if (!testStarted || testWords.length === 0 || !testAttemptId || answerSubmittingRef.current) return;
|
if (!testStarted || testWords.length === 0 || !testAttemptId || answerSubmittingRef.current) return;
|
||||||
@@ -518,6 +543,7 @@ const VocabularyStudy: React.FC = () => {
|
|||||||
}
|
}
|
||||||
const submittedAt = Date.now();
|
const submittedAt = Date.now();
|
||||||
const normalizedUserAnswer = userAnswer.trim();
|
const normalizedUserAnswer = userAnswer.trim();
|
||||||
|
const localResult = buildLocalTestResult(currentQuestion, normalizedUserAnswer);
|
||||||
const answerProof = buildAnswerProof(
|
const answerProof = buildAnswerProof(
|
||||||
testAttemptId,
|
testAttemptId,
|
||||||
currentQuestion.questionToken,
|
currentQuestion.questionToken,
|
||||||
@@ -534,34 +560,57 @@ const VocabularyStudy: React.FC = () => {
|
|||||||
answerProof
|
answerProof
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
const updatedTestResults = [
|
||||||
|
...testResults,
|
||||||
|
localResult
|
||||||
|
];
|
||||||
|
|
||||||
setPendingAnswers(updatedPendingAnswers);
|
setPendingAnswers(updatedPendingAnswers);
|
||||||
|
setTestResults(updatedTestResults);
|
||||||
|
setAnswerFeedback(localResult);
|
||||||
setShowAnswer(true);
|
setShowAnswer(true);
|
||||||
setUserAnswer('');
|
setUserAnswer('');
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (testIndex < testWords.length - 1) {
|
if (testIndex < testWords.length - 1) {
|
||||||
setTestIndex(testIndex + 1);
|
const nextIndex = testIndex + 1;
|
||||||
|
setTestIndex(nextIndex);
|
||||||
setShowAnswer(false);
|
setShowAnswer(false);
|
||||||
|
setAnswerFeedback(null);
|
||||||
answerSubmittingRef.current = false;
|
answerSubmittingRef.current = false;
|
||||||
if (testType === 'audio-to-english') {
|
if (testType === 'audio-to-english') {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
playWordSound(testWords[testIndex + 1]?.word || '');
|
playWordSound(testWords[nextIndex]?.word || '');
|
||||||
}, 500);
|
}, 500);
|
||||||
}
|
}
|
||||||
if (testType === 'multiple-choice') {
|
if (testType === 'multiple-choice') {
|
||||||
setOptions(testWords[testIndex + 1]?.options || []);
|
setOptions(testWords[nextIndex]?.options || []);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
answerSubmittingRef.current = false;
|
answerSubmittingRef.current = false;
|
||||||
finishTestWithResults(updatedPendingAnswers);
|
finishTestWithResults(updatedPendingAnswers, updatedTestResults);
|
||||||
}
|
}
|
||||||
}, 1500);
|
}, 1500);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 使用指定结果完成测试
|
// 使用指定结果完成测试
|
||||||
const finishTestWithResults = async (finalAnswers: PendingAnswer[]) => {
|
const finishTestWithResults = async (finalAnswers: PendingAnswer[], finalResults: TestResult[]) => {
|
||||||
try {
|
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);
|
setTestFinished(true);
|
||||||
|
|
||||||
// 添加调试信息
|
// 添加调试信息
|
||||||
@@ -591,7 +640,6 @@ const VocabularyStudy: React.FC = () => {
|
|||||||
endTime: new Date(savedRecord.stats.endTime),
|
endTime: new Date(savedRecord.stats.endTime),
|
||||||
duration: savedRecord.stats.duration
|
duration: savedRecord.stats.duration
|
||||||
});
|
});
|
||||||
setTestResults(savedRecord.results);
|
|
||||||
setPendingAnswers([]);
|
setPendingAnswers([]);
|
||||||
setTestAttemptId('');
|
setTestAttemptId('');
|
||||||
|
|
||||||
@@ -609,7 +657,7 @@ const VocabularyStudy: React.FC = () => {
|
|||||||
// 完成测试 - 兼容原来的调用方式
|
// 完成测试 - 兼容原来的调用方式
|
||||||
const finishTest = async () => {
|
const finishTest = async () => {
|
||||||
// 使用当前的测试结果完成测试
|
// 使用当前的测试结果完成测试
|
||||||
await finishTestWithResults([...pendingAnswers]);
|
await finishTestWithResults([...pendingAnswers], [...testResults]);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 重新开始测试
|
// 重新开始测试
|
||||||
@@ -622,6 +670,7 @@ const VocabularyStudy: React.FC = () => {
|
|||||||
setUserAnswer('');
|
setUserAnswer('');
|
||||||
setShowAnswer(false);
|
setShowAnswer(false);
|
||||||
setTestResults([]);
|
setTestResults([]);
|
||||||
|
setAnswerFeedback(null);
|
||||||
setPendingAnswers([]);
|
setPendingAnswers([]);
|
||||||
setOptions([]);
|
setOptions([]);
|
||||||
answerSubmittingRef.current = false;
|
answerSubmittingRef.current = false;
|
||||||
@@ -1222,18 +1271,24 @@ const VocabularyStudy: React.FC = () => {
|
|||||||
{showAnswer && (
|
{showAnswer && (
|
||||||
<div style={{
|
<div style={{
|
||||||
padding: 15,
|
padding: 15,
|
||||||
backgroundColor: '#f6f8fa',
|
backgroundColor: answerFeedback?.isCorrect ? '#f6ffed' : '#fff2f0',
|
||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
marginBottom: 15,
|
marginBottom: 15,
|
||||||
border: '1px solid #d9d9d9'
|
border: `1px solid ${answerFeedback?.isCorrect ? '#b7eb8f' : '#ffccc7'}`
|
||||||
}}>
|
}}>
|
||||||
<div style={{
|
<div style={{
|
||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
color: '#1677ff',
|
color: answerFeedback?.isCorrect ? '#389e0d' : '#cf1322',
|
||||||
marginBottom: 5
|
marginBottom: 5
|
||||||
}}>
|
}}>
|
||||||
本题已提交
|
{answerFeedback?.isCorrect ? '回答正确' : '回答错误'}
|
||||||
</div>
|
</div>
|
||||||
|
{answerFeedback && (
|
||||||
|
<>
|
||||||
|
<div>正确答案: {answerFeedback.correctAnswer}</div>
|
||||||
|
<div>你的答案: {answerFeedback.userAnswer || '(空)'}</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
<div>正在进入下一题...</div>
|
<div>正在进入下一题...</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user