feat:碎片完善
This commit is contained in:
1 parent
ec7e8e2bd2
commit
046501705f
74 files changed
+1602
-172
No files matched your search
@@ -13,7 +13,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;
|
||||
label: string;
|
||||
@@ -100,6 +100,8 @@ 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>(
|
||||
@@ -196,6 +198,7 @@ const ChenyunInternalization = () => {
|
||||
onContinue={() => {
|
||||
setActiveSuccess(null);
|
||||
setSuccessContent("");
|
||||
triggerPuzzleAnimation(16);
|
||||
setDragQuizValues((prev) => {
|
||||
const next = { ...prev };
|
||||
delete next[item.id];
|
||||
@@ -242,6 +245,7 @@ const ChenyunInternalization = () => {
|
||||
if (isCorrect) {
|
||||
setActiveSuccess(item.id);
|
||||
setSuccessContent("回答正确,请继续探索。");
|
||||
if (item.id === "quiz-1") triggerPuzzleAnimation(15);
|
||||
setResumeCallback(() => resume);
|
||||
}
|
||||
}}
|
||||
|
||||
@@ -11,6 +11,8 @@ import { useNavigate, useParams } from "react-router-dom";
|
||||
import { getBackgroundImage, getStaticImage } from "@/lib/utils";
|
||||
import { useState } from "react";
|
||||
import Cursor from "@/components/cursor";
|
||||
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
|
||||
import { useChenyunStore } from "@/store/chenyunStore";
|
||||
|
||||
// starName 固定为 chenyun
|
||||
const starName = "chenyun";
|
||||
@@ -28,6 +30,9 @@ const ChenyunPlace1 = () => {
|
||||
moduleName: string;
|
||||
}>();
|
||||
|
||||
// 获取碎片动画触发方法
|
||||
const { triggerPuzzleAnimation } = usePuzzleAnimation();
|
||||
|
||||
// 学习引导弹窗
|
||||
const [showGuideModal, setShowGuideModal] = useState(true);
|
||||
// 对话结束后的学习引导弹窗
|
||||
@@ -39,11 +44,18 @@ const ChenyunPlace1 = () => {
|
||||
const [visible, setVisible] = useState(false);
|
||||
// 当前激活的展品 ID(只存 ID,不直接存路径,便于作为唯一标识)
|
||||
const [activeExhibitId, setActiveExhibitId] = useState<1 | 2 | null>(null);
|
||||
// 已浏览展品的 ID 集合
|
||||
const [visited, setVisited] = useState<Set<number>>(new Set());
|
||||
|
||||
// 使用持久化的访问状态
|
||||
const place = "place1";
|
||||
const { markExhibitVisited, getVisitedExhibits } = useChenyunStore();
|
||||
|
||||
// 从 store 获取已访问的展品,转换为 Set 用于兼容现有代码
|
||||
const visitedExhibits = getVisitedExhibits(place);
|
||||
const visited = new Set(visitedExhibits);
|
||||
|
||||
const handleOpenExhibit = (index: 1 | 2) => {
|
||||
setActiveExhibitId(index);
|
||||
// ExhibitOverlay 打开时会自动通过 onOpen 通知 ResidenceLayout 执行动画
|
||||
};
|
||||
|
||||
const handleStartLearning = () => {
|
||||
@@ -128,13 +140,16 @@ const ChenyunPlace1 = () => {
|
||||
<ExhibitOverlay
|
||||
visible={activeExhibitId !== null}
|
||||
src={activeExhibitId ? exhibitSrcMap[activeExhibitId] : ""}
|
||||
onOpen={() => {
|
||||
// 示例:展品1对应碎片1,展品2对应碎片2
|
||||
// 根据实际需求调用 triggerPuzzleAnimation
|
||||
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 {
|
||||
} from "@/components";
|
||||
import { getBackgroundImage, getStaticImage } from "@/lib/utils";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { useChenyunStore } from "@/store/chenyunStore";
|
||||
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
|
||||
|
||||
// starName 固定为 chenyun
|
||||
const starName = "chenyun";
|
||||
@@ -31,10 +33,19 @@ const ChenyunPlace2 = () => {
|
||||
moduleName: string;
|
||||
}>();
|
||||
|
||||
// 获取碎片动画触发方法
|
||||
const { triggerPuzzleAnimation } = usePuzzleAnimation();
|
||||
|
||||
// 当前激活展品 ID,只存 ID 做唯一标识
|
||||
const [activeExhibitId, setActiveExhibitId] = useState<1 | 2 | null>(null);
|
||||
// 已浏览展品 ID 集合
|
||||
const [visited, setVisited] = useState<Set<number>>(new Set());
|
||||
|
||||
// 使用持久化的访问状态
|
||||
const place = "place2";
|
||||
const { markExhibitVisited, getVisitedExhibits } = useChenyunStore();
|
||||
|
||||
// 从 store 获取已访问的展品,转换为 Set 用于兼容现有代码
|
||||
const visitedExhibits = getVisitedExhibits(place);
|
||||
const visited = new Set(visitedExhibits);
|
||||
// GuideSteps 显示状态
|
||||
const [guideVisible, setGuideVisible] = useState(false);
|
||||
// GuideSteps 当前步骤
|
||||
@@ -128,13 +139,16 @@ const ChenyunPlace2 = () => {
|
||||
activeExhibitId ? exhibitDescriptionMap[activeExhibitId] : undefined
|
||||
}
|
||||
autoPlayDescription={activeExhibitId === 1}
|
||||
onOpen={() => {
|
||||
if (activeExhibitId === 1) {
|
||||
triggerPuzzleAnimation(2);
|
||||
} else if (activeExhibitId === 2) {
|
||||
triggerPuzzleAnimation(3);
|
||||
}
|
||||
}}
|
||||
onClose={() => {
|
||||
if (activeExhibitId !== null) {
|
||||
setVisited((prev) => {
|
||||
const next = new Set(prev);
|
||||
next.add(activeExhibitId);
|
||||
return next;
|
||||
});
|
||||
markExhibitVisited(place, activeExhibitId);
|
||||
}
|
||||
setActiveExhibitId(null);
|
||||
}}
|
||||
|
||||
@@ -11,6 +11,8 @@ import {
|
||||
} from "@/components";
|
||||
import { getBackgroundImage, getStaticImage, getPublicPath } from "@/lib/utils";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { useChenyunStore } from "@/store/chenyunStore";
|
||||
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
|
||||
|
||||
// starName 固定为 chenyun
|
||||
const starName = "chenyun";
|
||||
@@ -26,8 +28,18 @@ const ChenyunPlace3 = () => {
|
||||
moduleName: string;
|
||||
}>();
|
||||
|
||||
const [visited, setVisited] = useState<Set<number>>(new Set());
|
||||
// 获取碎片动画触发方法
|
||||
const { triggerPuzzleAnimation } = usePuzzleAnimation();
|
||||
|
||||
const [activeExhibitId, setActiveExhibitId] = useState<1 | 2 | null>(null);
|
||||
|
||||
// 使用持久化的访问状态
|
||||
const place = "place3";
|
||||
const { markExhibitVisited, getVisitedExhibits } = useChenyunStore();
|
||||
|
||||
// 从 store 获取已访问的展品,转换为 Set 用于兼容现有代码
|
||||
const visitedExhibits = getVisitedExhibits(place);
|
||||
const visited = new Set(visitedExhibits);
|
||||
const [videoVisible, setVideoVisible] = useState(false);
|
||||
// GuideSteps 显示状态
|
||||
const [guideVisible, setGuideVisible] = useState(false);
|
||||
@@ -125,7 +137,8 @@ const ChenyunPlace3 = () => {
|
||||
controls={false}
|
||||
onEnded={() => {
|
||||
setVideoVisible(false);
|
||||
setVisited((p) => new Set(p).add(2));
|
||||
markExhibitVisited(place, 2);
|
||||
triggerPuzzleAnimation(5);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
@@ -135,13 +148,14 @@ const ChenyunPlace3 = () => {
|
||||
<ExhibitOverlay
|
||||
visible={activeExhibitId !== null}
|
||||
src={activeExhibitId ? exhibitSrcMap[activeExhibitId] : ""}
|
||||
onOpen={() => {
|
||||
if (activeExhibitId === 1) {
|
||||
triggerPuzzleAnimation(4);
|
||||
}
|
||||
}}
|
||||
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 {
|
||||
} from "@/components";
|
||||
import { getBackgroundImage, getStaticImage } from "@/lib/utils";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { useChenyunStore } from "@/store/chenyunStore";
|
||||
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
|
||||
|
||||
// starName 固定为 chenyun
|
||||
const starName = "chenyun";
|
||||
@@ -25,8 +27,19 @@ const ChenyunPlace4 = () => {
|
||||
moduleName: string;
|
||||
}>();
|
||||
|
||||
const [visited, setVisited] = useState<Set<number>>(new Set());
|
||||
// 获取碎片动画触发方法
|
||||
const { triggerPuzzleAnimation } = usePuzzleAnimation();
|
||||
|
||||
const [activeExhibitId, setActiveExhibitId] = useState<1 | 2 | null>(null);
|
||||
|
||||
// 使用持久化的访问状态
|
||||
const place = "place4";
|
||||
const { isExhibitVisited, markExhibitVisited, getVisitedExhibits } =
|
||||
useChenyunStore();
|
||||
|
||||
// 从 store 获取已访问的展品,转换为 Set 用于兼容现有代码
|
||||
const visitedExhibits = getVisitedExhibits(place);
|
||||
const visited = new Set(visitedExhibits);
|
||||
// 展品1引导对话
|
||||
const [guideVisible, setGuideVisible] = useState(false);
|
||||
const [guideStep, setGuideStep] = useState(0);
|
||||
@@ -124,13 +137,14 @@ const ChenyunPlace4 = () => {
|
||||
<ExhibitOverlay
|
||||
visible={activeExhibitId !== null}
|
||||
src={activeExhibitId ? exhibitSrcMap[activeExhibitId] : ""}
|
||||
onOpen={() => {
|
||||
if (activeExhibitId === 1) {
|
||||
triggerPuzzleAnimation(6);
|
||||
}
|
||||
}}
|
||||
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 {
|
||||
} from "@/components";
|
||||
import { getBackgroundImage, getStaticImage, getPublicPath } from "@/lib/utils";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { useChenyunStore } from "@/store/chenyunStore";
|
||||
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
|
||||
|
||||
// starName 固定为 chenyun
|
||||
const starName = "chenyun";
|
||||
@@ -32,10 +34,21 @@ const ChenyunPlace5 = () => {
|
||||
moduleName: string;
|
||||
}>();
|
||||
|
||||
const [visited, setVisited] = useState<Set<number>>(new Set());
|
||||
// 获取碎片动画触发方法
|
||||
const { triggerPuzzleAnimation } = usePuzzleAnimation();
|
||||
|
||||
const [activeExhibitId, setActiveExhibitId] = useState<1 | 2 | 3 | null>(
|
||||
null
|
||||
);
|
||||
|
||||
// 使用持久化的访问状态
|
||||
const place = "place5";
|
||||
const { isExhibitVisited, markExhibitVisited, getVisitedExhibits } =
|
||||
useChenyunStore();
|
||||
|
||||
// 从 store 获取已访问的展品,转换为 Set 用于兼容现有代码
|
||||
const visitedExhibits = getVisitedExhibits(place);
|
||||
const visited = new Set(visitedExhibits);
|
||||
const [videoVisible3, setVideoVisible3] = useState(false);
|
||||
// 学习引导弹窗
|
||||
const [showGuideModal, setShowGuideModal] = useState(true);
|
||||
@@ -127,11 +140,8 @@ const ChenyunPlace5 = () => {
|
||||
showSkipButton
|
||||
onEnded={() => {
|
||||
setVideoVisible3(false);
|
||||
setVisited((prev) => {
|
||||
const next = new Set(prev);
|
||||
next.add(3);
|
||||
return next;
|
||||
});
|
||||
markExhibitVisited(place, 3);
|
||||
triggerPuzzleAnimation(8);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
@@ -144,13 +154,14 @@ const ChenyunPlace5 = () => {
|
||||
activeExhibitId ? exhibitDescriptionMap[activeExhibitId] : undefined
|
||||
}
|
||||
autoPlayDescription={activeExhibitId === 2}
|
||||
onOpen={() => {
|
||||
if (activeExhibitId === 2) {
|
||||
triggerPuzzleAnimation(7);
|
||||
}
|
||||
}}
|
||||
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 {
|
||||
} from "@/components";
|
||||
import { getBackgroundImage, getStaticImage } from "@/lib/utils";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { useChenyunStore } from "@/store/chenyunStore";
|
||||
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
|
||||
|
||||
// starName 固定为 chenyun
|
||||
const starName = "chenyun";
|
||||
@@ -24,8 +26,19 @@ const ChenyunPlace6 = () => {
|
||||
moduleName: string;
|
||||
}>();
|
||||
|
||||
const [visited, setVisited] = useState<Set<number>>(new Set());
|
||||
// 获取碎片动画触发方法
|
||||
const { triggerPuzzleAnimation } = usePuzzleAnimation();
|
||||
|
||||
const [activeExhibitId, setActiveExhibitId] = useState<1 | null>(null);
|
||||
|
||||
// 使用持久化的访问状态
|
||||
const place = "place6";
|
||||
const { isExhibitVisited, markExhibitVisited, getVisitedExhibits } =
|
||||
useChenyunStore();
|
||||
|
||||
// 从 store 获取已访问的展品,转换为 Set 用于兼容现有代码
|
||||
const visitedExhibits = getVisitedExhibits(place);
|
||||
const visited = new Set(visitedExhibits);
|
||||
const [guideVisible, setGuideVisible] = useState(false);
|
||||
const [guideStep, setGuideStep] = useState(0);
|
||||
// 学习引导弹窗
|
||||
@@ -101,13 +114,14 @@ const ChenyunPlace6 = () => {
|
||||
<ExhibitOverlay
|
||||
visible={activeExhibitId !== null}
|
||||
src={activeExhibitId ? exhibitSrcMap[activeExhibitId] : ""}
|
||||
onOpen={() => {
|
||||
if (activeExhibitId === 1) {
|
||||
triggerPuzzleAnimation(9);
|
||||
}
|
||||
}}
|
||||
onClose={() => {
|
||||
if (activeExhibitId !== null) {
|
||||
setVisited((prev) => {
|
||||
const next = new Set(prev);
|
||||
next.add(activeExhibitId);
|
||||
return next;
|
||||
});
|
||||
markExhibitVisited(place, activeExhibitId);
|
||||
}
|
||||
setActiveExhibitId(null);
|
||||
}}
|
||||
|
||||
@@ -16,6 +16,8 @@ import { SuccessAlert, DragQuizPanel } from "@/components/layout";
|
||||
import { getBackgroundImage, getStaticImage, getPublicPath } from "@/lib/utils";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import type { VideoRef } from "@/components/video";
|
||||
import { useChenyunStore } from "@/store/chenyunStore";
|
||||
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
|
||||
|
||||
// starName 固定为 chenyun
|
||||
const starName = "chenyun";
|
||||
@@ -53,8 +55,19 @@ const ChenyunPlace7 = () => {
|
||||
moduleName: string;
|
||||
}>();
|
||||
|
||||
const [visited, setVisited] = useState<Set<number>>(new Set());
|
||||
// 获取碎片动画触发方法
|
||||
const { triggerPuzzleAnimation } = usePuzzleAnimation();
|
||||
|
||||
const [activeExhibitId, setActiveExhibitId] = useState<1 | null>(null);
|
||||
|
||||
// 使用持久化的访问状态
|
||||
const place = "place7";
|
||||
const { isExhibitVisited, markExhibitVisited, getVisitedExhibits } =
|
||||
useChenyunStore();
|
||||
|
||||
// 从 store 获取已访问的展品,转换为 Set 用于兼容现有代码
|
||||
const visitedExhibits = getVisitedExhibits(place);
|
||||
const visited = new Set(visitedExhibits);
|
||||
const [videoVisible1, setVideoVisible1] = useState(false);
|
||||
const [videoPlaying, setVideoPlaying] = useState(false);
|
||||
const videoRef = useRef<VideoRef>(null);
|
||||
@@ -231,11 +244,8 @@ const ChenyunPlace7 = () => {
|
||||
showSkipButton
|
||||
onEnded={() => {
|
||||
setVideoPlaying(false);
|
||||
setVisited((prev) => {
|
||||
const next = new Set(prev);
|
||||
next.add(1);
|
||||
return next;
|
||||
});
|
||||
markExhibitVisited(place, 1);
|
||||
triggerPuzzleAnimation(10);
|
||||
// 视频播放完成后,出现单选题
|
||||
setQuizVisible(true);
|
||||
}}
|
||||
@@ -349,11 +359,7 @@ const ChenyunPlace7 = () => {
|
||||
src={activeExhibitId ? exhibitSrcMap[activeExhibitId] : ""}
|
||||
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 {
|
||||
} from "@/components";
|
||||
import { getBackgroundImage, getStaticImage } from "@/lib/utils";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { useChenyunStore } from "@/store/chenyunStore";
|
||||
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
|
||||
|
||||
// starName 固定为 chenyun
|
||||
const starName = "chenyun";
|
||||
@@ -25,8 +27,19 @@ const ChenyunPlace8 = () => {
|
||||
moduleName: string;
|
||||
}>();
|
||||
|
||||
const [visited, setVisited] = useState<Set<number>>(new Set());
|
||||
// 获取碎片动画触发方法
|
||||
const { triggerPuzzleAnimation } = usePuzzleAnimation();
|
||||
|
||||
const [activeExhibitId, setActiveExhibitId] = useState<1 | 2 | null>(null);
|
||||
|
||||
// 使用持久化的访问状态
|
||||
const place = "place8";
|
||||
const { isExhibitVisited, markExhibitVisited, getVisitedExhibits } =
|
||||
useChenyunStore();
|
||||
|
||||
// 从 store 获取已访问的展品,转换为 Set 用于兼容现有代码
|
||||
const visitedExhibits = getVisitedExhibits(place);
|
||||
const visited = new Set(visitedExhibits);
|
||||
// 展品2的对话引导
|
||||
const [guideVisible, setGuideVisible] = useState(false);
|
||||
const [guideStep, setGuideStep] = useState(0);
|
||||
@@ -117,6 +130,11 @@ const ChenyunPlace8 = () => {
|
||||
<ExhibitOverlay
|
||||
visible={activeExhibitId !== null}
|
||||
src={activeExhibitId ? exhibitSrcMap[activeExhibitId] : ""}
|
||||
onOpen={() => {
|
||||
if (activeExhibitId === 1) {
|
||||
triggerPuzzleAnimation(11);
|
||||
}
|
||||
}}
|
||||
description={
|
||||
activeExhibitId === 1
|
||||
? `打赢"银元之战""两白一黑"战役的过程彰显了中国共产党治理经济的卓越能力!这里有一份"一五"计划报告的手稿,陈云在"一五"计划的编制中做出了重大贡献。一九五四年,陈云的主要工作之一,就是主持编制第一个五年计划纲要。这次编制工作是在前四次基础上进行的,陈云提出计划编制工作中要注意几个问题:首先是工业投资金额,其次是市场供应和人民生活,第三是农业发展速度,这三个问题对编制计划有指导意义。第一个五年计划纲要草案的重点是优先发展重工业,特别是156项重点工程。当时陈云重点抓重工业建设和粮食保障,通过"统购统销"稳定粮价,为重点工程项目筑牢根基,亲手铺就了我国工业化的起步之路。`
|
||||
@@ -124,11 +142,7 @@ const ChenyunPlace8 = () => {
|
||||
}
|
||||
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 {
|
||||
import { getBackgroundImage, getStaticImage, getPublicPath } from "@/lib/utils";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import type { VideoRef } from "@/components/video";
|
||||
import { useChenyunStore } from "@/store/chenyunStore";
|
||||
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
|
||||
|
||||
// starName 固定为 chenyun
|
||||
const starName = "chenyun";
|
||||
@@ -26,8 +28,19 @@ const ChenyunPlace9 = () => {
|
||||
moduleName: string;
|
||||
}>();
|
||||
|
||||
const [visited, setVisited] = useState<Set<number>>(new Set());
|
||||
// 获取碎片动画触发方法
|
||||
const { triggerPuzzleAnimation } = usePuzzleAnimation();
|
||||
|
||||
const [activeExhibitId, setActiveExhibitId] = useState<1 | null>(null);
|
||||
|
||||
// 使用持久化的访问状态
|
||||
const place = "place9";
|
||||
const { isExhibitVisited, markExhibitVisited, getVisitedExhibits } =
|
||||
useChenyunStore();
|
||||
|
||||
// 从 store 获取已访问的展品,转换为 Set 用于兼容现有代码
|
||||
const visitedExhibits = getVisitedExhibits(place);
|
||||
const visited = new Set(visitedExhibits);
|
||||
// 视频播放状态
|
||||
const [videoVisible, setVideoVisible] = useState(false);
|
||||
const [videoPlaying, setVideoPlaying] = useState(false);
|
||||
@@ -127,14 +140,11 @@ const ChenyunPlace9 = () => {
|
||||
skipText="跳过"
|
||||
onEnded={() => {
|
||||
setVideoPlaying(false);
|
||||
setVisited((prev) => {
|
||||
const next = new Set(prev);
|
||||
next.add(1);
|
||||
return next;
|
||||
});
|
||||
markExhibitVisited(place, 1);
|
||||
// 视频播放完成后,显示对话引导
|
||||
setGuideVisible(true);
|
||||
setGuideStep(0);
|
||||
triggerPuzzleAnimation(12);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
@@ -167,11 +177,7 @@ const ChenyunPlace9 = () => {
|
||||
src={activeExhibitId ? exhibitSrcMap[activeExhibitId] : ""}
|
||||
onClose={() => {
|
||||
if (activeExhibitId !== null) {
|
||||
setVisited((prev) => {
|
||||
const next = new Set(prev);
|
||||
next.add(activeExhibitId);
|
||||
return next;
|
||||
});
|
||||
markExhibitVisited(place, activeExhibitId);
|
||||
}
|
||||
setActiveExhibitId(null);
|
||||
}}
|
||||
|
||||
@@ -7,7 +7,7 @@ import ErrorAlert from "@/components/layout/alert/error-alert";
|
||||
import ClueOverlay from "@/components/layout/clue";
|
||||
import type { ClueData } from "@/components/layout";
|
||||
import { getBackgroundImage, getStaticImage } from "@/lib/utils";
|
||||
import { useState } from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { WELCOME_TEXT, starName } from "./constants";
|
||||
import { useClueStates } from "./shared/useClueStates";
|
||||
@@ -17,6 +17,7 @@ import { MimianBattle } from "./mimian/MimianBattle";
|
||||
import type { FileType } from "./shared/types";
|
||||
import { YINYUAN_VIDEO_CONFIG } from "./constants/yinyuan";
|
||||
import { MIMIAN_VIDEO_CONFIG } from "./constants/mimian";
|
||||
import { useChenyunStore } from "@/store/chenyunStore";
|
||||
|
||||
const ChenyunReflection = () => {
|
||||
const navigate = useNavigate();
|
||||
@@ -73,9 +74,8 @@ const ChenyunReflection = () => {
|
||||
markClueAsViewed,
|
||||
} = useClueStates();
|
||||
|
||||
// ========== 银元之战状态(从 YinyuanBattle 获取) ==========
|
||||
// 注意:这些状态现在由 YinyuanBattle 内部管理,这里只保留必要的共享状态
|
||||
const [isYinyuanSolved, setIsYinyuanSolved] = useState(false);
|
||||
// ========== 银元之战状态(从 store 获取) ==========
|
||||
const { isYinyuanSolved } = useChenyunStore();
|
||||
|
||||
// ========== 事件处理函数 ==========
|
||||
|
||||
@@ -223,7 +223,6 @@ const ChenyunReflection = () => {
|
||||
onPlayVideo={(fileType) => {
|
||||
setPlayingVideo(fileType);
|
||||
}}
|
||||
onSolvedChange={setIsYinyuanSolved}
|
||||
/>
|
||||
|
||||
{/* 米棉之战模块 */}
|
||||
@@ -238,12 +237,6 @@ const ChenyunReflection = () => {
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 拼图盒 */}
|
||||
<Image
|
||||
className="absolute left-4 bottom-4"
|
||||
src={getStaticImage("components/拼图盒")}
|
||||
/>
|
||||
|
||||
{/* 欢迎语层 - 使用 StudyGuideModal,仅承担学习引导/欢迎语 */}
|
||||
<StudyGuideModal
|
||||
visible={showWelcomeGuide}
|
||||
|
||||
@@ -7,6 +7,9 @@ import { MimianFile } from "./MimianFile";
|
||||
import { MimianClues } from "./MimianClues";
|
||||
import { MimianQuizzes } from "./MimianQuizzes";
|
||||
import { useMimianState } from "./useMimianState";
|
||||
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
|
||||
import { usePuzzleStore } from "@/store/puzzleStore";
|
||||
import { starName } from "../constants";
|
||||
import type { FileType } from "../shared/types";
|
||||
import type { ClueData } from "@/components/layout";
|
||||
|
||||
@@ -41,6 +44,8 @@ export const MimianBattle = ({
|
||||
onEnd,
|
||||
}: MimianBattleProps) => {
|
||||
const state = useMimianState();
|
||||
const { triggerPuzzleAnimation } = usePuzzleAnimation();
|
||||
const { isPuzzleUnlocked } = usePuzzleStore();
|
||||
|
||||
// 如果所有米棉之战线索都已查看,自动显示单选题1
|
||||
useEffect(() => {
|
||||
@@ -104,6 +109,12 @@ export const MimianBattle = ({
|
||||
state.setMimianSuccessAlertContent("");
|
||||
// 显示旁白2(图4)
|
||||
state.setShowMimianNarration2(true);
|
||||
|
||||
// 检查碎片14是否已解锁,如果未解锁则触发动画
|
||||
const pieceId = 14;
|
||||
if (!isPuzzleUnlocked(starName, pieceId)) {
|
||||
triggerPuzzleAnimation(pieceId);
|
||||
}
|
||||
};
|
||||
|
||||
// 处理错误提示框继续
|
||||
|
||||
@@ -3,12 +3,21 @@
|
||||
* 管理米棉之战相关的所有状态
|
||||
*/
|
||||
import { useState } from "react";
|
||||
import { useChenyunStore } from "@/store/chenyunStore";
|
||||
|
||||
/**
|
||||
* 米棉之战状态管理 Hook
|
||||
* @returns 米棉之战的所有状态和更新函数
|
||||
*/
|
||||
export const useMimianState = () => {
|
||||
// 直接从 store 获取持久化的解决状态,不需要本地状态
|
||||
const { isMimianSolved, setMimianSolved } = useChenyunStore();
|
||||
|
||||
// 更新解决状态时,直接更新 store
|
||||
const handleSetIsMimianSolved = (solved: boolean) => {
|
||||
setMimianSolved(solved);
|
||||
};
|
||||
|
||||
// 单选题1相关状态
|
||||
const [showMimianQuiz1, setShowMimianQuiz1] = useState(false);
|
||||
|
||||
@@ -36,9 +45,6 @@ export const useMimianState = () => {
|
||||
// 旁白5相关状态(结束语)
|
||||
const [showMimianNarration5, setShowMimianNarration5] = useState(false);
|
||||
|
||||
// 米棉之战解决状态
|
||||
const [isMimianSolved, setIsMimianSolved] = useState(false);
|
||||
|
||||
// 米棉之战成功提示框状态
|
||||
const [showMimianSuccessAlert, setShowMimianSuccessAlert] = useState(false);
|
||||
const [mimianSuccessAlertContent, setMimianSuccessAlertContent] =
|
||||
@@ -75,7 +81,7 @@ export const useMimianState = () => {
|
||||
|
||||
// 解决状态
|
||||
isMimianSolved,
|
||||
setIsMimianSolved,
|
||||
setIsMimianSolved: handleSetIsMimianSolved,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -8,6 +8,9 @@ import { YinyuanClues } from "./YinyuanClues";
|
||||
import { YinyuanQuizzes } from "./YinyuanQuizzes";
|
||||
import { YinyuanGuides } from "./YinyuanGuides";
|
||||
import { useYinyuanState } from "./useYinyuanState";
|
||||
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
|
||||
import { usePuzzleStore } from "@/store/puzzleStore";
|
||||
import { starName } from "../constants";
|
||||
import type { FileType } from "../shared/types";
|
||||
import type { ClueData } from "@/components/layout";
|
||||
|
||||
@@ -24,8 +27,6 @@ export interface YinyuanBattleProps {
|
||||
onClueClick: (clue: ClueData, clueKey: string) => void;
|
||||
/** 播放视频回调 */
|
||||
onPlayVideo: (fileType: FileType) => void;
|
||||
/** 银元之战解决状态变化回调 */
|
||||
onSolvedChange?: (solved: boolean) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -39,16 +40,10 @@ export const YinyuanBattle = ({
|
||||
onFileClick,
|
||||
onClueClick,
|
||||
onPlayVideo,
|
||||
onSolvedChange,
|
||||
}: YinyuanBattleProps) => {
|
||||
const state = useYinyuanState();
|
||||
|
||||
// 当银元之战解决状态变化时,通知父组件
|
||||
useEffect(() => {
|
||||
if (onSolvedChange) {
|
||||
onSolvedChange(state.isYinyuanSolved);
|
||||
}
|
||||
}, [state.isYinyuanSolved, onSolvedChange]);
|
||||
const { triggerPuzzleAnimation } = usePuzzleAnimation();
|
||||
const { isPuzzleUnlocked } = usePuzzleStore();
|
||||
|
||||
// 监听"前往解决"按钮出现的条件,显示第一个提示
|
||||
useEffect(() => {
|
||||
@@ -157,6 +152,12 @@ export const YinyuanBattle = ({
|
||||
// 关闭聚光灯、标记银元之战为已解决、显示走势图
|
||||
state.setIsYinyuanSolved(true);
|
||||
state.setShowSuccessAlert(false);
|
||||
|
||||
// 检查碎片13是否已解锁,如果未解锁则触发动画
|
||||
const pieceId = 13;
|
||||
if (!isPuzzleUnlocked(starName, pieceId)) {
|
||||
triggerPuzzleAnimation(pieceId);
|
||||
}
|
||||
};
|
||||
|
||||
// 处理走势图点击
|
||||
|
||||
@@ -78,11 +78,9 @@ export const YinyuanGuides = ({
|
||||
visible={showTrendChartDetail}
|
||||
src="chenyun/reflection/银元价格走势图"
|
||||
description={`以上海为中心,代表"人民币信用"的绿色区域迅速覆盖全市,并向外辐射。`}
|
||||
disableDrag={true}
|
||||
onClick={onTrendChartClick}
|
||||
onClose={onTrendChartDetailClose}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -3,39 +3,45 @@
|
||||
* 管理银元之战相关的所有状态
|
||||
*/
|
||||
import { useState } from "react";
|
||||
import { useChenyunStore } from "@/store/chenyunStore";
|
||||
|
||||
/**
|
||||
* 银元之战状态管理 Hook
|
||||
* @returns 银元之战的所有状态和更新函数
|
||||
*/
|
||||
export const useYinyuanState = () => {
|
||||
// 直接从 store 获取持久化的解决状态,不需要本地状态
|
||||
const { isYinyuanSolved, setYinyuanSolved } = useChenyunStore();
|
||||
|
||||
// 更新解决状态时,直接更新 store
|
||||
const handleSetIsYinyuanSolved = (solved: boolean) => {
|
||||
setYinyuanSolved(solved);
|
||||
};
|
||||
|
||||
// 多选题1相关状态
|
||||
const [showQuiz1, setShowQuiz1] = useState(false);
|
||||
|
||||
|
||||
// 旁白文字状态(图2)
|
||||
const [showNarration2, setShowNarration2] = useState(false);
|
||||
const [narration2Text, setNarration2Text] = useState("");
|
||||
|
||||
|
||||
// 多选题2相关状态
|
||||
const [showQuiz2, setShowQuiz2] = useState(false);
|
||||
|
||||
|
||||
// 成功提示框状态
|
||||
const [showSuccessAlert, setShowSuccessAlert] = useState(false);
|
||||
|
||||
// 银元之战解决状态
|
||||
const [isYinyuanSolved, setIsYinyuanSolved] = useState(false);
|
||||
|
||||
|
||||
// 走势图图标显示状态(控制文件上的走势图图标是否显示)
|
||||
const [showTrendChartIcon, setShowTrendChartIcon] = useState(true);
|
||||
|
||||
|
||||
// 走势图大图显示状态
|
||||
const [showTrendChartDetail, setShowTrendChartDetail] = useState(false);
|
||||
|
||||
|
||||
// 三个引导提示框状态
|
||||
const [showGuide1, setShowGuide1] = useState(false); // 前往解决按钮出现后的提示
|
||||
const [showGuide2, setShowGuide2] = useState(false); // 走势图出现后的提示
|
||||
const [showGuide3, setShowGuide3] = useState(false); // 走势图大图打开后的提示
|
||||
|
||||
|
||||
// 记录是否已经显示过这些提示(避免重复显示)
|
||||
const [hasShownGuide1, setHasShownGuide1] = useState(false);
|
||||
const [hasShownGuide2, setHasShownGuide2] = useState(false);
|
||||
@@ -47,27 +53,27 @@ export const useYinyuanState = () => {
|
||||
setShowQuiz1,
|
||||
showQuiz2,
|
||||
setShowQuiz2,
|
||||
|
||||
|
||||
// 旁白相关状态
|
||||
showNarration2,
|
||||
setShowNarration2,
|
||||
narration2Text,
|
||||
setNarration2Text,
|
||||
|
||||
|
||||
// 成功提示框状态
|
||||
showSuccessAlert,
|
||||
setShowSuccessAlert,
|
||||
|
||||
|
||||
// 解决状态
|
||||
isYinyuanSolved,
|
||||
setIsYinyuanSolved,
|
||||
|
||||
setIsYinyuanSolved: handleSetIsYinyuanSolved,
|
||||
|
||||
// 走势图相关状态
|
||||
showTrendChartIcon,
|
||||
setShowTrendChartIcon,
|
||||
showTrendChartDetail,
|
||||
setShowTrendChartDetail,
|
||||
|
||||
|
||||
// 引导提示状态
|
||||
showGuide1,
|
||||
setShowGuide1,
|
||||
@@ -83,4 +89,3 @@ export const useYinyuanState = () => {
|
||||
setHasShownGuide3,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -269,7 +269,7 @@ const SunzhongshanReflectionScene1 = () => {
|
||||
{showVideo && (
|
||||
<div className="fixed inset-0 z-[9999] bg-black">
|
||||
<Video
|
||||
src={getPublicPath(`video/${starName}/sunzhongshan_10.mp4`)}
|
||||
src={getPublicPath(`video/${starName}/sunzhongshan_11.mp4`)}
|
||||
className="w-full h-full"
|
||||
onEnded={handleVideoEnd}
|
||||
autoPlay={true}
|
||||
|
||||
@@ -283,7 +283,7 @@ const SunzhongshanReflectionScene2 = () => {
|
||||
{showVideo && (
|
||||
<div className="fixed inset-0 z-[9999] bg-black">
|
||||
<Video
|
||||
src={getPublicPath(`video/${starName}/sunzhongshan_10.mp4`)}
|
||||
src={getPublicPath(`video/${starName}/sunzhongshan_11.mp4`)}
|
||||
className="w-full h-full"
|
||||
onEnded={handleVideoEnd}
|
||||
autoPlay={true}
|
||||
|
||||
@@ -311,7 +311,7 @@ const SunzhongshanReflectionScene3 = () => {
|
||||
{showVideo && (
|
||||
<div className="fixed inset-0 z-[9999] bg-black">
|
||||
<Video
|
||||
src={getPublicPath(`video/${starName}/sunzhongshan_10.mp4`)}
|
||||
src={getPublicPath(`video/${starName}/sunzhongshan_11.mp4`)}
|
||||
className="w-full h-full"
|
||||
onEnded={handleVideoEnd}
|
||||
autoPlay={true}
|
||||
|
||||
Reference in new issue
Block a user