feat: 引入持久化访问状态管理,优化展品访问逻辑
- 在 Luxun 和 Sunzhongshan 的多个组件中引入 useLuxunStore 和 useSunzhongshanStore,替代原有的访问状态管理方式。 - 通过 markExhibitVisited 方法标记展品为已访问,提升状态管理的效率和一致性。 - 从 store 获取已访问展品的状态,确保数据的持久性和准确性。
This commit is contained in:
1 parent
966b47ede3
commit
ac5a2159a1
15 files changed
+387
-168
No files matched your search
@@ -14,6 +14,7 @@ import { getBackgroundImage, getPublicPath, getStaticImage } from "@/lib/utils";
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useCursorController } from "@/hooks/useCursorController";
|
import { useCursorController } from "@/hooks/useCursorController";
|
||||||
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
|
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
|
||||||
|
import { useLuxunStore } from "@/store/luxunStore";
|
||||||
|
|
||||||
// starName 固定为 luxun
|
// starName 固定为 luxun
|
||||||
const starName = "luxun";
|
const starName = "luxun";
|
||||||
@@ -47,8 +48,13 @@ const LuxunPlace1 = () => {
|
|||||||
const [afterExhibitDialogStep, setAfterExhibitDialogStep] = useState(0);
|
const [afterExhibitDialogStep, setAfterExhibitDialogStep] = useState(0);
|
||||||
// 当前激活的展品 ID(只存 ID,不直接存路径,便于作为唯一标识)
|
// 当前激活的展品 ID(只存 ID,不直接存路径,便于作为唯一标识)
|
||||||
const [activeExhibitId, setActiveExhibitId] = useState<1 | 2 | null>(null);
|
const [activeExhibitId, setActiveExhibitId] = useState<1 | 2 | null>(null);
|
||||||
// 已浏览展品的 ID 集合
|
// 使用持久化的访问状态
|
||||||
const [visited, setVisited] = useState<Set<number>>(new Set());
|
const place = "place1";
|
||||||
|
const { markExhibitVisited, getVisitedExhibits } = useLuxunStore();
|
||||||
|
|
||||||
|
// 从 store 获取已访问的展品,转换为 Set 用于兼容现有代码
|
||||||
|
const visitedExhibits = getVisitedExhibits(place);
|
||||||
|
const visited = new Set(visitedExhibits);
|
||||||
// 连线题显示状态(展品1关闭后)
|
// 连线题显示状态(展品1关闭后)
|
||||||
const [showMatchQuiz, setShowMatchQuiz] = useState(false);
|
const [showMatchQuiz, setShowMatchQuiz] = useState(false);
|
||||||
// 连线题成功提示状态
|
// 连线题成功提示状态
|
||||||
@@ -171,11 +177,7 @@ const LuxunPlace1 = () => {
|
|||||||
setShowMatchQuiz(true);
|
setShowMatchQuiz(true);
|
||||||
} else {
|
} else {
|
||||||
// 展品2正常标记为已访问
|
// 展品2正常标记为已访问
|
||||||
setVisited((prev) => {
|
markExhibitVisited(place, activeExhibitId);
|
||||||
const next = new Set(prev);
|
|
||||||
next.add(activeExhibitId);
|
|
||||||
return next;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setActiveExhibitId(null);
|
setActiveExhibitId(null);
|
||||||
@@ -321,20 +323,12 @@ const LuxunPlace1 = () => {
|
|||||||
onContinue={() => {
|
onContinue={() => {
|
||||||
setShowMatchQuizSuccess(false);
|
setShowMatchQuizSuccess(false);
|
||||||
// 标记展品1为已访问
|
// 标记展品1为已访问
|
||||||
setVisited((prev) => {
|
markExhibitVisited(place, 1);
|
||||||
const next = new Set(prev);
|
|
||||||
next.add(1);
|
|
||||||
return next;
|
|
||||||
});
|
|
||||||
}}
|
}}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
setShowMatchQuizSuccess(false);
|
setShowMatchQuizSuccess(false);
|
||||||
// 标记展品1为已访问
|
// 标记展品1为已访问
|
||||||
setVisited((prev) => {
|
markExhibitVisited(place, 1);
|
||||||
const next = new Set(prev);
|
|
||||||
next.add(1);
|
|
||||||
return next;
|
|
||||||
});
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import { useState } from "react";
|
|||||||
import { useCursorController } from "@/hooks/useCursorController";
|
import { useCursorController } from "@/hooks/useCursorController";
|
||||||
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
|
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
|
||||||
import type { DragFillResult } from "@/components";
|
import type { DragFillResult } from "@/components";
|
||||||
|
import { useLuxunStore } from "@/store/luxunStore";
|
||||||
|
|
||||||
// starName 固定为 luxun
|
// starName 固定为 luxun
|
||||||
const starName = "luxun";
|
const starName = "luxun";
|
||||||
@@ -38,7 +39,14 @@ const LuxunPlace2 = () => {
|
|||||||
const [activeExhibitId, setActiveExhibitId] = useState<1 | 2 | 3 | null>(
|
const [activeExhibitId, setActiveExhibitId] = useState<1 | 2 | 3 | null>(
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
const [visited, setVisited] = useState<Set<number>>(new Set());
|
|
||||||
|
// 使用持久化的访问状态
|
||||||
|
const place = "place2";
|
||||||
|
const { markExhibitVisited, getVisitedExhibits } = useLuxunStore();
|
||||||
|
|
||||||
|
// 从 store 获取已访问的展品,转换为 Set 用于兼容现有代码
|
||||||
|
const visitedExhibits = getVisitedExhibits(place);
|
||||||
|
const visited = new Set(visitedExhibits);
|
||||||
|
|
||||||
// Cursor 顺序控制:1 -> 2 -> 3
|
// Cursor 顺序控制:1 -> 2 -> 3
|
||||||
const { isEnabled } = useCursorController({
|
const { isEnabled } = useCursorController({
|
||||||
@@ -214,11 +222,7 @@ const LuxunPlace2 = () => {
|
|||||||
// }
|
// }
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
if (activeExhibitId !== null) {
|
if (activeExhibitId !== null) {
|
||||||
setVisited((prev) => {
|
markExhibitVisited(place, activeExhibitId);
|
||||||
const next = new Set(prev);
|
|
||||||
next.add(activeExhibitId);
|
|
||||||
return next;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
setActiveExhibitId(null);
|
setActiveExhibitId(null);
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { useState } from "react";
|
|||||||
import SuccessAlert from "@/components/layout/alert/success-alert";
|
import SuccessAlert from "@/components/layout/alert/success-alert";
|
||||||
import { useCursorController } from "@/hooks/useCursorController";
|
import { useCursorController } from "@/hooks/useCursorController";
|
||||||
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
|
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
|
||||||
|
import { useLuxunStore } from "@/store/luxunStore";
|
||||||
|
|
||||||
// starName 固定为 luxun
|
// starName 固定为 luxun
|
||||||
const starName = "luxun";
|
const starName = "luxun";
|
||||||
@@ -55,7 +56,14 @@ const LuxunPlace3 = () => {
|
|||||||
const [activeExhibitId, setActiveExhibitId] = useState<
|
const [activeExhibitId, setActiveExhibitId] = useState<
|
||||||
1 | 2 | 3 | 4 | 5 | null
|
1 | 2 | 3 | 4 | 5 | null
|
||||||
>(null);
|
>(null);
|
||||||
const [visited, setVisited] = useState<Set<number>>(new Set());
|
|
||||||
|
// 使用持久化的访问状态
|
||||||
|
const place = "place3";
|
||||||
|
const { markExhibitVisited, getVisitedExhibits } = useLuxunStore();
|
||||||
|
|
||||||
|
// 从 store 获取已访问的展品,转换为 Set 用于兼容现有代码
|
||||||
|
const visitedExhibits = getVisitedExhibits(place);
|
||||||
|
const visited = new Set(visitedExhibits);
|
||||||
|
|
||||||
// Cursor 顺序控制:1 -> 2 -> 3 -> 4 -> 5
|
// Cursor 顺序控制:1 -> 2 -> 3 -> 4 -> 5
|
||||||
const { isEnabled } = useCursorController({
|
const { isEnabled } = useCursorController({
|
||||||
@@ -259,11 +267,7 @@ const LuxunPlace3 = () => {
|
|||||||
} else {
|
} else {
|
||||||
// 其他光标:正常关闭并标记为已读
|
// 其他光标:正常关闭并标记为已读
|
||||||
if (activeExhibitId !== null) {
|
if (activeExhibitId !== null) {
|
||||||
setVisited((prev) => {
|
markExhibitVisited(place, activeExhibitId);
|
||||||
const next = new Set(prev);
|
|
||||||
next.add(activeExhibitId);
|
|
||||||
return next;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
setActiveExhibitId(null);
|
setActiveExhibitId(null);
|
||||||
}
|
}
|
||||||
@@ -277,11 +281,7 @@ const LuxunPlace3 = () => {
|
|||||||
setGuide1Visible(visible);
|
setGuide1Visible(visible);
|
||||||
if (!visible) {
|
if (!visible) {
|
||||||
// 对话关闭时,标记光标1为已读
|
// 对话关闭时,标记光标1为已读
|
||||||
setVisited((prev) => {
|
markExhibitVisited(place, 1);
|
||||||
const next = new Set(prev);
|
|
||||||
next.add(1);
|
|
||||||
return next;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
currentStep={guide1Step}
|
currentStep={guide1Step}
|
||||||
@@ -383,11 +383,7 @@ const LuxunPlace3 = () => {
|
|||||||
onContinue={() => {
|
onContinue={() => {
|
||||||
setSuccessAlert2Visible(false);
|
setSuccessAlert2Visible(false);
|
||||||
// 标记光标2为已读
|
// 标记光标2为已读
|
||||||
setVisited((prev) => {
|
markExhibitVisited(place, 2);
|
||||||
const next = new Set(prev);
|
|
||||||
next.add(2);
|
|
||||||
return next;
|
|
||||||
});
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -398,11 +394,7 @@ const LuxunPlace3 = () => {
|
|||||||
setGuide3Visible(visible);
|
setGuide3Visible(visible);
|
||||||
if (!visible) {
|
if (!visible) {
|
||||||
// 对话关闭时,标记光标3为已读
|
// 对话关闭时,标记光标3为已读
|
||||||
setVisited((prev) => {
|
markExhibitVisited(place, 3);
|
||||||
const next = new Set(prev);
|
|
||||||
next.add(3);
|
|
||||||
return next;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
currentStep={guide3Step}
|
currentStep={guide3Step}
|
||||||
@@ -432,11 +424,7 @@ const LuxunPlace3 = () => {
|
|||||||
setGuide4Visible(visible);
|
setGuide4Visible(visible);
|
||||||
if (!visible) {
|
if (!visible) {
|
||||||
// 对话关闭时,标记光标4为已读
|
// 对话关闭时,标记光标4为已读
|
||||||
setVisited((prev) => {
|
markExhibitVisited(place, 4);
|
||||||
const next = new Set(prev);
|
|
||||||
next.add(4);
|
|
||||||
return next;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
currentStep={guide4Step}
|
currentStep={guide4Step}
|
||||||
@@ -496,11 +484,7 @@ const LuxunPlace3 = () => {
|
|||||||
onContinue={() => {
|
onContinue={() => {
|
||||||
setSuccessAlert5Visible(false);
|
setSuccessAlert5Visible(false);
|
||||||
// 标记光标5为已读
|
// 标记光标5为已读
|
||||||
setVisited((prev) => {
|
markExhibitVisited(place, 5);
|
||||||
const next = new Set(prev);
|
|
||||||
next.add(5);
|
|
||||||
return next;
|
|
||||||
});
|
|
||||||
// 打开光标5的引导对话
|
// 打开光标5的引导对话
|
||||||
setGuide5Visible(true);
|
setGuide5Visible(true);
|
||||||
setGuide5Step(0);
|
setGuide5Step(0);
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { useState, useRef, useMemo } from "react";
|
|||||||
import SuccessAlert from "@/components/layout/alert/success-alert";
|
import SuccessAlert from "@/components/layout/alert/success-alert";
|
||||||
import { useCursorController } from "@/hooks/useCursorController";
|
import { useCursorController } from "@/hooks/useCursorController";
|
||||||
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
|
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
|
||||||
|
import { useLuxunStore } from "@/store/luxunStore";
|
||||||
|
|
||||||
// starName 固定为 luxun
|
// starName 固定为 luxun
|
||||||
const starName = "luxun";
|
const starName = "luxun";
|
||||||
@@ -54,7 +55,14 @@ const LuxunPlace5 = () => {
|
|||||||
const [activeExhibitId, setActiveExhibitId] = useState<1 | 2 | 3 | null>(
|
const [activeExhibitId, setActiveExhibitId] = useState<1 | 2 | 3 | null>(
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
const [visited, setVisited] = useState<Set<number>>(new Set());
|
|
||||||
|
// 使用持久化的访问状态
|
||||||
|
const place = "place4";
|
||||||
|
const { markExhibitVisited, getVisitedExhibits } = useLuxunStore();
|
||||||
|
|
||||||
|
// 从 store 获取已访问的展品,转换为 Set 用于兼容现有代码
|
||||||
|
const visitedExhibits = getVisitedExhibits(place);
|
||||||
|
const visited = new Set(visitedExhibits);
|
||||||
|
|
||||||
// Cursor 顺序控制:1 -> 2 -> 3
|
// Cursor 顺序控制:1 -> 2 -> 3
|
||||||
const { isEnabled } = useCursorController({
|
const { isEnabled } = useCursorController({
|
||||||
@@ -252,13 +260,9 @@ const LuxunPlace5 = () => {
|
|||||||
setActiveExhibitId(null);
|
setActiveExhibitId(null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setVisited((prev) => {
|
|
||||||
const next = new Set(prev);
|
|
||||||
if (activeExhibitId !== null) {
|
if (activeExhibitId !== null) {
|
||||||
next.add(activeExhibitId);
|
markExhibitVisited(place, activeExhibitId);
|
||||||
}
|
}
|
||||||
return next;
|
|
||||||
});
|
|
||||||
setActiveExhibitId(null);
|
setActiveExhibitId(null);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -327,11 +331,7 @@ const LuxunPlace5 = () => {
|
|||||||
content="1933年,瞿秋白夫妇第三次到鲁迅家避难,就住进了这间仅6平方米的客房,与鲁迅常常彻夜长谈革命与文学。"
|
content="1933年,瞿秋白夫妇第三次到鲁迅家避难,就住进了这间仅6平方米的客房,与鲁迅常常彻夜长谈革命与文学。"
|
||||||
onContinue={() => {
|
onContinue={() => {
|
||||||
setSuccessAlertVisible(false);
|
setSuccessAlertVisible(false);
|
||||||
setVisited((prev) => {
|
markExhibitVisited(place, 2);
|
||||||
const next = new Set(prev);
|
|
||||||
next.add(2);
|
|
||||||
return next;
|
|
||||||
});
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -341,11 +341,7 @@ const LuxunPlace5 = () => {
|
|||||||
onVisibleChange={(visible) => {
|
onVisibleChange={(visible) => {
|
||||||
setGuide3Visible(visible);
|
setGuide3Visible(visible);
|
||||||
if (!visible) {
|
if (!visible) {
|
||||||
setVisited((prev) => {
|
markExhibitVisited(place, 3);
|
||||||
const next = new Set(prev);
|
|
||||||
next.add(3);
|
|
||||||
return next;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
currentStep={guide3Step}
|
currentStep={guide3Step}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import { SuccessAlert, DragQuizPanel } from "@/components/layout";
|
|||||||
import { MultipleChoiceQuizPanel } from "@/components/layout/quiz";
|
import { MultipleChoiceQuizPanel } from "@/components/layout/quiz";
|
||||||
import { useCursorController } from "@/hooks/useCursorController";
|
import { useCursorController } from "@/hooks/useCursorController";
|
||||||
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
|
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
|
||||||
|
import { useSunzhongshanStore } from "@/store/sunzhongshanStore";
|
||||||
|
|
||||||
// starName 固定为 sunzhongshan
|
// starName 固定为 sunzhongshan
|
||||||
const starName = "sunzhongshan";
|
const starName = "sunzhongshan";
|
||||||
@@ -50,8 +51,13 @@ const SunzhongshanPlace1 = () => {
|
|||||||
const [activeExhibitId, setActiveExhibitId] = useState<
|
const [activeExhibitId, setActiveExhibitId] = useState<
|
||||||
1 | 2 | 3 | 4 | 5 | null
|
1 | 2 | 3 | 4 | 5 | null
|
||||||
>(null);
|
>(null);
|
||||||
// 已浏览展品的 ID 集合
|
// 使用持久化的访问状态
|
||||||
const [visited, setVisited] = useState<Set<number>>(new Set());
|
const place = "place1";
|
||||||
|
const { markExhibitVisited, getVisitedExhibits } = useSunzhongshanStore();
|
||||||
|
|
||||||
|
// 从 store 获取已访问的展品,转换为 Set 用于兼容现有代码
|
||||||
|
const visitedExhibits = getVisitedExhibits(place);
|
||||||
|
const visited = new Set(visitedExhibits);
|
||||||
|
|
||||||
// 展品2窗口关闭的对话状态
|
// 展品2窗口关闭的对话状态
|
||||||
const [guide2CloseVisible, setGuide2CloseVisible] = useState(false);
|
const [guide2CloseVisible, setGuide2CloseVisible] = useState(false);
|
||||||
@@ -256,11 +262,7 @@ const SunzhongshanPlace1 = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (currentId !== null) {
|
if (currentId !== null) {
|
||||||
setVisited((prev) => {
|
markExhibitVisited(place, currentId);
|
||||||
const next = new Set(prev);
|
|
||||||
next.add(currentId);
|
|
||||||
return next;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
setActiveExhibitId(null);
|
setActiveExhibitId(null);
|
||||||
}}
|
}}
|
||||||
@@ -328,11 +330,7 @@ const SunzhongshanPlace1 = () => {
|
|||||||
onVisibleChange={(nextVisible) => {
|
onVisibleChange={(nextVisible) => {
|
||||||
setGuide2Visible(nextVisible);
|
setGuide2Visible(nextVisible);
|
||||||
if (!nextVisible) {
|
if (!nextVisible) {
|
||||||
setVisited((prev) => {
|
markExhibitVisited(place, 2);
|
||||||
const next = new Set(prev);
|
|
||||||
next.add(2);
|
|
||||||
return next;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
showMask={false}
|
showMask={false}
|
||||||
@@ -386,11 +384,7 @@ const SunzhongshanPlace1 = () => {
|
|||||||
onVisibleChange={(nextVisible) => {
|
onVisibleChange={(nextVisible) => {
|
||||||
setGuide5Visible(nextVisible);
|
setGuide5Visible(nextVisible);
|
||||||
if (!nextVisible) {
|
if (!nextVisible) {
|
||||||
setVisited((prev) => {
|
markExhibitVisited(place, 5);
|
||||||
const next = new Set(prev);
|
|
||||||
next.add(5);
|
|
||||||
return next;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
showMask={false}
|
showMask={false}
|
||||||
@@ -514,11 +508,7 @@ const SunzhongshanPlace1 = () => {
|
|||||||
onContinue={() => {
|
onContinue={() => {
|
||||||
setSuccessAlert4Visible(false);
|
setSuccessAlert4Visible(false);
|
||||||
// setShowVideo4Second(true);
|
// setShowVideo4Second(true);
|
||||||
setVisited((prev) => {
|
markExhibitVisited(place, 4);
|
||||||
const next = new Set(prev);
|
|
||||||
next.add(4);
|
|
||||||
return next;
|
|
||||||
});
|
|
||||||
}}
|
}}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
setSuccessAlert4Visible(false);
|
setSuccessAlert4Visible(false);
|
||||||
@@ -536,8 +526,8 @@ const SunzhongshanPlace1 = () => {
|
|||||||
showSkipButton
|
showSkipButton
|
||||||
onEnded={() => {
|
onEnded={() => {
|
||||||
setShowVideo4Second(false);
|
setShowVideo4Second(false);
|
||||||
|
// 视频播放结束后,标记展品3为已访问
|
||||||
setVisited((prev) => new Set([...prev, 3]));
|
markExhibitVisited(place, 3);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -645,20 +635,12 @@ const SunzhongshanPlace1 = () => {
|
|||||||
onContinue={() => {
|
onContinue={() => {
|
||||||
setSuccessAlert1Visible(false);
|
setSuccessAlert1Visible(false);
|
||||||
// 修改cursor状态
|
// 修改cursor状态
|
||||||
setVisited((prev) => {
|
markExhibitVisited(place, 1);
|
||||||
const next = new Set(prev);
|
|
||||||
next.add(1);
|
|
||||||
return next;
|
|
||||||
});
|
|
||||||
}}
|
}}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
setSuccessAlert1Visible(false);
|
setSuccessAlert1Visible(false);
|
||||||
// 修改cursor状态
|
// 修改cursor状态
|
||||||
setVisited((prev) => {
|
markExhibitVisited(place, 1);
|
||||||
const next = new Set(prev);
|
|
||||||
next.add(1);
|
|
||||||
return next;
|
|
||||||
});
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { useCursorController } from "@/hooks/useCursorController";
|
|||||||
import { getBackgroundImage, getPublicPath, getStaticImage } from "@/lib/utils";
|
import { getBackgroundImage, getPublicPath, getStaticImage } from "@/lib/utils";
|
||||||
import { useNavigate, useParams } from "react-router-dom";
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
|
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
|
||||||
|
import { useSunzhongshanStore } from "@/store/sunzhongshanStore";
|
||||||
|
|
||||||
const starName = "sunzhongshan";
|
const starName = "sunzhongshan";
|
||||||
|
|
||||||
@@ -35,7 +36,14 @@ const SunzhongshanPlace2 = () => {
|
|||||||
const [activeExhibitId, setActiveExhibitId] = useState<1 | 2 | 3 | 4 | null>(
|
const [activeExhibitId, setActiveExhibitId] = useState<1 | 2 | 3 | 4 | null>(
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
const [visited, setVisited] = useState<Set<number>>(new Set());
|
|
||||||
|
// 使用持久化的访问状态
|
||||||
|
const place = "place2";
|
||||||
|
const { markExhibitVisited, getVisitedExhibits } = useSunzhongshanStore();
|
||||||
|
|
||||||
|
// 从 store 获取已访问的展品,转换为 Set 用于兼容现有代码
|
||||||
|
const visitedExhibits = getVisitedExhibits(place);
|
||||||
|
const visited = new Set(visitedExhibits);
|
||||||
|
|
||||||
// Cursor 顺序控制:1 -> 2 -> 3 -> 4
|
// Cursor 顺序控制:1 -> 2 -> 3 -> 4
|
||||||
const { isEnabled } = useCursorController({
|
const { isEnabled } = useCursorController({
|
||||||
@@ -186,11 +194,7 @@ const SunzhongshanPlace2 = () => {
|
|||||||
// 光标4:正常关闭,更新visited状态
|
// 光标4:正常关闭,更新visited状态
|
||||||
}
|
}
|
||||||
if (currentId !== null) {
|
if (currentId !== null) {
|
||||||
setVisited((prev) => {
|
markExhibitVisited(place, currentId);
|
||||||
const next = new Set(prev);
|
|
||||||
next.add(currentId);
|
|
||||||
return next;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
setActiveExhibitId(null);
|
setActiveExhibitId(null);
|
||||||
}}
|
}}
|
||||||
@@ -237,11 +241,7 @@ const SunzhongshanPlace2 = () => {
|
|||||||
onEnded={() => {
|
onEnded={() => {
|
||||||
setShowVideo1(false);
|
setShowVideo1(false);
|
||||||
// 修改cursor状态
|
// 修改cursor状态
|
||||||
setVisited((prev) => {
|
markExhibitVisited(place, 1);
|
||||||
const next = new Set(prev);
|
|
||||||
next.add(1);
|
|
||||||
return next;
|
|
||||||
});
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -287,11 +287,7 @@ const SunzhongshanPlace2 = () => {
|
|||||||
onEnded={() => {
|
onEnded={() => {
|
||||||
setShowVideo2(false);
|
setShowVideo2(false);
|
||||||
// 修改cursor状态
|
// 修改cursor状态
|
||||||
setVisited((prev) => {
|
markExhibitVisited(place, 2);
|
||||||
const next = new Set(prev);
|
|
||||||
next.add(2);
|
|
||||||
return next;
|
|
||||||
});
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import { getBackgroundImage, getPublicPath, getStaticImage } from "@/lib/utils";
|
|||||||
import { useNavigate, useParams } from "react-router-dom";
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
import { SuccessAlert, DragQuizPanel } from "@/components/layout";
|
import { SuccessAlert, DragQuizPanel } from "@/components/layout";
|
||||||
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
|
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
|
||||||
|
import { useSunzhongshanStore } from "@/store/sunzhongshanStore";
|
||||||
|
|
||||||
const starName = "sunzhongshan";
|
const starName = "sunzhongshan";
|
||||||
|
|
||||||
@@ -37,7 +38,14 @@ const SunzhongshanPlace3 = () => {
|
|||||||
const [activeExhibitId, setActiveExhibitId] = useState<1 | 2 | 3 | 4 | null>(
|
const [activeExhibitId, setActiveExhibitId] = useState<1 | 2 | 3 | 4 | null>(
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
const [visited, setVisited] = useState<Set<number>>(new Set());
|
|
||||||
|
// 使用持久化的访问状态
|
||||||
|
const place = "place3";
|
||||||
|
const { markExhibitVisited, getVisitedExhibits } = useSunzhongshanStore();
|
||||||
|
|
||||||
|
// 从 store 获取已访问的展品,转换为 Set 用于兼容现有代码
|
||||||
|
const visitedExhibits = getVisitedExhibits(place);
|
||||||
|
const visited = new Set(visitedExhibits);
|
||||||
|
|
||||||
// Cursor 顺序控制:1 -> 2 -> 3 -> 4
|
// Cursor 顺序控制:1 -> 2 -> 3 -> 4
|
||||||
const { isEnabled } = useCursorController({
|
const { isEnabled } = useCursorController({
|
||||||
@@ -195,11 +203,7 @@ const SunzhongshanPlace3 = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (currentId !== null) {
|
if (currentId !== null) {
|
||||||
setVisited((prev) => {
|
markExhibitVisited(place, currentId);
|
||||||
const next = new Set(prev);
|
|
||||||
next.add(currentId);
|
|
||||||
return next;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
setActiveExhibitId(null);
|
setActiveExhibitId(null);
|
||||||
}}
|
}}
|
||||||
@@ -340,20 +344,12 @@ const SunzhongshanPlace3 = () => {
|
|||||||
onContinue={() => {
|
onContinue={() => {
|
||||||
setSuccessAlert2Visible(false);
|
setSuccessAlert2Visible(false);
|
||||||
// 修改cursor状态
|
// 修改cursor状态
|
||||||
setVisited((prev) => {
|
markExhibitVisited(place, 2);
|
||||||
const next = new Set(prev);
|
|
||||||
next.add(2);
|
|
||||||
return next;
|
|
||||||
});
|
|
||||||
}}
|
}}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
setSuccessAlert2Visible(false);
|
setSuccessAlert2Visible(false);
|
||||||
// 修改cursor状态
|
// 修改cursor状态
|
||||||
setVisited((prev) => {
|
markExhibitVisited(place, 2);
|
||||||
const next = new Set(prev);
|
|
||||||
next.add(2);
|
|
||||||
return next;
|
|
||||||
});
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { useCursorController } from "@/hooks/useCursorController";
|
|||||||
import { getBackgroundImage, getStaticImage, getPublicPath } from "@/lib/utils";
|
import { getBackgroundImage, getStaticImage, getPublicPath } from "@/lib/utils";
|
||||||
import { useNavigate, useParams } from "react-router-dom";
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
|
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
|
||||||
|
import { useSunzhongshanStore } from "@/store/sunzhongshanStore";
|
||||||
|
|
||||||
const starName = "sunzhongshan";
|
const starName = "sunzhongshan";
|
||||||
|
|
||||||
@@ -39,7 +40,14 @@ const SunzhongshanPlace4 = () => {
|
|||||||
const [showInitialDialog, setShowInitialDialog] = useState(true);
|
const [showInitialDialog, setShowInitialDialog] = useState(true);
|
||||||
const [initialDialogStep, setInitialDialogStep] = useState(0);
|
const [initialDialogStep, setInitialDialogStep] = useState(0);
|
||||||
const [activeExhibitId, setActiveExhibitId] = useState<1 | 2 | null>(null);
|
const [activeExhibitId, setActiveExhibitId] = useState<1 | 2 | null>(null);
|
||||||
const [visited, setVisited] = useState<Set<number>>(new Set());
|
|
||||||
|
// 使用持久化的访问状态
|
||||||
|
const place = "place4";
|
||||||
|
const { markExhibitVisited, getVisitedExhibits } = useSunzhongshanStore();
|
||||||
|
|
||||||
|
// 从 store 获取已访问的展品,转换为 Set 用于兼容现有代码
|
||||||
|
const visitedExhibits = getVisitedExhibits(place);
|
||||||
|
const visited = new Set(visitedExhibits);
|
||||||
|
|
||||||
// Cursor 顺序控制:1 -> 2
|
// Cursor 顺序控制:1 -> 2
|
||||||
const { isEnabled } = useCursorController({
|
const { isEnabled } = useCursorController({
|
||||||
@@ -143,11 +151,7 @@ const SunzhongshanPlace4 = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (currentId !== null) {
|
if (currentId !== null) {
|
||||||
setVisited((prev) => {
|
markExhibitVisited(place, currentId);
|
||||||
const next = new Set(prev);
|
|
||||||
next.add(currentId);
|
|
||||||
return next;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
setActiveExhibitId(null);
|
setActiveExhibitId(null);
|
||||||
}}
|
}}
|
||||||
@@ -177,11 +181,7 @@ const SunzhongshanPlace4 = () => {
|
|||||||
onContinue={() => {
|
onContinue={() => {
|
||||||
setShowVideo1Guide(false);
|
setShowVideo1Guide(false);
|
||||||
// 学习引导关闭后,修改cursor状态
|
// 学习引导关闭后,修改cursor状态
|
||||||
setVisited((prev) => {
|
markExhibitVisited(place, 1);
|
||||||
const next = new Set(prev);
|
|
||||||
next.add(1);
|
|
||||||
return next;
|
|
||||||
});
|
|
||||||
}}
|
}}
|
||||||
animationSpeed={200}
|
animationSpeed={200}
|
||||||
audioSrc={getPublicPath("audio/sunzhongshan/mp3_sunzhongshan_46.mp3")}
|
audioSrc={getPublicPath("audio/sunzhongshan/mp3_sunzhongshan_46.mp3")}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { useState, useEffect } from "react";
|
|||||||
import { useNavigate, useParams } from "react-router-dom";
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
import { Image, StudyGuideModal } from "@/components";
|
import { Image, StudyGuideModal } from "@/components";
|
||||||
import { getBackgroundImage, getPublicPath, getStaticImage } from "@/lib/utils";
|
import { getBackgroundImage, getPublicPath, getStaticImage } from "@/lib/utils";
|
||||||
|
import { useSunzhongshanStore } from "@/store/sunzhongshanStore";
|
||||||
|
|
||||||
const starName = "sunzhongshan";
|
const starName = "sunzhongshan";
|
||||||
const GUIDE_STORAGE_KEY = `${starName}-reflection-guide-shown`;
|
const GUIDE_STORAGE_KEY = `${starName}-reflection-guide-shown`;
|
||||||
@@ -24,17 +25,19 @@ const SunzhongshanReflection = () => {
|
|||||||
scene3: false,
|
scene3: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { getReflectionFlag, setReflectionFlag } = useSunzhongshanStore();
|
||||||
|
|
||||||
// 检查场景完成状态 - 每次组件挂载或从场景页面返回时重新检查
|
// 检查场景完成状态 - 每次组件挂载或从场景页面返回时重新检查
|
||||||
const checkSceneCompleted = () => {
|
const checkSceneCompleted = () => {
|
||||||
const scene1Completed =
|
const scene1Completed = getReflectionFlag(
|
||||||
localStorage.getItem(`${starName}-reflection-scene1-completed`) ===
|
`${starName}-reflection-scene1-completed`
|
||||||
"true";
|
);
|
||||||
const scene2Completed =
|
const scene2Completed = getReflectionFlag(
|
||||||
localStorage.getItem(`${starName}-reflection-scene2-completed`) ===
|
`${starName}-reflection-scene2-completed`
|
||||||
"true";
|
);
|
||||||
const scene3Completed =
|
const scene3Completed = getReflectionFlag(
|
||||||
localStorage.getItem(`${starName}-reflection-scene3-completed`) ===
|
`${starName}-reflection-scene3-completed`
|
||||||
"true";
|
);
|
||||||
|
|
||||||
setSceneCompleted({
|
setSceneCompleted({
|
||||||
scene1: scene1Completed,
|
scene1: scene1Completed,
|
||||||
@@ -55,15 +58,16 @@ const SunzhongshanReflection = () => {
|
|||||||
return () => {
|
return () => {
|
||||||
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
||||||
};
|
};
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// 检查是否是第一次进入,显示学习引导
|
// 检查是否是第一次进入,显示学习引导
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const hasShownGuide = localStorage.getItem(GUIDE_STORAGE_KEY);
|
const hasShownGuide = getReflectionFlag(GUIDE_STORAGE_KEY);
|
||||||
if (!hasShownGuide) {
|
if (!hasShownGuide) {
|
||||||
setShowGuide(true);
|
setShowGuide(true);
|
||||||
}
|
}
|
||||||
}, []);
|
}, [getReflectionFlag]);
|
||||||
|
|
||||||
// 检查场景是否可解锁
|
// 检查场景是否可解锁
|
||||||
const isSceneUnlocked = (sceneNumber: number): boolean => {
|
const isSceneUnlocked = (sceneNumber: number): boolean => {
|
||||||
@@ -82,7 +86,7 @@ const SunzhongshanReflection = () => {
|
|||||||
|
|
||||||
const handleGuideClose = () => {
|
const handleGuideClose = () => {
|
||||||
setShowGuide(false);
|
setShowGuide(false);
|
||||||
localStorage.setItem(GUIDE_STORAGE_KEY, "true");
|
setReflectionFlag(GUIDE_STORAGE_KEY, true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleStartExplore = () => {
|
const handleStartExplore = () => {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import { getBackgroundImage, getStaticImage, getPublicPath } from "@/lib/utils";
|
|||||||
import { useClueStore } from "@/store/clueStore";
|
import { useClueStore } from "@/store/clueStore";
|
||||||
import type { ClueHint } from "@/components/clue";
|
import type { ClueHint } from "@/components/clue";
|
||||||
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
|
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
|
||||||
|
import { useSunzhongshanStore } from "@/store/sunzhongshanStore";
|
||||||
|
|
||||||
const starName = "sunzhongshan";
|
const starName = "sunzhongshan";
|
||||||
const sceneId = `${starName}/reflection/scene1`;
|
const sceneId = `${starName}/reflection/scene1`;
|
||||||
@@ -72,6 +73,7 @@ const SunzhongshanReflectionScene1 = () => {
|
|||||||
|
|
||||||
// 获取碎片动画触发方法
|
// 获取碎片动画触发方法
|
||||||
const { triggerPuzzleAnimation } = usePuzzleAnimation();
|
const { triggerPuzzleAnimation } = usePuzzleAnimation();
|
||||||
|
const { getReflectionFlag, setReflectionFlag } = useSunzhongshanStore();
|
||||||
|
|
||||||
const [showClueHint, setShowClueHint] = useState(false);
|
const [showClueHint, setShowClueHint] = useState(false);
|
||||||
const [showClueBook, setShowClueBook] = useState(false);
|
const [showClueBook, setShowClueBook] = useState(false);
|
||||||
@@ -179,7 +181,7 @@ const SunzhongshanReflectionScene1 = () => {
|
|||||||
const handleVideoEnd = () => {
|
const handleVideoEnd = () => {
|
||||||
setShowVideo(false);
|
setShowVideo(false);
|
||||||
// 检查是否需要显示学习引导
|
// 检查是否需要显示学习引导
|
||||||
const hasShownGuide = localStorage.getItem(GUIDE_STORAGE_KEY);
|
const hasShownGuide = getReflectionFlag(GUIDE_STORAGE_KEY);
|
||||||
if (!hasShownGuide) {
|
if (!hasShownGuide) {
|
||||||
setShowGuide(true);
|
setShowGuide(true);
|
||||||
}
|
}
|
||||||
@@ -188,7 +190,7 @@ const SunzhongshanReflectionScene1 = () => {
|
|||||||
// 处理学习引导关闭
|
// 处理学习引导关闭
|
||||||
const handleGuideClose = () => {
|
const handleGuideClose = () => {
|
||||||
setShowGuide(false);
|
setShowGuide(false);
|
||||||
localStorage.setItem(GUIDE_STORAGE_KEY, "true");
|
setReflectionFlag(GUIDE_STORAGE_KEY, true);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 检查是否所有线索都已收集
|
// 检查是否所有线索都已收集
|
||||||
@@ -240,7 +242,7 @@ const SunzhongshanReflectionScene1 = () => {
|
|||||||
const handleCompleteGuideClose = () => {
|
const handleCompleteGuideClose = () => {
|
||||||
setShowCompleteGuide(false);
|
setShowCompleteGuide(false);
|
||||||
// 保存场景1完成状态
|
// 保存场景1完成状态
|
||||||
localStorage.setItem(`${starName}-reflection-scene1-completed`, "true");
|
setReflectionFlag(`${starName}-reflection-scene1-completed`, true);
|
||||||
if (moduleName && starName) {
|
if (moduleName && starName) {
|
||||||
navigate(`/${moduleName}/${starName}/reflection2`);
|
navigate(`/${moduleName}/${starName}/reflection2`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import { getBackgroundImage, getStaticImage, getPublicPath } from "@/lib/utils";
|
|||||||
import { useClueStore } from "@/store/clueStore";
|
import { useClueStore } from "@/store/clueStore";
|
||||||
import type { ClueHint } from "@/components/clue";
|
import type { ClueHint } from "@/components/clue";
|
||||||
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
|
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
|
||||||
|
import { useSunzhongshanStore } from "@/store/sunzhongshanStore";
|
||||||
|
|
||||||
const starName = "sunzhongshan";
|
const starName = "sunzhongshan";
|
||||||
const sceneId = `${starName}/reflection/scene2`;
|
const sceneId = `${starName}/reflection/scene2`;
|
||||||
@@ -82,6 +83,7 @@ const SunzhongshanReflectionScene2 = () => {
|
|||||||
|
|
||||||
// 获取碎片动画触发方法
|
// 获取碎片动画触发方法
|
||||||
const { triggerPuzzleAnimation } = usePuzzleAnimation();
|
const { triggerPuzzleAnimation } = usePuzzleAnimation();
|
||||||
|
const { getReflectionFlag, setReflectionFlag } = useSunzhongshanStore();
|
||||||
|
|
||||||
const [showClueHint, setShowClueHint] = useState(false);
|
const [showClueHint, setShowClueHint] = useState(false);
|
||||||
const [showClueBook, setShowClueBook] = useState(false);
|
const [showClueBook, setShowClueBook] = useState(false);
|
||||||
@@ -189,7 +191,7 @@ const SunzhongshanReflectionScene2 = () => {
|
|||||||
const handleVideoEnd = () => {
|
const handleVideoEnd = () => {
|
||||||
setShowVideo(false);
|
setShowVideo(false);
|
||||||
// 检查是否需要显示学习引导
|
// 检查是否需要显示学习引导
|
||||||
const hasShownGuide = localStorage.getItem(GUIDE_STORAGE_KEY);
|
const hasShownGuide = getReflectionFlag(GUIDE_STORAGE_KEY);
|
||||||
if (!hasShownGuide) {
|
if (!hasShownGuide) {
|
||||||
setShowGuide(true);
|
setShowGuide(true);
|
||||||
}
|
}
|
||||||
@@ -198,7 +200,7 @@ const SunzhongshanReflectionScene2 = () => {
|
|||||||
// 处理学习引导关闭
|
// 处理学习引导关闭
|
||||||
const handleGuideClose = () => {
|
const handleGuideClose = () => {
|
||||||
setShowGuide(false);
|
setShowGuide(false);
|
||||||
localStorage.setItem(GUIDE_STORAGE_KEY, "true");
|
setReflectionFlag(GUIDE_STORAGE_KEY, true);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 检查是否所有线索都已收集
|
// 检查是否所有线索都已收集
|
||||||
@@ -250,7 +252,7 @@ const SunzhongshanReflectionScene2 = () => {
|
|||||||
const handleCompleteGuideClose = () => {
|
const handleCompleteGuideClose = () => {
|
||||||
setShowCompleteGuide(false);
|
setShowCompleteGuide(false);
|
||||||
// 保存场景2完成状态
|
// 保存场景2完成状态
|
||||||
localStorage.setItem(`${starName}-reflection-scene2-completed`, "true");
|
setReflectionFlag(`${starName}-reflection-scene2-completed`, true);
|
||||||
if (moduleName && starName) {
|
if (moduleName && starName) {
|
||||||
navigate(`/${moduleName}/${starName}/reflection3`);
|
navigate(`/${moduleName}/${starName}/reflection3`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { getBackgroundImage, getStaticImage, getPublicPath } from "@/lib/utils";
|
|||||||
import { useClueStore } from "@/store/clueStore";
|
import { useClueStore } from "@/store/clueStore";
|
||||||
import type { ClueHint } from "@/components/clue";
|
import type { ClueHint } from "@/components/clue";
|
||||||
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
|
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
|
||||||
|
import { useSunzhongshanStore } from "@/store/sunzhongshanStore";
|
||||||
|
|
||||||
const starName = "sunzhongshan";
|
const starName = "sunzhongshan";
|
||||||
const sceneId = `${starName}/reflection/scene3`;
|
const sceneId = `${starName}/reflection/scene3`;
|
||||||
@@ -73,6 +74,7 @@ const SunzhongshanReflectionScene3 = () => {
|
|||||||
|
|
||||||
// 获取碎片动画触发方法
|
// 获取碎片动画触发方法
|
||||||
const { triggerPuzzleAnimation } = usePuzzleAnimation();
|
const { triggerPuzzleAnimation } = usePuzzleAnimation();
|
||||||
|
const { getReflectionFlag, setReflectionFlag } = useSunzhongshanStore();
|
||||||
|
|
||||||
const [showClueHint, setShowClueHint] = useState(false);
|
const [showClueHint, setShowClueHint] = useState(false);
|
||||||
const [showClueBook, setShowClueBook] = useState(false);
|
const [showClueBook, setShowClueBook] = useState(false);
|
||||||
@@ -202,7 +204,7 @@ const SunzhongshanReflectionScene3 = () => {
|
|||||||
const handleVideoEnd = () => {
|
const handleVideoEnd = () => {
|
||||||
setShowVideo(false);
|
setShowVideo(false);
|
||||||
// 检查是否需要显示学习引导
|
// 检查是否需要显示学习引导
|
||||||
const hasShownGuide = localStorage.getItem(GUIDE_STORAGE_KEY);
|
const hasShownGuide = getReflectionFlag(GUIDE_STORAGE_KEY);
|
||||||
if (!hasShownGuide) {
|
if (!hasShownGuide) {
|
||||||
setShowGuide(true);
|
setShowGuide(true);
|
||||||
}
|
}
|
||||||
@@ -211,7 +213,7 @@ const SunzhongshanReflectionScene3 = () => {
|
|||||||
// 处理学习引导关闭
|
// 处理学习引导关闭
|
||||||
const handleGuideClose = () => {
|
const handleGuideClose = () => {
|
||||||
setShowGuide(false);
|
setShowGuide(false);
|
||||||
localStorage.setItem(GUIDE_STORAGE_KEY, "true");
|
setReflectionFlag(GUIDE_STORAGE_KEY, true);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 检查是否所有线索都已收集
|
// 检查是否所有线索都已收集
|
||||||
@@ -262,7 +264,7 @@ const SunzhongshanReflectionScene3 = () => {
|
|||||||
const handleCompleteGuideClose = () => {
|
const handleCompleteGuideClose = () => {
|
||||||
setShowCompleteGuide(false);
|
setShowCompleteGuide(false);
|
||||||
// 保存场景3完成状态
|
// 保存场景3完成状态
|
||||||
localStorage.setItem(`${starName}-reflection-scene3-completed`, "true");
|
setReflectionFlag(`${starName}-reflection-scene3-completed`, true);
|
||||||
if (moduleName && starName) {
|
if (moduleName && starName) {
|
||||||
navigate(`/${moduleName}/${starName}/home`);
|
navigate(`/${moduleName}/${starName}/home`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
export { useChenyunStore } from "./chenyunStore";
|
export { useChenyunStore } from "./chenyunStore";
|
||||||
export { useHuangyanpeiStore } from "./huangyanpeiStore";
|
export { useHuangyanpeiStore } from "./huangyanpeiStore";
|
||||||
|
export { useLuxunStore } from "./luxunStore";
|
||||||
|
export { useSunzhongshanStore } from "./sunzhongshanStore";
|
||||||
export { useClueStore } from "./clueStore";
|
export { useClueStore } from "./clueStore";
|
||||||
export { usePuzzleStore } from "./puzzleStore";
|
export { usePuzzleStore } from "./puzzleStore";
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
import { create } from "zustand";
|
||||||
|
import { persist } from "zustand/middleware";
|
||||||
|
|
||||||
|
interface LuxunStore {
|
||||||
|
// 存储每个场景(place)的已访问展品ID集合
|
||||||
|
// 格式:{ place1: Set<1, 2>, place2: Set<1, 2>, ... }
|
||||||
|
visitedExhibits: Record<string, Set<number>>;
|
||||||
|
|
||||||
|
// 标记展品为已访问
|
||||||
|
markExhibitVisited: (place: string, exhibitId: number) => void;
|
||||||
|
|
||||||
|
// 检查展品是否已访问
|
||||||
|
isExhibitVisited: (place: string, exhibitId: number) => boolean;
|
||||||
|
|
||||||
|
// 获取某个场景已访问的展品ID数组
|
||||||
|
getVisitedExhibits: (place: string) => number[];
|
||||||
|
|
||||||
|
// 重置某个场景的访问记录
|
||||||
|
resetPlace: (place: string) => void;
|
||||||
|
|
||||||
|
// 重置所有访问记录
|
||||||
|
resetAll: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将 Set 转换为数组以便序列化
|
||||||
|
const serializeVisitedExhibits = (
|
||||||
|
visitedExhibits: Record<string, Set<number>>
|
||||||
|
): Record<string, number[]> => {
|
||||||
|
const serialized: Record<string, number[]> = {};
|
||||||
|
for (const [place, exhibits] of Object.entries(visitedExhibits)) {
|
||||||
|
serialized[place] = Array.from(exhibits);
|
||||||
|
}
|
||||||
|
return serialized;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 将数组转换回 Set
|
||||||
|
const deserializeVisitedExhibits = (
|
||||||
|
serialized: Record<string, number[]> | undefined
|
||||||
|
): Record<string, Set<number>> => {
|
||||||
|
if (!serialized || typeof serialized !== "object") {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const visitedExhibits: Record<string, Set<number>> = {};
|
||||||
|
for (const [place, exhibits] of Object.entries(serialized)) {
|
||||||
|
if (Array.isArray(exhibits)) {
|
||||||
|
visitedExhibits[place] = new Set(exhibits);
|
||||||
|
} else {
|
||||||
|
visitedExhibits[place] = new Set();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return visitedExhibits;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useLuxunStore = create<LuxunStore>()(
|
||||||
|
persist(
|
||||||
|
(set, get) => ({
|
||||||
|
visitedExhibits: {},
|
||||||
|
|
||||||
|
markExhibitVisited: (place: string, exhibitId: number) => {
|
||||||
|
set((state) => {
|
||||||
|
const currentExhibits =
|
||||||
|
state.visitedExhibits[place] || new Set<number>();
|
||||||
|
if (!currentExhibits.has(exhibitId)) {
|
||||||
|
const newExhibits = {
|
||||||
|
...state.visitedExhibits,
|
||||||
|
[place]: new Set([...currentExhibits, exhibitId]),
|
||||||
|
};
|
||||||
|
return { visitedExhibits: newExhibits };
|
||||||
|
}
|
||||||
|
return state;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
isExhibitVisited: (place: string, exhibitId: number) => {
|
||||||
|
const { visitedExhibits } = get();
|
||||||
|
return visitedExhibits[place]?.has(exhibitId) || false;
|
||||||
|
},
|
||||||
|
|
||||||
|
getVisitedExhibits: (place: string) => {
|
||||||
|
const { visitedExhibits } = get();
|
||||||
|
return Array.from(visitedExhibits[place] || []);
|
||||||
|
},
|
||||||
|
|
||||||
|
resetPlace: (place: string) => {
|
||||||
|
set((state) => {
|
||||||
|
const newExhibits = { ...state.visitedExhibits };
|
||||||
|
delete newExhibits[place];
|
||||||
|
return { visitedExhibits: newExhibits };
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
resetAll: () => {
|
||||||
|
set({ visitedExhibits: {} });
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
name: "luxun-storage",
|
||||||
|
partialize: (state) => ({
|
||||||
|
visitedExhibits: serializeVisitedExhibits(state.visitedExhibits),
|
||||||
|
}),
|
||||||
|
onRehydrateStorage: () => (state) => {
|
||||||
|
if (state) {
|
||||||
|
state.visitedExhibits = deserializeVisitedExhibits(
|
||||||
|
state.visitedExhibits as any
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
import { create } from "zustand";
|
||||||
|
import { persist } from "zustand/middleware";
|
||||||
|
|
||||||
|
interface SunzhongshanStore {
|
||||||
|
// 存储每个场景(place)的已访问展品ID集合
|
||||||
|
// 格式:{ place1: Set<1, 2>, place2: Set<1, 2>, ... }
|
||||||
|
visitedExhibits: Record<string, Set<number>>;
|
||||||
|
|
||||||
|
// 体悟模块通用布尔状态(引导是否展示、场景是否完成等)
|
||||||
|
reflectionFlags: Record<string, boolean>;
|
||||||
|
|
||||||
|
// 标记展品为已访问
|
||||||
|
markExhibitVisited: (place: string, exhibitId: number) => void;
|
||||||
|
|
||||||
|
// 检查展品是否已访问
|
||||||
|
isExhibitVisited: (place: string, exhibitId: number) => boolean;
|
||||||
|
|
||||||
|
// 获取某个场景已访问的展品ID数组
|
||||||
|
getVisitedExhibits: (place: string) => number[];
|
||||||
|
|
||||||
|
// 重置某个场景的访问记录
|
||||||
|
resetPlace: (place: string) => void;
|
||||||
|
|
||||||
|
// 重置所有访问记录
|
||||||
|
resetAll: () => void;
|
||||||
|
|
||||||
|
// 设置体悟模块布尔状态
|
||||||
|
setReflectionFlag: (key: string, value: boolean) => void;
|
||||||
|
|
||||||
|
// 获取体悟模块布尔状态
|
||||||
|
getReflectionFlag: (key: string) => boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将 Set 转换为数组以便序列化
|
||||||
|
const serializeVisitedExhibits = (
|
||||||
|
visitedExhibits: Record<string, Set<number>>
|
||||||
|
): Record<string, number[]> => {
|
||||||
|
const serialized: Record<string, number[]> = {};
|
||||||
|
for (const [place, exhibits] of Object.entries(visitedExhibits)) {
|
||||||
|
serialized[place] = Array.from(exhibits);
|
||||||
|
}
|
||||||
|
return serialized;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 将数组转换回 Set
|
||||||
|
const deserializeVisitedExhibits = (
|
||||||
|
serialized: Record<string, number[]> | undefined
|
||||||
|
): Record<string, Set<number>> => {
|
||||||
|
if (!serialized || typeof serialized !== "object") {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const visitedExhibits: Record<string, Set<number>> = {};
|
||||||
|
for (const [place, exhibits] of Object.entries(serialized)) {
|
||||||
|
if (Array.isArray(exhibits)) {
|
||||||
|
visitedExhibits[place] = new Set(exhibits);
|
||||||
|
} else {
|
||||||
|
visitedExhibits[place] = new Set();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return visitedExhibits;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useSunzhongshanStore = create<SunzhongshanStore>()(
|
||||||
|
persist(
|
||||||
|
(set, get) => ({
|
||||||
|
visitedExhibits: {},
|
||||||
|
reflectionFlags: {},
|
||||||
|
|
||||||
|
markExhibitVisited: (place: string, exhibitId: number) => {
|
||||||
|
set((state) => {
|
||||||
|
const currentExhibits =
|
||||||
|
state.visitedExhibits[place] || new Set<number>();
|
||||||
|
if (!currentExhibits.has(exhibitId)) {
|
||||||
|
const newExhibits = {
|
||||||
|
...state.visitedExhibits,
|
||||||
|
[place]: new Set([...currentExhibits, exhibitId]),
|
||||||
|
};
|
||||||
|
return { visitedExhibits: newExhibits };
|
||||||
|
}
|
||||||
|
return state;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
isExhibitVisited: (place: string, exhibitId: number) => {
|
||||||
|
const { visitedExhibits } = get();
|
||||||
|
return visitedExhibits[place]?.has(exhibitId) || false;
|
||||||
|
},
|
||||||
|
|
||||||
|
getVisitedExhibits: (place: string) => {
|
||||||
|
const { visitedExhibits } = get();
|
||||||
|
return Array.from(visitedExhibits[place] || []);
|
||||||
|
},
|
||||||
|
|
||||||
|
resetPlace: (place: string) => {
|
||||||
|
set((state) => {
|
||||||
|
const newExhibits = { ...state.visitedExhibits };
|
||||||
|
delete newExhibits[place];
|
||||||
|
return { visitedExhibits: newExhibits };
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
resetAll: () => {
|
||||||
|
set({ visitedExhibits: {}, reflectionFlags: {} });
|
||||||
|
},
|
||||||
|
|
||||||
|
setReflectionFlag: (key: string, value: boolean) => {
|
||||||
|
set((state) => ({
|
||||||
|
reflectionFlags: {
|
||||||
|
...state.reflectionFlags,
|
||||||
|
[key]: value,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
|
getReflectionFlag: (key: string) => {
|
||||||
|
const { reflectionFlags } = get();
|
||||||
|
return !!reflectionFlags[key];
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
name: "sunzhongshan-storage",
|
||||||
|
partialize: (state) => ({
|
||||||
|
visitedExhibits: serializeVisitedExhibits(state.visitedExhibits),
|
||||||
|
reflectionFlags: state.reflectionFlags,
|
||||||
|
}),
|
||||||
|
onRehydrateStorage: () => (state) => {
|
||||||
|
if (state) {
|
||||||
|
state.visitedExhibits = deserializeVisitedExhibits(
|
||||||
|
state.visitedExhibits as any
|
||||||
|
);
|
||||||
|
if (
|
||||||
|
!state.reflectionFlags ||
|
||||||
|
typeof state.reflectionFlags !== "object"
|
||||||
|
) {
|
||||||
|
state.reflectionFlags = {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
Reference in new issue
Block a user