341 lines
14 KiB
TypeScript
341 lines
14 KiB
TypeScript
import {
|
|
ExhibitOverlay,
|
|
GuideSteps,
|
|
GuideStepsItem,
|
|
Image,
|
|
Title,
|
|
Overlay,
|
|
StudyGuideModal,
|
|
} from '@/components'
|
|
import { useNavigate, useParams } from 'react-router-dom'
|
|
import { getBackgroundImage, getPublicPath, getStaticImage } from '@/lib/utils'
|
|
import { useState, useEffect } from 'react'
|
|
import Cursor from '@/components/cursor'
|
|
import { usePuzzleAnimation } from '@/contexts/PuzzleAnimationContext'
|
|
import { useChenyunStore, saveChenyunStore } from '@/store/chenyunStore'
|
|
import { useCursorController } from '@/hooks/useCursorController'
|
|
|
|
// starName 固定为 chenyun
|
|
const starName = 'chenyun'
|
|
|
|
// 展品 ID 与资源路径映射,ID 用来做唯一标识
|
|
const exhibitSrcMap: Record<1 | 2, string> = {
|
|
1: 'chenyun/place1/场景1展品1',
|
|
2: 'chenyun/place1/场景1展品2',
|
|
// 3: "chenyun/place1/场景1展品3",
|
|
// 4: "chenyun/place1/场景1展品4",
|
|
}
|
|
const ChenyunPlace1 = () => {
|
|
const navigate = useNavigate()
|
|
const { moduleName } = useParams<{
|
|
moduleName: string
|
|
}>()
|
|
|
|
// 获取碎片动画触发方法
|
|
const { triggerPuzzleAnimation } = usePuzzleAnimation()
|
|
|
|
// 学习引导弹窗
|
|
const [showGuideModal, setShowGuideModal] = useState(true)
|
|
// 对话结束后的学习引导弹窗
|
|
const [showAfterDialogGuide, setShowAfterDialogGuide] = useState(false)
|
|
const [afterDialogGuideShown, setAfterDialogGuideShown] = useState(false)
|
|
// 学习说明对话框显示状态
|
|
const [showIntroDialog, setShowIntroDialog] = useState(false)
|
|
// 引导对话显示状态
|
|
const [visible, setVisible] = useState(false)
|
|
// 当前激活的展品 ID(只存 ID,不直接存路径,便于作为唯一标识)
|
|
const [activeExhibitId, setActiveExhibitId] = useState<1 | 2 | null>(null)
|
|
|
|
// 使用持久化的访问状态
|
|
const place = 'place1'
|
|
const { markExhibitVisited, getVisitedExhibits, setLastVisitedPlace } = useChenyunStore()
|
|
|
|
// 从 store 获取已访问的展品,转换为 Set 用于兼容现有代码
|
|
const visitedExhibits = getVisitedExhibits(place)
|
|
const visited = new Set(visitedExhibits)
|
|
|
|
// Cursor 控制器:使用依赖映射实现自动解锁
|
|
// cursor 1 初始启用,cursor 2 依赖展品1(当展品1被访问后自动解锁)
|
|
const { isEnabled } = useCursorController({
|
|
initialEnabled: [1],
|
|
dependencies: {
|
|
2: [1], // cursor 2 依赖展品 1,当展品1被访问后自动解锁
|
|
},
|
|
visited, // 传入数组,而不是 Set
|
|
})
|
|
|
|
const handleOpenExhibit = (index: 1 | 2) => {
|
|
setActiveExhibitId(index)
|
|
// ExhibitOverlay 打开时会自动通过 onOpen 通知 ResidenceLayout 执行动画
|
|
}
|
|
|
|
const handleStartLearning = () => {
|
|
setShowIntroDialog(false)
|
|
setVisible(true)
|
|
}
|
|
|
|
const progressCount = visited.size
|
|
const total = 2
|
|
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 (
|
|
<div
|
|
className="w-full h-full bg-cover bg-center bg-no-repeat"
|
|
style={{
|
|
backgroundImage: getBackgroundImage(`chenyun/place1/场景1`),
|
|
}}
|
|
>
|
|
{/* 标题 */}
|
|
<Title
|
|
title="水乡少年·艰辛成长"
|
|
subtitle={subtitle}
|
|
subtitleClassName={subtitleClassName}
|
|
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-145 top-100"
|
|
onClick={() => handleOpenExhibit(1)}
|
|
read={visited.has(1)}
|
|
disabled={!isEnabled(1)}
|
|
/>
|
|
{/* 展品2 */}
|
|
<Cursor
|
|
className="absolute left-198 top-100"
|
|
onClick={() => handleOpenExhibit(2)}
|
|
read={visited.has(2)}
|
|
disabled={!isEnabled(2)}
|
|
/>
|
|
{/* 展品3 */}
|
|
{/* <Cursor
|
|
className="absolute right-170 bottom-60"
|
|
onClick={() => handleOpenExhibit(3)}
|
|
read={visited.has(3)}
|
|
/> */}
|
|
{/* 展品4 */}
|
|
{/* <Cursor
|
|
className="absolute right-98 top-130 cursor"
|
|
onClick={() => handleOpenExhibit(4)}
|
|
read={visited.has(4)}
|
|
/> */}
|
|
|
|
{/* 继续前进 - 仅在完成所有展品后显示 */}
|
|
{completed && (
|
|
<Image
|
|
className="absolute bottom-0 right-0"
|
|
src={getStaticImage('chenyun/place1/继续前进')}
|
|
button
|
|
onClick={() => {
|
|
setLastVisitedPlace('place2')
|
|
saveChenyunStore()
|
|
navigate(`/${moduleName}/${starName}/place2`)
|
|
}}
|
|
/>
|
|
)}
|
|
|
|
{/* 遮罩层 */}
|
|
<ExhibitOverlay
|
|
visible={activeExhibitId !== null}
|
|
src={activeExhibitId ? exhibitSrcMap[activeExhibitId] : ''}
|
|
onOpen={() => {
|
|
// 示例:展品1对应碎片1,展品2对应碎片2
|
|
// 根据实际需求调用 triggerPuzzleAnimation
|
|
if (activeExhibitId === 1) {
|
|
triggerPuzzleAnimation(1)
|
|
}
|
|
}}
|
|
onClose={() => {
|
|
if (activeExhibitId !== null) {
|
|
markExhibitVisited(place, activeExhibitId)
|
|
// cursor 状态会根据 visited 自动更新,无需手动调用 enable
|
|
}
|
|
setActiveExhibitId(null)
|
|
}}
|
|
/>
|
|
|
|
{/* 学习引导词 */}
|
|
<StudyGuideModal
|
|
visible={showGuideModal}
|
|
onContinue={() => {
|
|
setShowGuideModal(false)
|
|
setVisible(true)
|
|
}}
|
|
audioSrc={getPublicPath('audio/chenyun/mp3_chenyun_02.mp3')}
|
|
ailgn="left"
|
|
>
|
|
1.场景中出现动态光点的展品表示未浏览,可通过点击光点来了解展品背后的故事,通过滚轮可以放大展品,浏览展品后蓝色动态光点会变为已浏览状态。
|
|
{'\n'}
|
|
2.在学习过程中会掉落拼图碎片,拼图碎片会自动收入拼图盒中,拼图盒可以点开查看当前拼图收集情况,点击单个拼图可以回到获得拼图的场景再次学习。
|
|
{'\n'}
|
|
3.所有动态光点的展品全部学习后才能进入下一个场景学习。
|
|
</StudyGuideModal>
|
|
|
|
{/* 学习说明对话框 */}
|
|
{showIntroDialog && (
|
|
<Overlay
|
|
selectors={[]}
|
|
visible={showIntroDialog}
|
|
maskColor="rgba(0, 0, 0, 0.6)"
|
|
zIndex={50}
|
|
>
|
|
<LearningIntroduction handleStartLearning={handleStartLearning} />
|
|
</Overlay>
|
|
)}
|
|
|
|
{/* 场景对话 */}
|
|
<GuideSteps
|
|
visible={visible}
|
|
onVisibleChange={(nextVisible) => {
|
|
setVisible(nextVisible)
|
|
if (!nextVisible && !afterDialogGuideShown) {
|
|
setAfterDialogGuideShown(true)
|
|
setShowAfterDialogGuide(true)
|
|
}
|
|
}}
|
|
showMask={false}
|
|
>
|
|
<GuideStepsItem
|
|
speaker="student"
|
|
audioSrc={getPublicPath('audio/chenyun/mp3_chenyun_03.mp3')}
|
|
>
|
|
小沪!这就是陈云小时候生活的房子吗?看起来和普通农家差不多,他童年过得艰苦吗?
|
|
</GuideStepsItem>
|
|
<GuideStepsItem
|
|
speaker="guide"
|
|
audioSrc={getPublicPath('audio/chenyun/mp3_chenyun_04.mp3')}
|
|
>
|
|
这座房子就是陈云故居,位于上海市青浦区练塘镇下塘街95号,是一座坐南朝北、面向河畔的两层楼江南民居。
|
|
</GuideStepsItem>
|
|
<GuideStepsItem speaker="guide">
|
|
实际上是陈云舅父母的家,也是少年陈云从4岁至14岁生活的地方。童年的陈云,家庭生活十分不幸。他两岁时父亲病故,四岁时,母亲也悲苦地死去。
|
|
</GuideStepsItem>
|
|
<GuideStepsItem speaker="guide">
|
|
父母去世后,由外婆抚养。六岁时,外婆去世,陈云过继给舅父母当养子。陈云从小跟舅父廖文光和舅母廖倪氏一起生活。家里靠种田和做小生意勉强维持生计。
|
|
</GuideStepsItem>
|
|
</GuideSteps>
|
|
|
|
{/* 对话结束后的学习引导 */}
|
|
<StudyGuideModal
|
|
visible={showAfterDialogGuide}
|
|
onContinue={() => setShowAfterDialogGuide(false)}
|
|
audioSrc={getPublicPath('audio/chenyun/mp3_chenyun_01.mp3')}
|
|
firstLineIndent="2em"
|
|
>
|
|
同学你好,欢迎来到“走进故居·对话”学习模块!{'\n'}
|
|
此刻你正站在陈云同志的故居堂屋里,接下来请跟着时光的脚步,
|
|
一起翻开他人生的7帧珍贵篇章:水乡少年·艰辛成长、商务启航·投身革命、
|
|
苏区淬炼·崭露头角、延安担当·经济破局、恢复时期·经济奠基、建设时期·务实兴邦、
|
|
改革浪潮·擘画实业。准备好一起走进这段滚烫的岁月了吗?
|
|
</StudyGuideModal>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
// 学习说明内容
|
|
const LearningIntroduction = ({ handleStartLearning }: { handleStartLearning: () => void }) => {
|
|
return (
|
|
<div className="fixed inset-0 z-50 flex items-center justify-center">
|
|
<div className="relative w-full h-full bg-linear-to-b from-[#f5f1e8] via-[#ede8dc] to-[#e8e0d1] shadow-[0_20px_60px_rgba(0,0,0,0.4)] border-4 border-[#d4c5a9] overflow-hidden">
|
|
{/* 内容层 */}
|
|
<div className="relative h-full w-full p-16 pb-20 overflow-hidden flex flex-col">
|
|
{/* 装饰性顶部线条 */}
|
|
<div className="absolute top-0 left-0 right-0 h-2 bg-linear-to-r from-transparent via-[#c9b99b] to-transparent"></div>
|
|
|
|
{/* 标题 */}
|
|
<div className="text-center mb-8 shrink-0">
|
|
<h1 className="text-6xl font-bold text-[#5c4a37] mb-4 tracking-wide">
|
|
走进故居-对话模块学习说明
|
|
</h1>
|
|
<div className="w-32 h-1 bg-linear-to-r from-transparent via-[#8b7355] to-transparent mx-auto"></div>
|
|
</div>
|
|
|
|
{/* 介绍文字 */}
|
|
<div className="mb-8 space-y-4 shrink-0">
|
|
<p className="text-3xl text-[#6b5d47] leading-relaxed text-center font-medium">
|
|
同学你好,欢迎来到走进故居-对话学习模块!
|
|
</p>
|
|
<p className="text-3xl text-[#6b5d47] leading-relaxed text-center">
|
|
进入陈云故居场景后,我们将通过参观陈云故居和陈云纪念馆的形式,带你游览陈云同志经历的7个阶段:
|
|
</p>
|
|
</div>
|
|
|
|
{/* 7个阶段列表 - 卡片式设计 */}
|
|
<div className="mb-8 bg-[rgba(255,255,255,0.6)] rounded-2xl p-6 border-2 border-[#d4c5a9] flex-shrink-0">
|
|
<div className="flex flex-wrap items-center justify-center gap-4 text-2xl">
|
|
{[
|
|
'水乡少年 艰辛成长',
|
|
'商务启航 投身革命',
|
|
'苏区淬炼 崭露头角',
|
|
'延安担当 经济破局',
|
|
'恢复时期 经济奠基',
|
|
'建设时期 务实兴邦',
|
|
'改革浪潮 擘画实业',
|
|
].map((stage, index, array) => (
|
|
<div key={index} className="flex items-center">
|
|
<span className="px-4 py-2 bg-[rgba(139,115,85,0.15)] rounded-lg text-[#5c4a37] font-semibold">
|
|
{stage}
|
|
</span>
|
|
{index < array.length - 1 && (
|
|
<span className="mx-2 text-[#8b7355] font-bold text-3xl">→</span>
|
|
)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{/* 功能说明列表 */}
|
|
<div className="mb-8 space-y-4 flex-1 overflow-hidden">
|
|
<div className="h-full overflow-hidden">
|
|
{[
|
|
'走进故居-对话学习模块会包含多个场景,场景中有蓝色动态光点的展品,表示未浏览,可通过点击光点来学习展品背后的故事,学习展品后蓝色动态光点会变为绿色静态光点,表示已浏览。',
|
|
'场景中的展品有可能会包含一个或多个拼图,游览过程中如获得拼图,会自动掉落拼图并收入拼图盒中,拼图盒可以点开查看当前拼图收集情况,点击单个拼图可以回到获得拼图的场景再次学习。',
|
|
'所有有蓝色动态光点的展品全部学习后才能进入下一个场景进行学习。',
|
|
'每个阶段学习过后,会有学习回顾环节,以导图的形式总结了阶段的学习内容。',
|
|
].map((feature, index) => (
|
|
<div
|
|
key={index}
|
|
className="flex items-start gap-6 bg-[rgba(255,255,255,0.4)] rounded-xl p-6 border-l-4 border-[#8b7355] mb-4"
|
|
>
|
|
<span className="shrink-0 w-12 h-12 bg-linear-to-br from-[#8b7355] to-[#6b5d47] text-white text-3xl font-bold rounded-full flex items-center justify-center shadow-md">
|
|
{index + 1}
|
|
</span>
|
|
<p className="text-2xl text-[#5c4a37] leading-relaxed flex-1 pt-1">{feature}</p>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{/* 开始学习按钮 */}
|
|
<div className="flex justify-center shrink-0">
|
|
<button
|
|
onClick={handleStartLearning}
|
|
className="relative cursor-pointer px-20 py-5 bg-linear-to-r from-[#8b7355] to-[#6b5d47] text-white text-4xl font-bold rounded-2xl shadow-[0_8px_20px_rgba(139,115,85,0.4)] hover:shadow-[0_12px_30px_rgba(139,115,85,0.6)] hover:from-[#9b8365] hover:to-[#7b6d57] transition-all duration-300 active:scale-95 border-2 border-[#a08f75]"
|
|
>
|
|
<span className="relative z-10">开始学习</span>
|
|
<div className="absolute inset-0 bg-linear-to-r from-transparent via-white to-transparent opacity-0 hover:opacity-20 rounded-2xl transition-opacity duration-300"></div>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default ChenyunPlace1
|