/** * Exam Detail Page - with append upload and status polling */ import React, { useState, useEffect } from 'react' import { useParams, useNavigate } from 'react-router-dom' import { examAPI, questionAPI } from '../api/client' import Layout from '../components/Layout' import { ArrowLeft, Upload, Play, Loader, FileText, AlertCircle, RefreshCw } from 'lucide-react' import toast from 'react-hot-toast' import { getStatusColor, getStatusText, formatDate, calculateProgress, isValidFileType, getQuestionTypeText } from '../utils/helpers' export const ExamDetail = () => { const { examId } = useParams() const navigate = useNavigate() const [exam, setExam] = useState(null) const [questions, setQuestions] = useState([]) const [loading, setLoading] = useState(true) const [uploading, setUploading] = useState(false) const [showUploadModal, setShowUploadModal] = useState(false) const [uploadFile, setUploadFile] = useState(null) useEffect(() => { loadExamDetail() // Start polling if status is processing const interval = setInterval(() => { pollExamStatus() }, 3000) return () => clearInterval(interval) }, [examId]) const loadExamDetail = async () => { try { const [examRes, questionsRes] = await Promise.all([ examAPI.getDetail(examId), questionAPI.getExamQuestions(examId, 0, 10) // Load first 10 for preview ]) setExam(examRes.data) setQuestions(questionsRes.data.questions) } catch (error) { console.error('Failed to load exam:', error) toast.error('加载题库失败') } finally { setLoading(false) } } const pollExamStatus = async () => { try { const response = await examAPI.getDetail(examId) const newExam = response.data // If status changed from processing to ready if (exam?.status === 'processing' && newExam.status === 'ready') { toast.success('文档解析完成!') await loadExamDetail() // Reload to get updated questions } else if (exam?.status === 'processing' && newExam.status === 'failed') { toast.error('文档解析失败') } setExam(newExam) } catch (error) { console.error('Failed to poll exam:', error) } } const handleAppendDocument = async (e) => { e.preventDefault() if (!uploadFile) { toast.error('请选择文件') return } if (!isValidFileType(uploadFile.name)) { toast.error('不支持的文件类型') return } setUploading(true) try { await examAPI.appendDocument(examId, uploadFile) toast.success('文档上传成功,正在解析并去重...') setShowUploadModal(false) setUploadFile(null) await loadExamDetail() } catch (error) { console.error('Failed to append document:', error) } finally { setUploading(false) } } const handleStartQuiz = () => { if (exam.current_index >= exam.total_questions) { if (window.confirm('已经完成所有题目,是否从头开始?')) { navigate(`/quiz/${examId}?reset=true`) } } else { navigate(`/quiz/${examId}`) } } if (loading) { return (
) } if (!exam) { return (

题库不存在

) } const isProcessing = exam.status === 'processing' const isReady = exam.status === 'ready' const isFailed = exam.status === 'failed' const progress = calculateProgress(exam.current_index, exam.total_questions) return (
{/* Back Button */} {/* Header */}

{exam.title}

{getStatusText(exam.status)} {isProcessing && ( 正在处理中... )}
{/* Actions */}
{isReady && exam.total_questions > 0 && ( )}
{/* Stats */}

题目总数

{exam.total_questions}

已完成

{exam.current_index}

剩余

{Math.max(0, exam.total_questions - exam.current_index)}

完成度

{progress}%

{/* Progress Bar */} {exam.total_questions > 0 && (
)} {/* Info */}

创建时间:{formatDate(exam.created_at)}

最后更新:{formatDate(exam.updated_at)}

{/* Failed Status Warning */} {isFailed && (

文档解析失败

请检查文档格式是否正确,或尝试重新上传。

)} {/* Questions Preview */}

题目预览 {questions.length > 0 && `(前 ${questions.length} 题)`}

{questions.length === 0 ? (

{isProcessing ? '正在解析文档,请稍候...' : '暂无题目'}

) : (
{questions.map((q, index) => (
{index + 1}
{getQuestionTypeText(q.type)}

{q.content}

{q.options && q.options.length > 0 && (
    {q.options.map((opt, i) => (
  • {opt}
  • ))}
)}
))}
)}
{/* Upload Modal */} {showUploadModal && (

添加题目文档

上传新文档后,系统会自动解析题目并去除重复题目。

setUploadFile(e.target.files[0])} required accept=".txt,.pdf,.doc,.docx,.xlsx,.xls" className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-transparent" />

支持:TXT, PDF, DOC, DOCX, XLSX, XLS

)}
) } export default ExamDetail