import { useEffect, useMemo, useState, useRef } from 'react' import { useNavigate, useParams } from 'react-router-dom' import { Image, Video, type VideoAnchor, type VideoRef, type DragFillResult, LetterToSunzhongshan, Narration, EndingOverlay, } from '@/components' import { getBackgroundImage, getPublicPath, getStaticImage } from '@/lib/utils' import { SuccessAlert, DragQuizPanel } from '@/components/layout' import { MultipleChoiceQuizPanel } 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 }[] // 拖拽题专用:空白之间的分隔符数组 blankSeparators?: string[] // 拖拽题正确答案映射:blankId -> optionValue correctAnswersMap?: Record explanation?: string } // 星名固定为 sunzhongshan const starName = 'sunzhongshan' // 锚点数据:可根据实际视频时长调整 time const QUIZ_ANCHORS: QuizAnchor[] = [ { id: 'quiz-1', time: 60 + 14, //5.52分钟 title: '题目1', prompt: '【拖拽】习近平总书记在纪念孙中山先生诞辰150周年大会上指出: “中国共产党人是孙中山先生革命事业最坚定的', promptSuffix: '、最忠实的继承者。”', type: 'drag', blanks: [ { id: 'blank-1', label: '最坚定的' }, { id: 'blank-2', label: '最忠诚的' }, ], options: [ { value: '支持者', label: '支持者', isCorrect: true, }, { value: '合作者', label: '合作者', isCorrect: true, }, ], blankSeparators: ['、最忠诚的'], correctAnswersMap: { 'blank-1': '支持者', 'blank-2': '合作者', }, }, { id: 'quiz-2', time: 60 * 2, // 2分钟 title: '题目2', prompt: '【多选】新时代青年传承“振兴中华”的精神,能够具体落实为:', type: 'multiple', options: [ { value: 'A', label: '专注学习,提升自身技能与素养', isCorrect: true, explanation: '恭喜你,选择正确!青年通过专注学习、提升自身能力,用精益求精的精神服务于国家建设,才能成为“振兴中华”的践行者。', }, { value: 'B', label: '秉持精益求精的精神,在专业领域深耕', isCorrect: true, explanation: '恭喜你,选择正确!青年在自己的专业领域深耕细作,才能以扎实的能力为国家发展贡献力量。', }, { value: 'C', label: '遇到困难不退缩,不因失败而灰心', isCorrect: true, explanation: '恭喜你,选择正确!不因失败而灰心,也从不因困难而退缩,这种坚韧的态度是践行“振兴中华”过程中不可或缺的品质。', }, { value: 'D', label: '放弃个人职业规划,直接投身革命事业', isCorrect: false, explanation: '振兴中华要把个人理想融入国家命运,在自己的领域里成为“振兴中华”的践行者。请你再想想吧!', }, ], }, { id: 'quiz-3', time: 60 * 4 + 14, // 4分14秒 title: '题目3', prompt: '【多选】新时代青年涵养“天下为公”的情怀能够具体落实为:', type: 'multiple', options: [ { value: 'A', label: '做一名志愿者走进养老院服务老人', isCorrect: true, explanation: '恭喜你,选择正确!对他人的关爱与奉献是传承“天下为公”情怀的具体行动哦!', }, { value: 'B', label: '只专注于个人学业与职业发展,不参与公共事务', isCorrect: false, explanation: '这与“天下为公”的情怀完全相悖。请你再想想吧!', }, { value: 'C', label: '用自己所学的技能为企业、社区提供服务', isCorrect: true, explanation: '恭喜你,选择正确!用自己所学的技能为企业、社区提供服务是青年将个人能力转化为公共服务、践行“天下为公”的重要方式。', }, { value: 'D', label: '在日常生活中关心身边有需要的人', isCorrect: true, explanation: '恭喜你,选择正确!“天下为公”的情怀不需要宏大叙事,从身边小事做起就是具体落实。', }, ], explanation: '请再仔细分析一下题目,做出更全面的选择吧!', }, { id: 'quiz-4', time: 60 * 5 + 40.5, title: '题目4', prompt: '【多选】新时代青年学习“与时俱进”的智慧能够具体落实为:', type: 'multiple', options: [ { value: 'A', label: '主动学习新知识,关注科技等前沿领域', isCorrect: true, explanation: '恭喜你,选择正确!主动学习新知识、适应新趋势正是青年与时俱进、紧跟时代步伐的具体表现。', }, { value: 'B', label: '在工作中突破传统思维,用创新方法解决问题', isCorrect: true, explanation: '恭喜你,选择正确!在工作中突破传统思维,用创新解决问题正是与时俱进精神在职业场景中的落地。', }, { value: 'C', label: '固执己见,拒绝接受新事物和新变化', isCorrect: false, explanation: '这与“与时俱进”的智慧完全相悖。请你再想想吧!', }, ], explanation: '请再仔细分析一下题目,做出更全面的选择吧!', }, { id: 'quiz-5', time: 60 * 5 + 40.5, title: '题目5', prompt: '【多选】新时代的青年要从孙中山先生身上学习哪些精神?', type: 'multiple', options: [ { value: 'A', label: '“振兴中华”的初心', isCorrect: true, }, { value: 'B', label: '“天下为公”的情怀', isCorrect: true, }, { value: 'C', label: '“与时俱进”的智慧', isCorrect: true }, ], }, ] const SunzhongshanInternalization = () => { const navigate = useNavigate() const { moduleName } = useParams<{ moduleName: string }>() // 获取碎片动画触发方法 const { triggerPuzzleAnimation } = usePuzzleAnimation() const [activeSuccess, setActiveSuccess] = useState(null) const [resumeCallback, setResumeCallback] = useState<(() => void) | null>(null) // 拖拽题状态 const [dragQuizValues, setDragQuizValues] = useState>({}) const [dragQuizErrors, setDragQuizErrors] = useState>({}) const [showNarration, setShowNarration] = useState(true) // 是否显示旁白 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) // 旁白关闭后显示对话引导 useEffect(() => { if (!showNarration) { setVisible(true) } }, [showNarration]) // 视频加载完成后自动播放 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) setResumeCallback(() => resume) } else { setDragQuizErrors((prev) => ({ ...prev, [item.id]: '再想一想,选择正确答案~', })) } } 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) setResumeCallback(() => resume) } else { setDragQuizErrors((prev) => ({ ...prev, [item.id]: '再想一想,选择正确答案~', })) } } } 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) 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) triggerPuzzleAnimation(15) 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.id === 'quiz-2' ? (selectedValues: string[]) => { const picked = new Set(selectedValues) const pickedD = item.options.find((o) => o.value === 'D') if (picked.has('D')) return pickedD?.explanation || '' return '请再仔细分析一下题目,做出更全面的选择吧!' } : item.id === 'quiz-3' ? (selectedValues: string[]) => { const picked = new Set(selectedValues) const pickedB = item.options.find((o) => o.value === 'B') if (picked.has('B')) return pickedB?.explanation || '' return item.explanation || '请再仔细分析一下题目,做出更全面的选择吧!' } : item.id === 'quiz-4' ? (selectedValues: string[]) => { const picked = new Set(selectedValues) const pickedC = item.options.find((o) => o.value === 'C') if (picked.has('C')) return pickedC?.explanation || '' return ( item.explanation || '请再仔细分析一下题目,做出更全面的选择吧!' ) } : item.id === 'quiz-5' ? (selectedValues: string[]) => { const picked = new Set(selectedValues) const pickedC = item.options.find((o) => o.value === 'C') if (picked.has('C')) return pickedC?.explanation || '' return ( item.explanation || '请再仔细分析一下题目,做出更全面的选择吧!' ) } : item.explanation || '再想一想,确保选择了所有正确答案~' } onSubmit={(values: string[], isCorrect: boolean) => { if (isCorrect) { setActiveSuccess(item.id) // if (item.id === "quiz-2") { // const picked = new Set(values); // const content = item.options // .filter((o) => picked.has(o.value)) // .map((o) => o.explanation) // .filter(Boolean) // .join("\n"); // setSuccessContent(content || "回答正确,请继续探索。"); // } else { // setSuccessContent("回答正确,请继续探索。"); // } setResumeCallback(() => resume) } }} /> { setActiveSuccess(null) const cb = resumeCallback setResumeCallback(null) triggerPuzzleAnimation(16) cb?.() }} /> ), } } // 单选题处理(保留原有逻辑,以防万一) return { id: item.id, time: item.time, once: true, afterResume: 'remove', render: () => null, } }), [activeSuccess, resumeCallback, dragQuizValues, dragQuizErrors, triggerPuzzleAnimation] ) if (!moduleName) return null return (
{/* 离开按钮 */} navigate(`/${moduleName}/${starName}/home`)} /> {showVideo && (
) } export default SunzhongshanInternalization