import { ExhibitOverlay, GuideSteps, GuideStepsItem, Image, Title, Cursor, SingleChoiceQuizPanel, Video, type VideoAnchor, } from '@/components' import type { VideoRef } from '@/components/video' import { useNavigate, useParams } from 'react-router-dom' import { getBackgroundImage, getPublicPath, getStaticImage } from '@/lib/utils' import { useState, useRef, useMemo } from 'react' import SuccessAlert from '@/components/layout/alert/success-alert' import { useCursorController } from '@/hooks/useCursorController' import { usePuzzleAnimation } from '@/contexts/PuzzleAnimationContext' import { useLuxunStore, saveLuxunStore } from '@/store/luxunStore' import { setCourseTimeProgress } from '@/lib/utils/api-common' // starName 固定为 luxun const starName = 'luxun' // 展品 ID 与资源路径映射 const exhibitSrcMap: Record<1 | 2 | 3, string> = { 1: 'luxun/place4/展品11', 2: 'luxun/place4/展品12', 3: 'luxun/place4/展品13', } const quizDefaultError = '请结合刚刚的旁白内容,再思考一下吧!' const quizExplanations: Record = { // A: "陈独秀与鲁迅有思想交往,但他并未在这间“红色庇护所”中避难、与鲁迅夜谈革命议题。", // B: "恭喜你,选对啦。", // C: "李大钊于1927年牺牲,时间上不符合这间客房被用作“红色庇护所”的背景。", // D: "周恩来虽长期在上海从事革命活动,但并未在这里避难并与鲁迅夜谈。", A: '请你结合刚刚学习的内容,再思考一下吧!', B: '恭喜你,选择正确!', C: '请你结合刚刚学习的内容,再思考一下吧!', D: '请你结合刚刚学习的内容,再思考一下吧!', } const LuxunPlace5 = () => { const navigate = useNavigate() const { moduleName } = useParams<{ moduleName: string }>() //碎片动画触发方法 const { triggerPuzzleAnimation } = usePuzzleAnimation() const [introGuideVisible, setIntroGuideVisible] = useState(true) const [introGuideStep, setIntroGuideStep] = useState(0) const [activeExhibitId, setActiveExhibitId] = useState<1 | 2 | 3 | null>(null) // 使用持久化的访问状态 const place = 'place4' const { markExhibitVisited, getVisitedExhibits, setLastVisitedPlace } = useLuxunStore() // 从 store 获取已访问的展品,转换为 Set 用于兼容现有代码 const visitedExhibits = getVisitedExhibits(place) const visited = new Set(visitedExhibits) // Cursor 顺序控制:1 -> 2 -> 3 const { isEnabled } = useCursorController({ initialEnabled: [1], dependencies: { 2: [1], 3: [2], }, visited: visited, }) // 光标2的题目状态 const [quizVisible, setQuizVisible] = useState(false) const [quizError, setQuizError] = useState(quizDefaultError) const [successAlertVisible, setSuccessAlertVisible] = useState(false) // 光标3的引导对话状态 const [guide3Visible, setGuide3Visible] = useState(false) const [guide3Step, setGuide3Step] = useState(0) // 结尾视频与对话 const [showEndingVideo, setShowEndingVideo] = useState(false) const [endingGuideVisible, setEndingGuideVisible] = useState(false) // 视频锚点相关状态 const [activeVideoSuccess, setActiveVideoSuccess] = useState(false) const [resumeVideoCallback, setResumeVideoCallback] = useState<(() => void) | null>(null) const videoRef = useRef(null) const handleOpenExhibit = (index: 1 | 2 | 3) => { if (index === 3) { setActiveExhibitId(3) setGuide3Visible(true) setGuide3Step(0) return } setActiveExhibitId(index) } // 视频锚点配置 const videoAnchors: VideoAnchor[] = useMemo( () => [ { id: 'quiz-anchor', time: 41, once: true, afterResume: 'remove', render: ({ resume }) => ( <> { switch (value) { case 'B': return '这不是毛泽东主席的原话。请你再想想吧!' case 'C': return '这不是毛泽东主席的原话。请你再想想吧!' default: return '请你再想想吧!' } }} onSubmit={(value: string, isCorrect: boolean) => { if (isCorrect) { setActiveVideoSuccess(true) setResumeVideoCallback(() => resume) } }} zIndex={20000} /> { setActiveVideoSuccess(false) const cb = resumeVideoCallback setResumeVideoCallback(null) cb?.() }} onClose={() => { setActiveVideoSuccess(false) const cb = resumeVideoCallback setResumeVideoCallback(null) cb?.() }} /> ), }, ], [activeVideoSuccess, resumeVideoCallback] ) const progressCount = visited.size const total = 3 const completed = progressCount >= total const subtitle = completed ? '已完成' : `${progressCount}/${total}` const subtitleClassName = completed ? 'text-[#fff998] [text-shadow:_0_8px_8px_rgba(0,0,0,0.35),_0_0_16px_rgba(0,0,0,0.25)]' : undefined return (
<Image className="absolute right-5 top-5" src={getStaticImage('components/离开')} button onClick={() => { if (moduleName && starName) { navigate(`/${moduleName}/${starName}/place3`) } }} /> {/* 展品 */} <Cursor className="absolute left-167 bottom-30" onClick={() => handleOpenExhibit(1)} read={visited.has(1)} disabled={!isEnabled(1)} /> <Cursor className="absolute left-228 bottom-30" onClick={() => handleOpenExhibit(2)} read={visited.has(2)} disabled={!isEnabled(2)} /> <Cursor className="absolute right-115 bottom-18 z-20" onClick={() => handleOpenExhibit(3)} read={visited.has(3)} disabled={!isEnabled(3)} /> {completed && ( <Image className="absolute bottom-0 right-0" src={getStaticImage('components/place/继续前进')} button onClick={() => { // 完成后先播放视频,不立即跳转页面 saveLuxunStore() setShowEndingVideo(true) }} /> )} <ExhibitOverlay visible={activeExhibitId !== null} src={activeExhibitId ? exhibitSrcMap[activeExhibitId] : ''} onOpen={() => { if (activeExhibitId === 1) { triggerPuzzleAnimation(11) } if (activeExhibitId === 2) { triggerPuzzleAnimation(12) } if (activeExhibitId === 3) { triggerPuzzleAnimation(13) } }} description={ activeExhibitId === 1 ? '瞿秋白同志曾说,“鲁迅是莱谟斯,是野兽的奶汁所喂养大的,是封建宗法社会的逆子,是绅士阶级的贰臣,而同时也是一些浪漫谛克的革命家的诤友!”' : activeExhibitId === 2 ? '鲁迅先生曾经抄录清朝诗人何瓦琴的对联赠给瞿秋白,“人生得一知己足矣,斯世当以同怀视之。”' : undefined } onClose={() => { if (activeExhibitId === 2) { setActiveExhibitId(null) setQuizVisible(true) return } if (activeExhibitId === 3) { // 对话关闭时再标记已读 setActiveExhibitId(null) return } if (activeExhibitId !== null) { markExhibitVisited(place, activeExhibitId) } setActiveExhibitId(null) }} /> {/* 学习引导结束后的对话 */} <GuideSteps visible={introGuideVisible} onVisibleChange={setIntroGuideVisible} currentStep={introGuideStep} onStepChange={setIntroGuideStep} showMask={false} highlightSelectors={[]} zIndex={10000} > <GuideStepsItem speaker="student" audioSrc={getPublicPath('audio/luxun/mp3_luxun_26.mp3')}> 你说的是这间小小的客房吗,这里为什么会被称为“红色庇护所”呢? </GuideStepsItem> <GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/luxun/mp3_luxun_27.mp3')}> 你可别小看这间仅6平方米的客房,在白色恐怖笼罩的1933年至1936年,这里是很多共产党人的"安全港湾"。1933年,瞿秋白夫妇第三次到鲁迅家避难,就秘密住进了这里。 </GuideStepsItem> <GuideStepsItem speaker="guide"> 当时反动派到处搜捕共产党人,鲁迅冒着生命危险掩护他们,两人常彻夜长谈。瞿秋白的夫人杨之华回忆道:“秋白和鲁迅在一起,往往感觉夜太短。深刻的友谊的交谈,从政治到文艺,从实际到理论,从希腊到莫斯科,轻松,愉快,活泼。” </GuideStepsItem> </GuideSteps> {/* 光标2的题目 */} <SingleChoiceQuizPanel visible={quizVisible} title="【单选】上海鲁迅故居三楼客房被称为“红色庇护所”,以下哪位共产党人曾在此避难并与鲁迅探讨革命议题?" options={[ { value: 'A', label: 'A.陈独秀' }, { value: 'B', label: 'B.瞿秋白' }, { value: 'C', label: 'C.李大钊' }, { value: 'D', label: 'D.周恩来' }, ]} correctAnswer="B" errorMessage={quizError} onSubmit={(value, isCorrect) => { if (isCorrect) { setQuizError(quizDefaultError) setQuizVisible(false) setSuccessAlertVisible(true) } }} onWrongSelect={(value) => { const explanation = quizExplanations[value] if (explanation) { setQuizError(explanation) return explanation } setQuizError(quizDefaultError) return quizDefaultError }} zIndex={10001} /> {/* 光标2的成功提示 */} <SuccessAlert visible={successAlertVisible} content="1933年,瞿秋白夫妇第三次到鲁迅家避难,就住进了这间仅6平方米的客房,与鲁迅常常彻夜长谈革命与文学。" onContinue={() => { setSuccessAlertVisible(false) markExhibitVisited(place, 2) }} /> {/* 光标3的引导对话 */} <GuideSteps visible={guide3Visible} onVisibleChange={(visible) => { setGuide3Visible(visible) if (!visible) { markExhibitVisited(place, 3) } }} currentStep={guide3Step} onStepChange={setGuide3Step} showMask={false} highlightSelectors={[]} zIndex={10000} > <GuideStepsItem speaker="student" audioSrc={getPublicPath('audio/luxun/mp3_luxun_28.mp3')}> 除了瞿秋白,还有其他共产党人来过这里吗 </GuideStepsItem> <GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/luxun/mp3_luxun_29.mp3')}> 当然!1936年4月,中共中央特派员冯雪峰到上海开展工作,也先住进了这间客房。冯雪峰把红军长征胜利、遵义会议召开的消息告诉鲁迅,还带来了毛泽东同志提出的抗日民族统一战线主张。 </GuideStepsItem> <GuideStepsItem speaker="guide"> 鲁迅听后格外振奋,激动地说“我做一个小兵还是胜任的,用笔!”,这句话也成了他晚年投身抗日救亡运动的誓言。 </GuideStepsItem> </GuideSteps> {/* 结尾视频播放 */} {showEndingVideo && ( <Video ref={videoRef} src={getPublicPath('video/luxun/luxun_06.mp4')} className="fixed inset-0 z-20000" controls autoPlay playsInline showSkipButton anchors={videoAnchors} onEnded={() => { setEndingGuideVisible(true) }} /> )} {/* 视频结束后的结束语对话 */} <GuideSteps visible={endingGuideVisible} onVisibleChange={(visible) => { setShowEndingVideo(false) setEndingGuideVisible(visible) if (!visible && moduleName) { // 对话结束后返回鲁迅首页 setCourseTimeProgress(7) navigate(`/${moduleName}/${starName}/home`) } }} showMask={false} zIndex={20010} > <GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/luxun/mp3_luxun_30.mp3')}> 我们已经了解了鲁迅居住在上海大陆新村9号时的重要事件。 </GuideStepsItem> <GuideStepsItem speaker="guide"> 接下来,请你接过先生手中的“笔”,走出上海,跨越时空,追寻他从绍兴三味书屋到上海的创作足迹——解锁每个地标背后的故事,填充文字里的时代线索,在互动中,读懂先生笔墨间的温度与力量。 </GuideStepsItem> </GuideSteps> </div> ) } export default LuxunPlace5