Files
shanghai-residences/src/pages/chenyun/home.tsx
T
zhongyidianandClaude Opus 4.7 49ecabbf3a fix: 修复开发/测试包模拟进度被易班同步覆盖的问题
进度模拟面板写入 puzzleStore 后,App 启动时易班同步会将其覆盖;
现在在同步完成后按 sessionStorage 中保存的模式重放模拟进度,
确保测试包的"到达某故居/全流程完成"调试入口生效。

同时纳入近期未提交的页面、组件、资源与环境变量改动。

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

151 lines
4.8 KiB
TypeScript

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 { getUserDefineResult } from '@/lib/utils/api-common'
import { showYibanSyncErrorPrompt, showYibanUnauthenticatedPrompt } from '@/components'
import { useChenyunStore, CHENYUN_LESSON_ID, CHENYUN_KEY } from '@/store/chenyunStore'
const deserializeVisitedExhibits = (
data: Record<string, number[]> | undefined
): Record<string, Set<number>> => {
if (!data || typeof data !== 'object') return {}
const visitedExhibits: Record<string, Set<number>> = {}
for (const [place, exhibits] of Object.entries(data)) {
visitedExhibits[place] = new Set(Array.isArray(exhibits) ? exhibits : [])
}
return visitedExhibits
}
const Chenyun = () => {
const navigate = useNavigate()
const { moduleName } = useParams<{
moduleName: string
}>()
// starName 固定为 chenyun
const starName = 'chenyun'
const lastVisitedPlace = useChenyunStore((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])
useEffect(() => {
;(async () => {
try {
const result = await getUserDefineResult(CHENYUN_LESSON_ID, CHENYUN_KEY)
if (result.status !== 'success') {
useChenyunStore.setState((prev) => ({
...prev,
visitedExhibits: {},
isYinyuanSolved: false,
isMimianSolved: false,
lastVisitedPlace: null,
}))
if (result.status === 'unauthenticated') {
showYibanUnauthenticatedPrompt()
} else if (result.status === 'error') {
showYibanSyncErrorPrompt()
}
return
}
const data = JSON.parse(result.content) as {
visitedExhibits?: Record<string, number[]>
isYinyuanSolved?: boolean
isMimianSolved?: boolean
lastVisitedPlace?: string
}
useChenyunStore.setState((prev) => ({
...prev,
visitedExhibits: deserializeVisitedExhibits(data.visitedExhibits),
isYinyuanSolved: data.isYinyuanSolved ?? false,
isMimianSolved: data.isMimianSolved ?? false,
lastVisitedPlace: data.lastVisitedPlace ?? null,
}))
} catch (error) {
useChenyunStore.setState((prev) => ({
...prev,
visitedExhibits: {},
isYinyuanSolved: false,
isMimianSolved: false,
lastVisitedPlace: null,
}))
showYibanSyncErrorPrompt()
console.error('Failed to load Chenyun store', error)
}
})()
}, [])
if (!moduleName) {
return null
}
return (
<div
className="w-full h-full bg-cover bg-center bg-no-repeat"
style={{
backgroundImage: getBackgroundImage(`${starName}/陈云故居底图`),
}}
>
<Image
className="absolute right-37 top-22"
src={getStaticImage('components/革命先驱故居-返回')}
button
onClick={() => navigate(`/${moduleName}`)}
/>
<div className="absolute right-20 top-75 flex flex-col space-y-10">
{/* 对话 */}
<Image
button={dialogueUnlocked}
className={dialogueUnlocked ? undefined : 'opacity-50 grayscale cursor-not-allowed'}
src={getStaticImage('star/一-解锁')}
onClick={() => {
if (!dialogueUnlocked) return
navigate(`/${moduleName}/${starName}/${lastVisitedPlace || 'place1'}`)
}}
/>
{/* 体悟 */}
<Image
button={reflectionUnlocked}
className={reflectionUnlocked ? undefined : 'opacity-50 grayscale cursor-not-allowed'}
src={getStaticImage('star/二-解锁')}
onClick={() => {
if (!reflectionUnlocked) return
navigate(`/${moduleName}/${starName}/reflection`)
}}
/>
{/* 内化 */}
<Image
button={internalizationUnlocked}
className={
internalizationUnlocked ? undefined : 'opacity-50 grayscale cursor-not-allowed'
}
src={getStaticImage('star/三-解锁')}
onClick={() => {
if (!internalizationUnlocked) return
navigate(`/${moduleName}/${starName}/internalization`)
}}
/>
</div>
</div>
)
}
export default Chenyun