import { useState, useEffect } from 'react'; import { Card, Button, Radio, Checkbox, Input, message, Progress } from 'antd'; import { useNavigate, useLocation } from 'react-router-dom'; import { useUser, useQuiz } from '../contexts'; import { quizAPI } from '../services/api'; import { questionTypeMap } from '../utils/validation'; import { UserLayout } from '../layouts/UserLayout'; const { TextArea } = Input; interface Question { id: string; content: string; type: 'single' | 'multiple' | 'judgment' | 'text'; options?: string[]; answer: string | string[]; score: number; createdAt: string; category?: string; } interface LocationState { questions?: Question[]; totalScore?: number; timeLimit?: number; subjectId?: string; taskId?: string; } const QuizPage = () => { const navigate = useNavigate(); const location = useLocation(); const { user } = useUser(); const { questions, setQuestions, currentQuestionIndex, setCurrentQuestionIndex, answers, setAnswer, clearQuiz } = useQuiz(); const [loading, setLoading] = useState(false); const [submitting, setSubmitting] = useState(false); const [timeLeft, setTimeLeft] = useState(null); const [timeLimit, setTimeLimit] = useState(null); const [subjectId, setSubjectId] = useState(''); const [taskId, setTaskId] = useState(''); useEffect(() => { if (!user) { message.warning('请先填写个人信息'); navigate('/'); return; } const state = location.state as LocationState; if (state?.questions) { // 如果已经有题目数据(来自科目选择页面) setQuestions(state.questions); setTimeLimit(state.timeLimit || 60); setTimeLeft((state.timeLimit || 60) * 60); // 转换为秒 setSubjectId(state.subjectId || ''); setTaskId(state.taskId || ''); setCurrentQuestionIndex(0); } else { // 兼容旧版本,直接生成题目 generateQuiz(); } // 清除之前的答题状态 clearQuiz(); }, [user, navigate, location]); // 倒计时逻辑 useEffect(() => { if (timeLeft === null || timeLeft <= 0) return; const timer = setInterval(() => { setTimeLeft(prev => { if (prev === null || prev <= 1) { clearInterval(timer); handleTimeUp(); return 0; } return prev - 1; }); }, 1000); return () => clearInterval(timer); }, [timeLeft]); const generateQuiz = async () => { try { setLoading(true); const response = await quizAPI.generateQuiz(user!.id); setQuestions(response.data.questions); setCurrentQuestionIndex(0); } catch (error: any) { message.error(error.message || '生成试卷失败'); } finally { setLoading(false); } }; const handleTimeUp = () => { message.warning('考试时间已到,将自动提交答案'); setTimeout(() => { handleSubmit(true); }, 1000); }; const formatTime = (seconds: number) => { const minutes = Math.floor(seconds / 60); const remainingSeconds = seconds % 60; return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`; }; const getTagColor = (type: string) => { switch (type) { case 'single': return 'bg-blue-100 text-blue-800'; case 'multiple': return 'bg-purple-100 text-purple-800'; case 'judgment': return 'bg-orange-100 text-orange-800'; case 'text': return 'bg-green-100 text-green-800'; default: return 'bg-gray-100 text-gray-800'; } }; const handleAnswerChange = (questionId: string, value: string | string[]) => { setAnswer(questionId, value); }; const handleNext = () => { if (currentQuestionIndex < questions.length - 1) { setCurrentQuestionIndex(currentQuestionIndex + 1); } }; const handlePrevious = () => { if (currentQuestionIndex > 0) { setCurrentQuestionIndex(currentQuestionIndex - 1); } }; const handleSubmit = async (forceSubmit = false) => { try { setSubmitting(true); if (!forceSubmit) { // 检查是否所有题目都已回答 const unansweredQuestions = questions.filter(q => !answers[q.id]); if (unansweredQuestions.length > 0) { message.warning(`还有 ${unansweredQuestions.length} 道题未作答`); return; } } // 准备答案数据 const answersData = questions.map(question => { const isCorrect = checkAnswer(question, answers[question.id]); return { questionId: question.id, userAnswer: answers[question.id], score: isCorrect ? question.score : 0, isCorrect }; }); const response = await quizAPI.submitQuiz({ userId: user!.id, subjectId: subjectId || undefined, taskId: taskId || undefined, answers: answersData }); message.success('答题提交成功!'); navigate(`/result/${response.data.recordId}`); } catch (error: any) { message.error(error.message || '提交失败'); } finally { setSubmitting(false); } }; const checkAnswer = (question: Question, userAnswer: string | string[]): boolean => { if (!userAnswer) return false; if (question.type === 'multiple') { const correctAnswers = Array.isArray(question.answer) ? question.answer : [question.answer]; const userAnswers = Array.isArray(userAnswer) ? userAnswer : [userAnswer]; return correctAnswers.length === userAnswers.length && correctAnswers.every(answer => userAnswers.includes(answer)); } else { return userAnswer === question.answer; } }; const renderQuestion = (question: Question) => { const currentAnswer = answers[question.id]; switch (question.type) { case 'single': return ( handleAnswerChange(question.id, e.target.value)} className="w-full" > {question.options?.map((option, index) => ( {String.fromCharCode(65 + index)}. {option} ))} ); case 'multiple': return ( handleAnswerChange(question.id, checkedValues)} className="w-full" > {question.options?.map((option, index) => ( {String.fromCharCode(65 + index)}. {option} ))} ); case 'judgment': return ( handleAnswerChange(question.id, e.target.value)} className="w-full" > 正确 错误 ); case 'text': return (