Files
shanghai-residences/src/pages/nieer/place/place6.tsx
T
zhongyidianandClaude Opus 4.7 6d40e19346 feat: update residence learning flows
Commit non-media application and asset changes while leaving audio, video, and environment files out of version control.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 10:15:26 +08:00

184 lines
6.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import {
EndingOverlay,
ExhibitOverlay,
GuideSteps,
GuideStepsItem,
Image,
Title,
} from '@/components'
import Cursor from '@/components/cursor'
import { getBackgroundImage, getPublicPath, getStaticImage } from '@/lib/utils'
import { useRef, useState } from 'react'
import { useNavigate, useParams } from 'react-router-dom'
import { useNieerStore, saveNieerStore } from '@/store/nieerStore'
import { useCursorController } from '@/hooks/useCursorController'
import { usePuzzleAnimation } from '@/contexts/PuzzleAnimationContext'
import { setCourseTimeProgress } from '@/lib/utils/api-common'
const NieerPlace6 = () => {
const navigate = useNavigate()
const { moduleName } = useParams<{ moduleName: string }>()
const { triggerPuzzleAnimation } = usePuzzleAnimation()
const starName = 'nieer'
const place = 'place6'
const { markExhibitVisited, getVisitedExhibits } = useNieerStore()
const [activeExhibitId, setActiveExhibitId] = useState<1 | 2 | null>(null)
const [guide1Visible, setGuide1Visible] = useState(false)
const [guide1Step, setGuide1Step] = useState(0)
const total = 2
const visitedExhibits = getVisitedExhibits(place)
const visited = new Set(visitedExhibits)
const progressCount = visited.size
const completed = progressCount >= total
const [endingVisible, setEndingVisible] = useState(false)
// 是否已展示过结束语(用于控制“离开场景”按钮出现时机)
// 若用户刷新页面且已完成,则默认视为结束语已展示,避免卡住无法离开
const [endingShown, setEndingShown] = useState(() => completed)
const endingShownRef = useRef(false)
const subtitle = completed ? '已完成' : `${progressCount}/${total}`
const { isEnabled } = useCursorController({
initialEnabled: [2],
dependencies: {
1: [2],
},
visited: visitedExhibits,
})
if (!moduleName) return null
const exhibitSrcMap: Record<1 | 2, string> = {
1: 'nieer/place6/场景6展品1',
2: 'nieer/place6/场景6展品2',
}
const handleOpenExhibit = (id: 1 | 2) => {
setActiveExhibitId(id)
if (id === 1) {
setGuide1Step(0)
setGuide1Visible(true)
}
}
return (
<div
className="w-full h-full bg-cover bg-center bg-no-repeat"
style={{
backgroundImage: getBackgroundImage(`${starName}/${place}/场景6`),
}}
>
<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={() => navigate(`/${moduleName}/${starName}/place5`)}
/>
<Cursor
className="absolute left-90 bottom-130"
onClick={() => handleOpenExhibit(1)}
read={visited.has(1)}
disabled={!isEnabled(1)}
/>
<Cursor
className="absolute right-240 bottom-135"
onClick={() => handleOpenExhibit(2)}
read={visited.has(2)}
disabled={!isEnabled(2)}
/>
{/* 最后一个场景:完成后不显示“前进”按钮 */}
{completed && endingShown && !endingVisible && (
<Image
className="absolute bottom-0 right-0"
src={getStaticImage('components/place/离开场景')}
button
onClick={() => {
saveNieerStore()
setCourseTimeProgress(13)
navigate(`/${moduleName}/${starName}/home`)
}}
/>
)}
<ExhibitOverlay
visible={activeExhibitId !== null}
src={activeExhibitId ? exhibitSrcMap[activeExhibitId] : ''}
onOpen={() => {
if (activeExhibitId === 1) {
triggerPuzzleAnimation(13)
}
if (activeExhibitId === 2) {
triggerPuzzleAnimation(12)
}
}}
onClose={() => {
const closingId = activeExhibitId
setActiveExhibitId(null)
setGuide1Visible(false)
if (closingId !== null) {
markExhibitVisited(place, closingId)
saveNieerStore()
// 仅在“关闭展品”这一刻触发结束语:当本次关闭导致完成时,弹出 EndingOverlay
const nextVisitedSize = visited.has(closingId) ? visited.size : visited.size + 1
const willComplete = nextVisitedSize >= total
if (willComplete && !endingShownRef.current) {
endingShownRef.current = true
setEndingShown(true)
setEndingVisible(true)
}
}
}}
/>
{/* 光标1对话(图1) */}
<GuideSteps
visible={guide1Visible}
currentStep={guide1Step}
onStepChange={setGuide1Step}
onVisibleChange={setGuide1Visible}
showMask={false}
highlightSelectors={[]}
zIndex={10001}
>
<GuideStepsItem speaker="student" audioSrc={getPublicPath('audio/nieer/mp3_nieer_25.mp3')}>
小沪,这些不同场景下奏响国歌的画面太让人感动了!在和平年代,这首歌还有什么特别的意义呢?
</GuideStepsItem>
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/nieer/mp3_nieer_26_1.mp3')}>
和平年代里,它的精神力量依然不减。你看这张中国女排奥运夺金的照片,国歌奏响时,是民族自豪的宣告;神舟飞船升空时的国歌,是航天人攻坚克难的礼赞。
</GuideStepsItem>
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/nieer/mp3_nieer_26_2.mp3')}>
它就像一根精神纽带,无论在高光时刻还是艰难关头,都能凝聚起全国人民的力量,提醒我们不忘历史、砥砺前行。
</GuideStepsItem>
</GuideSteps>
{/* completed 后的结束语:统一使用 EndingOverlay */}
<EndingOverlay
visible={endingVisible}
audioSrc={getPublicPath('audio/nieer/mp3_nieer_27.mp3')}
text={
'从民族危机中觉醒的热血青年,到《义勇军进行曲》的谱曲者,聂耳用音符奏响了“救亡图存”的时代强音。' +
'而《义勇军进行曲》的影响远不止于此,接下来,请你化身国歌时空旅行者,踏入6个散落于岁月中的故事现场,' +
'一路聆听、一路共鸣,亲身体验这首旋律如何跨越时空,成为永远激荡在国人心中的精神号角吧!'
}
animationSpeed={200}
onContinue={() => setEndingVisible(false)}
/>
</div>
)
}
export default NieerPlace6