- 更新展品图片(鲁迅/毛泽东/孙中山/黄炎培/聂耳/张乐平故居) - 新增点击查看图标、报纸素材 - 优化视频组件、拼图组件、展品组件 - 更新陈云音频资源 - 新增输入验证文档和工具函数 - 组件布局和动画优化 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
320 lines
11 KiB
TypeScript
320 lines
11 KiB
TypeScript
import {
|
||
ExhibitOverlay,
|
||
GuideSteps,
|
||
GuideStepsItem,
|
||
Image,
|
||
StudyGuideModal,
|
||
Title,
|
||
SelectFillQuizPanel,
|
||
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 type { SelectFillResult } from '@/components/layout/quiz'
|
||
import { saveMaozedongStore, useMaozedongStore } from '@/store/maozedongStore'
|
||
import { usePuzzleAnimation } from '@/contexts/PuzzleAnimationContext'
|
||
|
||
// starName 固定为 maozedong
|
||
const starName = 'maozedong'
|
||
|
||
// 展品 ID 与资源路径映射,ID 用来做唯一标识
|
||
const exhibitSrcMap: Record<1 | 2, string> = {
|
||
1: 'maozedong/place1/场景1展品1',
|
||
2: 'maozedong/place1/场景1展品2',
|
||
}
|
||
|
||
const MaozedongPlace1 = () => {
|
||
const navigate = useNavigate()
|
||
const { moduleName } = useParams<{
|
||
moduleName: string
|
||
}>()
|
||
|
||
// 获取碎片动画触发方法
|
||
const { triggerPuzzleAnimation } = usePuzzleAnimation()
|
||
|
||
// 接入持久化:记录本场景 place1 的已访问展品与断点
|
||
const markVisited = useMaozedongStore((s) => s.markExhibitVisited)
|
||
const getVisited = useMaozedongStore((s) => s.getVisitedExhibits)
|
||
const setLastVisitedPlace = useMaozedongStore((s) => s.setLastVisitedPlace)
|
||
const persistedVisited = useMemo(() => new Set(getVisited('place1') as (1 | 2)[]), [getVisited])
|
||
|
||
// 学习引导弹窗
|
||
const [showGuideModal, setShowGuideModal] = useState(true)
|
||
|
||
// 对话结束后的学习引导弹窗
|
||
const [showAfterDialogGuide, setShowAfterDialogGuide] = useState(false)
|
||
|
||
// 展品2的对话引导状态
|
||
const [guideVisible2, setGuideVisible2] = useState(false)
|
||
const [guideStep2, setGuideStep2] = useState(0)
|
||
|
||
// 当前激活的展品 ID
|
||
const [activeExhibitId, setActiveExhibitId] = useState<1 | 2 | null>(null)
|
||
// 已访问的展品(初始化自 store;随后每次关闭展品时写回)
|
||
const [visited, setVisited] = useState<Set<1 | 2>>(() => new Set(persistedVisited))
|
||
|
||
// 展品2关闭后播放的视频 + 选择填空题
|
||
const [showVideo2, setShowVideo2] = useState(false)
|
||
const video2Ref = useRef<VideoRef>(null)
|
||
const [quizVisible, setQuizVisible] = useState(false)
|
||
const [successAlertVisible, setSuccessAlertVisible] = useState(false)
|
||
|
||
// Cursor 顺序控制:1 -> 2
|
||
const { isEnabled } = useCursorController({
|
||
initialEnabled: [2],
|
||
dependencies: {
|
||
1: [2],
|
||
},
|
||
visited,
|
||
})
|
||
|
||
const handleOpenExhibit = (id: 1 | 2) => {
|
||
setActiveExhibitId(id)
|
||
// 根据展品ID显示对应的对话
|
||
if (id === 1) {
|
||
setGuideVisible2(true)
|
||
setGuideStep2(0)
|
||
}
|
||
}
|
||
|
||
// 监听展品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 (
|
||
<div
|
||
className="w-full h-full bg-cover bg-center bg-no-repeat"
|
||
style={{
|
||
backgroundImage: getBackgroundImage(`${starName}/place1/场景1`),
|
||
}}
|
||
>
|
||
{/* 标题 */}
|
||
<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}/home`)
|
||
}
|
||
}}
|
||
/>
|
||
|
||
{/* 光标1 */}
|
||
<Cursor
|
||
className="absolute left-106 bottom-150"
|
||
onClick={() => handleOpenExhibit(1)}
|
||
read={visited.has(1)}
|
||
disabled={!isEnabled(1)}
|
||
/>
|
||
{/* 光标2 */}
|
||
<Cursor
|
||
className="absolute left-170 bottom-135"
|
||
onClick={() => handleOpenExhibit(2)}
|
||
read={visited.has(2)}
|
||
disabled={!isEnabled(2)}
|
||
/>
|
||
|
||
{/* 继续前进 - 仅在完成所有展品后显示 */}
|
||
{completed && (
|
||
<Image
|
||
className="absolute bottom-0 right-140"
|
||
src={getStaticImage(`${starName}/place1/场景1继续前进`)}
|
||
button
|
||
onClick={() => {
|
||
setLastVisitedPlace('place2')
|
||
saveMaozedongStore()
|
||
navigate(`/${moduleName}/${starName}/place2`)
|
||
}}
|
||
/>
|
||
)}
|
||
|
||
{/* 展品2的对话引导 */}
|
||
<GuideSteps
|
||
visible={guideVisible2}
|
||
onVisibleChange={handleGuideVisibleChange2}
|
||
currentStep={guideStep2}
|
||
onStepChange={handleGuideStepChange2}
|
||
showMask={false}
|
||
highlightSelectors={[]}
|
||
zIndex={10001}
|
||
>
|
||
<GuideStepsItem
|
||
speaker="student"
|
||
audioSrc={getPublicPath('audio/maozedong/mp3_mzd_03.mp3')}
|
||
>
|
||
那参加中共一大时,毛主席对未来有什么想法吗?
|
||
</GuideStepsItem>
|
||
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/maozedong/mp3_mzd_04.mp3')}>
|
||
当时全国只有50多个党员,毛主席所在的湖南也才几个。但他觉得,重要的是把马克思主义和中国的实际情况结合起来。就像种田,种子再好,也得看土壤——中国的
|
||
“土壤”就是农民占大多数,这是和苏联不一样的地方。
|
||
</GuideStepsItem>
|
||
</GuideSteps>
|
||
|
||
{/* 展品遮罩层 */}
|
||
<ExhibitOverlay
|
||
visible={activeExhibitId !== null}
|
||
src={activeExhibitId ? exhibitSrcMap[activeExhibitId] : ''}
|
||
onOpen={() => {
|
||
if (activeExhibitId === 2) {
|
||
triggerPuzzleAnimation(1)
|
||
}
|
||
if (activeExhibitId === 1) {
|
||
triggerPuzzleAnimation(2)
|
||
}
|
||
}}
|
||
onClose={() => {
|
||
if (activeExhibitId === 2) {
|
||
setActiveExhibitId(null)
|
||
setShowVideo2(true)
|
||
return
|
||
}
|
||
|
||
if (activeExhibitId !== null) {
|
||
setVisited((prev) => {
|
||
const next = new Set(prev)
|
||
next.add(activeExhibitId)
|
||
// 写回持久化
|
||
markVisited('place1', activeExhibitId)
|
||
return next
|
||
})
|
||
}
|
||
setActiveExhibitId(null)
|
||
}}
|
||
/>
|
||
|
||
{/* 展品2关闭后播放的视频 */}
|
||
{showVideo2 && (
|
||
<Video
|
||
ref={video2Ref}
|
||
src={getPublicPath('video/maozedong/mzd_00.mp4')}
|
||
className="fixed inset-0 z-20000"
|
||
autoPlay
|
||
playsInline
|
||
showSkipButton
|
||
skipKey="maozedong_place1_video"
|
||
showReadonlyProgress
|
||
onEnded={() => {
|
||
setShowVideo2(false)
|
||
setQuizVisible(true)
|
||
}}
|
||
onSkip={() => {
|
||
setShowVideo2(false)
|
||
setQuizVisible(true)
|
||
}}
|
||
/>
|
||
)}
|
||
|
||
{/* 展品2对话结束后的选择填空题 + 成功提示(使用 ScaledPortal) */}
|
||
<ScaledPortal enabled={quizVisible || successAlertVisible} zIndex={10050}>
|
||
<>
|
||
<SelectFillQuizPanel
|
||
visible={quizVisible}
|
||
titlePrefix="【填空】毛泽东主席认为,到了"
|
||
titleSuffix="年夏天,在理论上,而且在某种程度的行动上,他已成为一个马克思主义者了。"
|
||
blanks={[{ id: 'd1' }, { id: 'd2' }, { id: 'd3' }, { id: 'd4' }]}
|
||
options={[
|
||
{ value: '0', label: '0' },
|
||
{ value: '1', label: '1' },
|
||
{ value: '2', label: '2' },
|
||
{ value: '3', label: '3' },
|
||
{ value: '4', label: '4' },
|
||
{ value: '5', label: '5' },
|
||
{ value: '6', label: '6' },
|
||
{ value: '7', label: '7' },
|
||
{ value: '8', label: '8' },
|
||
{ value: '9', label: '9' },
|
||
]}
|
||
correctAnswers={{
|
||
d1: '1',
|
||
d2: '9',
|
||
d3: '2',
|
||
d4: '0',
|
||
}}
|
||
errorMessage="1920年5月5日到7月初,在上海居住的2个月期间,毛主席的思想完成了向马克思主义者的转变。请你再想想吧!"
|
||
onSubmit={(_results: SelectFillResult[], isCorrect: boolean) => {
|
||
if (isCorrect) {
|
||
setQuizVisible(false)
|
||
setSuccessAlertVisible(true)
|
||
}
|
||
}}
|
||
onError={(_error: string) => {
|
||
// 错误时仅提示,再让学生重做
|
||
}}
|
||
submitText="确定"
|
||
zIndex={10000}
|
||
/>
|
||
|
||
<SuccessAlert
|
||
visible={successAlertVisible}
|
||
onContinue={() => {
|
||
setSuccessAlertVisible(false)
|
||
setVisited((prev) => {
|
||
const next = new Set(prev)
|
||
next.add(2)
|
||
markVisited('place1', 2)
|
||
return next
|
||
})
|
||
}}
|
||
/>
|
||
</>
|
||
</ScaledPortal>
|
||
|
||
{/* 学习引导词 */}
|
||
<StudyGuideModal
|
||
visible={showGuideModal}
|
||
audioSrc={getPublicPath('audio/maozedong/mp3_mzd_02.mp3')}
|
||
onContinue={() => {
|
||
setShowGuideModal(false)
|
||
setShowAfterDialogGuide(true)
|
||
}}
|
||
ailgn="left"
|
||
>
|
||
1.场景中出现动态光点的展品表示未浏览,可通过点击光点来了解展品背后的故事,通过滚轮可以放大展品,浏览展品后蓝色动态光点会变为已浏览状态。
|
||
{'\n'}
|
||
2.在学习过程中会掉落拼图碎片,拼图碎片会自动收入拼图盒中,拼图盒可以点开查看当前拼图收集情况,点击单个拼图可以回到获得拼图的场景再次学习。
|
||
{'\n'}
|
||
3.所有动态光点的展品全部学习后才能进入下一个场景学习。
|
||
</StudyGuideModal>
|
||
|
||
{/* 对话结束后的学习引导 */}
|
||
<StudyGuideModal
|
||
visible={showAfterDialogGuide}
|
||
audioSrc={getPublicPath('audio/maozedong/mp3_mzd_01.mp3')}
|
||
onContinue={() => setShowAfterDialogGuide(false)}
|
||
firstLineIndent="2em"
|
||
>
|
||
{`同学你好,欢迎来到“走进故居·对话”学习模块!
|
||
此刻你正站在上海毛泽东旧居陈列馆的门口,接下来请跟着时光的脚步,一同回望毛泽东主席人生中4段风云激荡的岁月:摸索方向·确立信仰、播撒红色火种、吹响合作号角、唤起工农群众。准备好推开这扇门,走进那段滚烫的峥嵘岁月了吗?`}
|
||
</StudyGuideModal>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
export default MaozedongPlace1
|