- 在 Luxun 和 Sunzhongshan 的多个组件中引入 useLuxunStore 和 useSunzhongshanStore,替代原有的访问状态管理方式。 - 通过 markExhibitVisited 方法标记展品为已访问,提升状态管理的效率和一致性。 - 从 store 获取已访问展品的状态,确保数据的持久性和准确性。
524 lines
19 KiB
TypeScript
524 lines
19 KiB
TypeScript
import {
|
|
ExhibitOverlay,
|
|
Image,
|
|
Title,
|
|
Cursor,
|
|
GuideSteps,
|
|
GuideStepsItem,
|
|
SingleChoiceQuizPanel,
|
|
} from "@/components";
|
|
import { useNavigate, useParams } from "react-router-dom";
|
|
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";
|
|
import { useLuxunStore } from "@/store/luxunStore";
|
|
|
|
// starName 固定为 luxun
|
|
const starName = "luxun";
|
|
|
|
// 展品 ID 与资源路径映射
|
|
const exhibitSrcMap: Record<1 | 2 | 3 | 4 | 5, string> = {
|
|
1: "luxun/place3/6",
|
|
2: "luxun/place3/7",
|
|
3: "luxun/place3/8",
|
|
4: "luxun/place3/9",
|
|
5: "luxun/place3/10",
|
|
};
|
|
|
|
const quiz2DefaultError = "再想一想,注意题干中的时间和提示信息哦~";
|
|
const quiz2Explanations: Record<string, string> = {
|
|
A: "鲁迅身处旧中国文艺思惰僵化、缺乏革新活力的时代,他认为“没有拿来的,人不能自成为新人”。翻译外国作品正是为了打破文化封闭,为中国文艺引进新思想、新方法,为沉寂的文坛注入新血液。",
|
|
B: "当时中国青年普遍受传统旧思想束缚,接触先进文化的渠道有限。鲁迅希望通过翻译外国进步作品,让青年接触世界范围内的先进文化理念、思想成果,拓宽视野、启发心智,助力青年觉醒。",
|
|
C: "鲁迅的核心斗争方式是“以笔为刃”,通过文学创作、翻译、杂文批判等方式开展思想启蒙和文化斗争,从未直接参与武装革命的战略部署。",
|
|
D: "在国民党“文化围剿”、进步思想遭严控的背景下,鲁迅将翻译视为重要的斗争武器——通过引进外国进步文学、马克思主义理论等,揭露旧中国的黑暗与愚昧,唤醒民众的思想意识,助力思想启蒙运动。",
|
|
};
|
|
|
|
// 光标5-题目:纪念瞿秋白的译文集
|
|
const quiz5DefaultError = "再想一想,结合前面的旁白内容哦~";
|
|
const quiz5Explanations: Record<string, string> = {
|
|
A: "《仿自由书》是鲁迅在上海创作的杂文集,收录了1933年所作的多篇杂文,核心内容是针砭国民党当局的虚伪统治、批判社会乱象,并非为纪念瞿秋白编校的译文集。",
|
|
B: "《南腔北调集》是鲁迅的一部杂文集,收录了1927年至1933年的杂文作品,书名源于鲁迅对自己文章“不入调、不入流”的自嘲,内容涵盖文化批判、社会评论等,与纪念瞿秋白及译文集无关。",
|
|
C: "恭喜你,选对啦。",
|
|
D: "《故事新编》是鲁迅的历史小说集,酝酿多年后在大陆新村9号完成定稿。作品以神话传说、历史典故为载体借古讽今,批判现实社会的腐朽与愚昧,并非译文集,更与纪念瞿秋白无关。",
|
|
};
|
|
|
|
const LuxunPlace3 = () => {
|
|
const navigate = useNavigate();
|
|
const { moduleName } = useParams<{
|
|
moduleName: string;
|
|
}>();
|
|
|
|
//碎片动画触发方法
|
|
const { triggerPuzzleAnimation } = usePuzzleAnimation();
|
|
|
|
const [activeExhibitId, setActiveExhibitId] = useState<
|
|
1 | 2 | 3 | 4 | 5 | null
|
|
>(null);
|
|
|
|
// 使用持久化的访问状态
|
|
const place = "place3";
|
|
const { markExhibitVisited, getVisitedExhibits } = useLuxunStore();
|
|
|
|
// 从 store 获取已访问的展品,转换为 Set 用于兼容现有代码
|
|
const visitedExhibits = getVisitedExhibits(place);
|
|
const visited = new Set(visitedExhibits);
|
|
|
|
// Cursor 顺序控制:1 -> 2 -> 3 -> 4 -> 5
|
|
const { isEnabled } = useCursorController({
|
|
initialEnabled: [1],
|
|
dependencies: {
|
|
2: [1],
|
|
3: [2],
|
|
4: [3],
|
|
5: [4],
|
|
},
|
|
visited: visited,
|
|
});
|
|
|
|
// 光标1的引导对话状态
|
|
const [guide1Visible, setGuide1Visible] = useState(false);
|
|
const [guide1Step, setGuide1Step] = useState(0);
|
|
|
|
// 光标2的引导对话状态
|
|
const [guide2Visible, setGuide2Visible] = useState(false);
|
|
const [guide2Step, setGuide2Step] = useState(0);
|
|
|
|
// 光标2的题目状态
|
|
const [quiz2Visible, setQuiz2Visible] = useState(false);
|
|
const [successAlert2Visible, setSuccessAlert2Visible] = useState(false);
|
|
const [quiz2Error, setQuiz2Error] = useState(quiz2DefaultError);
|
|
|
|
// 光标3的引导对话状态
|
|
const [guide3Visible, setGuide3Visible] = useState(false);
|
|
const [guide3Step, setGuide3Step] = useState(0);
|
|
|
|
// 光标4的引导对话状态
|
|
const [guide4Visible, setGuide4Visible] = useState(false);
|
|
const [guide4Step, setGuide4Step] = useState(0);
|
|
|
|
// 光标5的题目 & 成功提示 & 引导对话状态
|
|
const [quiz5Visible, setQuiz5Visible] = useState(false);
|
|
const [quiz5Error, setQuiz5Error] = useState(quiz5DefaultError);
|
|
const [successAlert5Visible, setSuccessAlert5Visible] = useState(false);
|
|
const [guide5Visible, setGuide5Visible] = useState(false);
|
|
const [guide5Step, setGuide5Step] = useState(0);
|
|
|
|
const handleOpenExhibit = (index: 1 | 2 | 3 | 4 | 5) => {
|
|
if (index === 1) {
|
|
// 光标1:点击时同时打开展品和引导对话
|
|
setActiveExhibitId(1);
|
|
setGuide1Visible(true);
|
|
setGuide1Step(0);
|
|
} else if (index === 2) {
|
|
// 光标2:只打开展品(带描述)
|
|
setActiveExhibitId(2);
|
|
setGuide2Visible(true);
|
|
setGuide2Step(0);
|
|
} else if (index === 3) {
|
|
// 光标3:点击时同时打开展品和引导对话
|
|
setActiveExhibitId(3);
|
|
setGuide3Visible(true);
|
|
setGuide3Step(0);
|
|
} else if (index === 4) {
|
|
// 光标4:点击时同时打开展品和引导对话
|
|
setActiveExhibitId(4);
|
|
setGuide4Visible(true);
|
|
setGuide4Step(0);
|
|
} else {
|
|
// 光标5:先打开展品,关闭后进入答题
|
|
setActiveExhibitId(index);
|
|
}
|
|
};
|
|
|
|
const progressCount = visited.size;
|
|
const total = 5;
|
|
const completed = progressCount >= total;
|
|
const subtitle = completed
|
|
? "探索二楼卧室兼书房的书桌(已完成)"
|
|
: `探索二楼卧室兼书房的书桌(${progressCount}/${total})`;
|
|
const subtitleClassName = completed
|
|
? "text-[#fff998] [text-shadow:_0_8px_8px_rgba(0,0,0,0.35),_0_0_16px_rgba(0,0,0,0.25)]"
|
|
: undefined;
|
|
|
|
return (
|
|
<div
|
|
className="w-full h-full bg-cover bg-center bg-no-repeat"
|
|
style={{
|
|
backgroundImage: getBackgroundImage(`luxun/place3/背景`),
|
|
}}
|
|
>
|
|
<Title
|
|
title={`历史新编中的现实呐喊\n为亡友编纂的精神丰碑`}
|
|
subtitle={subtitle}
|
|
subtitleClassName={subtitleClassName}
|
|
className="absolute top-10 left-1/2 -translate-x-1/2"
|
|
/>
|
|
|
|
<Image
|
|
className="absolute right-5 top-5"
|
|
src={getStaticImage("components/离开")}
|
|
button
|
|
onClick={() => {
|
|
if (moduleName && starName) {
|
|
navigate(`/${moduleName}/${starName}/place2`);
|
|
}
|
|
}}
|
|
/>
|
|
|
|
{/* 光标1 */}
|
|
<Cursor
|
|
className="absolute right-114 bottom-130"
|
|
onClick={() => handleOpenExhibit(1)}
|
|
size={0.6}
|
|
read={visited.has(1)}
|
|
disabled={!isEnabled(1)}
|
|
/>
|
|
{/* 光标2 */}
|
|
<Cursor
|
|
className="absolute right-130 bottom-115"
|
|
onClick={() => handleOpenExhibit(2)}
|
|
size={0.6}
|
|
read={visited.has(2)}
|
|
disabled={!isEnabled(2)}
|
|
/>
|
|
{/* 光标3 */}
|
|
<Cursor
|
|
className="absolute left-76 bottom-140"
|
|
onClick={() => handleOpenExhibit(3)}
|
|
size={0.6}
|
|
read={visited.has(3)}
|
|
disabled={!isEnabled(3)}
|
|
/>
|
|
{/* 光标4 */}
|
|
<Cursor
|
|
className="absolute right-238 bottom-125"
|
|
onClick={() => handleOpenExhibit(4)}
|
|
size={0.6}
|
|
read={visited.has(4)}
|
|
disabled={!isEnabled(4)}
|
|
/>
|
|
{/* 光标5 */}
|
|
<Cursor
|
|
className="absolute right-258 bottom-118"
|
|
onClick={() => handleOpenExhibit(5)}
|
|
size={0.6}
|
|
read={visited.has(5)}
|
|
disabled={!isEnabled(5)}
|
|
/>
|
|
|
|
{completed && (
|
|
<Image
|
|
className="absolute bottom-0 right-130"
|
|
src={getStaticImage("luxun/place3/前进按钮")}
|
|
button
|
|
onClick={() => {
|
|
navigate(`/${moduleName}/${starName}/place4`);
|
|
}}
|
|
/>
|
|
)}
|
|
|
|
<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年《海上述林》上卷出版时,鲁迅的身体已经极度虚弱,但他仍一遍遍翻看样书,说“这是我对秋白最好的纪念”。"
|
|
: undefined
|
|
}
|
|
audioSrc={
|
|
activeExhibitId === 5
|
|
? getPublicPath("audio/luxun/mp3_luxun_23.mp3")
|
|
: undefined
|
|
}
|
|
onClose={() => {
|
|
if (activeExhibitId === 2) {
|
|
// 光标2:关闭展品框后显示题目
|
|
setActiveExhibitId(null);
|
|
setQuiz2Visible(true);
|
|
} else if (activeExhibitId === 5) {
|
|
// 光标5:关闭展品框后显示题目
|
|
setActiveExhibitId(null);
|
|
setQuiz5Visible(true);
|
|
} else if (activeExhibitId === 1 || activeExhibitId === 3) {
|
|
// 光标1和光标3:只关闭展品,不标记为已读(等对话关闭时再标记)
|
|
setActiveExhibitId(null);
|
|
} else if (activeExhibitId === 4) {
|
|
// 光标4:只关闭展品,不标记为已读(等对话关闭时再标记)
|
|
setActiveExhibitId(null);
|
|
} else {
|
|
// 其他光标:正常关闭并标记为已读
|
|
if (activeExhibitId !== null) {
|
|
markExhibitVisited(place, activeExhibitId);
|
|
}
|
|
setActiveExhibitId(null);
|
|
}
|
|
}}
|
|
/>
|
|
|
|
{/* 光标1的引导对话 */}
|
|
<GuideSteps
|
|
visible={guide1Visible}
|
|
onVisibleChange={(visible) => {
|
|
setGuide1Visible(visible);
|
|
if (!visible) {
|
|
// 对话关闭时,标记光标1为已读
|
|
markExhibitVisited(place, 1);
|
|
}
|
|
}}
|
|
currentStep={guide1Step}
|
|
onStepChange={setGuide1Step}
|
|
showMask={false}
|
|
highlightSelectors={[]}
|
|
zIndex={10000}
|
|
>
|
|
<GuideStepsItem
|
|
speaker="student"
|
|
audioSrc={getPublicPath("audio/luxun/mp3_luxun_15.mp3")}
|
|
>
|
|
小沪,书架上还有《故事新编》,这不是历史小说吗?鲁迅为什么要在这个时期写古代故事呢?
|
|
</GuideStepsItem>
|
|
<GuideStepsItem
|
|
speaker="guide"
|
|
audioSrc={getPublicPath("audio/luxun/mp3_luxun_16.mp3")}
|
|
>
|
|
《故事新编》是鲁迅酝酿十几年的作品,最终在大陆新村9号完成定稿。表面上写的是神话传说和历史典故,实则句句都在影射现实。当时反动派妄图用复古思想麻痹民众,鲁迅就用"故事新编"的方式,借古讽今,唤醒民众。
|
|
</GuideStepsItem>
|
|
</GuideSteps>
|
|
|
|
{/* 光标2的引导对话 */}
|
|
<GuideSteps
|
|
visible={guide2Visible}
|
|
onVisibleChange={setGuide2Visible}
|
|
currentStep={guide2Step}
|
|
onStepChange={setGuide2Step}
|
|
showMask={false}
|
|
highlightSelectors={[]}
|
|
zIndex={10000}
|
|
>
|
|
<GuideStepsItem
|
|
speaker="student"
|
|
audioSrc={getPublicPath("audio/luxun/mp3_luxun_17.mp3")}
|
|
>
|
|
这本书里面还收录了《理水》,是大禹治水的故事吗?
|
|
</GuideStepsItem>
|
|
<GuideStepsItem
|
|
speaker="guide"
|
|
audioSrc={getPublicPath("audio/luxun/mp3_luxun_18.mp3")}
|
|
>
|
|
不错,文章写的是大禹治水“三过家门而不入”的上古传说,实则藏着对现实的尖锐针砭。当时反动派统治腐朽,面对内忧外患只会粉饰太平,还有一群空谈理论、脱离实际的“学者”混淆视听。
|
|
</GuideStepsItem>
|
|
<GuideStepsItem speaker="guide">
|
|
大禹带领民众跋山涉水、实地勘察,摒弃“堵”的旧法改用“疏”的良方,历经千辛万苦终平水患。这哪里是写古代治水?分明是在赞颂脚踏实地、敢破敢立的实干精神,讽刺那些庸碌无为的当权者与空谈家,用远古的正义之光映照现实的黑暗。
|
|
</GuideStepsItem>
|
|
</GuideSteps>
|
|
|
|
{/* 光标2的题目 */}
|
|
<SingleChoiceQuizPanel
|
|
visible={quiz2Visible}
|
|
title="【单选】鲁迅在上海大陆新村9号居住期间,翻译外国作品的核心目的不包括:"
|
|
options={[
|
|
{
|
|
value: "A",
|
|
label: "A.为中国文艺引进新思想、新血液",
|
|
},
|
|
{
|
|
value: "B",
|
|
label: "B.让中国青年接触更先进的文化理念",
|
|
},
|
|
{
|
|
value: "C",
|
|
label: "C.直接参与武装革命的战略部署",
|
|
},
|
|
{
|
|
value: "D",
|
|
label: "D.以翻译为武器助力思想启蒙",
|
|
},
|
|
]}
|
|
correctAnswer="C"
|
|
errorMessage={quiz2Error}
|
|
onSubmit={(value, isCorrect) => {
|
|
if (isCorrect) {
|
|
setQuiz2Error(quiz2DefaultError);
|
|
setQuiz2Visible(false);
|
|
setSuccessAlert2Visible(true);
|
|
}
|
|
}}
|
|
onWrongSelect={(value) => {
|
|
const explanation = quiz2Explanations[value];
|
|
if (explanation) {
|
|
setQuiz2Error(explanation);
|
|
return explanation;
|
|
}
|
|
setQuiz2Error(quiz2DefaultError);
|
|
return quiz2DefaultError;
|
|
}}
|
|
zIndex={10001}
|
|
/>
|
|
|
|
{/* 光标2的成功提示 */}
|
|
<SuccessAlert
|
|
visible={successAlert2Visible}
|
|
content={
|
|
"鲁迅的核心斗争方式是“以笔为刃”,通过文学创作、翻译、杂文批判等方式开展思想启蒙和文化斗争,从未直接参与武装革命的战略部署。"
|
|
}
|
|
onContinue={() => {
|
|
setSuccessAlert2Visible(false);
|
|
// 标记光标2为已读
|
|
markExhibitVisited(place, 2);
|
|
}}
|
|
/>
|
|
|
|
{/* 光标3的引导对话 */}
|
|
<GuideSteps
|
|
visible={guide3Visible}
|
|
onVisibleChange={(visible) => {
|
|
setGuide3Visible(visible);
|
|
if (!visible) {
|
|
// 对话关闭时,标记光标3为已读
|
|
markExhibitVisited(place, 3);
|
|
}
|
|
}}
|
|
currentStep={guide3Step}
|
|
onStepChange={setGuide3Step}
|
|
showMask={false}
|
|
highlightSelectors={[]}
|
|
zIndex={10000}
|
|
>
|
|
<GuideStepsItem
|
|
speaker="student"
|
|
audioSrc={getPublicPath("audio/luxun/mp3_luxun_19.mp3")}
|
|
>
|
|
我还看到书架上有很多翻译作品,这也是他在这儿完成的吗?
|
|
</GuideStepsItem>
|
|
<GuideStepsItem
|
|
speaker="guide"
|
|
audioSrc={getPublicPath("audio/luxun/mp3_luxun_20.mp3")}
|
|
>
|
|
没错!鲁迅在这儿翻译了《死魂灵》《表》等大量外国作品。他说"没有拿来的,人不能自成为新人",就是想通过翻译,为中国文艺引进新思想、新方法,让更多人看到世界的真相,找到救国的道路。
|
|
</GuideStepsItem>
|
|
</GuideSteps>
|
|
|
|
{/* 光标4的引导对话 */}
|
|
<GuideSteps
|
|
visible={guide4Visible}
|
|
onVisibleChange={(visible) => {
|
|
setGuide4Visible(visible);
|
|
if (!visible) {
|
|
// 对话关闭时,标记光标4为已读
|
|
markExhibitVisited(place, 4);
|
|
}
|
|
}}
|
|
currentStep={guide4Step}
|
|
onStepChange={setGuide4Step}
|
|
showMask={false}
|
|
highlightSelectors={[]}
|
|
zIndex={10000}
|
|
>
|
|
<GuideStepsItem
|
|
speaker="student"
|
|
audioSrc={getPublicPath("audio/luxun/mp3_luxun_21.mp3")}
|
|
>
|
|
小沪,这两本书看起来格外厚重,它和鲁迅在这儿的经历有什么关联呢?
|
|
</GuideStepsItem>
|
|
<GuideStepsItem
|
|
speaker="guide"
|
|
audioSrc={getPublicPath("audio/luxun/mp3_luxun_22.mp3")}
|
|
>
|
|
这不是鲁迅的著作,却是他在大陆新村9号倾注了全部心血的作品——为亡友瞿秋白编辑的译文集《海上述林》。1935年6月,瞿秋白被国民党反动派杀害,鲁迅悲痛万分,当即决定要为挚友出版译文集,让他的思想和精神流传下去。
|
|
</GuideStepsItem>
|
|
</GuideSteps>
|
|
|
|
{/* 光标5的题目 */}
|
|
<SingleChoiceQuizPanel
|
|
visible={quiz5Visible}
|
|
title="【单选】鲁迅在上海大陆新村9号居住期间,为纪念瞿秋白编校的译文集是:"
|
|
options={[
|
|
{ value: "A", label: "A.《仿自由书》" },
|
|
{ value: "B", label: "B.《南腔北调集》" },
|
|
{ value: "C", label: "C.《海上述林》" },
|
|
{ value: "D", label: "D.《故事新编》" },
|
|
]}
|
|
correctAnswer="C"
|
|
errorMessage={quiz5Error}
|
|
onSubmit={(value, isCorrect) => {
|
|
if (isCorrect) {
|
|
setQuiz5Error(quiz5DefaultError);
|
|
setQuiz5Visible(false);
|
|
setSuccessAlert5Visible(true);
|
|
}
|
|
}}
|
|
onWrongSelect={(value) => {
|
|
const explanation = quiz5Explanations[value];
|
|
if (explanation) {
|
|
setQuiz5Error(explanation);
|
|
return explanation;
|
|
}
|
|
setQuiz5Error(quiz5DefaultError);
|
|
return quiz5DefaultError;
|
|
}}
|
|
zIndex={10001}
|
|
/>
|
|
|
|
{/* 光标5的成功提示 */}
|
|
<SuccessAlert
|
|
visible={successAlert5Visible}
|
|
onContinue={() => {
|
|
setSuccessAlert5Visible(false);
|
|
// 标记光标5为已读
|
|
markExhibitVisited(place, 5);
|
|
// 打开光标5的引导对话
|
|
setGuide5Visible(true);
|
|
setGuide5Step(0);
|
|
}}
|
|
/>
|
|
|
|
{/* 光标5的引导对话 */}
|
|
<GuideSteps
|
|
visible={guide5Visible}
|
|
onVisibleChange={(visible) => {
|
|
setGuide5Visible(visible);
|
|
}}
|
|
currentStep={guide5Step}
|
|
onStepChange={setGuide5Step}
|
|
showMask={false}
|
|
highlightSelectors={[]}
|
|
zIndex={10000}
|
|
>
|
|
<GuideStepsItem
|
|
speaker="student"
|
|
audioSrc={getPublicPath("audio/luxun/mp3_luxun_24.mp3")}
|
|
>
|
|
原来这本书不仅是译文集,更是两人革命友谊和共同信仰的见证!
|
|
</GuideStepsItem>
|
|
<GuideStepsItem
|
|
speaker="guide"
|
|
audioSrc={getPublicPath("audio/luxun/mp3_luxun_25.mp3")}
|
|
>
|
|
太对了!鲁迅和瞿秋白的故事不仅在于《海上述林》的字里行间,这座小楼的三楼也藏着他们为了革命事业并肩战斗的初心。
|
|
</GuideStepsItem>
|
|
</GuideSteps>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default LuxunPlace3;
|