feat: add progression logic for puzzle residences

- Implemented functions to manage puzzle progress including checking if residences are started, complete, and unlocked.
- Added utility functions to retrieve piece IDs based on residence and route.
- Introduced a new TypeScript file for progression management.
- Integrated puzzle data configuration and residence data for seamless progression tracking.
This commit is contained in:
zhongyidian committed 2026-05-01 21:19:25 +08:00
1 parent 5a1c3cc368
commit 284302341c
72 files changed
+1104 -517

No files matched your search

+34 -6
View File
@@ -1,6 +1,9 @@
import { useEffect } from 'react'
import { getBackgroundImage, getStaticImage } from '@/lib/utils'
import { useNavigate, useParams } from 'react-router-dom'
import { Image } from '@/components'
import { usePuzzleStore } from '@/store/puzzleStore'
import { getResidenceStageUnlocks, isResidenceUnlocked } from '@/config/progression'
import { useMaozedongStore } from '@/store/maozedongStore'
const Maozedong = () => {
@@ -13,6 +16,17 @@ const Maozedong = () => {
const starName = 'maozedong'
const lastVisitedPlace = useMaozedongStore((state) => state.lastVisitedPlace)
const puzzles = usePuzzleStore((state) => state.puzzles)
const isLoaded = usePuzzleStore((state) => state.isLoaded)
const { dialogueUnlocked, reflectionUnlocked, internalizationUnlocked } =
getResidenceStageUnlocks(starName, puzzles)
useEffect(() => {
if (isLoaded && !isResidenceUnlocked(starName, puzzles)) {
navigate('/menu', { replace: true })
}
}, [isLoaded, navigate, puzzles, starName])
if (!moduleName) return null
return (
@@ -31,19 +45,33 @@ const Maozedong = () => {
<div className="absolute right-20 top-75 flex flex-col space-y-10">
<Image
button
button={dialogueUnlocked}
className={dialogueUnlocked ? undefined : 'opacity-50 grayscale cursor-not-allowed'}
src={getStaticImage('star/一-解锁')}
onClick={() => navigate(`/${moduleName}/${starName}/${lastVisitedPlace || 'place1'}`)}
onClick={() => {
if (!dialogueUnlocked) return
navigate(`/${moduleName}/${starName}/${lastVisitedPlace || 'place1'}`)
}}
/>
<Image
button
button={reflectionUnlocked}
className={reflectionUnlocked ? undefined : 'opacity-50 grayscale cursor-not-allowed'}
src={getStaticImage('star/二-解锁')}
onClick={() => navigate(`/${moduleName}/${starName}/reflection`)}
onClick={() => {
if (!reflectionUnlocked) return
navigate(`/${moduleName}/${starName}/reflection`)
}}
/>
<Image
button
button={internalizationUnlocked}
className={
internalizationUnlocked ? undefined : 'opacity-50 grayscale cursor-not-allowed'
}
src={getStaticImage('star/三-解锁')}
onClick={() => navigate(`/${moduleName}/${starName}/internalization`)}
onClick={() => {
if (!internalizationUnlocked) return
navigate(`/${moduleName}/${starName}/internalization`)
}}
/>
</div>
</div>
+2 -1
View File
@@ -386,6 +386,7 @@ const MaozedongInternalization = () => {
{/* 过渡语 */}
<Narration
visible={showTransition}
audioSrc={getPublicPath('audio/maozedong/mp3_mzd_27.mp3')}
onClose={() => {
setShowTransition(false)
setIntroDialogVisible(true)
@@ -464,7 +465,7 @@ const MaozedongInternalization = () => {
speaker="student"
audioSrc={getPublicPath('audio/maozedong/mp3_mzd_24.mp3')}
>
关于“实事求是、群众路线、独立自主”这十二个字我还是有些不理解,总感觉这十二个字离我们很遥远。
小沪,关于“实事求是、群众路线、独立自主”这十二个字我还是有些不理解,总感觉这十二个字离我们很遥远。
</GuideStepsItem>
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/maozedong/mp3_mzd_25.mp3')}>
“实事求是、群众路线、独立自主”这十二个字,可以说是中国共产党人认识世界、改造世界的根本方法与价值遵循。今天我们有幸邀请到了华东师范大学马克思主义学院马立政副教授。关于你的问题,我想马教授会给你指点迷津!
+25 -21
View File
@@ -10,7 +10,7 @@ import { SuccessAlert } from '@/components/layout'
import { usePuzzleAnimation } from '@/contexts/PuzzleAnimationContext'
import { cn, getStaticImage } from '@/lib/utils'
import { getPublicPath } from '@/lib/utils'
import { useMemo, useRef, useState } from 'react'
import { useCallback, useMemo, useRef, useState } from 'react'
import { useNavigate } from 'react-router-dom'
// 光标组件 - 带旋转动画
@@ -222,17 +222,36 @@ const Home = () => {
const bgmRef = useRef<HTMLAudioElement>(null)
const videoRef = useRef<VideoRef>(null)
const navigate = useNavigate()
const [showGuideModal, setShowGuideModal] = useState(true)
const [endingVisible, setEndingVisible] = useState(false)
const moduleName = 'maozedong'
const playBGM = () => {
const playBGM = useCallback(() => {
bgmRef.current?.play().catch(() => {})
}
}, [])
const anchors: VideoAnchor[] = useMemo(
() => [
{
id: 'guide-modal',
time: 5,
afterResume: 'hide',
render: ({ resume }) => (
<StudyGuideModal
visible
audioSrc={getPublicPath('audio/maozedong/mp3_mzd_22.mp3')}
onContinue={() => {
playBGM()
resume()
}}
firstLineIndent="2em"
>
{`同学你好,欢迎来到“跨越时空·体悟”学习模块!
请登陆“时空回溯・亲历时代”体验仓,下一站:1924年,上海。
1924年的上海,革命浪潮暗涌,中共四大筹备工作关乎革命前途。无产阶级领导权、工农联盟等核心议题亟待明确,毛泽东同志在筹备关键节点面临诸多重要抉择。请你化身历史见证者,站在毛主席的视角做出选择,解锁四大筹备的关键密码,感受革命先辈的坚定与智慧!完成三次选择后,可进入下一阶段的学习。时空门已开,请做好准备迎接考验吧!`}
</StudyGuideModal>
),
},
{
id: 'anchor-0',
time: 3,
@@ -461,7 +480,7 @@ const Home = () => {
render: ({ resume, unmute }) => <EndingQuizCursor resume={resume} unmute={unmute} />,
},
],
[triggerPuzzleAnimation]
[playBGM, triggerPuzzleAnimation]
)
return (
@@ -472,7 +491,7 @@ const Home = () => {
src={getPublicPath('video/maozedong/mzd_7_8_9_tiwu.mp4')}
className="w-full h-full aspect-video"
anchors={anchors}
autoPlay={false}
autoPlay
muted
playsInline
onEnded={() => {
@@ -480,21 +499,6 @@ const Home = () => {
}}
/>
<StudyGuideModal
visible={showGuideModal}
audioSrc={getPublicPath('audio/maozedong/mp3_mzd_22.mp3')}
onContinue={() => {
setShowGuideModal(false)
playBGM()
videoRef.current?.play()
}}
firstLineIndent="2em"
>
{`同学你好,欢迎来到“跨越时空·体悟”学习模块!
请登陆“时空回溯・亲历时代”体验仓,下一站:1924年,上海。
1924年的上海,革命浪潮暗涌,中共四大筹备工作关乎革命前途。无产阶级领导权、工农联盟等核心议题亟待明确,毛泽东同志在筹备关键节点面临诸多重要抉择。请你化身历史见证者,站在毛主席的视角做出选择,解锁四大筹备的关键密码,感受革命先辈的坚定与智慧!完成三次选择后,可进入下一阶段的学习。时空门已开,请做好准备迎接考验吧!`}
</StudyGuideModal>
<EndingOverlay
visible={endingVisible}
audioSrc={getPublicPath('audio/maozedong/mp3_mzd_23.mp3')}