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

+84 -27
View File
@@ -1,11 +1,14 @@
import { getBackgroundImage, getStaticImage } from '@/lib/utils'
import { useNavigate, useParams } from 'react-router-dom'
import { Image } from '@/components'
import { isResidenceStarted, isResidenceUnlocked } from '@/config/progression'
import { usePuzzleStore } from '@/store/puzzleStore'
//英文命名 "革命先驱" | "文化名人" | "艺术名家" | "经济巨擘"
type ModuleName = 'revolutionary' | 'cultural' | 'artistic' | 'economic'
type EntranceActionState = 'locked' | 'unvisited' | 'visited'
type EntranceSide = 'left' | 'right'
interface Star {
image: string
name: string
starName: string
}
@@ -19,6 +22,36 @@ interface EntranceBackground {
}
}
const entranceActionConfig: Record<
EntranceActionState,
{
image: string
position: Record<EntranceSide, string>
}
> = {
locked: {
image: 'entrance/暂未解锁',
position: {
left: 'absolute left-95 bottom-58',
right: 'absolute right-106 bottom-58',
},
},
unvisited: {
image: 'entrance/点击前往浏览',
position: {
left: 'absolute left-90.5 bottom-37',
right: 'absolute right-90.5 bottom-37',
},
},
visited: {
image: 'entrance/该故居已访问',
position: {
left: 'absolute left-95 bottom-58',
right: 'absolute right-102 bottom-58',
},
},
}
/**
* 故居入口页
* 根据模块名称显示不同的背景图
@@ -34,12 +67,10 @@ const EntranceBackgroundMap: Record<ModuleName, EntranceBackground> = {
backgroundImage: getBackgroundImage('entrance/革命先驱'),
Star: {
Star1: {
image: getStaticImage('entrance/毛泽东故居入口'),
name: '毛泽东',
starName: 'maozedong',
},
Star2: {
image: getStaticImage('entrance/孙中山故居入口-解锁'),
name: '孙中山',
starName: 'sunzhongshan',
},
@@ -49,12 +80,10 @@ const EntranceBackgroundMap: Record<ModuleName, EntranceBackground> = {
backgroundImage: getBackgroundImage('entrance/文教名人'),
Star: {
Star1: {
image: getStaticImage('entrance/鲁迅故居-解锁'),
name: '鲁迅',
starName: 'luxun',
},
Star2: {
image: getStaticImage('entrance/黄元培-解锁'),
name: '黄炎培',
starName: 'huangyanpei',
},
@@ -64,12 +93,10 @@ const EntranceBackgroundMap: Record<ModuleName, EntranceBackground> = {
backgroundImage: getBackgroundImage('entrance/艺术名家'),
Star: {
Star1: {
image: getStaticImage('entrance/聂耳亮'),
name: '聂耳',
starName: 'nieer',
},
Star2: {
image: getStaticImage('entrance/张乐平亮'),
name: '张乐平',
starName: 'zhangleping',
},
@@ -79,12 +106,10 @@ const EntranceBackgroundMap: Record<ModuleName, EntranceBackground> = {
backgroundImage: getBackgroundImage('entrance/经济巨擎'),
Star: {
Star1: {
image: getStaticImage('entrance/荣德生-解锁'),
name: '荣德生',
starName: 'rongdesheng',
},
Star2: {
image: getStaticImage('entrance/陈云故居-解锁'),
name: '陈云',
starName: 'chenyun',
},
@@ -95,12 +120,39 @@ const EntranceBackgroundMap: Record<ModuleName, EntranceBackground> = {
const Entrance = () => {
const { moduleName } = useParams()
const navigate = useNavigate()
const puzzles = usePuzzleStore((state) => state.puzzles)
// const decodedModuleName = decodeURIComponent(moduleName || "");
const { backgroundImage, Star } = EntranceBackgroundMap[moduleName as ModuleName] || {}
if (!Star) {
return null
}
const { Star1, Star2 } = Star
const isStar1Unlocked = isResidenceUnlocked(Star1.starName, puzzles)
const isStar2Unlocked = isResidenceUnlocked(Star2.starName, puzzles)
const isStar1Started = isResidenceStarted(Star1.starName, puzzles)
const isStar2Started = isResidenceStarted(Star2.starName, puzzles)
const getStarImage = (star: Star, isUnlocked: boolean) =>
getStaticImage(`entrance/${star.name}-${isUnlocked ? '解锁' : '未解锁'}`)
const getEntranceActionState = (isUnlocked: boolean, isStarted: boolean): EntranceActionState => {
if (!isUnlocked) return 'locked'
return isStarted ? 'visited' : 'unvisited'
}
const getEntranceAction = (side: EntranceSide, state: EntranceActionState) => {
const config = entranceActionConfig[state]
return {
className: config.position[side],
image: config.image,
}
}
const star1Action = getEntranceAction(
'left',
getEntranceActionState(isStar1Unlocked, isStar1Started)
)
const star2Action = getEntranceAction(
'right',
getEntranceActionState(isStar2Unlocked, isStar2Started)
)
return (
<div className="w-full h-full bg-cover bg-center bg-no-repeat" style={{ backgroundImage }}>
<Image
@@ -110,28 +162,33 @@ const Entrance = () => {
onClick={() => navigate('/menu')}
/>
{/* 名人1 */}
<Image className="absolute left-73 top-[calc(50%+38px)] -translate-y-1/2" src={Star1.image} />
{Star1.image && (
<Image
className="absolute left-90.5 bottom-40"
src={getStaticImage('entrance/点击前往浏览')}
button
onClick={() => navigate(`/${moduleName}/${Star1.starName}/home`)}
/>
)}
<Image
className="absolute left-73 top-[calc(50%+38px)] -translate-y-1/2"
src={getStarImage(Star1, isStar1Unlocked)}
/>
<Image
className={star1Action.className}
src={getStaticImage(star1Action.image)}
button={isStar1Unlocked}
onClick={() => {
if (!isStar1Unlocked) return
navigate(`/${moduleName}/${Star1.starName}/home`)
}}
/>
{/* 名人2 */}
<Image
className="absolute right-77.5 top-[calc(50%+39px)] -translate-y-1/2"
src={Star2.image}
src={getStarImage(Star2, isStar2Unlocked)}
/>
<Image
className={star2Action.className}
src={getStaticImage(star2Action.image)}
button={isStar2Unlocked}
onClick={() => {
if (!isStar2Unlocked) return
navigate(`/${moduleName}/${Star2.starName}/home`)
}}
/>
{Star2.image && (
<Image
className="absolute right-90.5 bottom-40"
src={getStaticImage('entrance/点击前往浏览')}
button
onClick={() => navigate(`/${moduleName}/${Star2.starName}/home`)}
/>
)}
{/* <Image
className="absolute right-5 top-1/2 -translate-y-1/2"
src={Star2.image}