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:
1 parent
e0fa47d21a
commit
f5f36f75b5
153 files changed
+3846
-2352
No files matched your search
@@ -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
|
||||
})
|
||||
}}
|
||||
|
||||
Reference in new issue
Block a user