feat: add puzzle residence configuration and reflection progress logic

- Introduced `puzzleResidences.ts` to define puzzle residence configurations including properties like display name, position, and puzzle availability.
- Added `reflectionProgress.ts` to manage badge unlocking logic based on place completion, including functions to determine unlocked badges and video sources for scenes.
This commit is contained in:
zhongyidian committed 2026-04-27 11:49:30 +08:00
1 parent e0fa47d21a
commit f5f36f75b5
153 files changed
+3846 -2352

No files matched your search

+28 -40
View File
@@ -10,21 +10,22 @@ import {
} from '@/components'
import { useNavigate, useParams } from 'react-router-dom'
import { getBackgroundImage, getStaticImage, getPublicPath } from '@/lib/utils'
import { useMemo, useRef, useState } from 'react'
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展品1',
1: 'zhangleping/place3/张乐平场景3展品3',
2: 'zhangleping/place3/张乐平场景3展品2',
3: 'zhangleping/place3/张乐平场景3展品3',
3: 'zhangleping/place3/张乐平场景3展品1',
}
// 阶段三:三毛之父(3 个光标)
@@ -34,16 +35,13 @@ const ZhanglepingPlace3 = () => {
moduleName: string
}>()
const { triggerPuzzleAnimation } = usePuzzleAnimation()
const [activeExhibitId, setActiveExhibitId] = useState<1 | 2 | 3 | null>(null)
// 持久化接入
const markVisited = useZhanglepingStore((s) => s.markExhibitVisited)
const getVisited = useZhanglepingStore((s) => s.getVisitedExhibits)
const setLastVisitedPlace = useZhanglepingStore((s) => s.setLastVisitedPlace)
const persistedVisited = useMemo(
() => new Set(getVisited('place3') as (1 | 2 | 3)[]),
[getVisited]
)
const [visited, setVisited] = useState<Set<1 | 2 | 3>>(() => new Set(persistedVisited))
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)
@@ -121,7 +119,7 @@ const ZhanglepingPlace3 = () => {
button
onClick={() => {
if (moduleName && starName) {
navigate(`/${moduleName}/${starName}/home`)
navigate(`/${moduleName}/${starName}/place2`)
}
}}
/>
@@ -231,6 +229,17 @@ const ZhanglepingPlace3 = () => {
<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:关闭后先播放一段视频,视频结束再进入题目
@@ -243,20 +252,10 @@ const ZhanglepingPlace3 = () => {
// 展品3:需完成两道题目后才标记为已访问
if (activeExhibitId === 3) {
if (quiz3Finished) {
setVisited((prev) => {
const next = new Set(prev)
next.add(3)
markVisited('place3', 3)
return next
})
markExhibitVisited(place, 3)
}
} else {
setVisited((prev) => {
const next = new Set(prev)
next.add(activeExhibitId)
markVisited('place3', activeExhibitId)
return next
})
markExhibitVisited(place, activeExhibitId)
}
}
setActiveExhibitId(null)
@@ -320,23 +319,17 @@ const ZhanglepingPlace3 = () => {
content={successAlert2Content}
onContinue={() => {
setSuccessAlert2Visible(false)
// 答题成功后标记展品2为已访问
setVisited((prev) => {
const next = new Set(prev)
next.add(2)
return next
})
markExhibitVisited(place, 2)
}}
/>
</>
</ScaledPortal>
{/* 展品2对话结束后的讲解视频 */}
{showVideo3 && (
<ScaledPortal enabled={true} zIndex={10050}>
<Video
ref={video3Ref}
src={getPublicPath('video/zhangleping/zhangleping_5.mp4')}
src={getPublicPath('video/zhangleping/zhangleping_01.mp4')}
className="fixed inset-0 z-20000"
autoPlay
playsInline
@@ -409,6 +402,7 @@ const ZhanglepingPlace3 = () => {
{/* 每题答对后的统一提示框 */}
<SuccessAlert
visible={successAlert3Visible}
title={quiz3Step === 2 ? '恭喜你,判断正确!' : '恭喜你,选择正确!'}
content={successAlert3Content}
onContinue={() => {
setSuccessAlert3Visible(false)
@@ -419,14 +413,8 @@ const ZhanglepingPlace3 = () => {
setQuiz3Visible(true)
return 2
}
// 两题全部完成
setQuiz3Finished(true)
// 标记展品3为已访问
setVisited((prevVisited) => {
const nextVisited = new Set(prevVisited)
nextVisited.add(3)
return nextVisited
})
markExhibitVisited(place, 3)
return prev
})
}}