import { ExhibitOverlay, GuideSteps, GuideStepsItem, Image, Title, MultipleChoiceQuizPanel, Video, type VideoRef, } from '@/components' import { useNavigate, useParams } from 'react-router-dom' import { getBackgroundImage, getPublicPath, getStaticImage } from '@/lib/utils' import { useMemo, useRef, useState } from 'react' import Cursor from '@/components/cursor' import { useCursorController } from '@/hooks/useCursorController' import SuccessAlert from '@/components/layout/alert/success-alert' import ScaledPortal from '@/components/layout/ScaledPortal' import { useMaozedongStore, saveMaozedongStore } from '@/store/maozedongStore' import { usePuzzleAnimation } from '@/contexts/PuzzleAnimationContext' // starName 固定为 maozedong const starName = 'maozedong' // 展品 ID 与资源路径映射,ID 用来做唯一标识 const exhibitSrcMap: Record<1 | 2, string> = { 1: 'maozedong/place2/场景2展品1', 2: 'maozedong/place2/场景2展品2', } const MaozedongPlace2 = () => { const navigate = useNavigate() const { moduleName } = useParams<{ moduleName: string }>() // 获取碎片动画触发方法 const { triggerPuzzleAnimation } = usePuzzleAnimation() // 当前激活的展品 ID const [activeExhibitId, setActiveExhibitId] = useState<1 | 2 | null>(null) // 持久化接入 const markVisited = useMaozedongStore((s) => s.markExhibitVisited) const getVisited = useMaozedongStore((s) => s.getVisitedExhibits) const setLastVisitedPlace = useMaozedongStore((s) => s.setLastVisitedPlace) const persistedVisited = useMemo(() => new Set(getVisited('place2') as (1 | 2)[]), [getVisited]) const [visited, setVisited] = useState>(() => new Set(persistedVisited)) // 展品1的对话引导状态 const [guideVisible1, setGuideVisible1] = useState(false) const [guideStep1, setGuideStep1] = useState(0) // 展品2的对话引导状态 const [guideVisible2, setGuideVisible2] = useState(false) const [guideStep2, setGuideStep2] = useState(0) // 展品1对话结束后播放的视频状态(播完后再出题目) const [showVideo, setShowVideo] = useState(false) const videoRef = useRef(null) // 展品1对话结束后的多选题 const [quizVisible, setQuizVisible] = useState(false) const [quizPassed, setQuizPassed] = useState(false) const [successAlertVisible, setSuccessAlertVisible] = useState(false) // Cursor 顺序控制:1 -> 2 const { isEnabled } = useCursorController({ initialEnabled: [1], dependencies: { 2: [1], }, visited, }) const handleOpenExhibit = (id: 1 | 2) => { setActiveExhibitId(id) if (id === 1) { setGuideVisible1(true) setGuideStep1(0) // 重新进入时重置题目完成状态 setQuizPassed(false) } else if (id === 2) { // 展品2:先看展品,关闭展品后再显示对话 setGuideVisible2(false) setGuideStep2(0) } } // 展品1对话步骤变化 const handleGuideStepChange1 = (step: number) => { setGuideStep1(step) } // 展品1对话完成 const handleGuideVisibleChange1 = (visible: boolean) => { setGuideVisible1(visible) if (!visible && !quizPassed) { setShowVideo(true) } } // 展品2对话步骤变化 const handleGuideStepChange2 = (step: number) => { setGuideStep2(step) } // 展品2对话完成 const handleGuideVisibleChange2 = (visible: boolean) => { setGuideVisible2(visible) } const progressCount = visited.size const total = 2 const completed = progressCount >= total const subtitle = completed ? '已完成' : `${progressCount}/${total}` if (!moduleName) return null return (
{/* 标题 */} <Image className="absolute right-5 top-5" src={getStaticImage('components/离开')} button onClick={() => { if (moduleName && starName) { navigate(`/${moduleName}/${starName}/place1`) } }} /> {/* 光标1 */} <Cursor className="absolute left-217 bottom-145" onClick={() => handleOpenExhibit(1)} read={visited.has(1)} disabled={!isEnabled(1)} /> {/* 光标2 */} <Cursor className="absolute right-92 bottom-105" onClick={() => handleOpenExhibit(2)} read={visited.has(2)} disabled={!isEnabled(2)} /> {/* 继续前进 - 仅在完成所有展品后显示 */} {completed && ( <Image className="absolute bottom-0 right-0" src={getStaticImage('components/place/继续前进')} button onClick={() => { setLastVisitedPlace('place3') saveMaozedongStore() navigate(`/${moduleName}/${starName}/place3`) }} /> )} {/* 展品遮罩层 */} <ExhibitOverlay visible={activeExhibitId !== null} src={activeExhibitId ? exhibitSrcMap[activeExhibitId] : ''} onOpen={() => { if (activeExhibitId === 1) { triggerPuzzleAnimation(3) } if (activeExhibitId === 2) { triggerPuzzleAnimation(4) } }} onClose={() => { const currentId = activeExhibitId if (currentId !== null) { setVisited((prev) => { const next = new Set(prev) next.add(currentId) markVisited('place2', currentId) saveMaozedongStore() return next }) } setActiveExhibitId(null) // 展品2:关闭后显示对话 if (currentId === 2) { setGuideStep2(0) setGuideVisible2(true) } }} /> {/* 展品1对话结束后播放的视频:播放完毕后出现题目 */} {showVideo && ( <ScaledPortal zIndex={20000}> <Video ref={videoRef} src={getPublicPath('video/maozedong/mzd_002.mp4')} className="absolute inset-0 w-full h-full" autoPlay playsInline showSkipButton onEnded={() => { setShowVideo(false) setQuizVisible(true) }} /> </ScaledPortal> )} {/* 展品1的对话引导(文字+语音播报占位) */} <GuideSteps visible={guideVisible1} onVisibleChange={handleGuideVisibleChange1} currentStep={guideStep1} onStepChange={handleGuideStepChange1} showMask={false} highlightSelectors={[]} zIndex={10001} > <GuideStepsItem speaker="student" audioSrc={getPublicPath('audio/maozedong/mp3_mzd_05.mp3')} > 中共一大定了“推翻资产阶级政权”的目标,但中共二大提 出了“反帝反封建的民主革命纲领”,为什么会有这样的转变呢? </GuideStepsItem> <GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/maozedong/mp3_mzd_06.mp3')}> 中共一大时我们中国共产党刚成立,像刚学走路的孩子, 目标很宏大——“建立无产阶级专政”“消灭阶级”。但毛主席回到湖南后, 做了实际调查发现必须先解决“眼前的敌人”。 </GuideStepsItem> <GuideStepsItem speaker="student" audioSrc={getPublicPath('audio/maozedong/mp3_mzd_07.mp3')} > “眼前的敌人”? </GuideStepsItem> <GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/maozedong/mp3_mzd_08.mp3')}> 对!想想看1922年长沙的码头工人罢工,安源的路矿工人罢工,他们的敌人是谁?是外国资本家,是军阀。中共二大时毛主席虽然没有出席,但完全赞同“最低纲领”——打倒军阀、推翻帝国主义压迫,实现民族独立和人民民主。 </GuideStepsItem> <GuideStepsItem speaker="guide"> 这就像盖房子,得先把地基打好;等打倒了军阀和帝国主义,再谈社会主义也不迟。 </GuideStepsItem> </GuideSteps> {/* 展品2的对话引导 */} <GuideSteps visible={guideVisible2} onVisibleChange={handleGuideVisibleChange2} currentStep={guideStep2} onStepChange={handleGuideStepChange2} showMask={false} highlightSelectors={[]} zIndex={10001} > <GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/maozedong/mp3_mzd_09.mp3')}> 后来喊出的“打倒列强,除军阀”的口号,就是从这儿来的。 </GuideStepsItem> </GuideSteps> {/* 展品1对话结束后的多选题 + 成功提示(使用 ScaledPortal) */} <ScaledPortal enabled={quizVisible || successAlertVisible} zIndex={10050}> <> <MultipleChoiceQuizPanel visible={quizVisible} title="【多选】1922年7月16日至23日,中国共产党第二次全国代表大会在上海南成都路辅德里625号召开。大会制定了党的最低纲领和最高纲领,党的最低纲领,即党在民主革命阶段的纲领是:" options={[ { value: 'A', label: 'A.打倒军阀,建立国内和平' }, { value: 'B', label: 'B.推翻国际帝国主义的压迫,达到中华民族的完全独立', }, { value: 'C', label: 'C.统一中国为真正的民主共和国' }, ]} correctAnswer={['A', 'B', 'C']} minSelections={1} maxSelections={3} errorMessage={(selectedValues) => { if (selectedValues.length < 3) { return '你还没有全面了解中共二大提出的党的最低纲领哦。请你再想想吧!' } return '还不太对,再仔细对照对话内容想一想吧~' }} onSubmit={(_values, isCorrect) => { if (isCorrect) { setQuizVisible(false) setQuizPassed(true) setSuccessAlertVisible(true) } }} zIndex={10000} /> <SuccessAlert visible={successAlertVisible} onContinue={() => { setSuccessAlertVisible(false) }} /> </> </ScaledPortal> </div> ) } export default MaozedongPlace2