feat: 添加全局键盘快捷键和更新故居数据,提升用户体验
- 在 main.tsx 中添加全局键盘快捷键功能,允许用户通过 Ctrl+Shift+R 重置所有缓存。 - 更新多个故居的拼图数据,增加黄炎培和鲁迅的相关信息,丰富学习内容。 - 调整 ResidenceLayout 组件以支持新的故居,确保数据一致性。 - 清理不再使用的拼图布局配置文件,提升代码整洁性。
This commit is contained in:
1 parent
8ba36a4a1a
commit
a365964f54
155 files changed
+1387
-348
No files matched your search
@@ -118,8 +118,10 @@ const ChenyunInternalization = () => {
|
||||
const [showVideo, setShowVideo] = useState(false);
|
||||
const [postGuideVisible, setPostGuideVisible] = useState(false);
|
||||
const [showLetter, setShowLetter] = useState(false);
|
||||
const [showLetterVideo, setShowLetterVideo] = useState(false);
|
||||
const [showStudyGuide, setShowStudyGuide] = useState(false);
|
||||
const videoRef = useRef<VideoRef>(null);
|
||||
const letterVideoRef = useRef<VideoRef>(null);
|
||||
const audioRef = useRef<HTMLAudioElement | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -145,6 +147,15 @@ const ChenyunInternalization = () => {
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 信件视频加载完成后自动播放
|
||||
const handleLetterVideoCanPlay = () => {
|
||||
if (showLetterVideo && letterVideoRef.current) {
|
||||
letterVideoRef.current.play().catch((error) => {
|
||||
console.warn("信件视频播放失败:", error);
|
||||
});
|
||||
}
|
||||
};
|
||||
const anchors: VideoAnchor[] = useMemo(
|
||||
() =>
|
||||
QUIZ_ANCHORS.map((item) => {
|
||||
@@ -413,10 +424,25 @@ const ChenyunInternalization = () => {
|
||||
// 提交信件的逻辑(values 为多个填空的内容)
|
||||
console.log("提交信件", values);
|
||||
setShowLetter(false);
|
||||
setShowStudyGuide(true);
|
||||
setShowLetterVideo(true);
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* 信件关闭后的视频 */}
|
||||
{showLetterVideo && (
|
||||
<Video
|
||||
ref={letterVideoRef}
|
||||
src={getPublicPath("video/letter/neihua_jixin.mp4")}
|
||||
className="h-full w-full"
|
||||
playsInline
|
||||
onCanPlay={handleLetterVideoCanPlay}
|
||||
onEnded={() => {
|
||||
setShowLetterVideo(false);
|
||||
setShowStudyGuide(true);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<EndingOverlay
|
||||
visible={showStudyGuide}
|
||||
text="恭喜你已经顺利完成了陈云纪念馆的学习,至此,你已经完成了本课程所有故居的学习,
|
||||
|
||||
@@ -14,6 +14,7 @@ import { getBackgroundImage, getPublicPath, getStaticImage } from "@/lib/utils";
|
||||
import { SuccessAlert, DragQuizPanel } from "@/components/layout";
|
||||
import { MultipleChoiceQuizPanel } from "@/components/layout/quiz";
|
||||
import { GuideSteps, GuideStepsItem } from "@/components";
|
||||
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
|
||||
|
||||
type QuizOption = {
|
||||
value: string;
|
||||
@@ -138,6 +139,8 @@ const HuangyanpeiInternalization = () => {
|
||||
const navigate = useNavigate();
|
||||
const { moduleName } = useParams<{ moduleName: string }>();
|
||||
|
||||
const { triggerPuzzleAnimation } = usePuzzleAnimation();
|
||||
|
||||
const [narrationVisible, setNarrationVisible] = useState(false);
|
||||
const [activeSuccess, setActiveSuccess] = useState<string | null>(null);
|
||||
const [successContent, setSuccessContent] = useState<string>("");
|
||||
@@ -154,8 +157,10 @@ const HuangyanpeiInternalization = () => {
|
||||
const [showVideo, setShowVideo] = useState(false);
|
||||
const [postGuideVisible, setPostGuideVisible] = useState(false);
|
||||
const [showLetter, setShowLetter] = useState(false);
|
||||
const [showLetterVideo, setShowLetterVideo] = useState(false);
|
||||
const [showStudyGuide, setShowStudyGuide] = useState(false);
|
||||
const videoRef = useRef<VideoRef>(null);
|
||||
const letterVideoRef = useRef<VideoRef>(null);
|
||||
const audioRef = useRef<HTMLAudioElement | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -180,6 +185,15 @@ const HuangyanpeiInternalization = () => {
|
||||
}
|
||||
};
|
||||
|
||||
// 信件视频加载完成后自动播放
|
||||
const handleLetterVideoCanPlay = () => {
|
||||
if (showLetterVideo && letterVideoRef.current) {
|
||||
letterVideoRef.current.play().catch((error) => {
|
||||
console.warn("信件视频播放失败:", error);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const anchors: VideoAnchor[] = useMemo(
|
||||
() =>
|
||||
QUIZ_ANCHORS.map((item) => {
|
||||
@@ -275,6 +289,7 @@ const HuangyanpeiInternalization = () => {
|
||||
const cb = resumeCallback;
|
||||
setResumeCallback(null);
|
||||
cb?.();
|
||||
triggerPuzzleAnimation(16);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
@@ -343,6 +358,7 @@ const HuangyanpeiInternalization = () => {
|
||||
successContent,
|
||||
dragQuizValues,
|
||||
dragQuizErrors,
|
||||
triggerPuzzleAnimation,
|
||||
]
|
||||
);
|
||||
|
||||
@@ -458,10 +474,25 @@ const HuangyanpeiInternalization = () => {
|
||||
onSubmit={(values: string[]) => {
|
||||
console.log("提交信件", values);
|
||||
setShowLetter(false);
|
||||
setShowStudyGuide(true);
|
||||
setShowLetterVideo(true);
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* 信件关闭后的视频 */}
|
||||
{showLetterVideo && (
|
||||
<Video
|
||||
ref={letterVideoRef}
|
||||
src={getPublicPath("video/letter/neihua_jixin.mp4")}
|
||||
className="h-full w-full"
|
||||
playsInline
|
||||
onCanPlay={handleLetterVideoCanPlay}
|
||||
onEnded={() => {
|
||||
setShowLetterVideo(false);
|
||||
setShowStudyGuide(true);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<EndingOverlay
|
||||
visible={showStudyGuide}
|
||||
text="恭喜你已经顺利完成了黄炎培故居的学习,请继续前往下一个故居吧!"
|
||||
|
||||
@@ -10,6 +10,8 @@ import { useNavigate, useParams } from "react-router-dom";
|
||||
import { getBackgroundImage, getPublicPath, getStaticImage } from "@/lib/utils";
|
||||
import { useState } from "react";
|
||||
import Cursor from "@/components/cursor";
|
||||
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
|
||||
import { useHuangyanpeiStore } from "@/store/huangyanpeiStore";
|
||||
|
||||
// starName 固定为 huangyanpei
|
||||
const starName = "huangyanpei";
|
||||
@@ -25,6 +27,8 @@ const HuangyanpeiPlace1 = () => {
|
||||
moduleName: string;
|
||||
}>();
|
||||
|
||||
const { triggerPuzzleAnimation } = usePuzzleAnimation();
|
||||
|
||||
// 学习引导弹窗
|
||||
const [showGuideModal, setShowGuideModal] = useState(true);
|
||||
// 对话结束后的学习引导弹窗
|
||||
@@ -34,8 +38,14 @@ const HuangyanpeiPlace1 = () => {
|
||||
const [visible, setVisible] = useState(false);
|
||||
// 当前激活的展品 ID(只存 ID,不直接存路径,便于作为唯一标识)
|
||||
const [activeExhibitId, setActiveExhibitId] = useState<1 | null>(null);
|
||||
// 已浏览展品的 ID 集合
|
||||
const [visited, setVisited] = useState<Set<number>>(new Set());
|
||||
|
||||
// 使用持久化的访问状态
|
||||
const place = "place1";
|
||||
const { markExhibitVisited, getVisitedExhibits } = useHuangyanpeiStore();
|
||||
|
||||
// 从 store 获取已访问的展品,转换为 Set 用于兼容现有代码
|
||||
const visitedExhibits = getVisitedExhibits(place);
|
||||
const visited = new Set(visitedExhibits);
|
||||
|
||||
const handleOpenExhibit = (index: 1 | null) => {
|
||||
setActiveExhibitId(index);
|
||||
@@ -100,13 +110,14 @@ const HuangyanpeiPlace1 = () => {
|
||||
<ExhibitOverlay
|
||||
visible={activeExhibitId !== null}
|
||||
src={activeExhibitId ? exhibitSrcMap[activeExhibitId] : ""}
|
||||
onOpen={() => {
|
||||
if (activeExhibitId === 1) {
|
||||
triggerPuzzleAnimation(1);
|
||||
}
|
||||
}}
|
||||
onClose={() => {
|
||||
if (activeExhibitId !== null) {
|
||||
setVisited((prev) => {
|
||||
const next = new Set(prev);
|
||||
next.add(activeExhibitId);
|
||||
return next;
|
||||
});
|
||||
markExhibitVisited(place, activeExhibitId);
|
||||
}
|
||||
setActiveExhibitId(null);
|
||||
}}
|
||||
|
||||
@@ -10,6 +10,8 @@ import {
|
||||
import { getBackgroundImage, getPublicPath, getStaticImage } from "@/lib/utils";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { useCursorController } from "@/hooks/useCursorController";
|
||||
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
|
||||
import { useHuangyanpeiStore } from "@/store/huangyanpeiStore";
|
||||
|
||||
const starName = "huangyanpei";
|
||||
|
||||
@@ -24,12 +26,22 @@ const HuangyanpeiPlace2 = () => {
|
||||
moduleName: string;
|
||||
}>();
|
||||
|
||||
const { triggerPuzzleAnimation } = usePuzzleAnimation();
|
||||
|
||||
const [activeExhibitId, setActiveExhibitId] = useState<1 | 2 | null>(null);
|
||||
const [visited, setVisited] = useState<Set<number>>(new Set());
|
||||
|
||||
// 使用持久化的访问状态
|
||||
const place = "place2";
|
||||
const { markExhibitVisited, getVisitedExhibits } = useHuangyanpeiStore();
|
||||
|
||||
// 从 store 获取已访问的展品,转换为 Set 用于兼容现有代码
|
||||
const visitedExhibits = getVisitedExhibits(place);
|
||||
const visited = new Set(visitedExhibits);
|
||||
|
||||
const { isEnabled } = useCursorController({
|
||||
initialEnabled: [1],
|
||||
dependencies: { 2: [1] },
|
||||
visited,
|
||||
visited: visitedExhibits, // 传入数组,而不是 Set
|
||||
});
|
||||
|
||||
// GuideSteps 当前步骤
|
||||
@@ -116,13 +128,14 @@ const HuangyanpeiPlace2 = () => {
|
||||
<ExhibitOverlay
|
||||
visible={activeExhibitId !== null}
|
||||
src={activeExhibitId ? exhibitSrcMap[activeExhibitId] : ""}
|
||||
onOpen={() => {
|
||||
if (activeExhibitId === 1) {
|
||||
triggerPuzzleAnimation(2);
|
||||
}
|
||||
}}
|
||||
onClose={() => {
|
||||
if (activeExhibitId !== null) {
|
||||
setVisited((prev) => {
|
||||
const next = new Set(prev);
|
||||
next.add(activeExhibitId);
|
||||
return next;
|
||||
});
|
||||
markExhibitVisited(place, activeExhibitId);
|
||||
}
|
||||
setActiveExhibitId(null);
|
||||
}}
|
||||
|
||||
@@ -12,6 +12,8 @@ import { SuccessAlert } from "@/components/layout";
|
||||
import { getBackgroundImage, getPublicPath, getStaticImage } from "@/lib/utils";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { useCursorController } from "@/hooks/useCursorController";
|
||||
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
|
||||
import { useHuangyanpeiStore } from "@/store/huangyanpeiStore";
|
||||
|
||||
const starName = "huangyanpei";
|
||||
|
||||
@@ -27,14 +29,24 @@ const HuangyanpeiPlace3 = () => {
|
||||
moduleName: string;
|
||||
}>();
|
||||
|
||||
const { triggerPuzzleAnimation } = usePuzzleAnimation();
|
||||
|
||||
const [activeExhibitId, setActiveExhibitId] = useState<1 | 2 | 3 | null>(
|
||||
null
|
||||
);
|
||||
const [visited, setVisited] = useState<Set<number>>(new Set());
|
||||
|
||||
// 使用持久化的访问状态
|
||||
const place = "place3";
|
||||
const { markExhibitVisited, getVisitedExhibits } = useHuangyanpeiStore();
|
||||
|
||||
// 从 store 获取已访问的展品,转换为 Set 用于兼容现有代码
|
||||
const visitedExhibits = getVisitedExhibits(place);
|
||||
const visited = new Set(visitedExhibits);
|
||||
|
||||
const { isEnabled } = useCursorController({
|
||||
initialEnabled: [3],
|
||||
dependencies: { 2: [3], 1: [2] },
|
||||
visited,
|
||||
visited: visitedExhibits, // 传入数组,而不是 Set
|
||||
});
|
||||
|
||||
// 光标3的引导对话状态
|
||||
@@ -158,6 +170,17 @@ const HuangyanpeiPlace3 = () => {
|
||||
<ExhibitOverlay
|
||||
visible={activeExhibitId !== null}
|
||||
src={activeExhibitId ? exhibitSrcMap[activeExhibitId] : ""}
|
||||
onOpen={() => {
|
||||
if (activeExhibitId === 3) {
|
||||
triggerPuzzleAnimation(3);
|
||||
}
|
||||
if (activeExhibitId === 2) {
|
||||
triggerPuzzleAnimation(4);
|
||||
}
|
||||
if (activeExhibitId === 1) {
|
||||
triggerPuzzleAnimation(5);
|
||||
}
|
||||
}}
|
||||
onClose={() => {
|
||||
if (activeExhibitId !== null) {
|
||||
setActiveExhibitId(null);
|
||||
@@ -165,11 +188,7 @@ const HuangyanpeiPlace3 = () => {
|
||||
setShowQuiz3(true);
|
||||
return;
|
||||
}
|
||||
setVisited((prev) => {
|
||||
const next = new Set(prev);
|
||||
next.add(activeExhibitId);
|
||||
return next;
|
||||
});
|
||||
markExhibitVisited(place, activeExhibitId);
|
||||
} else {
|
||||
setActiveExhibitId(null);
|
||||
}
|
||||
@@ -229,11 +248,7 @@ const HuangyanpeiPlace3 = () => {
|
||||
visible={showSuccessAlert}
|
||||
onContinue={() => {
|
||||
setShowSuccessAlert(false);
|
||||
setVisited((prev) => {
|
||||
const next = new Set(prev);
|
||||
next.add(3);
|
||||
return next;
|
||||
});
|
||||
markExhibitVisited(place, 3);
|
||||
}}
|
||||
onClose={() => {
|
||||
setShowSuccessAlert(false);
|
||||
@@ -244,7 +259,7 @@ const HuangyanpeiPlace3 = () => {
|
||||
<GuideSteps
|
||||
visible={guide12Visible}
|
||||
onVisibleChange={setGuide12Visible}
|
||||
showMask={true}
|
||||
showMask={false}
|
||||
zIndex={10000}
|
||||
>
|
||||
<GuideStepsItem
|
||||
|
||||
@@ -14,11 +14,13 @@ import { getBackgroundImage, getStaticImage, getPublicPath } from "@/lib/utils";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import type { VideoRef } from "@/components/video";
|
||||
import { useCursorController } from "@/hooks/useCursorController";
|
||||
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
|
||||
import { useHuangyanpeiStore } from "@/store/huangyanpeiStore";
|
||||
|
||||
const starName = "huangyanpei";
|
||||
|
||||
const exhibitSrcMap: Record<1 | 2 | 3 | 4 | 5, string> = {
|
||||
1: "huangyanpei/place4/6_006",
|
||||
1: "huangyanpei/place4/6_6&7",
|
||||
2: "huangyanpei/place4/6_007",
|
||||
3: "huangyanpei/place4/6_008",
|
||||
4: "huangyanpei/place4/6_009",
|
||||
@@ -31,10 +33,19 @@ const HuangyanpeiPlace4 = () => {
|
||||
moduleName: string;
|
||||
}>();
|
||||
|
||||
const { triggerPuzzleAnimation } = usePuzzleAnimation();
|
||||
const [activeExhibitId, setActiveExhibitId] = useState<1 | 3 | 4 | 5 | null>(
|
||||
null
|
||||
);
|
||||
const [visited, setVisited] = useState<Set<number>>(new Set());
|
||||
|
||||
// 使用持久化的访问状态
|
||||
const place = "place4";
|
||||
const { markExhibitVisited, getVisitedExhibits } = useHuangyanpeiStore();
|
||||
|
||||
// 从 store 获取已访问的展品,转换为 Set 用于兼容现有代码
|
||||
const visitedExhibits = getVisitedExhibits(place);
|
||||
const visited = new Set(visitedExhibits);
|
||||
|
||||
const { isEnabled } = useCursorController({
|
||||
initialEnabled: [3, 5],
|
||||
dependencies: {
|
||||
@@ -42,7 +53,7 @@ const HuangyanpeiPlace4 = () => {
|
||||
4: [1],
|
||||
// 2 已注释,不计入顺序
|
||||
},
|
||||
visited,
|
||||
visited: visitedExhibits, // 传入数组,而不是 Set
|
||||
});
|
||||
|
||||
// 光标1的引导对话状态
|
||||
@@ -187,13 +198,23 @@ const HuangyanpeiPlace4 = () => {
|
||||
// : undefined
|
||||
// }
|
||||
src={activeExhibitId ? exhibitSrcMap[activeExhibitId] : ""}
|
||||
onOpen={() => {
|
||||
if (activeExhibitId === 3) {
|
||||
triggerPuzzleAnimation(7);
|
||||
}
|
||||
if (activeExhibitId === 5) {
|
||||
triggerPuzzleAnimation(6);
|
||||
}
|
||||
if (activeExhibitId === 1) {
|
||||
triggerPuzzleAnimation(8);
|
||||
}
|
||||
if (activeExhibitId === 4) {
|
||||
triggerPuzzleAnimation(10);
|
||||
}
|
||||
}}
|
||||
onClose={() => {
|
||||
if (activeExhibitId !== null) {
|
||||
setVisited((prev) => {
|
||||
const next = new Set(prev);
|
||||
next.add(activeExhibitId);
|
||||
return next;
|
||||
});
|
||||
markExhibitVisited(place, activeExhibitId);
|
||||
}
|
||||
setActiveExhibitId(null);
|
||||
}}
|
||||
@@ -205,6 +226,9 @@ const HuangyanpeiPlace4 = () => {
|
||||
currentStep={guide1Step}
|
||||
onStepChange={setGuide1Step}
|
||||
onVisibleChange={(visible) => {
|
||||
if (!visible) {
|
||||
triggerPuzzleAnimation(9);
|
||||
}
|
||||
setGuide1Visible(visible);
|
||||
}}
|
||||
showMask={false}
|
||||
|
||||
@@ -14,6 +14,8 @@ import { getBackgroundImage, getStaticImage, getPublicPath } from "@/lib/utils";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import type { VideoRef } from "@/components/video";
|
||||
import { useCursorController } from "@/hooks/useCursorController";
|
||||
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
|
||||
import { useHuangyanpeiStore } from "@/store/huangyanpeiStore";
|
||||
|
||||
const starName = "huangyanpei";
|
||||
|
||||
@@ -28,8 +30,9 @@ const HuangyanpeiPlace5 = () => {
|
||||
moduleName: string;
|
||||
}>();
|
||||
|
||||
const { triggerPuzzleAnimation } = usePuzzleAnimation();
|
||||
|
||||
const [activeExhibitId, setActiveExhibitId] = useState<1 | 2 | null>(null);
|
||||
const [visited, setVisited] = useState<Set<number>>(new Set());
|
||||
const [videoVisible, setVideoVisible] = useState(false);
|
||||
const videoRef = useRef<VideoRef>(null);
|
||||
// 光标1的引导对话状态
|
||||
@@ -43,11 +46,19 @@ const HuangyanpeiPlace5 = () => {
|
||||
// 成功提示状态
|
||||
const [showSuccessAlert, setShowSuccessAlert] = useState(false);
|
||||
|
||||
// 使用持久化的访问状态
|
||||
const place = "place5";
|
||||
const { markExhibitVisited, getVisitedExhibits } = useHuangyanpeiStore();
|
||||
|
||||
// 从 store 获取已访问的展品,转换为 Set 用于兼容现有代码
|
||||
const visitedExhibits = getVisitedExhibits(place);
|
||||
const visited = new Set(visitedExhibits);
|
||||
|
||||
// 光标顺序:1 -> 2(需完成1后解锁2)
|
||||
const { isEnabled } = useCursorController({
|
||||
initialEnabled: [1],
|
||||
dependencies: { 2: [1] },
|
||||
visited,
|
||||
visited: visitedExhibits, // 传入数组,而不是 Set
|
||||
});
|
||||
|
||||
const handleOpenExhibit = (index: 1 | 2) => {
|
||||
@@ -159,13 +170,14 @@ const HuangyanpeiPlace5 = () => {
|
||||
// : undefined
|
||||
// }
|
||||
src={activeExhibitId ? exhibitSrcMap[activeExhibitId] : ""}
|
||||
onOpen={() => {
|
||||
if (activeExhibitId === 1) {
|
||||
triggerPuzzleAnimation(11);
|
||||
}
|
||||
}}
|
||||
onClose={() => {
|
||||
if (activeExhibitId !== null) {
|
||||
setVisited((prev) => {
|
||||
const next = new Set(prev);
|
||||
next.add(activeExhibitId);
|
||||
return next;
|
||||
});
|
||||
markExhibitVisited(place, activeExhibitId);
|
||||
}
|
||||
setActiveExhibitId(null);
|
||||
}}
|
||||
@@ -268,11 +280,8 @@ const HuangyanpeiPlace5 = () => {
|
||||
visible={showSuccessAlert}
|
||||
onContinue={() => {
|
||||
setShowSuccessAlert(false);
|
||||
setVisited((prev) => {
|
||||
const next = new Set(prev);
|
||||
next.add(2);
|
||||
return next;
|
||||
});
|
||||
markExhibitVisited(place, 2);
|
||||
triggerPuzzleAnimation(12);
|
||||
}}
|
||||
onClose={() => {
|
||||
setShowSuccessAlert(false);
|
||||
|
||||
@@ -10,6 +10,8 @@ import {
|
||||
import { getBackgroundImage, getPublicPath, getStaticImage } from "@/lib/utils";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { EndingOverlay } from "@/components/overlay/EndingOverlay";
|
||||
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
|
||||
import { useHuangyanpeiStore } from "@/store/huangyanpeiStore";
|
||||
|
||||
const starName = "huangyanpei";
|
||||
|
||||
@@ -23,8 +25,9 @@ const HuangyanpeiPlace6 = () => {
|
||||
moduleName: string;
|
||||
}>();
|
||||
|
||||
const { triggerPuzzleAnimation } = usePuzzleAnimation();
|
||||
|
||||
const [activeExhibitId, setActiveExhibitId] = useState<1 | null>(null);
|
||||
const [visited, setVisited] = useState<Set<number>>(new Set());
|
||||
// 对话引导状态
|
||||
const [guideVisible, setGuideVisible] = useState(false);
|
||||
// 结束窗口状态
|
||||
@@ -32,6 +35,14 @@ const HuangyanpeiPlace6 = () => {
|
||||
// 是否已经显示过结束窗口(防止重复弹出)
|
||||
const [hasShownEnding, setHasShownEnding] = useState(false);
|
||||
|
||||
// 使用持久化的访问状态
|
||||
const place = "place6";
|
||||
const { markExhibitVisited, getVisitedExhibits } = useHuangyanpeiStore();
|
||||
|
||||
// 从 store 获取已访问的展品,转换为 Set 用于兼容现有代码
|
||||
const visitedExhibits = getVisitedExhibits(place);
|
||||
const visited = new Set(visitedExhibits);
|
||||
|
||||
const [Leave, setLeave] = useState(false);
|
||||
|
||||
const handleOpenExhibit = (index: 1) => {
|
||||
@@ -41,16 +52,14 @@ const HuangyanpeiPlace6 = () => {
|
||||
};
|
||||
|
||||
// 当 cursor 已浏览后,显示结束窗口(只显示一次)
|
||||
useEffect(() => {
|
||||
if (visited.has(1) && !hasShownEnding && !endingVisible) {
|
||||
// 延迟一下显示,确保展品关闭动画完成
|
||||
const timer = setTimeout(() => {
|
||||
setEndingVisible(true);
|
||||
setHasShownEnding(true);
|
||||
}, 500);
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
}, [visited, hasShownEnding, endingVisible]);
|
||||
// useEffect(() => {
|
||||
// if (visited.has(1) && !hasShownEnding && !endingVisible) {
|
||||
// // 延迟一下显示,确保展品关闭动画完成
|
||||
// const timer = setTimeout(() => {
|
||||
// }, 500);
|
||||
// return () => clearTimeout(timer);
|
||||
// }
|
||||
// }, [visited, hasShownEnding, endingVisible]);
|
||||
|
||||
const progressCount = visited.size;
|
||||
const total = 1;
|
||||
@@ -107,15 +116,19 @@ const HuangyanpeiPlace6 = () => {
|
||||
<ExhibitOverlay
|
||||
visible={activeExhibitId !== null}
|
||||
src={activeExhibitId ? exhibitSrcMap[activeExhibitId] : ""}
|
||||
onOpen={() => {
|
||||
if (activeExhibitId === 1) {
|
||||
triggerPuzzleAnimation(13);
|
||||
}
|
||||
}}
|
||||
onClose={() => {
|
||||
if (activeExhibitId !== null) {
|
||||
setVisited((prev) => {
|
||||
const next = new Set(prev);
|
||||
next.add(activeExhibitId);
|
||||
return next;
|
||||
});
|
||||
markExhibitVisited(place, activeExhibitId);
|
||||
}
|
||||
setActiveExhibitId(null);
|
||||
|
||||
setEndingVisible(true);
|
||||
setHasShownEnding(true);
|
||||
}}
|
||||
/>
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { useState, useCallback, useEffect } from "react";
|
||||
import { useState, useCallback, useEffect, useMemo } from "react";
|
||||
import { Title, Image, Cursor, StudyGuideModal } from "@/components";
|
||||
import { BambooPuzzleGame, QuestionModal } from "./components";
|
||||
import { SuccessAlert } from "@/components/layout";
|
||||
import { getBackgroundImage, getPublicPath, getStaticImage } from "@/lib/utils";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { useHuangyanpeiStore } from "@/store/huangyanpeiStore";
|
||||
|
||||
const starName = "huangyanpei";
|
||||
|
||||
@@ -13,16 +14,22 @@ const HuangyanpeiReflectionScene1 = () => {
|
||||
moduleName: string;
|
||||
}>();
|
||||
|
||||
const [visited, setVisited] = useState<Set<number>>(new Set());
|
||||
const [showGuideModal, setShowGuideModal] = useState(true);
|
||||
const [showQuestionModal, setShowQuestionModal] = useState(false);
|
||||
const [questionCompleted, setQuestionCompleted] = useState(false);
|
||||
const [showGame, setShowGame] = useState(false);
|
||||
const [gameCompleted, setGameCompleted] = useState(false);
|
||||
const [showSuccessAlert, setShowSuccessAlert] = useState(false);
|
||||
const [showNextGuide, setShowNextGuide] = useState(false);
|
||||
const [nextGuideShown, setNextGuideShown] = useState(false);
|
||||
|
||||
// 使用持久化的reflection cursor状态
|
||||
const scene = "scene1";
|
||||
const { markReflectionCursorVisited, getVisitedReflectionCursors } =
|
||||
useHuangyanpeiStore();
|
||||
|
||||
// 从 store 获取已完成的cursor,转换为 Set 用于兼容现有代码
|
||||
const visitedCursors = getVisitedReflectionCursors(scene);
|
||||
const visited = useMemo(() => new Set(visitedCursors), [visitedCursors]);
|
||||
|
||||
const progressCount = visited.size;
|
||||
const total = 2;
|
||||
const completed = progressCount >= total;
|
||||
@@ -35,14 +42,14 @@ const HuangyanpeiReflectionScene1 = () => {
|
||||
|
||||
// 两个任务都完成后 500ms 弹出下一站提示(仅一次)
|
||||
useEffect(() => {
|
||||
if (questionCompleted && gameCompleted && !nextGuideShown) {
|
||||
if (visited.has(1) && visited.has(2) && !nextGuideShown) {
|
||||
const timer = setTimeout(() => {
|
||||
setShowNextGuide(true);
|
||||
setNextGuideShown(true);
|
||||
}, 500);
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
}, [questionCompleted, gameCompleted, nextGuideShown]);
|
||||
}, [visited, nextGuideShown]);
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -86,29 +93,28 @@ const HuangyanpeiReflectionScene1 = () => {
|
||||
<Cursor
|
||||
className="absolute left-68 top-55"
|
||||
onClick={() => {
|
||||
if (!questionCompleted) {
|
||||
if (!visited.has(1)) {
|
||||
setShowQuestionModal(true);
|
||||
}
|
||||
}}
|
||||
read={questionCompleted}
|
||||
read={visited.has(1)}
|
||||
/>
|
||||
<Cursor
|
||||
className="absolute left-160 bottom-50"
|
||||
onClick={() => {
|
||||
// 如果游戏已完成,不再打开
|
||||
if (!gameCompleted) {
|
||||
if (!visited.has(2)) {
|
||||
setShowGame(true);
|
||||
}
|
||||
}}
|
||||
read={gameCompleted}
|
||||
read={visited.has(2)}
|
||||
/>
|
||||
|
||||
<QuestionModal
|
||||
visible={showQuestionModal}
|
||||
onSuccess={() => {
|
||||
setShowQuestionModal(false);
|
||||
setQuestionCompleted(true);
|
||||
setVisited((prev) => new Set(prev).add(1));
|
||||
markReflectionCursorVisited(scene, 1);
|
||||
}}
|
||||
onClose={() => setShowQuestionModal(false)}
|
||||
/>
|
||||
@@ -135,7 +141,7 @@ const HuangyanpeiReflectionScene1 = () => {
|
||||
// 游戏完成后,显示成功提示
|
||||
setShowGame(false);
|
||||
setShowSuccessAlert(true);
|
||||
setVisited((prev) => new Set(prev).add(2));
|
||||
markReflectionCursorVisited(scene, 2);
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -144,13 +150,8 @@ const HuangyanpeiReflectionScene1 = () => {
|
||||
title="拼接毛竹单杠成功,同学们可以在课间之余强身健体!"
|
||||
onContinue={useCallback(() => {
|
||||
setShowSuccessAlert(false);
|
||||
setGameCompleted(true);
|
||||
setVisited((prev) => {
|
||||
const next = new Set(prev);
|
||||
next.add(2);
|
||||
return next;
|
||||
});
|
||||
}, [])}
|
||||
markReflectionCursorVisited(scene, 2);
|
||||
}, [markReflectionCursorVisited, scene])}
|
||||
/>
|
||||
|
||||
<StudyGuideModal
|
||||
|
||||
@@ -4,6 +4,7 @@ import { getBackgroundImage, getPublicPath, getStaticImage } from "@/lib/utils";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import ChoiceDialog1 from "./scene2/ChoiceDialog1";
|
||||
import ChoiceDialog2 from "./scene2/ChoiceDialog2";
|
||||
import { useHuangyanpeiStore } from "@/store/huangyanpeiStore";
|
||||
|
||||
const starName = "huangyanpei";
|
||||
|
||||
@@ -16,14 +17,19 @@ const HuangyanpeiReflectionScene2 = () => {
|
||||
const [showGuideModal, setShowGuideModal] = useState(true);
|
||||
const [showDialog1, setShowDialog1] = useState(false);
|
||||
const [showDialog2, setShowDialog2] = useState(false);
|
||||
const [visited, setVisited] = useState<Set<number>>(new Set());
|
||||
const [choice1, setChoice1] = useState<
|
||||
"读书" | "没出息" | "女子不能上学" | null
|
||||
>(null);
|
||||
const [choice2, setChoice2] = useState<"铁工科" | "商科" | null>(null);
|
||||
const [showCompleteGuide, setShowCompleteGuide] = useState(false);
|
||||
const [completeGuideShown, setCompleteGuideShown] = useState(false);
|
||||
|
||||
// 使用持久化的reflection cursor状态
|
||||
const scene = "scene2";
|
||||
const { markReflectionCursorVisited, getVisitedReflectionCursors } =
|
||||
useHuangyanpeiStore();
|
||||
|
||||
// 从 store 获取已完成的cursor,转换为 Set 用于兼容现有代码
|
||||
const visitedCursors = getVisitedReflectionCursors(scene);
|
||||
const visited = new Set(visitedCursors);
|
||||
|
||||
const progressCount = visited.size;
|
||||
const total = 2;
|
||||
const completed = progressCount >= total;
|
||||
@@ -105,28 +111,27 @@ const HuangyanpeiReflectionScene2 = () => {
|
||||
{/* 互动展品光标1:显示第一个选择弹窗 */}
|
||||
<Cursor
|
||||
className="absolute left-118 bottom-105"
|
||||
onClick={() => setShowDialog1(true)}
|
||||
read={choice1 !== null}
|
||||
onClick={() => {
|
||||
setShowDialog1(true);
|
||||
}}
|
||||
read={visited.has(1)}
|
||||
/>
|
||||
|
||||
{/* 互动展品光标2:显示第二个选择弹窗 */}
|
||||
<Cursor
|
||||
className="absolute right-74 bottom-68"
|
||||
onClick={() => setShowDialog2(true)}
|
||||
read={choice2 !== null}
|
||||
onClick={() => {
|
||||
setShowDialog2(true);
|
||||
}}
|
||||
read={visited.has(2)}
|
||||
/>
|
||||
|
||||
{/* 第一个选择弹窗 */}
|
||||
<ChoiceDialog1
|
||||
visible={showDialog1}
|
||||
onClose={() => setShowDialog1(false)}
|
||||
onSelect={(choice) => {
|
||||
setChoice1(choice);
|
||||
setVisited((prev) => {
|
||||
const next = new Set(prev);
|
||||
next.add(1);
|
||||
return next;
|
||||
});
|
||||
onSelect={() => {
|
||||
markReflectionCursorVisited(scene, 1);
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -136,11 +141,7 @@ const HuangyanpeiReflectionScene2 = () => {
|
||||
onClose={() => setShowDialog2(false)}
|
||||
onSelect={(choice) => {
|
||||
setChoice2(choice);
|
||||
setVisited((prev) => {
|
||||
const next = new Set(prev);
|
||||
next.add(2);
|
||||
return next;
|
||||
});
|
||||
markReflectionCursorVisited(scene, 2);
|
||||
}}
|
||||
/>
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import { AnimatePresence, motion } from "framer-motion";
|
||||
import { cn, getStaticImage } from "@/lib/utils";
|
||||
import { Image, StudyGuideModal } from "@/components";
|
||||
import { SuccessAlert, ErrorAlert } from "@/components/layout";
|
||||
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
|
||||
|
||||
export interface ChoiceDialog1Props {
|
||||
/** 是否可见 */
|
||||
@@ -30,6 +31,8 @@ const ChoiceDialog1 = ({
|
||||
const choice2Src = getStaticImage("huangyanpei/reflection/scene2/10_006");
|
||||
const choice3Src = getStaticImage("huangyanpei/reflection/scene2/10_007");
|
||||
|
||||
const { triggerPuzzleAnimation } = usePuzzleAnimation();
|
||||
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const [showGuide, setShowGuide] = useState(false);
|
||||
const [showSuccessAlert, setShowSuccessAlert] = useState(false);
|
||||
@@ -113,7 +116,10 @@ const ChoiceDialog1 = ({
|
||||
|
||||
<StudyGuideModal
|
||||
visible={showGuide}
|
||||
onContinue={() => setShowGuide(false)}
|
||||
onContinue={() => {
|
||||
setShowGuide(false);
|
||||
triggerPuzzleAnimation(14);
|
||||
}}
|
||||
verticalAlign="center"
|
||||
>
|
||||
黄校长来考你啦!他设计这所学校,就是为了让学生摆脱旧观念,你知道是哪种吗?
|
||||
|
||||
@@ -7,6 +7,7 @@ import { useNavigate, useParams } from "react-router-dom";
|
||||
import ViseAssemblyDialog from "./scene3/ViseAssemblyDialog";
|
||||
import type { VideoRef } from "@/components/video";
|
||||
import { QuizDialog } from "./components";
|
||||
import { useHuangyanpeiStore } from "@/store/huangyanpeiStore";
|
||||
|
||||
const starName = "huangyanpei";
|
||||
|
||||
@@ -27,9 +28,17 @@ const HuangyanpeiReflectionScene3 = () => {
|
||||
const [showErrorAlertForQuiz, setShowErrorAlertForQuiz] = useState(false);
|
||||
const [showCompleteGuide, setShowCompleteGuide] = useState(false);
|
||||
const [showVideo, setShowVideo] = useState(false);
|
||||
const [visited, setVisited] = useState<Set<number>>(new Set());
|
||||
const videoRef = useRef<VideoRef>(null);
|
||||
|
||||
// 使用持久化的reflection cursor状态
|
||||
const scene = "scene3";
|
||||
const { markReflectionCursorVisited, getVisitedReflectionCursors } =
|
||||
useHuangyanpeiStore();
|
||||
|
||||
// 从 store 获取已完成的cursor,转换为 Set 用于兼容现有代码
|
||||
const visitedCursors = getVisitedReflectionCursors(scene);
|
||||
const visited = new Set(visitedCursors);
|
||||
|
||||
if (!moduleName) {
|
||||
return null;
|
||||
}
|
||||
@@ -100,12 +109,10 @@ const HuangyanpeiReflectionScene3 = () => {
|
||||
<Cursor
|
||||
className="absolute right-100 bottom-55"
|
||||
onClick={() => {
|
||||
if (!showGuide1) {
|
||||
if (visited.has(2)) {
|
||||
setShowDialog(true);
|
||||
} else {
|
||||
setShowErrorAlert(true);
|
||||
}
|
||||
if (visited.has(2)) {
|
||||
setShowDialog(true);
|
||||
} else {
|
||||
setShowErrorAlert(true);
|
||||
}
|
||||
}}
|
||||
read={visited.has(1)}
|
||||
@@ -115,9 +122,7 @@ const HuangyanpeiReflectionScene3 = () => {
|
||||
<Cursor
|
||||
className="absolute left-145 top-68"
|
||||
onClick={() => {
|
||||
if (!showGuide1) {
|
||||
setShowQuiz(true);
|
||||
}
|
||||
setShowQuiz(true);
|
||||
}}
|
||||
read={visited.has(2)}
|
||||
/>
|
||||
@@ -129,11 +134,7 @@ const HuangyanpeiReflectionScene3 = () => {
|
||||
onComplete={() => {
|
||||
setShowDialog(false);
|
||||
setShowCompleteGuide(true);
|
||||
setVisited((prev) => {
|
||||
const next = new Set(prev);
|
||||
next.add(1);
|
||||
return next;
|
||||
});
|
||||
markReflectionCursorVisited(scene, 1);
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -173,7 +174,7 @@ const HuangyanpeiReflectionScene3 = () => {
|
||||
content='黄先生说"手脑并用",懂了理论,也要动手实践。'
|
||||
onContinue={() => {
|
||||
setShowSuccessAlert(false);
|
||||
setVisited((prev) => new Set(prev).add(2));
|
||||
markReflectionCursorVisited(scene, 2);
|
||||
}}
|
||||
onClose={() => {
|
||||
setShowSuccessAlert(false);
|
||||
@@ -234,7 +235,7 @@ const HuangyanpeiReflectionScene3 = () => {
|
||||
onClose={() => setShowQuizDialog(false)}
|
||||
onComplete={() => {
|
||||
setShowQuizDialog(false);
|
||||
setVisited((prev) => new Set(prev).add(3));
|
||||
markReflectionCursorVisited(scene, 3);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -7,6 +7,8 @@ import { useNavigate, useParams } from "react-router-dom";
|
||||
import TowelSalesDialog from "./scene4/TowelSalesDialog";
|
||||
import type { VideoRef } from "@/components/video";
|
||||
import { QuizDialog } from "./components";
|
||||
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
|
||||
import { useHuangyanpeiStore } from "@/store/huangyanpeiStore";
|
||||
|
||||
const starName = "huangyanpei";
|
||||
|
||||
@@ -16,8 +18,9 @@ const HuangyanpeiReflectionScene4 = () => {
|
||||
moduleName: string;
|
||||
}>();
|
||||
|
||||
const { triggerPuzzleAnimation } = usePuzzleAnimation();
|
||||
|
||||
const [_activeExhibitId, _setActiveExhibitId] = useState<1 | 2 | null>(null);
|
||||
const [visited, setVisited] = useState<Set<number>>(new Set());
|
||||
const [showQuiz1, setShowQuiz1] = useState(false);
|
||||
const [showSuccessAlert, setShowSuccessAlert] = useState(false);
|
||||
const [showTowelDialog, setShowTowelDialog] = useState(false);
|
||||
@@ -26,6 +29,15 @@ const HuangyanpeiReflectionScene4 = () => {
|
||||
const [showGuide1, setShowGuide1] = useState(true);
|
||||
const [showErrorAlertForQuiz, setShowErrorAlertForQuiz] = useState(false);
|
||||
|
||||
// 使用持久化的reflection cursor状态
|
||||
const scene = "scene4";
|
||||
const { markReflectionCursorVisited, getVisitedReflectionCursors } =
|
||||
useHuangyanpeiStore();
|
||||
|
||||
// 从 store 获取已完成的cursor,转换为 Set 用于兼容现有代码
|
||||
const visitedCursors = getVisitedReflectionCursors(scene);
|
||||
const visited = new Set(visitedCursors);
|
||||
|
||||
//喇叭考题弹窗
|
||||
const [showQuizDialog, setShowQuizDialog] = useState(false);
|
||||
|
||||
@@ -33,22 +45,14 @@ const HuangyanpeiReflectionScene4 = () => {
|
||||
const handleSuccessAlertContinue = useCallback(() => {
|
||||
setShowSuccessAlert(false);
|
||||
// 更新visited状态
|
||||
setVisited((prev) => {
|
||||
const next = new Set(prev);
|
||||
next.add(1);
|
||||
return next;
|
||||
});
|
||||
}, []);
|
||||
markReflectionCursorVisited(scene, 1);
|
||||
}, [markReflectionCursorVisited, scene]);
|
||||
|
||||
const handleSuccessAlertClose = useCallback(() => {
|
||||
setShowSuccessAlert(false);
|
||||
// 更新visited状态
|
||||
setVisited((prev) => {
|
||||
const next = new Set(prev);
|
||||
next.add(1);
|
||||
return next;
|
||||
});
|
||||
}, []);
|
||||
markReflectionCursorVisited(scene, 1);
|
||||
}, [markReflectionCursorVisited, scene]);
|
||||
|
||||
if (!moduleName) {
|
||||
return null;
|
||||
@@ -106,14 +110,18 @@ const HuangyanpeiReflectionScene4 = () => {
|
||||
{/* 互动展品光标1 */}
|
||||
<Cursor
|
||||
className="absolute left-100 bottom-88"
|
||||
onClick={() => setShowQuiz1(true)}
|
||||
onClick={() => {
|
||||
setShowQuiz1(true);
|
||||
}}
|
||||
read={visited.has(1)}
|
||||
/>
|
||||
|
||||
{/* 互动展品光标2 */}
|
||||
<Cursor
|
||||
className="absolute right-82 bottom-88"
|
||||
onClick={() => setShowTowelDialog(true)}
|
||||
onClick={() => {
|
||||
setShowTowelDialog(true);
|
||||
}}
|
||||
read={visited.has(2)}
|
||||
/>
|
||||
|
||||
@@ -166,11 +174,9 @@ const HuangyanpeiReflectionScene4 = () => {
|
||||
onClose={() => setShowTowelDialog(false)}
|
||||
onComplete={() => {
|
||||
// 完成拖拽后更新visited状态
|
||||
setVisited((prev) => {
|
||||
const next = new Set(prev);
|
||||
next.add(2);
|
||||
return next;
|
||||
});
|
||||
markReflectionCursorVisited(scene, 2);
|
||||
|
||||
triggerPuzzleAnimation(15);
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -218,7 +224,7 @@ const HuangyanpeiReflectionScene4 = () => {
|
||||
onClose={() => setShowQuizDialog(false)}
|
||||
onComplete={() => {
|
||||
setShowQuizDialog(false);
|
||||
setVisited((prev) => new Set(prev).add(3));
|
||||
markReflectionCursorVisited(scene, 3);
|
||||
}}
|
||||
/>
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
SingleChoiceQuizPanel,
|
||||
} from "@/components/layout/quiz";
|
||||
import { GuideSteps, GuideStepsItem } from "@/components";
|
||||
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
|
||||
|
||||
type QuizOption = {
|
||||
value: string;
|
||||
@@ -151,10 +152,13 @@ const LuxunInternalization = () => {
|
||||
const [showVideo, setShowVideo] = useState(false);
|
||||
const [postGuideVisible, setPostGuideVisible] = useState(false);
|
||||
const [showLetter, setShowLetter] = useState(false);
|
||||
const [showLetterVideo, setShowLetterVideo] = useState(false);
|
||||
const [showStudyGuide, setShowStudyGuide] = useState(false);
|
||||
const videoRef = useRef<VideoRef>(null);
|
||||
const letterVideoRef = useRef<VideoRef>(null);
|
||||
const audioRef = useRef<HTMLAudioElement | null>(null);
|
||||
|
||||
//碎片动画触发方法
|
||||
const { triggerPuzzleAnimation } = usePuzzleAnimation();
|
||||
// 旁白关闭后显示对话引导
|
||||
useEffect(() => {
|
||||
if (!showNarration) {
|
||||
@@ -180,6 +184,15 @@ const LuxunInternalization = () => {
|
||||
}
|
||||
};
|
||||
|
||||
// 信件视频加载完成后自动播放
|
||||
const handleLetterVideoCanPlay = () => {
|
||||
if (showLetterVideo && letterVideoRef.current) {
|
||||
letterVideoRef.current.play().catch((error) => {
|
||||
console.warn("信件视频播放失败:", error);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const anchors: VideoAnchor[] = useMemo(
|
||||
() =>
|
||||
QUIZ_ANCHORS.map((item) => {
|
||||
@@ -394,6 +407,7 @@ const LuxunInternalization = () => {
|
||||
setSuccessContent("");
|
||||
const cb = resumeCallback;
|
||||
setResumeCallback(null);
|
||||
triggerPuzzleAnimation(16);
|
||||
cb?.();
|
||||
}}
|
||||
/>
|
||||
@@ -539,10 +553,25 @@ const LuxunInternalization = () => {
|
||||
onSubmit={(values: string[]) => {
|
||||
console.log("提交信件", values);
|
||||
setShowLetter(false);
|
||||
setShowStudyGuide(true);
|
||||
setShowLetterVideo(true);
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* 信件关闭后的视频 */}
|
||||
{showLetterVideo && (
|
||||
<Video
|
||||
ref={letterVideoRef}
|
||||
src={getPublicPath("video/letter/neihua_jixin.mp4")}
|
||||
className="h-full w-full"
|
||||
playsInline
|
||||
onCanPlay={handleLetterVideoCanPlay}
|
||||
onEnded={() => {
|
||||
setShowLetterVideo(false);
|
||||
setShowStudyGuide(true);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<EndingOverlay
|
||||
visible={showStudyGuide}
|
||||
text="恭喜你已经顺利完成了鲁迅故居的学习,请继续前往下一个故居吧!"
|
||||
|
||||
@@ -11,6 +11,7 @@ import { useNavigate, useParams } from "react-router-dom";
|
||||
import { getBackgroundImage, getPublicPath, getStaticImage } from "@/lib/utils";
|
||||
import { useState, useEffect } from "react";
|
||||
import { useCursorController } from "@/hooks/useCursorController";
|
||||
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
|
||||
|
||||
// starName 固定为 luxun
|
||||
const starName = "luxun";
|
||||
@@ -27,6 +28,9 @@ const LuxunPlace1 = () => {
|
||||
moduleName: string;
|
||||
}>();
|
||||
|
||||
//碎片动画触发方法
|
||||
const { triggerPuzzleAnimation } = usePuzzleAnimation();
|
||||
|
||||
// 学习引导弹窗
|
||||
const [showGuideModal, setShowGuideModal] = useState(true);
|
||||
// 对话结束后的学习引导弹窗
|
||||
@@ -141,6 +145,14 @@ const LuxunPlace1 = () => {
|
||||
<ExhibitOverlay
|
||||
visible={activeExhibitId !== null}
|
||||
src={activeExhibitId ? exhibitSrcMap[activeExhibitId] : ""}
|
||||
onOpen={()=>{
|
||||
if( activeExhibitId === 1){
|
||||
triggerPuzzleAnimation(1);
|
||||
}
|
||||
if( activeExhibitId === 2){
|
||||
triggerPuzzleAnimation(2);
|
||||
}
|
||||
}}
|
||||
// description={
|
||||
// activeExhibitId == 1
|
||||
// ? `1892-1898年,少年时期在绍兴三味书屋求学积淀;\n1919-1923年,于北京八道湾参加新文化运动、酝酿《阿Q正传》;\n1926年9-12月,赴厦门大学任教期间聚焦底层创作杂文;\n1927年3-9月,广州白云楼整理旧作定稿《朝花夕拾》部分篇章。`
|
||||
|
||||
@@ -10,6 +10,7 @@ import { useNavigate, useParams } from "react-router-dom";
|
||||
import { getBackgroundImage, getPublicPath, getStaticImage } from "@/lib/utils";
|
||||
import { useState } from "react";
|
||||
import { useCursorController } from "@/hooks/useCursorController";
|
||||
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
|
||||
|
||||
// starName 固定为 luxun
|
||||
const starName = "luxun";
|
||||
@@ -27,6 +28,9 @@ const LuxunPlace2 = () => {
|
||||
moduleName: string;
|
||||
}>();
|
||||
|
||||
//碎片动画触发方法
|
||||
const { triggerPuzzleAnimation } = usePuzzleAnimation();
|
||||
|
||||
const [activeExhibitId, setActiveExhibitId] = useState<1 | 2 | 3 | null>(
|
||||
null
|
||||
);
|
||||
@@ -36,8 +40,8 @@ const LuxunPlace2 = () => {
|
||||
const { isEnabled } = useCursorController({
|
||||
initialEnabled: [1],
|
||||
dependencies: {
|
||||
2: [1],
|
||||
3: [2],
|
||||
2: [3],
|
||||
3: [1],
|
||||
},
|
||||
visited: visited,
|
||||
});
|
||||
@@ -165,6 +169,17 @@ const LuxunPlace2 = () => {
|
||||
<ExhibitOverlay
|
||||
visible={activeExhibitId !== null}
|
||||
src={activeExhibitId ? exhibitSrcMap[activeExhibitId] : ""}
|
||||
onOpen={()=>{
|
||||
if( activeExhibitId === 1){
|
||||
triggerPuzzleAnimation(3);
|
||||
}
|
||||
if( activeExhibitId === 3){
|
||||
triggerPuzzleAnimation(4);
|
||||
}
|
||||
if( activeExhibitId === 2){
|
||||
triggerPuzzleAnimation(5);
|
||||
}
|
||||
}}
|
||||
// description={
|
||||
// activeExhibitId === 3
|
||||
// ? '这篇《中国人失掉自信力了吗》收录于《且介亭杂文》中,它将"匕首与投枪"的特质发挥到极致。当时国民党当局接连丧权辱国,一些文人却散布"中国人失掉自信力"的悲观论调,迎合反动派的卖国行径。鲁迅直接驳斥这种谬论,一针见血指出:"我们从古以来,就有埋头苦干的人,有拼命硬干的人,有为民请命的人,有舍身求法的人……这就是中国的脊梁。"文字如利刃般戳破虚假论调,揭露了反动派掩盖自身无能、麻痹民众的阴谋,唤醒国人的民族自信,尽显战斗文字的锋芒与力量。'
|
||||
|
||||
@@ -12,6 +12,7 @@ import { getBackgroundImage, getPublicPath, getStaticImage } from "@/lib/utils";
|
||||
import { useState } from "react";
|
||||
import SuccessAlert from "@/components/layout/alert/success-alert";
|
||||
import { useCursorController } from "@/hooks/useCursorController";
|
||||
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
|
||||
|
||||
// starName 固定为 luxun
|
||||
const starName = "luxun";
|
||||
@@ -48,6 +49,9 @@ const LuxunPlace3 = () => {
|
||||
moduleName: string;
|
||||
}>();
|
||||
|
||||
//碎片动画触发方法
|
||||
const { triggerPuzzleAnimation } = usePuzzleAnimation();
|
||||
|
||||
const [activeExhibitId, setActiveExhibitId] = useState<
|
||||
1 | 2 | 3 | 4 | 5 | null
|
||||
>(null);
|
||||
@@ -210,6 +214,23 @@ const LuxunPlace3 = () => {
|
||||
<ExhibitOverlay
|
||||
visible={activeExhibitId !== null}
|
||||
src={activeExhibitId ? exhibitSrcMap[activeExhibitId] : ""}
|
||||
onOpen={()=>{
|
||||
if( activeExhibitId === 1){
|
||||
triggerPuzzleAnimation(6);
|
||||
}
|
||||
if( activeExhibitId === 2){
|
||||
triggerPuzzleAnimation(7);
|
||||
}
|
||||
if( activeExhibitId === 3){
|
||||
triggerPuzzleAnimation(8);
|
||||
}
|
||||
if( activeExhibitId === 4){
|
||||
triggerPuzzleAnimation(9);
|
||||
}
|
||||
if( activeExhibitId === 5){
|
||||
triggerPuzzleAnimation(10);
|
||||
}
|
||||
}}
|
||||
description={
|
||||
activeExhibitId === 5
|
||||
? "为了妥善出版亡友遗著,鲁迅亲自选稿、校对、设计封面。为了躲避反动派的审查,他还特意用“诸夏怀霜社”的名义出版,“怀霜”就是为了纪念瞿秋白。1936年《海上述林》上卷出版时,鲁迅的身体已经极度虚弱,但他仍一遍遍翻看样书,说“这是我对秋白最好的纪念”。"
|
||||
|
||||
@@ -13,6 +13,7 @@ import { getBackgroundImage, getPublicPath, getStaticImage } from "@/lib/utils";
|
||||
import { useState } from "react";
|
||||
import SuccessAlert from "@/components/layout/alert/success-alert";
|
||||
import { useCursorController } from "@/hooks/useCursorController";
|
||||
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
|
||||
|
||||
// starName 固定为 luxun
|
||||
const starName = "luxun";
|
||||
@@ -38,6 +39,9 @@ const LuxunPlace5 = () => {
|
||||
moduleName: string;
|
||||
}>();
|
||||
|
||||
//碎片动画触发方法
|
||||
const { triggerPuzzleAnimation } = usePuzzleAnimation();
|
||||
|
||||
const [introGuideVisible, setIntroGuideVisible] = useState(true);
|
||||
const [introGuideStep, setIntroGuideStep] = useState(0);
|
||||
|
||||
@@ -149,6 +153,17 @@ const LuxunPlace5 = () => {
|
||||
<ExhibitOverlay
|
||||
visible={activeExhibitId !== null}
|
||||
src={activeExhibitId ? exhibitSrcMap[activeExhibitId] : ""}
|
||||
onOpen={()=>{
|
||||
if( activeExhibitId === 1){
|
||||
triggerPuzzleAnimation(11);
|
||||
}
|
||||
if( activeExhibitId === 2){
|
||||
triggerPuzzleAnimation(12);
|
||||
}
|
||||
if( activeExhibitId === 3){
|
||||
triggerPuzzleAnimation(13);
|
||||
}
|
||||
}}
|
||||
description={
|
||||
activeExhibitId === 1
|
||||
? '瞿秋白同志曾说,"鲁迅是莱谟斯,是野兽的奶汁所喂养大的,是封建宗法社会的逆子,是绅士阶级的贰臣,而同时也是一些浪漫谛克的革命家的诤友!"'
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useNavigate, useParams } from "react-router-dom";
|
||||
import { Image, StudyGuideModal } from "@/components";
|
||||
import SuccessAlert from "@/components/layout/alert/success-alert";
|
||||
import { getBackgroundImage, getPublicPath, getStaticImage } from "@/lib/utils";
|
||||
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
|
||||
|
||||
const starName = "luxun";
|
||||
|
||||
@@ -71,6 +72,9 @@ const LuxunReflectionScene1 = () => {
|
||||
moduleName: string;
|
||||
}>();
|
||||
|
||||
//碎片动画触发方法
|
||||
const { triggerPuzzleAnimation } = usePuzzleAnimation();
|
||||
|
||||
// 已点击的城市ID集合
|
||||
const [clickedCities, setClickedCities] = useState<Set<number>>(new Set());
|
||||
// 是否显示成功提示(全部完成时)
|
||||
@@ -123,6 +127,7 @@ const LuxunReflectionScene1 = () => {
|
||||
// 如果关闭的是最后一个城市(上海鲁迅故居,id=5),则显示全部完成的提示
|
||||
const lastCity = cityConfig[cityConfig.length - 1];
|
||||
if (lastCity && cityId === lastCity.id) {
|
||||
triggerPuzzleAnimation(14);
|
||||
setShowSuccessAlert(true);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
} from "@/components";
|
||||
import { SingleChoiceQuizPanel } from "@/components/layout/quiz";
|
||||
import SuccessAlert from "@/components/layout/alert/success-alert";
|
||||
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
|
||||
|
||||
const starName = "luxun";
|
||||
|
||||
@@ -23,6 +24,8 @@ type HintPosition =
|
||||
| "none";
|
||||
|
||||
const LuxunReflectionScene3 = () => {
|
||||
//碎片动画触发方法
|
||||
const { triggerPuzzleAnimation } = usePuzzleAnimation();
|
||||
const navigate = useNavigate();
|
||||
const { moduleName } = useParams<{
|
||||
moduleName: string;
|
||||
@@ -60,6 +63,7 @@ const LuxunReflectionScene3 = () => {
|
||||
if (showSummary) {
|
||||
const timer = setTimeout(() => {
|
||||
setCompletionGuideVisible(true);
|
||||
triggerPuzzleAnimation(15);
|
||||
}, 500);
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import { getBackgroundImage, getPublicPath, getStaticImage } from "@/lib/utils";
|
||||
import { SuccessAlert, DragQuizPanel } from "@/components/layout";
|
||||
import { MultipleChoiceQuizPanel } from "@/components/layout/quiz";
|
||||
import { GuideSteps, GuideStepsItem } from "@/components";
|
||||
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
|
||||
|
||||
type QuizOption = {
|
||||
value: string;
|
||||
@@ -100,6 +101,10 @@ const QUIZ_ANCHORS: QuizAnchor[] = [
|
||||
const ChenyunInternalization = () => {
|
||||
const navigate = useNavigate();
|
||||
const { moduleName } = useParams<{ moduleName: string }>();
|
||||
|
||||
// 获取碎片动画触发方法
|
||||
const { triggerPuzzleAnimation } = usePuzzleAnimation();
|
||||
|
||||
const [activeSuccess, setActiveSuccess] = useState<string | null>(null);
|
||||
const [successContent, setSuccessContent] = useState<string>("");
|
||||
const [resumeCallback, setResumeCallback] = useState<(() => void) | null>(
|
||||
@@ -117,8 +122,10 @@ const ChenyunInternalization = () => {
|
||||
const [showVideo, setShowVideo] = useState(false);
|
||||
const [postGuideVisible, setPostGuideVisible] = useState(false);
|
||||
const [showLetter, setShowLetter] = useState(false);
|
||||
const [showLetterVideo, setShowLetterVideo] = useState(false);
|
||||
const [showStudyGuide, setShowStudyGuide] = useState(false);
|
||||
const videoRef = useRef<VideoRef>(null);
|
||||
const letterVideoRef = useRef<VideoRef>(null);
|
||||
const audioRef = useRef<HTMLAudioElement | null>(null);
|
||||
|
||||
// 旁白关闭后显示对话引导
|
||||
@@ -146,6 +153,15 @@ const ChenyunInternalization = () => {
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 信件视频加载完成后自动播放
|
||||
const handleLetterVideoCanPlay = () => {
|
||||
if (showLetterVideo && letterVideoRef.current) {
|
||||
letterVideoRef.current.play().catch((error) => {
|
||||
console.warn("信件视频播放失败:", error);
|
||||
});
|
||||
}
|
||||
};
|
||||
const anchors: VideoAnchor[] = useMemo(
|
||||
() =>
|
||||
QUIZ_ANCHORS.map((item) => {
|
||||
@@ -246,6 +262,7 @@ const ChenyunInternalization = () => {
|
||||
});
|
||||
const cb = resumeCallback;
|
||||
setResumeCallback(null);
|
||||
triggerPuzzleAnimation(15);
|
||||
cb?.();
|
||||
}}
|
||||
/>
|
||||
@@ -293,6 +310,7 @@ const ChenyunInternalization = () => {
|
||||
setSuccessContent("");
|
||||
const cb = resumeCallback;
|
||||
setResumeCallback(null);
|
||||
triggerPuzzleAnimation(16);
|
||||
cb?.();
|
||||
}}
|
||||
/>
|
||||
@@ -316,6 +334,7 @@ const ChenyunInternalization = () => {
|
||||
successContent,
|
||||
dragQuizValues,
|
||||
dragQuizErrors,
|
||||
triggerPuzzleAnimation,
|
||||
]
|
||||
);
|
||||
|
||||
@@ -429,10 +448,25 @@ const ChenyunInternalization = () => {
|
||||
// 提交信件的逻辑(values 为多个填空的内容)
|
||||
console.log("提交信件", values);
|
||||
setShowLetter(false);
|
||||
setShowStudyGuide(true);
|
||||
setShowLetterVideo(true);
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* 信件关闭后的视频 */}
|
||||
{showLetterVideo && (
|
||||
<Video
|
||||
ref={letterVideoRef}
|
||||
src={getPublicPath("video/letter/neihua_jixin.mp4")}
|
||||
className="h-full w-full"
|
||||
playsInline
|
||||
onCanPlay={handleLetterVideoCanPlay}
|
||||
onEnded={() => {
|
||||
setShowLetterVideo(false);
|
||||
setShowStudyGuide(true);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<EndingOverlay
|
||||
visible={showStudyGuide}
|
||||
text="恭喜你已经顺利完成了孙中山故居的学习,请继续前往下一个故居吧!"
|
||||
|
||||
@@ -16,6 +16,7 @@ import Cursor from "@/components/cursor";
|
||||
import { SuccessAlert, DragQuizPanel } from "@/components/layout";
|
||||
import { MultipleChoiceQuizPanel } from "@/components/layout/quiz";
|
||||
import { useCursorController } from "@/hooks/useCursorController";
|
||||
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
|
||||
|
||||
// starName 固定为 sunzhongshan
|
||||
const starName = "sunzhongshan";
|
||||
@@ -35,6 +36,9 @@ const SunzhongshanPlace1 = () => {
|
||||
moduleName: string;
|
||||
}>();
|
||||
|
||||
// 获取碎片动画触发方法
|
||||
const { triggerPuzzleAnimation } = usePuzzleAnimation();
|
||||
|
||||
// 学习引导弹窗
|
||||
const [showGuideModal, setShowGuideModal] = useState(true);
|
||||
// 对话结束后的学习引导弹窗
|
||||
@@ -204,6 +208,20 @@ const SunzhongshanPlace1 = () => {
|
||||
<ExhibitOverlay
|
||||
visible={activeExhibitId !== null}
|
||||
src={activeExhibitId ? exhibitSrcMap[activeExhibitId] : ""}
|
||||
onOpen={() => {
|
||||
if( activeExhibitId === 2){
|
||||
triggerPuzzleAnimation(1);
|
||||
}
|
||||
if( activeExhibitId === 5){
|
||||
triggerPuzzleAnimation(2);
|
||||
}
|
||||
if( activeExhibitId === 4){
|
||||
triggerPuzzleAnimation(3);
|
||||
}
|
||||
if( activeExhibitId === 3){
|
||||
triggerPuzzleAnimation(4);
|
||||
}
|
||||
}}
|
||||
onClose={() => {
|
||||
const currentId = activeExhibitId;
|
||||
// 关闭展品框时,重置对应对话的进度并隐藏对话
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
import { useCursorController } from "@/hooks/useCursorController";
|
||||
import { getBackgroundImage, getPublicPath, getStaticImage } from "@/lib/utils";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
|
||||
|
||||
const starName = "sunzhongshan";
|
||||
|
||||
@@ -28,6 +29,9 @@ const SunzhongshanPlace2 = () => {
|
||||
moduleName: string;
|
||||
}>();
|
||||
|
||||
// 获取碎片动画触发方法
|
||||
const { triggerPuzzleAnimation } = usePuzzleAnimation();
|
||||
|
||||
const [activeExhibitId, setActiveExhibitId] = useState<1 | 2 | 3 | 4 | null>(
|
||||
null
|
||||
);
|
||||
@@ -148,6 +152,17 @@ const SunzhongshanPlace2 = () => {
|
||||
<ExhibitOverlay
|
||||
visible={activeExhibitId !== null}
|
||||
src={activeExhibitId ? exhibitSrcMap[activeExhibitId] : ""}
|
||||
onOpen={()=>{
|
||||
if( activeExhibitId === 1){
|
||||
triggerPuzzleAnimation(5);
|
||||
}
|
||||
if( activeExhibitId === 4){
|
||||
triggerPuzzleAnimation(6);
|
||||
}
|
||||
if( activeExhibitId === 3){
|
||||
triggerPuzzleAnimation(7);
|
||||
}
|
||||
}}
|
||||
onClose={() => {
|
||||
const currentId = activeExhibitId;
|
||||
// 关闭展品框时,重置所有对话的进度并隐藏对话,确保遮罩层正确清理
|
||||
|
||||
@@ -14,6 +14,7 @@ import { useCursorController } from "@/hooks/useCursorController";
|
||||
import { getBackgroundImage, getPublicPath, getStaticImage } from "@/lib/utils";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { SuccessAlert, DragQuizPanel } from "@/components/layout";
|
||||
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
|
||||
|
||||
const starName = "sunzhongshan";
|
||||
|
||||
@@ -30,6 +31,9 @@ const SunzhongshanPlace3 = () => {
|
||||
moduleName: string;
|
||||
}>();
|
||||
|
||||
// 获取碎片动画触发方法
|
||||
const { triggerPuzzleAnimation } = usePuzzleAnimation();
|
||||
|
||||
const [activeExhibitId, setActiveExhibitId] = useState<1 | 2 | 3 | 4 | null>(
|
||||
null
|
||||
);
|
||||
@@ -160,6 +164,17 @@ const SunzhongshanPlace3 = () => {
|
||||
<ExhibitOverlay
|
||||
visible={activeExhibitId !== null}
|
||||
src={activeExhibitId ? exhibitSrcMap[activeExhibitId] : ""}
|
||||
onOpen={()=>{
|
||||
if( activeExhibitId === 4){
|
||||
triggerPuzzleAnimation(8);
|
||||
}
|
||||
if( activeExhibitId === 1){
|
||||
triggerPuzzleAnimation(9);
|
||||
}
|
||||
if( activeExhibitId === 2){
|
||||
triggerPuzzleAnimation(10);
|
||||
}
|
||||
}}
|
||||
onClose={() => {
|
||||
const currentId = activeExhibitId;
|
||||
// 关闭展品框时,重置所有对话的进度并隐藏对话,确保遮罩层正确清理
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
import { useCursorController } from "@/hooks/useCursorController";
|
||||
import { getBackgroundImage, getStaticImage, getPublicPath } from "@/lib/utils";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
|
||||
|
||||
const starName = "sunzhongshan";
|
||||
|
||||
@@ -31,6 +32,9 @@ const SunzhongshanPlace4 = () => {
|
||||
moduleName: string;
|
||||
}>();
|
||||
|
||||
// 获取碎片动画触发方法
|
||||
const { triggerPuzzleAnimation } = usePuzzleAnimation();
|
||||
|
||||
// 关闭学习引导后显示的对话(图1)
|
||||
const [showInitialDialog, setShowInitialDialog] = useState(true);
|
||||
const [initialDialogStep, setInitialDialogStep] = useState(0);
|
||||
@@ -134,6 +138,7 @@ const SunzhongshanPlace4 = () => {
|
||||
// 光标1:关闭窗口后播放视频
|
||||
setActiveExhibitId(null);
|
||||
setShowVideo1(true);
|
||||
triggerPuzzleAnimation(11);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import { SingleChoiceQuizPanel } from "@/components/layout/quiz";
|
||||
import { getBackgroundImage, getStaticImage, getPublicPath } from "@/lib/utils";
|
||||
import { useClueStore } from "@/store/clueStore";
|
||||
import type { ClueHint } from "@/components/clue";
|
||||
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
|
||||
|
||||
const starName = "sunzhongshan";
|
||||
const sceneId = `${starName}/reflection/scene1`;
|
||||
@@ -68,6 +69,9 @@ const SunzhongshanReflectionScene1 = () => {
|
||||
const { moduleName } = useParams<{
|
||||
moduleName: string;
|
||||
}>();
|
||||
|
||||
// 获取碎片动画触发方法
|
||||
const { triggerPuzzleAnimation } = usePuzzleAnimation();
|
||||
|
||||
const [showClueHint, setShowClueHint] = useState(false);
|
||||
const [showClueBook, setShowClueBook] = useState(false);
|
||||
@@ -417,6 +421,7 @@ const SunzhongshanReflectionScene1 = () => {
|
||||
setShowQuiz3(true);
|
||||
setPendingQuiz(null);
|
||||
} else if (pendingQuiz === "complete") {
|
||||
triggerPuzzleAnimation(12);
|
||||
setShowCompleteGuide(true);
|
||||
setPendingQuiz(null);
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import { SingleChoiceQuizPanel } from "@/components/layout/quiz";
|
||||
import { getBackgroundImage, getStaticImage, getPublicPath } from "@/lib/utils";
|
||||
import { useClueStore } from "@/store/clueStore";
|
||||
import type { ClueHint } from "@/components/clue";
|
||||
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
|
||||
|
||||
const starName = "sunzhongshan";
|
||||
const sceneId = `${starName}/reflection/scene2`;
|
||||
@@ -79,6 +80,9 @@ const SunzhongshanReflectionScene2 = () => {
|
||||
moduleName: string;
|
||||
}>();
|
||||
|
||||
// 获取碎片动画触发方法
|
||||
const { triggerPuzzleAnimation } = usePuzzleAnimation();
|
||||
|
||||
const [showClueHint, setShowClueHint] = useState(false);
|
||||
const [showClueBook, setShowClueBook] = useState(false);
|
||||
const [showSuccessAlert, setShowSuccessAlert] = useState(false);
|
||||
@@ -442,6 +446,7 @@ const SunzhongshanReflectionScene2 = () => {
|
||||
setShowQuiz3(true);
|
||||
setPendingQuiz(null);
|
||||
} else if (pendingQuiz === "complete") {
|
||||
triggerPuzzleAnimation(13);
|
||||
setShowCompleteGuide(true);
|
||||
setPendingQuiz(null);
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import { SingleChoiceQuizPanel } from "@/components/layout/quiz";
|
||||
import { getBackgroundImage, getStaticImage, getPublicPath } from "@/lib/utils";
|
||||
import { useClueStore } from "@/store/clueStore";
|
||||
import type { ClueHint } from "@/components/clue";
|
||||
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
|
||||
|
||||
const starName = "sunzhongshan";
|
||||
const sceneId = `${starName}/reflection/scene3`;
|
||||
@@ -70,6 +71,9 @@ const SunzhongshanReflectionScene3 = () => {
|
||||
moduleName: string;
|
||||
}>();
|
||||
|
||||
// 获取碎片动画触发方法
|
||||
const { triggerPuzzleAnimation } = usePuzzleAnimation();
|
||||
|
||||
const [showClueHint, setShowClueHint] = useState(false);
|
||||
const [showClueBook, setShowClueBook] = useState(false);
|
||||
const [showSuccessAlert, setShowSuccessAlert] = useState(false);
|
||||
@@ -488,6 +492,7 @@ const SunzhongshanReflectionScene3 = () => {
|
||||
setShowQuiz3(true);
|
||||
setPendingQuiz(null);
|
||||
} else if (pendingQuiz === "complete") {
|
||||
triggerPuzzleAnimation(14);
|
||||
setShowCompleteGuide(true);
|
||||
setPendingQuiz(null);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user