import { useEffect, useMemo, useState, useRef } from "react"; import { useNavigate, useParams } from "react-router-dom"; import { Image, Video, type VideoAnchor, type VideoRef, type DragFillResult, LetterToLuxun, StudyGuideModal, Narration, } 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"; 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 }[]; // 拖拽题专用:空白之间的分隔符数组 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: "本句出自毛泽东的《忆秦娥・娄山关》,请再想想吧!", }, ], }, ]; 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< Record >({}); const [dragQuizErrors, setDragQuizErrors] = useState>( {} ); const [showNarration, setShowNarration] = useState(false); // 是否显示旁白 const [visible, setVisible] = useState(false); const [showVideo, setShowVideo] = useState(false); const [postGuideVisible, setPostGuideVisible] = useState(false); const [showLetter, setShowLetter] = useState(false); const [showStudyGuide, setShowStudyGuide] = useState(false); const videoRef = useRef(null); const audioRef = useRef(null); // 旁白关闭后显示对话引导 useEffect(() => { if (!showNarration) { setVisible(true); } }, [showNarration]); // 组件卸载时清理背景音乐 useEffect(() => { return () => { if (audioRef.current) { audioRef.current.pause(); audioRef.current.currentTime = 0; } }; }, []); 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 || []} 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); cb?.(); }} /> ), }; } return { id: item.id, time: item.time, once: true, afterResume: "remove", render: () => null, }; }), [ activeSuccess, resumeCallback, successContent, dragQuizValues, dragQuizErrors, ] ); if (!moduleName) return null; return (
{/* 离开按钮 */} navigate(`/${moduleName}/${starName}/home`)} /> {/* 内化结束后的背景音乐 */}
); }; export default LuxunInternalization;