import { useEffect, useMemo, useState, useRef } from 'react' import { useNavigate, useParams } from 'react-router-dom' import { Image, Video, type VideoAnchor, type VideoRef, type DragFillResult, LetterToLuxun, Narration, EndingOverlay, } from '@/components' import { getBackgroundImage, getPublicPath, getStaticImage } from '@/lib/utils' import { SuccessAlert, DragQuizPanel } from '@/components/layout' import { MultipleChoiceQuizPanel, SingleChoiceQuizPanel } from '@/components/layout/quiz' import { GuideSteps, GuideStepsItem } from '@/components' import { usePuzzleAnimation } from '@/contexts/PuzzleAnimationContext' import { setCourseTimeProgress } from '@/lib/utils/api-common' type QuizOption = { value: string label: string isCorrect?: boolean explanation?: string } type QuizAnchor = { id: string time: number title: string prompt: string // 拖拽题专用:题干后缀(空白区域之后的文本) promptSuffix?: string type: 'single' | 'multiple' | 'drag' options: QuizOption[] // 拖拽题专用 blanks?: { id: string; label: string }[] // 拖拽题专用:每个空前面的前缀文案 blankPrefixes?: string[] // 拖拽题专用:空白之间的分隔符数组 blankSeparators?: string[] // 拖拽题正确答案映射:blankId -> optionValue correctAnswersMap?: Record explanation?: string } const starName = 'luxun' // 锚点数据:根据视频时长调整 time const QUIZ_ANCHORS: QuizAnchor[] = [ { id: 'quiz-1', time: 60 + 41, // 1:41 title: '', prompt: '【拖拽】有学者认为鲁迅有一个宏大的知识大众化计划,对于文盲,鲁迅推荐的是', promptSuffix: '。', type: 'drag', blanks: [ { id: 'blank-1', label: '___' }, { id: 'blank-2', label: '___' }, { id: 'blank-3', label: '___' }, ], options: [ { value: '电影、讲演、戏剧演出', label: '电影、讲演、戏剧演出' }, { value: '木刻艺术', label: '木刻艺术' }, { value: '努力推行新文字和文学通俗化', label: '努力推行新文字和文学通俗化', }, ], blankSeparators: [';在介于文盲和和识字者之间,鲁迅运用的是', ';鲁迅对作家们的要求是'], correctAnswersMap: { 'blank-1': '电影、讲演、戏剧演出', 'blank-2': '木刻艺术', 'blank-3': '努力推行新文字和文学通俗化', }, explanation: '有学者认为鲁迅有一个宏大的知识大众化计划,对于文盲,鲁迅推荐的是电影、讲演、戏剧演出;在介于文盲和识字者之间,鲁迅运用的是木刻艺术;鲁迅对作家们的要求是努力推行新文字和文学通俗化。', }, { id: 'quiz-2', time: 60 * 5 + 2, // 5:02 title: '', prompt: '【多选】对于鼓励和培养青年一代,鲁迅先生的做法是:', type: 'multiple', options: [ { value: 'A', label: '组织文学社团', isCorrect: true }, { value: 'B', label: '木刻运动', isCorrect: true }, { value: 'C', label: '写纪念烈士文章', isCorrect: true }, // { // value: "D", // label: "让青年都去参军", // }, ], // explanation: // "虽然鲁迅热爱革命,但他主要通过文学社团、木刻艺术和纪念文章来教育和启发青年,而不是通过参军来培养青年。", explanation: '请你再想想吧!', }, { id: 'quiz-3', time: 60 * 6 + 39, // 6:39 title: '', prompt: '【单选】以下那句诗出自鲁迅的作品:', type: 'single', options: [ { value: 'A', label: '寄意寒星荃不察,我以我血荐轩辕', isCorrect: true, explanation: '本句出自鲁迅的《自题小像》', }, { value: 'B', label: '我自横刀向天笑,去留肝胆两昆仑', explanation: '本句出自谭嗣同的《狱中题壁》,请你再想想吧!', }, { value: 'C', label: '此去泉台招旧部,旌旗十万斩阎罗', explanation: '本句出自陈毅的《梅岭三章》,请你再想想吧!', }, { value: 'D', label: '雄关漫道真如铁,而今迈步从头越', explanation: '本句出自毛泽东的《忆秦娥・娄山关》,请你再想想吧!', }, ], }, { id: 'quiz-4', time: 60 * 7 + 59, // 7:59 title: '', prompt: '【拖拽】鲁迅先生一生为国为民,写下了大量抨击黑暗社会的檄文', promptSuffix: ',他以无所畏惧的姿态和极大的热情,参加反对封建专制、反对帝国主义侵略和国民党反动统治的实际斗争。', type: 'drag', blanks: [ { id: 'blank-1', label: '___' }, { id: 'blank-2', label: '___' }, ], blankPrefixes: [',如', '如'], blankSeparators: [''], options: [ { value: '七首', label: '七首' }, { value: '投枪', label: '投枪' }, { value: '清泉', label: '清泉' }, ], correctAnswersMap: { 'blank-1': '七首', 'blank-2': '投枪', }, explanation: '“清泉”不是鲁迅文章的意象。请你再想想吧!', }, { id: 'quiz-5', time: 60 * 9 + 53.5, // 9:53 title: '', prompt: '【多选】我们能从鲁迅身上学习到:', type: 'multiple', options: [ { value: 'A', label: '至诚爱国,为民族独立与人民幸福奋斗不息的精神', isCorrect: true, }, { value: 'B', label: '追求光明、追求真理的人生追求', isCorrect: true, }, { value: 'C', label: '博大的文化胸襟与崇高的人格力量', isCorrect: true, }, ], explanation: '请再仔细分析一下题目,做出更全面的选择吧!', }, ] const LuxunInternalization = () => { const navigate = useNavigate() const { moduleName } = useParams<{ moduleName: string }>() const [activeSuccess, setActiveSuccess] = useState(null) const [successContent, setSuccessContent] = useState('') const [resumeCallback, setResumeCallback] = useState<(() => void) | null>(null) // 拖拽题状态 const [dragQuizValues, setDragQuizValues] = useState>({}) const [dragQuizErrors, setDragQuizErrors] = useState>({}) const [showNarration, setShowNarration] = useState(false) // 是否显示旁白 const [visible, setVisible] = useState(false) const [showVideo, setShowVideo] = useState(false) const [showLetter, setShowLetter] = useState(false) const [showStudyGuide, setShowStudyGuide] = useState(false) const videoRef = useRef(null) //碎片动画触发方法 const { triggerPuzzleAnimation } = usePuzzleAnimation() // 旁白关闭后显示对话引导 useEffect(() => { setShowNarration(true) }, []) const handleVideoCanPlay = () => { if (showVideo && videoRef.current) { videoRef.current.play().catch((error) => { console.warn('视频播放失败:', error) }) } } const anchors: VideoAnchor[] = useMemo( () => QUIZ_ANCHORS.map((item) => { const correctAnswers = item.options.filter((opt) => opt.isCorrect).map((opt) => opt.value) // 拖拽题处理 if (item.type === 'drag' && item.blanks) { return { id: item.id, time: item.time, once: true, afterResume: 'remove', render: ({ resume }) => { const handleDragChange = (val: DragFillResult[]) => { setDragQuizValues((prev) => ({ ...prev, [item.id]: val })) setDragQuizErrors((prev) => ({ ...prev, [item.id]: '' })) if (!item.blanks || val.length < item.blanks.length) return // 如果有位置特定的答案映射,使用它进行验证 if (item.correctAnswersMap) { const isCorrect = item.blanks.every((blank) => { const filledResult = val.find((v) => v.blankId === blank.id) const correctAnswer = item.correctAnswersMap![blank.id] return filledResult?.optionValue === correctAnswer }) if (isCorrect) { setActiveSuccess(item.id) setSuccessContent('') setResumeCallback(() => resume) } else { setDragQuizErrors((prev) => ({ ...prev, [item.id]: item.explanation || '再想一想,选择正确答案~', })) } } else { // 如果没有位置特定的映射,使用原来的逻辑(检查是否包含了所有正确答案) const filledAnswers = val.map((v) => v.optionValue) const answerSet = new Set(correctAnswers) const filledSet = new Set(filledAnswers) const isCorrect = answerSet.size === filledSet.size && [...answerSet].every((answer) => filledSet.has(answer)) if (isCorrect) { setActiveSuccess(item.id) setSuccessContent('') setResumeCallback(() => resume) } else { setDragQuizErrors((prev) => ({ ...prev, [item.id]: item.explanation || '再想一想,选择正确答案~', })) } } } return ( <> ({ value: opt.value, label: opt.label, }))} blanks={item.blanks || []} blankPrefixes={item.blankPrefixes} blankSeparators={item.blankSeparators} value={dragQuizValues[item.id] || []} error={dragQuizErrors[item.id]} onChange={handleDragChange} /> { setActiveSuccess(null) setSuccessContent('') setDragQuizValues((prev) => { const next = { ...prev } delete next[item.id] return next }) setDragQuizErrors((prev) => { const next = { ...prev } delete next[item.id] return next }) const cb = resumeCallback setResumeCallback(null) cb?.() }} /> ) }, } } // 单选题处理 if (item.type === 'single') { return { id: item.id, time: item.time, once: true, afterResume: 'remove', render: ({ resume }) => ( <> ({ value: opt.value, label: `${opt.value}. ${opt.label}`, }))} correctAnswer={correctAnswers[0]} errorMessage="再思考一下,选择正确答案。" onWrongSelect={(value: string) => { // 为题目3提供详细的选项解释 if (item.id === 'quiz-3') { const selectedOption = item.options.find((opt) => opt.value === value) return selectedOption?.explanation || '再思考一下,选择正确答案。' } return undefined }} onSubmit={(_value: string, isCorrect: boolean) => { if (isCorrect) { setActiveSuccess(item.id) // 为题目3提供正确答案的详细解释 if (item.id === 'quiz-3') { const correctOption = item.options.find((opt) => opt.isCorrect) setSuccessContent(correctOption?.explanation || '') } else { setSuccessContent('') } setResumeCallback(() => resume) } }} /> { setActiveSuccess(null) setSuccessContent('') const cb = resumeCallback setResumeCallback(null) cb?.() }} /> ), } } // 多选题处理 if (item.type === 'multiple') { return { id: item.id, time: item.time, once: true, afterResume: 'remove', render: ({ resume }) => ( <> ({ value: opt.value, label: `${opt.value}. ${opt.label}`, }))} correctAnswer={correctAnswers} errorMessage={item.explanation || '再思考一下,注意选择所有正确答案。'} onSubmit={(_values: string[], isCorrect: boolean) => { if (isCorrect) { setActiveSuccess(item.id) setSuccessContent('') setResumeCallback(() => resume) } }} /> { setActiveSuccess(null) setSuccessContent('') const cb = resumeCallback setResumeCallback(null) triggerPuzzleAnimation(16) cb?.() }} /> ), } } return { id: item.id, time: item.time, once: true, afterResume: 'remove', render: () => null, } }), [ activeSuccess, resumeCallback, successContent, dragQuizValues, dragQuizErrors, triggerPuzzleAnimation, ] ) if (!moduleName) return null return (
{/* 离开按钮 */} navigate(`/${moduleName}/${starName}/home`)} /> {showVideo && (
) } export default LuxunInternalization