- 更新展品图片(鲁迅/毛泽东/孙中山/黄炎培/聂耳/张乐平故居) - 新增点击查看图标、报纸素材 - 优化视频组件、拼图组件、展品组件 - 更新陈云音频资源 - 新增输入验证文档和工具函数 - 组件布局和动画优化 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
439 lines
16 KiB
TypeScript
439 lines
16 KiB
TypeScript
import {
|
||
ExhibitOverlay,
|
||
GuideSteps,
|
||
GuideStepsItem,
|
||
Image,
|
||
Title,
|
||
SingleChoiceQuizPanel,
|
||
Video,
|
||
type VideoRef,
|
||
} from '@/components'
|
||
import { useNavigate, useParams } from 'react-router-dom'
|
||
import { getBackgroundImage, getStaticImage, getPublicPath } from '@/lib/utils'
|
||
import { 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 { saveZhanglepingStore, useZhanglepingStore } from '@/store/zhanglepingStore'
|
||
import { usePuzzleAnimation } from '@/contexts/PuzzleAnimationContext'
|
||
|
||
// starName 固定为 zhangleping
|
||
const starName = 'zhangleping'
|
||
|
||
// 展品 ID 与资源路径映射
|
||
const exhibitSrcMap: Record<1 | 2 | 3, string> = {
|
||
1: 'zhangleping/place3/张乐平场景3展品3',
|
||
2: 'zhangleping/place3/张乐平场景3展品2',
|
||
3: 'zhangleping/place3/张乐平场景3展品1',
|
||
}
|
||
|
||
// 阶段三:三毛之父(3 个光标)
|
||
const ZhanglepingPlace3 = () => {
|
||
const navigate = useNavigate()
|
||
const { moduleName } = useParams<{
|
||
moduleName: string
|
||
}>()
|
||
|
||
const { triggerPuzzleAnimation } = usePuzzleAnimation()
|
||
const [activeExhibitId, setActiveExhibitId] = useState<1 | 2 | 3 | null>(null)
|
||
const place = 'place3'
|
||
const { markExhibitVisited, getVisitedExhibits, setLastVisitedPlace } = useZhanglepingStore()
|
||
|
||
const visitedExhibits = getVisitedExhibits(place) as (1 | 2 | 3)[]
|
||
const visited = new Set<1 | 2 | 3>(visitedExhibits)
|
||
|
||
const [guideVisible1, setGuideVisible1] = useState(false)
|
||
const [guideStep1, setGuideStep1] = useState(0)
|
||
|
||
// 展品2对话已移除(保留占位,避免未来扩展时大改)
|
||
const [_guideVisible2, _setGuideVisible2] = useState(false)
|
||
const [_guideStep2, _setGuideStep2] = useState(0)
|
||
|
||
const [guideVisible3, setGuideVisible3] = useState(false)
|
||
const [guideStep3, setGuideStep3] = useState(0)
|
||
|
||
// 展品2:关闭后播放的视频 + 单选题
|
||
const [showVideo2, setShowVideo2] = useState(false)
|
||
const video2Ref = useRef<VideoRef>(null)
|
||
const [quiz2Visible, setQuiz2Visible] = useState(false)
|
||
const [successAlert2Visible, setSuccessAlert2Visible] = useState(false)
|
||
const [successAlert2Content, setSuccessAlert2Content] = useState('')
|
||
|
||
// 展品3对话结束后的讲解视频 + 两道单选题
|
||
const [showVideo3, setShowVideo3] = useState(false)
|
||
const video3Ref = useRef<VideoRef>(null)
|
||
const [quiz3Visible, setQuiz3Visible] = useState(false)
|
||
const [quiz3Step, setQuiz3Step] = useState<1 | 2>(1)
|
||
const [quiz3Finished, setQuiz3Finished] = useState(false)
|
||
const [successAlert3Visible, setSuccessAlert3Visible] = useState(false)
|
||
const [successAlert3Content, setSuccessAlert3Content] = useState('')
|
||
|
||
const { isEnabled } = useCursorController({
|
||
initialEnabled: [3],
|
||
dependencies: {
|
||
2: [3],
|
||
1: [2],
|
||
},
|
||
visited,
|
||
})
|
||
|
||
if (!moduleName) return null
|
||
|
||
const total = 3
|
||
const progressCount = visited.size
|
||
const completed = progressCount >= total
|
||
const subtitle = completed ? '已完成' : `${progressCount}/${total}`
|
||
|
||
const handleOpenExhibit = (id: 1 | 2 | 3) => {
|
||
setActiveExhibitId(id)
|
||
if (id === 1) {
|
||
setGuideVisible1(true)
|
||
setGuideStep1(0)
|
||
} else if (id === 2) {
|
||
// 展品2:去掉对话,打开弹窗同时直接播放讲解视频(其余逻辑保持不变)
|
||
setShowVideo3(true)
|
||
} else {
|
||
setGuideVisible3(true)
|
||
setGuideStep3(0)
|
||
}
|
||
}
|
||
|
||
return (
|
||
<div
|
||
className="w-full h-full bg-cover bg-center bg-no-repeat"
|
||
style={{
|
||
backgroundImage: getBackgroundImage(`${starName}/place3/张乐平场景3`),
|
||
}}
|
||
>
|
||
{/* 标题 */}
|
||
<Title
|
||
title="三毛之父"
|
||
subtitle={subtitle}
|
||
className="absolute top-10 left-1/2 -translate-x-1/2"
|
||
/>
|
||
|
||
<Image
|
||
className="absolute right-5 top-5"
|
||
src={getStaticImage('components/离开')}
|
||
button
|
||
onClick={() => {
|
||
if (moduleName && starName) {
|
||
navigate(`/${moduleName}/${starName}/place2`)
|
||
}
|
||
}}
|
||
/>
|
||
|
||
{/* 光标1 */}
|
||
<Cursor
|
||
className="absolute left-202 bottom-109"
|
||
size={0.8}
|
||
onClick={() => handleOpenExhibit(1)}
|
||
read={visited.has(1)}
|
||
disabled={!isEnabled(1)}
|
||
/>
|
||
{/* 光标2 */}
|
||
<Cursor
|
||
className="absolute right-178 bottom-62"
|
||
size={0.8}
|
||
onClick={() => handleOpenExhibit(2)}
|
||
read={visited.has(2)}
|
||
disabled={!isEnabled(2)}
|
||
/>
|
||
{/* 光标3 */}
|
||
<Cursor
|
||
className="absolute right-58 bottom-68"
|
||
onClick={() => handleOpenExhibit(3)}
|
||
read={visited.has(3)}
|
||
disabled={!isEnabled(3)}
|
||
/>
|
||
|
||
{/* 继续前进 */}
|
||
{completed && (
|
||
<Image
|
||
className="absolute bottom-0 right-0"
|
||
src={getStaticImage('components/place/继续前进')}
|
||
button
|
||
onClick={() => {
|
||
setLastVisitedPlace('place4')
|
||
saveZhanglepingStore()
|
||
navigate(`/${moduleName}/${starName}/place4`)
|
||
}}
|
||
/>
|
||
)}
|
||
|
||
{/* 展品1:三毛的诞生 */}
|
||
<GuideSteps
|
||
visible={guideVisible1}
|
||
onVisibleChange={setGuideVisible1}
|
||
currentStep={guideStep1}
|
||
onStepChange={setGuideStep1}
|
||
showMask={false}
|
||
highlightSelectors={[]}
|
||
zIndex={10001}
|
||
>
|
||
<GuideStepsItem
|
||
speaker="student"
|
||
audioSrc={getPublicPath('audio/zhangleping/mp3_zhangleping_12.mp3')}
|
||
>
|
||
那我明白了!这个戴上红领巾的三毛体现的应该是已经摆脱苦难的中国人民了。
|
||
</GuideStepsItem>
|
||
<GuideStepsItem
|
||
speaker="guide"
|
||
audioSrc={getPublicPath('audio/zhangleping/mp3_zhangleping_13_1.mp3')}
|
||
>
|
||
确实如此。这幅名叫《向日葵》的画选自于1950—1965年的《三毛日记》。像这样的“新三毛”形象再次发生重要转变是自1949年新中国成立之后,三毛戴上红领巾,成为新中国的少先队员。
|
||
</GuideStepsItem>
|
||
<GuideStepsItem
|
||
speaker="guide"
|
||
audioSrc={getPublicPath('audio/zhangleping/mp3_zhangleping_13_2.mp3')}
|
||
>
|
||
这一变化不仅标志着角色自身的成长,更是整个中国社会由战乱走向安定、由贫弱迈向新生的时代缩影。三毛那三根标志性的头发,仿佛穿越时光的见证者,记录下一个民族从山河破碎到国泰民安的巨大变迁。
|
||
</GuideStepsItem>
|
||
<GuideStepsItem
|
||
speaker="guide"
|
||
audioSrc={getPublicPath('audio/zhangleping/mp3_zhangleping_13_3.mp3')}
|
||
>
|
||
他的每一次蜕变,不仅是一个漫画人物的成长,更是一个国家与一代人的共同记忆。
|
||
</GuideStepsItem>
|
||
</GuideSteps>
|
||
|
||
{/* 展品2:三毛形象的特点(原对话已移除) */}
|
||
{/* 现改为:打开展品窗口的同时直接播放讲解视频 */}
|
||
|
||
{/* 展品3:三毛的社会影响 */}
|
||
<GuideSteps
|
||
visible={guideVisible3}
|
||
onVisibleChange={(visible) => {
|
||
setGuideVisible3(visible)
|
||
// 对话结束后依次进入两道题目(仅在未完成时触发)
|
||
if (!visible) {
|
||
setQuiz3Step(1)
|
||
setQuiz3Visible(true)
|
||
}
|
||
}}
|
||
currentStep={guideStep3}
|
||
onStepChange={setGuideStep3}
|
||
showMask={false}
|
||
highlightSelectors={[]}
|
||
zIndex={10001}
|
||
>
|
||
<GuideStepsItem
|
||
speaker="student"
|
||
audioSrc={getPublicPath('audio/zhangleping/mp3_zhangleping_10.mp3')}
|
||
>
|
||
小沪,我一直知道张乐平先生被称为“三毛之父”,但是今天才发现张先生笔下成长时期的三毛看起来比早期的三毛多了一些磨难,张先生塑造的这样一个小流浪儿也在跟着时代一起成长吗?
|
||
</GuideStepsItem>
|
||
<GuideStepsItem
|
||
speaker="guide"
|
||
audioSrc={getPublicPath('audio/zhangleping/mp3_zhangleping_11.mp3')}
|
||
>
|
||
你的感受很准确。三毛初次亮相于1935年的上海,他的创作者张乐平先生也开创了中国无文字儿童连环漫画之先河。
|
||
</GuideStepsItem>
|
||
</GuideSteps>
|
||
|
||
<ExhibitOverlay
|
||
visible={activeExhibitId !== null}
|
||
src={activeExhibitId ? exhibitSrcMap[activeExhibitId] : ''}
|
||
onOpen={() => {
|
||
if (activeExhibitId === 3) {
|
||
triggerPuzzleAnimation(6)
|
||
}
|
||
if (activeExhibitId === 2) {
|
||
triggerPuzzleAnimation(7)
|
||
}
|
||
if (activeExhibitId === 1) {
|
||
triggerPuzzleAnimation(8)
|
||
}
|
||
}}
|
||
onClose={() => {
|
||
if (activeExhibitId !== null) {
|
||
// 展品2:关闭后先播放一段视频,视频结束再进入题目
|
||
if (activeExhibitId === 2) {
|
||
setActiveExhibitId(null)
|
||
setShowVideo2(true)
|
||
return
|
||
}
|
||
|
||
// 展品3:需完成两道题目后才标记为已访问
|
||
if (activeExhibitId === 3) {
|
||
if (quiz3Finished) {
|
||
markExhibitVisited(place, 3)
|
||
}
|
||
} else {
|
||
markExhibitVisited(place, activeExhibitId)
|
||
}
|
||
}
|
||
setActiveExhibitId(null)
|
||
}}
|
||
/>
|
||
|
||
{/* 展品2关闭后的讲解视频 */}
|
||
{showVideo2 && (
|
||
<Video
|
||
ref={video2Ref}
|
||
src={getPublicPath('video/zhangleping/zhangleping_5.mp4')}
|
||
className="fixed inset-0 z-20000"
|
||
autoPlay
|
||
playsInline
|
||
showSkipButton
|
||
skipKey="zhangleping_place3_video1"
|
||
showReadonlyProgress
|
||
onEnded={() => {
|
||
setShowVideo2(false)
|
||
setQuiz2Visible(true)
|
||
}}
|
||
onSkip={() => {
|
||
setShowVideo2(false)
|
||
setQuiz2Visible(true)
|
||
}}
|
||
/>
|
||
)}
|
||
|
||
{/* 展品2关闭后的视频 + 单选题(如图1) */}
|
||
<ScaledPortal enabled={quiz2Visible || successAlert2Visible} zIndex={10040}>
|
||
<>
|
||
<SingleChoiceQuizPanel
|
||
visible={quiz2Visible}
|
||
zIndex={10000}
|
||
title="【单选】张乐平先生根据自己抗战前线的经历见闻创作的作品是:"
|
||
options={[
|
||
{ value: 'A', label: 'A.三毛流浪记' },
|
||
{ value: 'B', label: 'B.三毛日记' },
|
||
{ value: 'C', label: 'C.三毛从军记' },
|
||
]}
|
||
correctAnswer="C"
|
||
onWrongSelect={(value) => {
|
||
if (value === 'A') {
|
||
return '《三毛流浪记》以旧上海为背景,聚焦三毛在城市底层的流浪生活,主要反映旧社会儿童的苦难遭遇与人性冷暖。请你再想想吧!'
|
||
}
|
||
if (value === 'B') {
|
||
return '《三毛日记》以日记体形式记录三毛的日常琐事与童真视角,内容偏向儿童生活的趣味表达。请你再想想吧!'
|
||
}
|
||
return '请你再想想吧!'
|
||
}}
|
||
onSubmit={(_value, isCorrect) => {
|
||
if (!isCorrect) return
|
||
setQuiz2Visible(false)
|
||
setSuccessAlert2Content(
|
||
'《三毛从军记》是张乐平根据自己奔赴抗战前线的亲身经历与见闻创作的作品,通过三毛从军的荒诞经历,讽刺了当时的社会乱象,也传递出抗战背景下的民生百态。'
|
||
)
|
||
setSuccessAlert2Visible(true)
|
||
}}
|
||
/>
|
||
|
||
<SuccessAlert
|
||
visible={successAlert2Visible}
|
||
content={successAlert2Content}
|
||
onContinue={() => {
|
||
setSuccessAlert2Visible(false)
|
||
markExhibitVisited(place, 2)
|
||
}}
|
||
/>
|
||
</>
|
||
</ScaledPortal>
|
||
|
||
{showVideo3 && (
|
||
<ScaledPortal enabled={true} zIndex={10050}>
|
||
<Video
|
||
ref={video3Ref}
|
||
src={getPublicPath('video/zhangleping/zhangleping_01.mp4')}
|
||
className="fixed inset-0 z-20000"
|
||
autoPlay
|
||
playsInline
|
||
showSkipButton
|
||
skipKey="zhangleping_place3_video2"
|
||
showReadonlyProgress
|
||
onEnded={() => {
|
||
setShowVideo3(false)
|
||
}}
|
||
onSkip={() => {
|
||
setShowVideo3(false)
|
||
}}
|
||
/>
|
||
</ScaledPortal>
|
||
)}
|
||
|
||
{/* 展品3对话结束后的两道单选题 + 每题答对后的提示(使用 ScaledPortal) */}
|
||
<ScaledPortal enabled={quiz3Visible || successAlert3Visible} zIndex={10050}>
|
||
<>
|
||
{/* 题目1:三毛形象首次亮相年份与地点 */}
|
||
<SingleChoiceQuizPanel
|
||
visible={quiz3Visible && quiz3Step === 1}
|
||
zIndex={10000}
|
||
title="【单选】1935年,张乐平先生创作的三毛形象初次亮相于:"
|
||
options={[
|
||
{ value: 'A', label: 'A.上海' },
|
||
{ value: 'B', label: 'B.延安' },
|
||
{ value: 'C', label: 'C.浙江' },
|
||
]}
|
||
correctAnswer="A"
|
||
onWrongSelect={(value) => {
|
||
if (value === 'B') {
|
||
return '延安是当时的革命根据地,但张乐平先生的主要创作活动和延安没有关联。请你再想想吧!'
|
||
}
|
||
if (value === 'C') {
|
||
return '浙江是张乐平先生的故乡,不是三毛初次亮相的地方。请你再想想吧!'
|
||
}
|
||
return '请再仔细阅读题干中的时间和地点信息~'
|
||
}}
|
||
onSubmit={(_value, isCorrect) => {
|
||
if (!isCorrect) return
|
||
setQuiz3Visible(false)
|
||
setSuccessAlert3Content('张乐平先生创作的三毛初次亮相于1935年上海的《晨报》副刊。')
|
||
setSuccessAlert3Visible(true)
|
||
}}
|
||
/>
|
||
|
||
{/* 题目2:三毛与中国无文字儿童连环漫画 */}
|
||
<SingleChoiceQuizPanel
|
||
visible={quiz3Visible && quiz3Step === 2}
|
||
zIndex={10000}
|
||
title="【判断】张乐平先生创作的三毛开创了中国无文字儿童连环漫画之先河。"
|
||
options={[
|
||
{ value: 'A', label: 'A.正确' },
|
||
{ value: 'B', label: 'B.错误' },
|
||
]}
|
||
correctAnswer="A"
|
||
onWrongSelect={(value) => {
|
||
if (value === 'B') {
|
||
return '再想一想三毛在中国儿童漫画史上的地位吧~'
|
||
}
|
||
return '请再仔细对照题干信息选择~'
|
||
}}
|
||
onSubmit={(_value, isCorrect) => {
|
||
if (!isCorrect) return
|
||
setQuiz3Visible(false)
|
||
setSuccessAlert3Content('')
|
||
setSuccessAlert3Visible(true)
|
||
}}
|
||
/>
|
||
|
||
{/* 每题答对后的统一提示框 */}
|
||
<SuccessAlert
|
||
visible={successAlert3Visible}
|
||
title={quiz3Step === 2 ? '恭喜你,判断正确!' : '恭喜你,选择正确!'}
|
||
content={successAlert3Content}
|
||
onContinue={() => {
|
||
setSuccessAlert3Visible(false)
|
||
// 进入下一题或标记完成
|
||
setQuiz3Step((prev) => {
|
||
const next = (prev + 1) as 1 | 2 | 3
|
||
if (next === 2) {
|
||
setQuiz3Visible(true)
|
||
return 2
|
||
}
|
||
setQuiz3Finished(true)
|
||
markExhibitVisited(place, 3)
|
||
return prev
|
||
})
|
||
}}
|
||
/>
|
||
</>
|
||
</ScaledPortal>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
export default ZhanglepingPlace3
|