import React, { useState, useEffect } from 'react' import { useParams, useNavigate, useSearchParams } from 'react-router-dom' import { Card, Button, Typography, message, Spin, Space } from 'antd' import { ArrowLeftOutlined, PrinterOutlined, FileTextOutlined } from '@ant-design/icons' import * as examApi from '../api/exam' import type { Question } from '../types/question' import type { GetExamResponse } from '../types/exam' import styles from './ExamPrint.module.less' const { Title, Paragraph, Text } = Typography // 题型顺序映射 const TYPE_ORDER: Record = { 'fill-in-blank': 1, 'true-false': 2, 'multiple-choice': 3, 'multiple-selection': 4, 'short-answer': 5, 'ordinary-essay': 6, 'management-essay': 6, } // 题型名称映射 const TYPE_NAME: Record = { 'fill-in-blank': '一、填空题', 'true-false': '二、判断题', 'multiple-choice': '三、单选题', 'multiple-selection': '四、多选题', 'short-answer': '五、简答题', 'ordinary-essay': '六、论述题', 'management-essay': '六、论述题', } // 题型分值映射 const TYPE_SCORE: Record = { 'fill-in-blank': 2.0, 'true-false': 2.0, 'multiple-choice': 1.0, 'multiple-selection': 2.5, 'short-answer': 0, // 不计分 'ordinary-essay': 25.0, 'management-essay': 25.0, } const ExamPrint: React.FC = () => { const { examId } = useParams<{ examId: string }>() const navigate = useNavigate() const [searchParams] = useSearchParams() const showAnswer = searchParams.get('show_answer') === 'true' const [loading, setLoading] = useState(false) const [examData, setExamData] = useState(null) const [groupedQuestions, setGroupedQuestions] = useState>({}) // 加载考试详情 useEffect(() => { if (!examId) { message.error('考试ID不存在') navigate('/exam/prepare') return } const loadExam = async () => { setLoading(true) try { const res = await examApi.getExam(Number(examId), showAnswer) if (res.success && res.data) { setExamData(res.data) // 按题型分组 const grouped = groupQuestionsByType(res.data.questions) setGroupedQuestions(grouped) } else { message.error('加载考试失败') navigate('/exam/prepare') } } catch (error: any) { message.error(error.response?.data?.message || '加载考试失败') navigate('/exam/prepare') } finally { setLoading(false) } } loadExam() }, [examId, showAnswer, navigate]) // 按题型分组题目 const groupQuestionsByType = (questions: Question[]) => { const grouped: Record = {} questions.forEach((q) => { if (!grouped[q.type]) { grouped[q.type] = [] } grouped[q.type].push(q) }) return grouped } // 打印试卷(不显示答案) const handlePrintPaper = () => { if (showAnswer) { // 重新加载不显示答案的页面 window.location.href = `/exam/${examId}/print?show_answer=false` } else { window.print() } } // 打印答案(显示答案) const handlePrintAnswer = () => { if (!showAnswer) { // 重新加载显示答案的页面 window.location.href = `/exam/${examId}/print?show_answer=true` } else { window.print() } } // 格式化答案显示 const formatAnswer = (question: Question): string => { if (!question.answer) return '' switch (question.type) { case 'fill-in-blank': if (Array.isArray(question.answer)) { return question.answer.join('、') } return String(question.answer) case 'true-false': return question.answer === 'true' || question.answer === true ? '正确' : '错误' case 'multiple-choice': return String(question.answer) case 'multiple-selection': if (Array.isArray(question.answer)) { return question.answer.sort().join('') } return String(question.answer) case 'short-answer': case 'ordinary-essay': case 'management-essay': return String(question.answer) default: return String(question.answer) } } // 渲染填空题 const renderFillInBlank = (question: Question, index: number) => { // 获取答案数量 const answerCount = question.answer && Array.isArray(question.answer) ? question.answer.length : 1 return (
{index + 1}. {question.content}
{showAnswer ? ( 答案:{formatAnswer(question)} ) : ( <> {Array.from({ length: answerCount }).map((_, i) => (
第 {i + 1} 空:__________________________________________
))} )}
) } // 渲染判断题 const renderTrueFalse = (question: Question, index: number) => { return (
{index + 1}. {question.content}
{showAnswer ? ( 答案:{formatAnswer(question)} ) : ( 答案:____ )}
) } // 渲染单选题 const renderMultipleChoice = (question: Question, index: number) => { return (
{index + 1}. {question.content}
{question.options.map((opt) => (
{opt.key}. {opt.value}
))}
{showAnswer ? ( 答案:{formatAnswer(question)} ) : ( 答案:____ )}
) } // 渲染多选题 const renderMultipleSelection = (question: Question, index: number) => { return (
{index + 1}. {question.content}
{question.options.map((opt) => (
{opt.key}. {opt.value}
))}
{showAnswer ? ( 答案:{formatAnswer(question)} ) : ( 答案:____ )}
) } // 渲染简答题 const renderShortAnswer = (question: Question, index: number) => { return (
{index + 1}. {question.content} {!showAnswer && ( (仅供参考,不计分) )}
{showAnswer ? (
参考答案: {formatAnswer(question)}
) : (
{Array.from({ length: 5 }).map((_, i) => (
_____________________________________________________________________________
))}
)}
) } // 渲染论述题 const renderEssay = (question: Question, index: number) => { return (
{index + 1}. {question.content}
{showAnswer ? (
参考答案: {formatAnswer(question)}
) : (
{Array.from({ length: 10 }).map((_, i) => (
_____________________________________________________________________________
))}
)}
) } // 渲染题目组 const renderQuestionGroup = (type: string, questions: Question[]) => { let startIndex = 0 // 计算该题型的起始序号 Object.keys(groupedQuestions) .filter((t) => TYPE_ORDER[t] < TYPE_ORDER[type]) .forEach((t) => { startIndex += groupedQuestions[t].length }) // 计算该题型总分 const totalScore = questions.length * TYPE_SCORE[type] return (
{TYPE_NAME[type]} {TYPE_SCORE[type] > 0 && ( (共{questions.length}题,每题{TYPE_SCORE[type]}分,共{totalScore}分) )}
{questions.map((question, index) => { switch (type) { case 'fill-in-blank': return renderFillInBlank(question, startIndex + index) case 'true-false': return renderTrueFalse(question, startIndex + index) case 'multiple-choice': return renderMultipleChoice(question, startIndex + index) case 'multiple-selection': return renderMultipleSelection(question, startIndex + index) case 'short-answer': return renderShortAnswer(question, startIndex + index) case 'ordinary-essay': case 'management-essay': return renderEssay(question, startIndex + index) default: return null } })}
) } if (loading) { return (
加载考试中...
) } if (!examData) { return null } // 获取论述题(合并普通和管理两类) const essayQuestions = [ ...(groupedQuestions['ordinary-essay'] || []), ...(groupedQuestions['management-essay'] || []), ] return (
{/* 操作按钮区 - 打印时隐藏 */}
{/* 打印内容区 */}
{/* 试卷头部 */}
保密知识模拟考试{showAnswer ? '(答案)' : ''}
姓名:__________________
日期:__________________
{/* 考试说明 */} {!showAnswer && ( 考试说明
  • 本试卷满分100分,考试时间为90分钟
  • 请在答题区域内作答,字迹清晰工整
  • 简答题仅供参考,不计入总分
  • 论述题从以下2道题目中任选1道作答
)} {/* 按题型渲染题目 */} {Object.keys(groupedQuestions) .filter((type) => type !== 'ordinary-essay' && type !== 'management-essay') .sort((a, b) => TYPE_ORDER[a] - TYPE_ORDER[b]) .map((type) => renderQuestionGroup(type, groupedQuestions[type]))} {/* 论述题部分 */} {essayQuestions.length > 0 && (
{TYPE_NAME['ordinary-essay']} (以下2道论述题任选1道作答,共25分)
{essayQuestions.map((question, index) => renderEssay(question, index))}
)}
) } export default ExamPrint