From b1d55b94642b9d9a624012bce29c3bb142db7635 Mon Sep 17 00:00:00 2001 From: zhongyingmin Date: Fri, 6 Mar 2026 16:33:44 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E6=96=AD=E7=82=B9?= =?UTF-8?q?=E7=BB=AD=E8=AF=BB=E5=8A=9F=E8=83=BD=EF=BC=8C=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E4=BD=93=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在 chenyunStore 中引入 lastVisitedPlace 和 setLastVisitedPlace 方法,支持记录用户上次访问的场景。 - 在各个场景组件中使用 useEffect 设置 lastVisitedPlace,确保用户在返回时能继续上次访问的地点。 - 更新相关组件,确保在导航时能够正确使用 lastVisitedPlace,提升用户交互体验。 - 在 puzzleStore 中添加保存用户定义的功能,增强数据持久性。 --- .claude/settings.local.json | 4 ++- src/App.tsx | 25 ++++++++++++++++ src/components/layout/ResidenceLayout.tsx | 4 +-- src/pages/chenyun/home.tsx | 10 +++++-- src/pages/chenyun/place/place1.tsx | 26 +++++++++------- src/pages/chenyun/place/place2.tsx | 8 +++-- src/pages/chenyun/place/place3.tsx | 8 +++-- src/pages/chenyun/place/place4.tsx | 8 +++-- src/pages/chenyun/place/place5.tsx | 8 +++-- src/pages/chenyun/place/place6.tsx | 8 +++-- src/pages/chenyun/place/place7.tsx | 8 +++-- src/pages/chenyun/place/place8.tsx | 8 +++-- src/pages/chenyun/place/place9.tsx | 30 +++++++------------ src/pages/chenyun/reflection/index.tsx | 8 +++-- .../reflection/yinyuan/useYinyuanState.ts | 4 ++- src/store/chenyunStore.ts | 14 +++++++++ src/store/puzzleStore.ts | 20 ++++++++++++- 17 files changed, 147 insertions(+), 54 deletions(-) diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 0ae58e1..d45b72b 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -4,7 +4,9 @@ "mcp__chrome-devtools__click", "Bash(for i in 1 2 3 4 5 6 7 8)", "Bash(do echo \"=== place$i import line ===\" grep -n \"useChenyunStore\\\\|saveChenyunStore\" \"/Users/zhongyidian/工作/育享/沪上又见故人来/shanghai-residences/src/pages/chenyun/place/place$i.tsx\" echo \"=== place$i 继续前进 block ===\" grep -n -A5 \"继续前进\" \"/Users/zhongyidian/工作/育享/沪上又见故人来/shanghai-residences/src/pages/chenyun/place/place$i.tsx\")", - "Bash(done)" + "Bash(done)", + "Bash(grep -E \"\\\\.tsx$\")", + "Bash(grep -r \"navigate\" /Users/zhongyidian/工作/育享/沪上又见故人来/shanghai-residences/src/pages/chenyun/place/place*.tsx)" ] } } diff --git a/src/App.tsx b/src/App.tsx index 0a4cb7b..f309251 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -4,6 +4,13 @@ import router from './router' import { ScaleProvider, useScale } from './contexts/ScaleContext' import { RotateHint } from './components/RotateHint' import { useUserStore } from './store' +import { getUserDefine } from '@/lib/utils/api-common' +import { + usePuzzleStore, + deserializePuzzles, + PUZZLE_LESSON_ID, + PUZZLE_KEY, +} from '@/store/puzzleStore' /** * 应用内容组件 @@ -26,6 +33,24 @@ function AppContent() { } }, [setCourseInfo]) + useEffect(() => { + ;(async () => { + try { + const raw = await getUserDefine(PUZZLE_LESSON_ID, PUZZLE_KEY) + if (!raw) return + const data = JSON.parse(raw) as { puzzles?: Record } + if (data.puzzles) { + usePuzzleStore.setState((prev) => ({ + ...prev, + puzzles: deserializePuzzles(data.puzzles), + })) + } + } catch (e) { + console.error('Failed to load puzzle store', e) + } + })() + }, []) + return ( <>
diff --git a/src/components/layout/ResidenceLayout.tsx b/src/components/layout/ResidenceLayout.tsx index 0da722e..614f67e 100644 --- a/src/components/layout/ResidenceLayout.tsx +++ b/src/components/layout/ResidenceLayout.tsx @@ -277,7 +277,7 @@ const ResidenceLayoutContent = () => { {hasPuzzleFeature && (
{ e.stopPropagation() if (residence) { @@ -336,7 +336,7 @@ const ResidenceLayoutContent = () => { {isAnimationPlaying && createPortal(
e.stopPropagation()} onTouchStart={(e) => e.preventDefault()} diff --git a/src/pages/chenyun/home.tsx b/src/pages/chenyun/home.tsx index 97d0820..3b5a1ad 100644 --- a/src/pages/chenyun/home.tsx +++ b/src/pages/chenyun/home.tsx @@ -28,19 +28,21 @@ const Chenyun = () => { // starName 固定为 chenyun const starName = 'chenyun' + const { lastVisitedPlace } = useChenyunStore() + useEffect(() => { ;(async () => { - console.log('1233') try { const raw = await getUserDefine(CHENYUN_LESSON_ID, CHENYUN_KEY) - console.log(raw) if (!raw) return const data = JSON.parse(raw) as { visitedExhibits?: Record isYinyuanSolved?: boolean isMimianSolved?: boolean + lastVisitedPlace?: string | null } + console.log(data) useChenyunStore.setState((prev) => ({ ...prev, @@ -49,6 +51,8 @@ const Chenyun = () => { data.isYinyuanSolved !== undefined ? data.isYinyuanSolved : prev.isYinyuanSolved, isMimianSolved: data.isMimianSolved !== undefined ? data.isMimianSolved : prev.isMimianSolved, + lastVisitedPlace: + data.lastVisitedPlace !== undefined ? data.lastVisitedPlace : prev.lastVisitedPlace, })) } catch (error) { console.error('Failed to load Chenyun store', error) @@ -79,7 +83,7 @@ const Chenyun = () => { navigate(`/${moduleName}/${starName}/place1`)} + onClick={() => navigate(`/${moduleName}/${starName}/${lastVisitedPlace ?? 'place1'}`)} /> {/* 体悟 */} { // 使用持久化的访问状态 const place = 'place1' - const { markExhibitVisited, getVisitedExhibits } = useChenyunStore() + const { markExhibitVisited, getVisitedExhibits, setLastVisitedPlace } = useChenyunStore() + + useEffect(() => { + setLastVisitedPlace(place) + }, [setLastVisitedPlace]) // 从 store 获取已访问的展品,转换为 Set 用于兼容现有代码 const visitedExhibits = getVisitedExhibits(place) @@ -251,22 +255,22 @@ const ChenyunPlace1 = () => { const LearningIntroduction = ({ handleStartLearning }: { handleStartLearning: () => void }) => { return (
-
+
{/* 内容层 */}
{/* 装饰性顶部线条 */} -
+
{/* 标题 */} -
+

走进故居-对话模块学习说明

-
+
{/* 介绍文字 */} -
+

同学你好,欢迎来到走进故居-对话学习模块!

@@ -312,7 +316,7 @@ const LearningIntroduction = ({ handleStartLearning }: { handleStartLearning: () key={index} className="flex items-start gap-6 bg-[rgba(255,255,255,0.4)] rounded-xl p-6 border-l-4 border-[#8b7355] mb-4" > - + {index + 1}

{feature}

@@ -322,13 +326,13 @@ const LearningIntroduction = ({ handleStartLearning }: { handleStartLearning: ()
{/* 开始学习按钮 */} -
+
diff --git a/src/pages/chenyun/place/place2.tsx b/src/pages/chenyun/place/place2.tsx index c203b04..acdd24d 100644 --- a/src/pages/chenyun/place/place2.tsx +++ b/src/pages/chenyun/place/place2.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react' +import { useState, useEffect } from 'react' import { ExhibitOverlay, Title, Image, Cursor, GuideSteps, GuideStepsItem } from '@/components' import { getBackgroundImage, getPublicPath, getStaticImage } from '@/lib/utils' import { useNavigate, useParams } from 'react-router-dom' @@ -30,7 +30,11 @@ const ChenyunPlace2 = () => { // 使用持久化的访问状态 const place = 'place2' - const { markExhibitVisited, getVisitedExhibits } = useChenyunStore() + const { markExhibitVisited, getVisitedExhibits, setLastVisitedPlace } = useChenyunStore() + + useEffect(() => { + setLastVisitedPlace(place) + }, [setLastVisitedPlace]) // 从 store 获取已访问的展品,转换为 Set 用于兼容现有代码 const visitedExhibits = getVisitedExhibits(place) diff --git a/src/pages/chenyun/place/place3.tsx b/src/pages/chenyun/place/place3.tsx index 8a9b7e3..8129fe2 100644 --- a/src/pages/chenyun/place/place3.tsx +++ b/src/pages/chenyun/place/place3.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react' +import { useState, useEffect } from 'react' import { ExhibitOverlay, Title, @@ -35,7 +35,11 @@ const ChenyunPlace3 = () => { // 使用持久化的访问状态 const place = 'place3' - const { markExhibitVisited, getVisitedExhibits } = useChenyunStore() + const { markExhibitVisited, getVisitedExhibits, setLastVisitedPlace } = useChenyunStore() + + useEffect(() => { + setLastVisitedPlace(place) + }, [setLastVisitedPlace]) // 从 store 获取已访问的展品,转换为 Set 用于兼容现有代码 const visitedExhibits = getVisitedExhibits(place) diff --git a/src/pages/chenyun/place/place4.tsx b/src/pages/chenyun/place/place4.tsx index 646a622..f48e8d6 100644 --- a/src/pages/chenyun/place/place4.tsx +++ b/src/pages/chenyun/place/place4.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react' +import { useState, useEffect } from 'react' import { ExhibitOverlay, Title, @@ -37,7 +37,11 @@ const ChenyunPlace4 = () => { // 使用持久化的访问状态 const place = 'place4' - const { markExhibitVisited, getVisitedExhibits } = useChenyunStore() + const { markExhibitVisited, getVisitedExhibits, setLastVisitedPlace } = useChenyunStore() + + useEffect(() => { + setLastVisitedPlace(place) + }, [setLastVisitedPlace]) // 从 store 获取已访问的展品,转换为 Set 用于兼容现有代码 const visitedExhibits = getVisitedExhibits(place) diff --git a/src/pages/chenyun/place/place5.tsx b/src/pages/chenyun/place/place5.tsx index e4387ea..dee2871 100644 --- a/src/pages/chenyun/place/place5.tsx +++ b/src/pages/chenyun/place/place5.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react' +import { useState, useEffect } from 'react' import { ExhibitOverlay, Title, @@ -45,7 +45,11 @@ const ChenyunPlace5 = () => { // 使用持久化的访问状态 const place = 'place5' - const { markExhibitVisited, getVisitedExhibits } = useChenyunStore() + const { markExhibitVisited, getVisitedExhibits, setLastVisitedPlace } = useChenyunStore() + + useEffect(() => { + setLastVisitedPlace(place) + }, [setLastVisitedPlace]) // 从 store 获取已访问的展品,转换为 Set 用于兼容现有代码 const visitedExhibits = getVisitedExhibits(place) diff --git a/src/pages/chenyun/place/place6.tsx b/src/pages/chenyun/place/place6.tsx index be984ed..76f8cc5 100644 --- a/src/pages/chenyun/place/place6.tsx +++ b/src/pages/chenyun/place/place6.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react' +import { useState, useEffect } from 'react' import { ExhibitOverlay, Title, Image, Cursor, GuideSteps, GuideStepsItem } from '@/components' import { getBackgroundImage, getPublicPath, getStaticImage } from '@/lib/utils' import { useNavigate, useParams } from 'react-router-dom' @@ -25,7 +25,11 @@ const ChenyunPlace6 = () => { // 使用持久化的访问状态 const place = 'place6' - const { isExhibitVisited, markExhibitVisited, getVisitedExhibits } = useChenyunStore() + const { isExhibitVisited, markExhibitVisited, getVisitedExhibits, setLastVisitedPlace } = useChenyunStore() + + useEffect(() => { + setLastVisitedPlace(place) + }, [setLastVisitedPlace]) // 从 store 获取已访问的展品,转换为 Set 用于兼容现有代码 const visitedExhibits = getVisitedExhibits(place) diff --git a/src/pages/chenyun/place/place7.tsx b/src/pages/chenyun/place/place7.tsx index 3161b77..8194854 100644 --- a/src/pages/chenyun/place/place7.tsx +++ b/src/pages/chenyun/place/place7.tsx @@ -1,4 +1,4 @@ -import { useState, useRef } from 'react' +import { useState, useRef, useEffect } from 'react' import { ExhibitOverlay, Title, @@ -61,7 +61,11 @@ const ChenyunPlace7 = () => { // 使用持久化的访问状态 const place = 'place7' - const { isExhibitVisited, markExhibitVisited, getVisitedExhibits } = useChenyunStore() + const { markExhibitVisited, getVisitedExhibits, setLastVisitedPlace } = useChenyunStore() + + useEffect(() => { + setLastVisitedPlace(place) + }, [setLastVisitedPlace]) // 从 store 获取已访问的展品,转换为 Set 用于兼容现有代码 const visitedExhibits = getVisitedExhibits(place) diff --git a/src/pages/chenyun/place/place8.tsx b/src/pages/chenyun/place/place8.tsx index 94a8911..44d6688 100644 --- a/src/pages/chenyun/place/place8.tsx +++ b/src/pages/chenyun/place/place8.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react' +import { useState, useEffect } from 'react' import { ExhibitOverlay, Title, @@ -38,7 +38,11 @@ const ChenyunPlace8 = () => { // 使用持久化的访问状态 const place = 'place8' - const { markExhibitVisited, getVisitedExhibits } = useChenyunStore() + const { markExhibitVisited, getVisitedExhibits, setLastVisitedPlace } = useChenyunStore() + + useEffect(() => { + setLastVisitedPlace(place) + }, [setLastVisitedPlace]) // 从 store 获取已访问的展品,转换为 Set 用于兼容现有代码 const visitedExhibits = getVisitedExhibits(place) diff --git a/src/pages/chenyun/place/place9.tsx b/src/pages/chenyun/place/place9.tsx index 42698b9..ea796fe 100644 --- a/src/pages/chenyun/place/place9.tsx +++ b/src/pages/chenyun/place/place9.tsx @@ -1,13 +1,5 @@ -import { useState, useRef } from 'react' -import { - ExhibitOverlay, - Title, - Image, - Cursor, - Video, - GuideSteps, - GuideStepsItem, -} from '@/components' +import { useState, useRef, useEffect } from 'react' +import { Title, Image, Cursor, Video, GuideSteps, GuideStepsItem } from '@/components' import { getBackgroundImage, getStaticImage, getPublicPath } from '@/lib/utils' import { useNavigate, useParams } from 'react-router-dom' import type { VideoRef } from '@/components/video' @@ -17,10 +9,6 @@ import { usePuzzleAnimation } from '@/contexts/PuzzleAnimationContext' // starName 固定为 chenyun const starName = 'chenyun' -// 展品 ID 与资源路径映射 -const exhibitSrcMap: Record<1, string> = { - 1: 'chenyun/place9/场景9展品1', -} const ChenyunPlace9 = () => { const navigate = useNavigate() const { moduleName } = useParams<{ @@ -30,11 +18,15 @@ const ChenyunPlace9 = () => { // 获取碎片动画触发方法 const { triggerPuzzleAnimation } = usePuzzleAnimation() - const [activeExhibitId, setActiveExhibitId] = useState<1 | null>(null) + // const [activeExhibitId, setActiveExhibitId] = useState<1 | null>(null) // 使用持久化的访问状态 const place = 'place9' - const { markExhibitVisited, getVisitedExhibits } = useChenyunStore() + const { markExhibitVisited, getVisitedExhibits, setLastVisitedPlace } = useChenyunStore() + + useEffect(() => { + setLastVisitedPlace(place) + }, [setLastVisitedPlace]) // 从 store 获取已访问的展品,转换为 Set 用于兼容现有代码 const visitedExhibits = getVisitedExhibits(place) @@ -124,7 +116,7 @@ const ChenyunPlace9 = () => { {/* 展品1视频播放(全屏) */} {videoVisible && ( -
+
) } diff --git a/src/pages/chenyun/reflection/index.tsx b/src/pages/chenyun/reflection/index.tsx index 8f9990a..b4e5e9d 100644 --- a/src/pages/chenyun/reflection/index.tsx +++ b/src/pages/chenyun/reflection/index.tsx @@ -35,7 +35,9 @@ const ChenyunReflection = () => { // ========== 顶层状态管理 ========== /** 当前展开的文件 */ - const [expandedFile, setExpandedFile] = useState(null) + const [expandedFile, setExpandedFile] = useState( + () => (useChenyunStore.getState().isYinyuanSolved ? 'mimian' : null) + ) /** 当前播放的视频 */ const [playingVideo, setPlayingVideo] = useState(null) @@ -62,7 +64,9 @@ const ChenyunReflection = () => { const [showClueOverlay, setShowClueOverlay] = useState(false) /** 欢迎语弹窗(使用 StudyGuideModal 展示入口引导) */ - const [showWelcomeGuide, setShowWelcomeGuide] = useState(true) + const [showWelcomeGuide, setShowWelcomeGuide] = useState( + () => !useChenyunStore.getState().isYinyuanSolved + ) /** 第一幕银元之战 - 学习引导弹窗 */ const [showYinyuanGuide, setShowYinyuanGuide] = useState(false) diff --git a/src/pages/chenyun/reflection/yinyuan/useYinyuanState.ts b/src/pages/chenyun/reflection/yinyuan/useYinyuanState.ts index a93c59f..fcb8fe2 100644 --- a/src/pages/chenyun/reflection/yinyuan/useYinyuanState.ts +++ b/src/pages/chenyun/reflection/yinyuan/useYinyuanState.ts @@ -44,7 +44,9 @@ export const useYinyuanState = () => { // 记录是否已经显示过这些提示(避免重复显示) const [hasShownGuide1, setHasShownGuide1] = useState(false) - const [hasShownGuide2, setHasShownGuide2] = useState(false) + const [hasShownGuide2, setHasShownGuide2] = useState( + () => useChenyunStore.getState().isYinyuanSolved + ) const [hasShownGuide3, setHasShownGuide3] = useState(false) return { diff --git a/src/store/chenyunStore.ts b/src/store/chenyunStore.ts index 4edbfaf..2a4dca9 100644 --- a/src/store/chenyunStore.ts +++ b/src/store/chenyunStore.ts @@ -23,6 +23,11 @@ interface ChenyunStore { // 重置所有访问记录 resetAll: () => void + // ========== 断点续读 ========== + // 上次访问的场景(place1~place9) + lastVisitedPlace: string | null + setLastVisitedPlace: (place: string) => void + // ========== 体悟模块状态 ========== // 银元之战是否已解决 isYinyuanSolved: boolean @@ -105,6 +110,10 @@ export const useChenyunStore = create()( set({ visitedExhibits: {} }) }, + // ========== 断点续读 ========== + lastVisitedPlace: null, + setLastVisitedPlace: (place: string) => set({ lastVisitedPlace: place }), + // ========== 体悟模块状态 ========== isYinyuanSolved: false, setYinyuanSolved: (solved: boolean) => { @@ -122,6 +131,7 @@ export const useChenyunStore = create()( visitedExhibits: serializeVisitedExhibits(state.visitedExhibits), isYinyuanSolved: state.isYinyuanSolved, isMimianSolved: state.isMimianSolved, + lastVisitedPlace: state.lastVisitedPlace, }), onRehydrateStorage: () => (state) => { if (state) { @@ -134,6 +144,9 @@ export const useChenyunStore = create()( if (state.isMimianSolved === undefined) { state.isMimianSolved = false } + if (state.lastVisitedPlace === undefined) { + state.lastVisitedPlace = null + } } }, } @@ -151,6 +164,7 @@ export const saveChenyunStore = (): void => { visitedExhibits: serializeVisitedExhibits(state.visitedExhibits), isYinyuanSolved: state.isYinyuanSolved, isMimianSolved: state.isMimianSolved, + lastVisitedPlace: state.lastVisitedPlace, } try { diff --git a/src/store/puzzleStore.ts b/src/store/puzzleStore.ts index 63796e1..78ce015 100644 --- a/src/store/puzzleStore.ts +++ b/src/store/puzzleStore.ts @@ -1,6 +1,7 @@ import { create } from 'zustand' import { persist } from 'zustand/middleware' import { STORE_NAMES } from './storeNames' +import { setUserDefine } from '@/lib/utils/api-common' interface PuzzleStore { // 每个故居的已解锁碎片集合,格式:{ residence: Set } @@ -35,7 +36,7 @@ const serializePuzzles = (puzzles: Record>) => { } // 将数组转换回 Set -const deserializePuzzles = (serialized: Record | undefined) => { +export const deserializePuzzles = (serialized: Record | undefined) => { if (!serialized || typeof serialized !== 'object') { return {} } @@ -83,6 +84,7 @@ export const usePuzzleStore = create()( if (!residencePuzzles.has(pieceId)) { const newPuzzles = { ...fixedPuzzles } newPuzzles[residence] = new Set([...residencePuzzles, pieceId]) + savePuzzleStore(newPuzzles) set({ puzzles: newPuzzles }) } }, @@ -156,3 +158,19 @@ export const usePuzzleStore = create()( } ) ) + +// ========== 易班平台数据保存 ========== +export const PUZZLE_LESSON_ID = 'Cheny_neihua' +export const PUZZLE_KEY = 24 + +export const savePuzzleStore = (puzzles: Record>): void => { + const payload = { + puzzles: serializePuzzles(puzzles), + } + + try { + setUserDefine(PUZZLE_LESSON_ID, PUZZLE_KEY, JSON.stringify(payload)) + } catch (error) { + console.error('Failed to save Chenyun store', error) + } +}