import { useCallback, useRef, useState } from 'react' import { Title, Image, Cursor, Video, GuideSteps, GuideStepsItem, type SingleChoiceOption, type DragFillResult, SingleChoiceQuizPanel, } from '@/components' import { SuccessAlert, DragQuizPanel } from '@/components/layout' import { getBackgroundImage, getStaticImage, getPublicPath } from '@/lib/utils' import { useNavigate, useParams } from 'react-router-dom' import type { VideoRef } from '@/components/video' import { useChenyunStore, saveChenyunStore } from '@/store/chenyunStore' import { usePuzzleAnimation } from '@/contexts/PuzzleAnimationContext' const STAR_NAME = 'chenyun' const PLACE = 'place7' const EXHIBIT_ID = 1 const PUZZLE_PIECE_ID = 10 const TOTAL_EXHIBITS = 1 // ─── 资源路径 ─── const BACKGROUND_KEY = `${STAR_NAME}/${PLACE}/场景7` const CONTINUE_IMAGE_KEY = `${STAR_NAME}/${PLACE}/继续前进` const VIDEO_1_SRC = getPublicPath(`video/${STAR_NAME}/chenyun_10.mp4`) const VIDEO_2_SRC = getPublicPath(`video/${STAR_NAME}/chenyun_11.mp4`) const AUDIO_PREFIX = `audio/${STAR_NAME}` // ─── 题目1:单选 ─── const QUIZ1_OPTIONS: SingleChoiceOption[] = [ { value: 'A', label: 'A.推动了淮海战役的胜利' }, { value: 'B', label: 'B.有利于稳定人民币的发行和地位' }, { value: 'C', label: 'C.巩固了新中国人民政权' }, ] const QUIZ1_CORRECT: string = 'B' const QUIZ1_TITLE = '【单选】1949年6月,中共中央指示打击银元投机活动,金条、银元一律由人民银行挂牌收兑,铁路交通事业,税收一律收人民币。同时,上海军管会查封了证券大楼。这次“银元之战”:' const QUIZ1_ERROR_HINTS: Record = { A: '淮海战役于1949年1月胜利结束,发生在这次“银元之战”前,不存在因果关系。请你再想想吧!', C: '1949年6月新中国政权尚未建立。请你再想想吧', } const QUIZ1_FALLBACK_HINT = '再想一想,注意题干中的时间和提示信息哦~' const QUIZ1_SUCCESS_CONTENT = '“银元之战”属于当时人民政府整顿金融的重要举措,黄金、银元投机不利于人民币币值稳定和流通,因此“银元之战”打击贵金属投机活动,有利于稳定人民币的发行和地位。' // ─── 题目2:拖拽 ─── const QUIZ2_BLANKS = [ { id: 'blank-1', label: '____' }, { id: 'blank-2', label: '____' }, { id: 'blank-3', label: '____' }, ] const QUIZ2_OPTIONS = [ { value: '棉纱', label: '棉纱' }, { value: '大米', label: '大米' }, { value: '煤炭', label: '煤炭' }, { value: '石油', label: '石油' }, ] const QUIZ2_CORRECT_SET = new Set(['棉纱', '大米', '煤炭']) const QUIZ2_PREFIX = '【拖拽】新中国成立后,为了稳定物价,中央政府组织了“两白一黑”的集中调运,“两白一黑”指的是' const QUIZ2_ERROR = '石油当时不是主要调控对象。请你再想想吧!' const isQuiz2Correct = (answers: DragFillResult[]): boolean => { if (answers.length !== QUIZ2_CORRECT_SET.size) return false const filled = new Set(answers.map((a) => a.optionValue)) if (filled.size !== QUIZ2_CORRECT_SET.size) return false for (const value of QUIZ2_CORRECT_SET) { if (!filled.has(value)) return false } return true } const ChenyunPlace7 = () => { const navigate = useNavigate() const { moduleName } = useParams<{ moduleName: string }>() const { triggerPuzzleAnimation } = usePuzzleAnimation() const { markExhibitVisited, getVisitedExhibits, setLastVisitedPlace } = useChenyunStore() const visited = new Set(getVisitedExhibits(PLACE)) const completed = visited.size >= TOTAL_EXHIBITS // ─── 第一阶段:视频1 + 对话1 + 单选题 ─── const videoRef1 = useRef(null) const [video1Visible, setVideo1Visible] = useState(false) const [video1Playing, setVideo1Playing] = useState(false) const [guide1Visible, setGuide1Visible] = useState(false) const [guide1Step, setGuide1Step] = useState(0) const [quiz1Visible, setQuiz1Visible] = useState(false) const [quiz1Completed, setQuiz1Completed] = useState(false) // ─── 第二阶段:视频2 + 对话2 + 拖拽题 ─── const videoRef2 = useRef(null) const [video2Visible, setVideo2Visible] = useState(false) const [video2Playing, setVideo2Playing] = useState(false) const [guide2Visible, setGuide2Visible] = useState(false) const [guide2Step, setGuide2Step] = useState(0) const [quiz2Visible, setQuiz2Visible] = useState(false) const [quiz2Value, setQuiz2Value] = useState([]) const [quiz2Error, setQuiz2Error] = useState('') const [quiz2Completed, setQuiz2Completed] = useState(false) // ─── 成功提示 ─── const [successVisible, setSuccessVisible] = useState(false) const [successContent, setSuccessContent] = useState('') // ─── 流程辅助 ─── const resetFlow = useCallback(() => { setVideo1Visible(false) setVideo1Playing(false) setGuide1Visible(false) setGuide1Step(0) setQuiz1Visible(false) setQuiz1Completed(false) setVideo2Visible(false) setVideo2Playing(false) setGuide2Visible(false) setGuide2Step(0) setQuiz2Visible(false) setQuiz2Value([]) setQuiz2Error('') setQuiz2Completed(false) setSuccessVisible(false) setSuccessContent('') }, []) const enterPhase1 = useCallback(() => { setVideo1Visible(true) setVideo1Playing(false) setGuide1Visible(true) setGuide1Step(0) }, []) const enterPhase2 = useCallback(() => { setVideo2Visible(true) setVideo2Playing(false) setGuide2Visible(true) setGuide2Step(0) }, []) // 点击展品光标:每次都从头走完整流程 const handleOpenExhibit = useCallback(() => { resetFlow() enterPhase1() }, [resetFlow, enterPhase1]) // 对话1 结束 → 播放视频1 const handleGuide1VisibleChange = (visible: boolean) => { setGuide1Visible(visible) if (!visible && video1Visible) { setVideo1Playing(true) videoRef1.current?.play() } } // 对话2 结束 → 播放视频2 const handleGuide2VisibleChange = (visible: boolean) => { setGuide2Visible(visible) if (!visible && video2Visible) { setVideo2Playing(true) videoRef2.current?.play() } } // 视频1 结束:标记访问 + 触发拼图 + 出题1 const handleVideo1Ended = () => { setVideo1Playing(false) markExhibitVisited(PLACE, EXHIBIT_ID) triggerPuzzleAnimation(PUZZLE_PIECE_ID) setQuiz1Visible(true) } // 题1 答对:弹成功提示,关闭后进入第二阶段 const handleQuiz1Submit = () => { setQuiz1Visible(false) setQuiz1Completed(true) setSuccessContent(QUIZ1_SUCCESS_CONTENT) setSuccessVisible(true) } // 视频2 结束:出题2 const handleVideo2Ended = () => { setVideo2Playing(false) setQuiz2Visible(true) } // 题2 实时校验:填齐后判定 const handleQuiz2Change = (val: DragFillResult[]) => { setQuiz2Value(val) setQuiz2Error('') if (val.length < QUIZ2_CORRECT_SET.size) return if (!isQuiz2Correct(val)) { setQuiz2Error(QUIZ2_ERROR) return } setQuiz2Visible(false) setVideo1Visible(false) setVideo2Visible(false) setQuiz2Completed(true) setSuccessContent('') setSuccessVisible(true) } // 成功提示关闭:题1答对后才进入第二阶段;题2答对后则停留在场景 const handleSuccessDismiss = () => { setSuccessVisible(false) if (quiz1Completed && !quiz2Completed && !quiz2Visible) { enterPhase2() } } if (!moduleName) return null const subtitle = completed ? '已完成' : `${visited.size}/${TOTAL_EXHIBITS}` 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={() => navigate(`/${moduleName}/${STAR_NAME}/place6`)} /> {/* 展品1 光标 */} <Cursor className="absolute left-230 top-88" onClick={handleOpenExhibit} read={visited.has(EXHIBIT_ID)} /> {/* 继续前进 - 仅在完成所有展品后显示 */} {completed && ( <Image className="absolute bottom-0 right-0" src={getStaticImage(CONTINUE_IMAGE_KEY)} button onClick={() => { setLastVisitedPlace('place8') saveChenyunStore() navigate(`/${moduleName}/${STAR_NAME}/place8`) }} /> )} {/* 视频1(全屏) */} {video1Visible && ( <div className="fixed inset-0 z-9999 bg-black"> <Video ref={videoRef1} src={VIDEO_1_SRC} className="w-full h-full" autoPlay={video1Playing} playsInline preload="auto" muted={!video1Playing} showSkipButton onEnded={handleVideo1Ended} /> </div> )} {/* 对话1(叠加在视频1之上) */} <GuideSteps visible={guide1Visible} onVisibleChange={handleGuide1VisibleChange} currentStep={guide1Step} onStepChange={setGuide1Step} showMask={false} highlightSelectors={[]} zIndex={10001} > <GuideStepsItem speaker="student" audioSrc={getPublicPath(`${AUDIO_PREFIX}/mp3_chenyun_19.mp3`)} > 那"银元之战"是如何取得胜利的呢? </GuideStepsItem> <GuideStepsItem speaker="guide" audioSrc={getPublicPath(`${AUDIO_PREFIX}/mp3_chenyun_20.mp3`)} > 由于通货膨胀,国民党发行的法币、金圆券变成了一堆废纸。当时坊间流传“大街过三道,物价跳三跳”。 </GuideStepsItem> <GuideStepsItem speaker="guide"> 有人计算,一百元法币,在一九三七年可买两头牛,一九四五年只能买两个鸡蛋,一九四七年只能买一个煤球,到一九四九年五月连一粒米也买不到。可见,当时通货膨胀多么严重。 </GuideStepsItem> <GuideStepsItem speaker="guide"> 打击投机资本,稳定金融物价,陈云代中央起草《中共中央关于打击银元使人民币占领阵地的指示》,提出以强硬手段打击投机势力。 </GuideStepsItem> </GuideSteps> {/* 题目1:单选 */} <SingleChoiceQuizPanel visible={quiz1Visible} zIndex={10002} title={QUIZ1_TITLE} options={QUIZ1_OPTIONS} correctAnswer={QUIZ1_CORRECT} onSubmit={handleQuiz1Submit} onWrongSelect={(val) => QUIZ1_ERROR_HINTS[val] || QUIZ1_FALLBACK_HINT} /> {/* 视频2(全屏) */} {video2Visible && ( <div className="fixed inset-0 z-10010 bg-black"> <Video ref={videoRef2} src={VIDEO_2_SRC} className="w-full h-full" autoPlay={video2Playing} playsInline preload="auto" muted={!video2Playing} showSkipButton skipText="跳过" onEnded={handleVideo2Ended} /> </div> )} {/* 对话2(叠加在视频2之上) */} <GuideSteps visible={guide2Visible} onVisibleChange={handleGuide2VisibleChange} currentStep={guide2Step} onStepChange={setGuide2Step} showMask={false} highlightSelectors={[]} zIndex={10011} > <GuideStepsItem speaker="student" audioSrc={getPublicPath(`${AUDIO_PREFIX}/mp3_chenyun_21.mp3`)} > 那"两白一黑"战役是如何打赢的呢? </GuideStepsItem> <GuideStepsItem speaker="guide" audioSrc={getPublicPath(`${AUDIO_PREFIX}/mp3_chenyun_22.mp3`)} > “银元之战”的胜利并没有遏制住投机者的嚣张气焰,物价稳定仅十几天后,投机者就由银元投机转向“两白一黑”的投机,即:棉纱、大米和煤炭,引发了又一次全国性涨价狂潮。 </GuideStepsItem> </GuideSteps> {/* 题目2:拖拽 */} <DragQuizPanel visible={quiz2Visible} zIndex={10012} questionPrefix={QUIZ2_PREFIX} questionSuffix="。" options={QUIZ2_OPTIONS} blanks={QUIZ2_BLANKS} value={quiz2Value} error={quiz2Error} onChange={handleQuiz2Change} /> {/* 成功提示(盖在视频/题目之上) */} {successVisible && ( <div className="fixed inset-0" style={{ zIndex: 20000 }}> <SuccessAlert visible title="恭喜你, 选择正确!" content={successContent} onClose={handleSuccessDismiss} onContinue={handleSuccessDismiss} /> </div> )} </div> ) } export default ChenyunPlace7