feat:鲁迅添加引导

This commit is contained in:
zhongyidian committed 2026-01-08 18:41:24 +08:00
1 parent e8dc5d4b95
commit 3129e01257
26 files changed
+951 -86

No files matched your search

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 MiB

After

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 800 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

+1 -1
View File
@@ -22,7 +22,7 @@ const DragQuizPanel = ({
className="fixed inset-0 flex items-center justify-center bg-black/40" className="fixed inset-0 flex items-center justify-center bg-black/40"
style={{ zIndex }} style={{ zIndex }}
> >
<div className="relative w-[1728px] h-[972px] flex flex-col items-center pt-12 pb-14 gap-10"> <div className="relative w-[1728px] h-[972px] flex flex-col items-center pt-12 pb-14">
{/* 题干卡片 */} {/* 题干卡片 */}
<div className="w-[1600px] rounded-[32px] bg-black/90 px-20 py-12 shadow-[0_20px_60px_rgba(240,190,117,0.8)] border-4 border-[#d4c5a9]"> <div className="w-[1600px] rounded-[32px] bg-black/90 px-20 py-12 shadow-[0_20px_60px_rgba(240,190,117,0.8)] border-4 border-[#d4c5a9]">
<div className="text-[32px] leading-relaxed text-white"> <div className="text-[32px] leading-relaxed text-white">
@@ -36,6 +36,7 @@ const SingleChoiceQuizPanel = ({
correctAnswer, correctAnswer,
errorMessage, errorMessage,
onSubmit, onSubmit,
onWrongSelect,
onError, onError,
disabled = false, disabled = false,
}: SingleChoiceQuizPanelProps) => { }: SingleChoiceQuizPanelProps) => {
@@ -65,7 +66,9 @@ const SingleChoiceQuizPanel = ({
} else { } else {
// 答题失败:清空状态,执行 onError,允许重新选择 // 答题失败:清空状态,执行 onError,允许重新选择
setSelectedValue(""); setSelectedValue("");
const errorMsg = errorMessage || "很抱歉,你选错了!请你再思考一下吧!"; const customError = onWrongSelect?.(value);
const errorMsg =
customError || errorMessage || "很抱歉,你选错了!请你再思考一下吧!";
setError(errorMsg); setError(errorMsg);
onError?.(errorMsg); onError?.(errorMsg);
} }
+2
View File
@@ -45,6 +45,8 @@ export interface SingleChoiceQuizPanelProps {
errorMessage?: string; errorMessage?: string;
/** 提交回调,返回用户选择和是否正确 */ /** 提交回调,返回用户选择和是否正确 */
onSubmit?: (value: string, isCorrect: boolean) => void; onSubmit?: (value: string, isCorrect: boolean) => void;
/** 答错时回调,可返回自定义解析文案覆盖默认 errorMessage */
onWrongSelect?: (value: string) => string | void;
/** 错误回调(可选) */ /** 错误回调(可选) */
onError?: (error: string) => void; onError?: (error: string) => void;
/** 是否禁用 */ /** 是否禁用 */
+5 -2
View File
@@ -30,6 +30,7 @@ type QuizAnchor = {
options: QuizOption[]; options: QuizOption[];
// 拖拽题专用 // 拖拽题专用
blanks?: { id: string; label: string }[]; blanks?: { id: string; label: string }[];
explanation?: string;
}; };
// 星名固定为 chenyun // 星名固定为 chenyun
@@ -48,11 +49,11 @@ const QUIZ_ANCHORS: QuizAnchor[] = [
{ {
value: "B", value: "B",
label: "鸟笼比喻", label: "鸟笼比喻",
explanation: "鸟笼比喻是正确的处理好计划与市场的关系的一个发展阶段",
}, },
{ value: "C", label: "正确的处理好计划与市场的关系", isCorrect: true }, { value: "C", label: "正确的处理好计划与市场的关系", isCorrect: true },
{ value: "D", label: "经济建设的目的论", isCorrect: true }, { value: "D", label: "经济建设的目的论", isCorrect: true },
], ],
explanation: "鸟笼比喻是正确的处理好计划与市场的关系的一个发展阶段",
}, },
{ {
id: "quiz-2", id: "quiz-2",
@@ -234,7 +235,9 @@ const ChenyunInternalization = () => {
label: `${opt.value}. ${opt.label}`, label: `${opt.value}. ${opt.label}`,
}))} }))}
correctAnswer={correctAnswers} correctAnswer={correctAnswers}
errorMessage="再思考一下,注意选择所有正确答案。" errorMessage={
item.explanation || "再思考一下,注意选择所有正确答案。"
}
onSubmit={(values: string[], isCorrect: boolean) => { onSubmit={(values: string[], isCorrect: boolean) => {
if (isCorrect) { if (isCorrect) {
setActiveSuccess(item.id); setActiveSuccess(item.id);
+9 -6
View File
@@ -29,6 +29,7 @@ type QuizAnchor = {
type: "single" | "multiple" | "drag"; type: "single" | "multiple" | "drag";
options: QuizOption[]; options: QuizOption[];
blanks?: { id: string; label: string }[]; blanks?: { id: string; label: string }[];
explanation?: string;
}; };
const starName = "huangyanpei"; const starName = "huangyanpei";
@@ -47,12 +48,12 @@ const QUIZ_ANCHORS: QuizAnchor[] = [
{ {
value: "B", value: "B",
label: "自动控制", label: "自动控制",
explanation:
"这一工程理论框架成形于20世纪中叶,70年代实现集中控制,新中国成立前,没有相关学科基础、设备与技术条件",
}, },
{ value: "C", label: "木工", isCorrect: true }, { value: "C", label: "木工", isCorrect: true },
{ value: "D", label: "纽扣科", isCorrect: true }, { value: "D", label: "纽扣科", isCorrect: true },
], ],
explanation:
"自动控制”这一工程理论框架成形于20世纪中叶,70年代实现集中控制,在民国时期,没有相关学科基础、设备与技术条件",
}, },
{ {
id: "quiz-2", id: "quiz-2",
@@ -75,7 +76,6 @@ const QUIZ_ANCHORS: QuizAnchor[] = [
{ {
value: "C", value: "C",
label: "学习四书五经、八股文", label: "学习四书五经、八股文",
explanation: "黄炎培认为教育不能只谈书本、脱离实际",
}, },
{ {
value: "D", value: "D",
@@ -83,6 +83,7 @@ const QUIZ_ANCHORS: QuizAnchor[] = [
isCorrect: true, isCorrect: true,
}, },
], ],
explanation: "黄炎培认为教育不能只谈书本、脱离实际",
}, },
{ {
id: "quiz-3", id: "quiz-3",
@@ -125,10 +126,10 @@ const QUIZ_ANCHORS: QuizAnchor[] = [
{ {
value: "D", value: "D",
label: "美术设计与制作→影像与影视技术", label: "美术设计与制作→影像与影视技术",
explanation:
"两个专业为不同技能方向的学习,前者为平面绘画等,后者以视频为主",
}, },
], ],
explanation:
"美术设计与制作专业侧重于平面设计,而影像与影视技术专业侧重于影视特效后期,两者的专业核心课程涉及的软硬件也有差异",
}, },
]; ];
@@ -286,7 +287,9 @@ const HuangyanpeiInternalization = () => {
label: `${opt.value}. ${opt.label}`, label: `${opt.value}. ${opt.label}`,
}))} }))}
correctAnswer={correctAnswers} correctAnswer={correctAnswers}
errorMessage="再思考一下,注意选择所有正确答案。" errorMessage={
item.explanation || "再思考一下,注意选择所有正确答案。"
}
onSubmit={(values: string[], isCorrect: boolean) => { onSubmit={(values: string[], isCorrect: boolean) => {
if (isCorrect) { if (isCorrect) {
setActiveSuccess(item.id); setActiveSuccess(item.id);
+20 -1
View File
@@ -9,6 +9,7 @@ import {
GuideStepsItem, GuideStepsItem,
SingleChoiceQuizPanel, SingleChoiceQuizPanel,
} from "@/components"; } from "@/components";
import { SuccessAlert } from "@/components/layout";
import { getBackgroundImage, getStaticImage } from "@/lib/utils"; import { getBackgroundImage, getStaticImage } from "@/lib/utils";
import { useNavigate, useParams } from "react-router-dom"; import { useNavigate, useParams } from "react-router-dom";
@@ -38,6 +39,8 @@ const HuangyanpeiPlace3 = () => {
const [guide3Step, setGuide3Step] = useState(0); const [guide3Step, setGuide3Step] = useState(0);
// 光标3的题目显示状态 // 光标3的题目显示状态
const [showQuiz3, setShowQuiz3] = useState(false); const [showQuiz3, setShowQuiz3] = useState(false);
// 成功提示状态
const [showSuccessAlert, setShowSuccessAlert] = useState(false);
// 光标1、2的引导对话状态(在两者都浏览后显示) // 光标1、2的引导对话状态(在两者都浏览后显示)
const [guide12Visible, setGuide12Visible] = useState(false); const [guide12Visible, setGuide12Visible] = useState(false);
@@ -69,7 +72,7 @@ const HuangyanpeiPlace3 = () => {
const handleQuiz3Submit = (value: string, isCorrect: boolean) => { const handleQuiz3Submit = (value: string, isCorrect: boolean) => {
if (isCorrect) { if (isCorrect) {
setShowQuiz3(false); setShowQuiz3(false);
setVisited((prev) => prev.add(3)); setShowSuccessAlert(true);
} }
}; };
@@ -212,6 +215,22 @@ const HuangyanpeiPlace3 = () => {
onSubmit={handleQuiz3Submit} onSubmit={handleQuiz3Submit}
/> />
{/* 成功提示 */}
<SuccessAlert
visible={showSuccessAlert}
onContinue={() => {
setShowSuccessAlert(false);
setVisited((prev) => {
const next = new Set(prev);
next.add(3);
return next;
});
}}
onClose={() => {
setShowSuccessAlert(false);
}}
/>
{/* 光标1、2的引导对话 */} {/* 光标1、2的引导对话 */}
<GuideSteps <GuideSteps
visible={guide12Visible} visible={guide12Visible}
+15
View File
@@ -10,6 +10,7 @@ import {
SingleChoiceQuizPanel, SingleChoiceQuizPanel,
Video, Video,
} from "@/components"; } from "@/components";
import { SuccessAlert } from "@/components/layout";
import { getBackgroundImage, getStaticImage, getPublicPath } from "@/lib/utils"; import { getBackgroundImage, getStaticImage, getPublicPath } from "@/lib/utils";
import { useNavigate, useParams } from "react-router-dom"; import { useNavigate, useParams } from "react-router-dom";
import type { VideoRef } from "@/components/video"; import type { VideoRef } from "@/components/video";
@@ -44,6 +45,8 @@ const HuangyanpeiPlace4 = () => {
const [showVideo, setShowVideo] = useState(false); const [showVideo, setShowVideo] = useState(false);
// 题目显示状态 // 题目显示状态
const [showQuiz, setShowQuiz] = useState(false); const [showQuiz, setShowQuiz] = useState(false);
// 成功提示状态
const [showSuccessAlert, setShowSuccessAlert] = useState(false);
const videoRef = useRef<VideoRef>(null); const videoRef = useRef<VideoRef>(null);
const handleOpenExhibit = (index: 1 | 2 | 3 | 4 | 5) => { const handleOpenExhibit = (index: 1 | 2 | 3 | 4 | 5) => {
@@ -62,6 +65,7 @@ const HuangyanpeiPlace4 = () => {
const handleQuizSubmit = (value: string, isCorrect: boolean) => { const handleQuizSubmit = (value: string, isCorrect: boolean) => {
if (isCorrect) { if (isCorrect) {
setShowQuiz(false); setShowQuiz(false);
setShowSuccessAlert(true);
} }
}; };
@@ -225,6 +229,17 @@ const HuangyanpeiPlace4 = () => {
zIndex={10001} zIndex={10001}
/> />
{/* 成功提示 */}
<SuccessAlert
visible={showSuccessAlert}
onContinue={() => {
setShowSuccessAlert(false);
}}
onClose={() => {
setShowSuccessAlert(false);
}}
/>
{/* 学习引导(进入场景即显示,与 place1 保持一致) */} {/* 学习引导(进入场景即显示,与 place1 保持一致) */}
<StudyGuideModal <StudyGuideModal
visible={showGuideModal} visible={showGuideModal}
+2 -2
View File
@@ -115,10 +115,10 @@ const HuangyanpeiPlace6 = () => {
新中国成立后,黄炎培先生还继续从事教育工作吗?我们现在的中职教育体系,是不是也有他当年的理念呀? 新中国成立后,黄炎培先生还继续从事教育工作吗?我们现在的中职教育体系,是不是也有他当年的理念呀?
</GuideStepsItem> </GuideStepsItem>
<GuideStepsItem speaker="guide"> <GuideStepsItem speaker="guide">
当然啦!1949年10月19日,71岁的黄炎培出任政务院副总理兼轻工业部部长,依然没有放下他毕生追求的职业教育事业。他把中华职教社的骨干力量全部调到北京,亲自起草《关于改革学制的决定》,第一次把"中等专业学校"正式纳入国家学制,让职业教育有了明确的国家定位。 当然啦!1949年10月19日,71岁的黄炎培出任政务院副总理兼轻工业部部长,依然没有放下他毕生追求的职业教育事业。他把中华职教社的骨干力量全部调到北京,亲自起草《关于改革学制的决定》...
</GuideStepsItem> </GuideStepsItem>
<GuideStepsItem speaker="guide"> <GuideStepsItem speaker="guide">
1952年,他又推动建立了上海机械学校、南京航空学校等五所中专,这些学校为国家"一五"计划输送了6.3万名技术工人,为工业发展提供了坚实的人才支撑。 第一次把"中等专业学校"正式纳入国家学制,让职业教育有了明确的国家定位。1952年,他又推动建立了上海机械学校、南京航空学校等五所中专,这些学校为国家"一五"计划输送了6.3万名技术工人,为工业发展提供了坚实的人才支撑。
</GuideStepsItem> </GuideStepsItem>
<GuideStepsItem speaker="guide"> <GuideStepsItem speaker="guide">
黄炎培先生曾说过:"我要把最后一滴血,滴在职业教育的讲台上。"他用一生践行了这句话,而我们现在中职教育强调的"工学结合、德技并修",正是对他"手脑并用、注重实践"理念的传承和发展。 黄炎培先生曾说过:"我要把最后一滴血,滴在职业教育的讲台上。"他用一生践行了这句话,而我们现在中职教育强调的"工学结合、德技并修",正是对他"手脑并用、注重实践"理念的传承和发展。
+6 -2
View File
@@ -30,6 +30,7 @@ type QuizAnchor = {
prompt: string; prompt: string;
type: "single" | "multiple"; type: "single" | "multiple";
options: QuizOption[]; options: QuizOption[];
explanation?: string;
}; };
const starName = "luxun"; const starName = "luxun";
@@ -62,9 +63,10 @@ const QUIZ_ANCHORS: QuizAnchor[] = [
{ {
value: "D", value: "D",
label: "让青年都去参军", label: "让青年都去参军",
explanation: "鲁迅先生还是在文学和思想领域培养青年一代,传递思想的火苗",
}, },
], ],
explanation:
"虽然鲁迅热爱革命,但他主要通过文学社团、木刻艺术和纪念文章来教育和启发青年,而不是通过参军来培养青年。",
}, },
{ {
id: "quiz-3", id: "quiz-3",
@@ -189,7 +191,9 @@ const LuxunInternalization = () => {
label: `${opt.value}. ${opt.label}`, label: `${opt.value}. ${opt.label}`,
}))} }))}
correctAnswer={correctAnswers} correctAnswer={correctAnswers}
errorMessage="再思考一下,注意选择所有正确答案。" errorMessage={
item.explanation || "再思考一下,注意选择所有正确答案。"
}
onSubmit={(values: string[], isCorrect: boolean) => { onSubmit={(values: string[], isCorrect: boolean) => {
if (isCorrect) { if (isCorrect) {
setActiveSuccess(item.id); setActiveSuccess(item.id);
+95 -9
View File
@@ -5,10 +5,11 @@ import {
Image, Image,
Title, Title,
Cursor, Cursor,
StudyGuideModal,
} from "@/components"; } from "@/components";
import { useNavigate, useParams } from "react-router-dom"; import { useNavigate, useParams } from "react-router-dom";
import { getBackgroundImage, getStaticImage } from "@/lib/utils"; import { getBackgroundImage, getStaticImage } from "@/lib/utils";
import { useState } from "react"; import { useState, useEffect } from "react";
// starName 固定为 luxun // starName 固定为 luxun
const starName = "luxun"; const starName = "luxun";
@@ -25,8 +26,18 @@ const LuxunPlace1 = () => {
moduleName: string; moduleName: string;
}>(); }>();
// 学习引导弹窗
const [showGuideModal, setShowGuideModal] = useState(true);
// 对话结束后的学习引导弹窗
const [showAfterDialogGuide, setShowAfterDialogGuide] = useState(false);
const [afterDialogGuideShown, setAfterDialogGuideShown] = useState(false);
// 引导对话显示状态 // 引导对话显示状态
const [visible, setVisible] = useState(true); const [visible, setVisible] = useState(false);
// 浏览完展品后的对话显示状态
const [showAfterExhibitDialog, setShowAfterExhibitDialog] = useState(false);
const [afterExhibitDialogShown, setAfterExhibitDialogShown] = useState(false);
// 对话结束后是否显示前进按钮
const [showForwardButton, setShowForwardButton] = useState(false);
// 当前激活的展品 ID(只存 ID,不直接存路径,便于作为唯一标识) // 当前激活的展品 ID(只存 ID,不直接存路径,便于作为唯一标识)
const [activeExhibitId, setActiveExhibitId] = useState<1 | 2 | null>(null); const [activeExhibitId, setActiveExhibitId] = useState<1 | 2 | null>(null);
// 已浏览展品的 ID 集合 // 已浏览展品的 ID 集合
@@ -39,6 +50,14 @@ const LuxunPlace1 = () => {
const progressCount = visited.size; const progressCount = visited.size;
const total = 2; const total = 2;
const completed = progressCount >= total; const completed = progressCount >= total;
// 当两个展品都浏览完成后,自动显示对话
useEffect(() => {
if (completed && !afterExhibitDialogShown) {
setShowAfterExhibitDialog(true);
setAfterExhibitDialogShown(true);
}
}, [completed, afterExhibitDialogShown]);
const subtitle = completed const subtitle = completed
? "探索鲁迅故居一楼客厅(已完成)" ? "探索鲁迅故居一楼客厅(已完成)"
: `探索鲁迅故居一楼客厅(${progressCount}/${total})`; : `探索鲁迅故居一楼客厅(${progressCount}/${total})`;
@@ -85,8 +104,8 @@ const LuxunPlace1 = () => {
read={visited.has(2)} read={visited.has(2)}
/> />
{/* 继续前进 - 仅在完成所有展品后显示 */} {/* 继续前进 - 仅在完成所有展品且对话结束后显示 */}
{completed && ( {showForwardButton && (
<Image <Image
className="absolute bottom-0 right-0" className="absolute bottom-0 right-0"
src={getStaticImage("components/place/继续前进")} src={getStaticImage("components/place/继续前进")}
@@ -101,6 +120,11 @@ const LuxunPlace1 = () => {
<ExhibitOverlay <ExhibitOverlay
visible={activeExhibitId !== null} visible={activeExhibitId !== null}
src={activeExhibitId ? exhibitSrcMap[activeExhibitId] : ""} src={activeExhibitId ? exhibitSrcMap[activeExhibitId] : ""}
description={
activeExhibitId == 1
? `1892-1898年,少年时期在绍兴三味书屋求学积淀;\n1919-1923年,于北京八道湾参加新文化运动、酝酿《阿Q正传》;\n1926年9-12月,赴厦门大学任教期间聚焦底层创作杂文;\n1927年3-9月,广州白云楼整理旧作定稿《朝花夕拾》部分篇章。`
: undefined
}
onClose={() => { onClose={() => {
if (activeExhibitId !== null) { if (activeExhibitId !== null) {
setVisited((prev) => { setVisited((prev) => {
@@ -113,19 +137,81 @@ const LuxunPlace1 = () => {
}} }}
/> />
{/* 学习引导词 */}
<StudyGuideModal
visible={showGuideModal}
onContinue={() => {
setShowGuideModal(false);
setVisible(true);
}}
>
{`同学你好,欢迎来到走进故居-对话学习模块!
进入鲁迅故居后,我们一起来了解鲁迅先生经历的5个阶段:"风雨前夕的思想淬炼"->"杂文集里的匕首与投枪"->"历史新编中的现实呐喊"->"为亡友编纂的精神丰碑"->"6平方米的红色庇护所";`}
</StudyGuideModal>
{/* 引导 */} {/* 引导 */}
<GuideSteps <GuideSteps
visible={visible} visible={visible}
onVisibleChange={setVisible} onVisibleChange={(nextVisible) => {
setVisible(nextVisible);
if (!nextVisible && !afterDialogGuideShown) {
setAfterDialogGuideShown(true);
setShowAfterDialogGuide(true);
}
}}
showMask={true} showMask={true}
highlightSelectors={[]} highlightSelectors={[]}
> >
<GuideStepsItem speaker="guide"> <GuideStepsItem speaker="student">
同学你好,欢迎来到走进故居-对话学习模块! 小沪!我知道鲁迅是1933年搬到大陆新村9号的,那在这之前他为什么会选择来到上海,又有哪些思想转变呢?
进入鲁迅故居纪念馆后,我们一起来了解鲁迅先生的思想历程和文学成就。
</GuideStepsItem> </GuideStepsItem>
<GuideStepsItem speaker="guide"> <GuideStepsItem speaker="guide">
在这个场景中,我们将探索鲁迅故居一楼客厅,了解风雨前夕的思想淬炼。 你问得很关键!而1927年是鲁迅思想的重要转折点,这一年他在广州亲历了"四一二"反革命政变,亲眼看见进步青年和革命志士倒在血泊中,曾经对"温和改革"的幻想彻底破灭。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
此前他写下《狂人日记》《阿Q正传》批判封建礼教,参加语丝社针砭时弊,但始终在"彷徨"中摸索救国之路。广州的血雨腥风,让他认清了反动派的真面目。
</GuideStepsItem>
</GuideSteps>
{/* 对话结束后的学习引导 */}
<StudyGuideModal
visible={showAfterDialogGuide}
onContinue={() => setShowAfterDialogGuide(false)}
ailgn="left"
>
1、场景中出现动态光点的展品表示未浏览,可通过点击光点来了解展品背后的故事,通过滚轮可以放大展品,浏览展品后蓝色动态光点会变为已浏览状态;
{"\n"}
2、在学习过程中会掉落拼图碎片,拼图碎片会自动收入拼图盒中,拼图盒可以点开查看当前拼图收集情况,点击单个拼图可以回到获得拼图的场景再次学习;
{"\n"}
3、所有动态光点的展品全部学习后才能进入下一个场景学习;
</StudyGuideModal>
{/* 浏览完展品后的对话 */}
<GuideSteps
visible={showAfterExhibitDialog}
onVisibleChange={(nextVisible) => {
setShowAfterExhibitDialog(nextVisible);
if (!nextVisible) {
setShowForwardButton(true);
}
}}
showMask={true}
highlightSelectors={[]}
>
<GuideStepsItem speaker="student">
刚到上海的鲁迅住在哪里,又在做什么?
</GuideStepsItem>
<GuideStepsItem speaker="guide">
1927年10月,鲁迅带着许广平来到上海,先住进了虹口区横浜路景云里。在景云里的两年半,他结识了冯雪峰等共产党员,参与发起中国左翼作家联盟,从"独战的英雄"转变为革命文艺战线的旗手。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
这段时间他写下《三闲集》《二心集》,表达他在当时历史条件下针对社会现象、文化问题和论战的看法与社会批判,此间还翻译了大量马列文艺理论著作,为此后的战斗打下了坚实思想基础。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
鲁迅在发起成立左联后,为躲避国民党当局监视,1930年5月,与许广平、周海婴迁居到拉摩斯公寓。1933年4月,受战火纷扰影响,为安全及健康计,同时也为了更方便开展革命文化工作,鲁迅以"内山书店职员"的名义,搬到了大陆新村9号。
</GuideStepsItem>
<GuideStepsItem speaker="student">
这个过程的确十分曲折。
</GuideStepsItem> </GuideStepsItem>
</GuideSteps> </GuideSteps>
</div> </div>
+114 -3
View File
@@ -1,4 +1,12 @@
import { ExhibitOverlay, Image, Title, Cursor } from "@/components"; import {
ExhibitOverlay,
Image,
Title,
Cursor,
StudyGuideModal,
GuideSteps,
GuideStepsItem,
} from "@/components";
import { useNavigate, useParams } from "react-router-dom"; import { useNavigate, useParams } from "react-router-dom";
import { getBackgroundImage, getStaticImage } from "@/lib/utils"; import { getBackgroundImage, getStaticImage } from "@/lib/utils";
import { useState } from "react"; import { useState } from "react";
@@ -19,13 +27,57 @@ const LuxunPlace2 = () => {
moduleName: string; moduleName: string;
}>(); }>();
// 学习引导弹窗
const [showGuideModal, setShowGuideModal] = useState(true);
const [activeExhibitId, setActiveExhibitId] = useState<1 | 2 | 3 | null>( const [activeExhibitId, setActiveExhibitId] = useState<1 | 2 | 3 | null>(
null null
); );
const [visited, setVisited] = useState<Set<number>>(new Set()); const [visited, setVisited] = useState<Set<number>>(new Set());
// 光标1的引导对话状态
const [guide1Visible, setGuide1Visible] = useState(false);
const [guide1Step, setGuide1Step] = useState(0);
// 光标2的引导对话状态
const [guide2Visible, setGuide2Visible] = useState(false);
const [guide2Step, setGuide2Step] = useState(0);
const handleOpenExhibit = (index: 1 | 2 | 3) => { const handleOpenExhibit = (index: 1 | 2 | 3) => {
setActiveExhibitId(index); if (index === 1) {
// 光标1:点击时同时打开展品和引导对话
setActiveExhibitId(1);
setGuide1Visible(true);
setGuide1Step(0);
} else if (index === 2) {
// 光标2:点击时同时打开展品和引导对话
setActiveExhibitId(2);
setGuide2Visible(true);
setGuide2Step(0);
} else {
// 光标3:直接打开
setActiveExhibitId(index);
}
};
// 监听光标1的引导对话步骤变化
const handleGuide1StepChange = (step: number) => {
setGuide1Step(step);
};
// 监听光标1的引导对话关闭
const handleGuide1VisibleChange = (visible: boolean) => {
setGuide1Visible(visible);
};
// 监听光标2的引导对话步骤变化
const handleGuide2StepChange = (step: number) => {
setGuide2Step(step);
};
// 监听光标2的引导对话关闭
const handleGuide2VisibleChange = (visible: boolean) => {
setGuide2Visible(visible);
}; };
const progressCount = visited.size; const progressCount = visited.size;
@@ -63,19 +115,21 @@ const LuxunPlace2 = () => {
}} }}
/> />
{/* 展品 */} {/* 光标1 */}
<Cursor <Cursor
className="absolute right-218 bottom-104" className="absolute right-218 bottom-104"
size={0.4} size={0.4}
onClick={() => handleOpenExhibit(1)} onClick={() => handleOpenExhibit(1)}
read={visited.has(1)} read={visited.has(1)}
/> />
{/* 光标2 */}
<Cursor <Cursor
className="absolute right-225 bottom-95" className="absolute right-225 bottom-95"
size={0.4} size={0.4}
onClick={() => handleOpenExhibit(2)} onClick={() => handleOpenExhibit(2)}
read={visited.has(2)} read={visited.has(2)}
/> />
{/* 光标3 */}
<Cursor <Cursor
className="absolute right-232 bottom-84" className="absolute right-232 bottom-84"
size={0.4} size={0.4}
@@ -97,6 +151,11 @@ const LuxunPlace2 = () => {
<ExhibitOverlay <ExhibitOverlay
visible={activeExhibitId !== null} visible={activeExhibitId !== null}
src={activeExhibitId ? exhibitSrcMap[activeExhibitId] : ""} src={activeExhibitId ? exhibitSrcMap[activeExhibitId] : ""}
description={
activeExhibitId === 3
? '这篇《中国人失掉自信力了吗》收录于《且介亭杂文》中,它将"匕首与投枪"的特质发挥到极致。当时国民党当局接连丧权辱国,一些文人却散布"中国人失掉自信力"的悲观论调,迎合反动派的卖国行径。鲁迅直接驳斥这种谬论,一针见血指出:"我们从古以来,就有埋头苦干的人,有拼命硬干的人,有为民请命的人,有舍身求法的人……这就是中国的脊梁。"文字如利刃般戳破虚假论调,揭露了反动派掩盖自身无能、麻痹民众的阴谋,唤醒国人的民族自信,尽显战斗文字的锋芒与力量。'
: undefined
}
onClose={() => { onClose={() => {
if (activeExhibitId !== null) { if (activeExhibitId !== null) {
setVisited((prev) => { setVisited((prev) => {
@@ -108,6 +167,58 @@ const LuxunPlace2 = () => {
setActiveExhibitId(null); setActiveExhibitId(null);
}} }}
/> />
{/* 光标1的引导对话 */}
<GuideSteps
visible={guide1Visible}
onVisibleChange={handleGuide1VisibleChange}
currentStep={guide1Step}
onStepChange={handleGuide1StepChange}
showMask={false}
highlightSelectors={[]}
zIndex={10000}
>
<GuideStepsItem speaker="student">
小沪,这里的《且介亭杂文》就是鲁迅先生在大陆新村9号创作的吧,有人将先生的杂文评价为"匕首与投枪",《且介亭杂文》中有具体体现吗?
</GuideStepsItem>
<GuideStepsItem speaker="guide">
当然有!20世纪30年代,国民党"文化围剿"愈烈,进步思想遭严控,鲁迅在大陆新村9号创作出《且介亭杂文》等集子。这些杂文聚焦文化乱象与社会沉疴,篇幅精炼却直击要害,像匕首般刺穿虚伪表象,似投枪般直戳问题核心...
</GuideStepsItem>
<GuideStepsItem speaker="guide">
用锋利文字与黑暗势力对峙,成为那个时代最具战斗力的思想武器,"匕首与投枪"的赞誉恰是其战斗性的生动写照。
</GuideStepsItem>
</GuideSteps>
{/* 光标2的引导对话 */}
<GuideSteps
visible={guide2Visible}
onVisibleChange={handleGuide2VisibleChange}
currentStep={guide2Step}
onStepChange={handleGuide2StepChange}
showMask={false}
highlightSelectors={[]}
zIndex={10000}
>
<GuideStepsItem speaker="student">
那《南腔北调集》这个名字又有什么深意呢?
</GuideStepsItem>
<GuideStepsItem speaker="guide">
这是鲁迅的自嘲,他曾说"我不会说绵软的苏白,不会打响亮的京腔,不入调,不入流",所以戏称自己的文章是"南腔北调"。这本书里的《〈守常全集》题记》,赞美了李大钊同志,说"他的遗文却将永驻,因为这是先驱者的遗产,革命史上的丰碑。"
</GuideStepsItem>
</GuideSteps>
{/* 学习引导 */}
<StudyGuideModal
visible={showGuideModal}
onContinue={() => setShowGuideModal(false)}
ailgn="left"
>
1、场景中出现动态光点的展品表示未浏览,可通过点击光点来了解展品背后的故事,通过滚轮可以放大展品,浏览展品后蓝色动态光点会变为已浏览状态;
{"\n"}
2、在学习过程中会掉落拼图碎片,拼图碎片会自动收入拼图盒中,拼图盒可以点开查看当前拼图收集情况,点击单个拼图可以回到获得拼图的场景再次学习;
{"\n"}
3、所有动态光点的展品全部学习后才能进入下一个场景学习;
</StudyGuideModal>
</div> </div>
); );
}; };
+334 -6
View File
@@ -1,7 +1,17 @@
import { ExhibitOverlay, Image, Title, Cursor } from "@/components"; import {
ExhibitOverlay,
Image,
Title,
Cursor,
StudyGuideModal,
GuideSteps,
GuideStepsItem,
SingleChoiceQuizPanel,
} from "@/components";
import { useNavigate, useParams } from "react-router-dom"; import { useNavigate, useParams } from "react-router-dom";
import { getBackgroundImage, getStaticImage } from "@/lib/utils"; import { getBackgroundImage, getStaticImage } from "@/lib/utils";
import { useState } from "react"; import { useState } from "react";
import SuccessAlert from "@/components/layout/alert/success-alert";
// starName 固定为 luxun // starName 固定为 luxun
const starName = "luxun"; const starName = "luxun";
@@ -15,19 +25,84 @@ const exhibitSrcMap: Record<1 | 2 | 3 | 4 | 5, string> = {
5: "luxun/place3/10", 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 LuxunPlace3 = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const { moduleName } = useParams<{ const { moduleName } = useParams<{
moduleName: string; moduleName: string;
}>(); }>();
// 学习引导弹窗
const [showGuideModal, setShowGuideModal] = useState(true);
const [activeExhibitId, setActiveExhibitId] = useState< const [activeExhibitId, setActiveExhibitId] = useState<
1 | 2 | 3 | 4 | 5 | null 1 | 2 | 3 | 4 | 5 | null
>(null); >(null);
const [visited, setVisited] = useState<Set<number>>(new Set()); const [visited, setVisited] = useState<Set<number>>(new Set());
// 光标1的引导对话状态
const [guide1Visible, setGuide1Visible] = useState(false);
const [guide1Step, setGuide1Step] = 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) => { const handleOpenExhibit = (index: 1 | 2 | 3 | 4 | 5) => {
setActiveExhibitId(index); if (index === 1) {
// 光标1:点击时同时打开展品和引导对话
setActiveExhibitId(1);
setGuide1Visible(true);
setGuide1Step(0);
} else if (index === 2) {
// 光标2:只打开展品(带描述)
setActiveExhibitId(2);
} 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 progressCount = visited.size;
@@ -65,31 +140,35 @@ const LuxunPlace3 = () => {
}} }}
/> />
{/* 展品 */} {/* 光标1 */}
<Cursor <Cursor
className="absolute right-114 bottom-130" className="absolute right-114 bottom-130"
onClick={() => handleOpenExhibit(1)} onClick={() => handleOpenExhibit(1)}
size={0.6} size={0.6}
read={visited.has(1)} read={visited.has(1)}
/> />
{/* 光标2 */}
<Cursor <Cursor
className="absolute right-130 bottom-115" className="absolute right-130 bottom-115"
onClick={() => handleOpenExhibit(2)} onClick={() => handleOpenExhibit(2)}
size={0.6} size={0.6}
read={visited.has(2)} read={visited.has(2)}
/> />
{/* 光标3 */}
<Cursor <Cursor
className="absolute left-76 bottom-140" className="absolute left-76 bottom-140"
onClick={() => handleOpenExhibit(3)} onClick={() => handleOpenExhibit(3)}
size={0.6} size={0.6}
read={visited.has(3)} read={visited.has(3)}
/> />
{/* 光标4 */}
<Cursor <Cursor
className="absolute right-238 bottom-125" className="absolute right-238 bottom-125"
onClick={() => handleOpenExhibit(4)} onClick={() => handleOpenExhibit(4)}
size={0.6} size={0.6}
read={visited.has(4)} read={visited.has(4)}
/> />
{/* 光标5 */}
<Cursor <Cursor
className="absolute right-258 bottom-118" className="absolute right-258 bottom-118"
onClick={() => handleOpenExhibit(5)} onClick={() => handleOpenExhibit(5)}
@@ -111,17 +190,266 @@ const LuxunPlace3 = () => {
<ExhibitOverlay <ExhibitOverlay
visible={activeExhibitId !== null} visible={activeExhibitId !== null}
src={activeExhibitId ? exhibitSrcMap[activeExhibitId] : ""} src={activeExhibitId ? exhibitSrcMap[activeExhibitId] : ""}
description={
activeExhibitId === 2
? '本书里面还收录了《理水》,文章写的是大禹治水"三过家门而不入"的上古传说,实则藏着对现实的尖锐针砭。当时反动派统治腐朽,面对内忧外患只会粉饰太平,还有一群空谈理论、脱离实际的"学者"混淆视听。大禹带领民众跋山涉水、实地勘察,摒弃"堵"的旧法改用"疏"的良方,历经千辛万苦终平水患。这哪里是写古代治水?分明是在赞颂脚踏实地、敢破敢立的实干精神,讽刺那些庸碌无为的当权者与空谈家,用远古的正义之光映照现实的黑暗。'
: activeExhibitId === 5
? "为了妥善出版亡友遗著,鲁迅亲自选稿、校对、设计封面。为了躲避反动派的审查,他还特意用“诸夏怀霜社”的名义出版,“怀霜”就是为了纪念瞿秋白。1936年《海上述林》上卷出版时,鲁迅的身体已经极度虚弱,但他仍一遍遍翻看样书,说“这是我对秋白最好的纪念”。"
: undefined
}
onClose={() => { onClose={() => {
if (activeExhibitId !== null) { 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) {
setVisited((prev) => {
const next = new Set(prev);
next.add(activeExhibitId);
return next;
});
}
setActiveExhibitId(null);
}
}}
/>
{/* 光标1的引导对话 */}
<GuideSteps
visible={guide1Visible}
onVisibleChange={(visible) => {
setGuide1Visible(visible);
if (!visible) {
// 对话关闭时,标记光标1为已读
setVisited((prev) => { setVisited((prev) => {
const next = new Set(prev); const next = new Set(prev);
next.add(activeExhibitId); next.add(1);
return next; return next;
}); });
} }
setActiveExhibitId(null); }}
currentStep={guide1Step}
onStepChange={setGuide1Step}
showMask={false}
highlightSelectors={[]}
zIndex={10000}
>
<GuideStepsItem speaker="student">
小沪,书架上还有《故事新编》,这不是历史小说吗?鲁迅为什么要在这个时期写古代故事呢?
</GuideStepsItem>
<GuideStepsItem speaker="guide">
《故事新编》是鲁迅酝酿十几年的作品,最终在大陆新村9号完成定稿。表面上写的是神话传说和历史典故,实则句句都在影射现实。当时反动派妄图用复古思想麻痹民众,鲁迅就用"故事新编"的方式,借古讽今,唤醒民众。
</GuideStepsItem>
</GuideSteps>
{/* 光标2的题目 */}
<SingleChoiceQuizPanel
visible={quiz2Visible}
title="鲁迅在上海大陆新村9号居住期间,翻译外国作品的核心目的不包括()"
options={[
{
value: "A",
label: "为中国文艺引进新思想、新血液",
},
{
value: "B",
label: "让中国青年接触更先进的文化理念",
},
{
value: "C",
label: "直接参与武装革命的战略部署",
},
{
value: "D",
label: "以翻译为武器助力思想启蒙",
},
]}
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为已读
setVisited((prev) => {
const next = new Set(prev);
next.add(2);
return next;
});
}} }}
/> />
{/* 光标3的引导对话 */}
<GuideSteps
visible={guide3Visible}
onVisibleChange={(visible) => {
setGuide3Visible(visible);
if (!visible) {
// 对话关闭时,标记光标3为已读
setVisited((prev) => {
const next = new Set(prev);
next.add(3);
return next;
});
}
}}
currentStep={guide3Step}
onStepChange={setGuide3Step}
showMask={false}
highlightSelectors={[]}
zIndex={10000}
>
<GuideStepsItem speaker="student">
我还看到书架上有很多翻译作品,这也是他在这儿完成的吗?
</GuideStepsItem>
<GuideStepsItem speaker="guide">
没错!鲁迅在这儿翻译了《死魂灵》《表》等大量外国作品。他说"没有拿来的,人不能自成为新人",就是想通过翻译,为中国文艺引进新思想、新方法,让更多人看到世界的真相,找到救国的道路。
</GuideStepsItem>
</GuideSteps>
{/* 光标4的引导对话 */}
<GuideSteps
visible={guide4Visible}
onVisibleChange={(visible) => {
setGuide4Visible(visible);
if (!visible) {
// 对话关闭时,标记光标4为已读
setVisited((prev) => {
const next = new Set(prev);
next.add(4);
return next;
});
}
}}
currentStep={guide4Step}
onStepChange={setGuide4Step}
showMask={false}
highlightSelectors={[]}
zIndex={10000}
>
<GuideStepsItem speaker="student">
小沪,这两本书看起来格外厚重,它和鲁迅在这儿的经历有什么关联呢?
</GuideStepsItem>
<GuideStepsItem speaker="guide">
这不是鲁迅的著作,却是他在大陆新村9号倾注了全部心血的作品——为亡友瞿秋白编辑的译文集《海上述林》。1935年6月,瞿秋白被国民党反动派杀害,鲁迅悲痛万分,当即决定要为挚友出版译文集,让他的思想和精神流传下去。
</GuideStepsItem>
</GuideSteps>
{/* 光标5的题目 */}
<SingleChoiceQuizPanel
visible={quiz5Visible}
title="鲁迅在上海大陆新村9号居住期间,为纪念瞿秋白编校的译文集是()"
options={[
{ value: "A", label: "《仿自由书》" },
{ value: "B", label: "《南腔北调集》" },
{ value: "C", label: "《海上述林》" },
{ value: "D", label: "《故事新编》" },
]}
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为已读
setVisited((prev) => {
const next = new Set(prev);
next.add(5);
return next;
});
// 打开光标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">
原来这本书不仅是译文集,更是两人革命友谊和共同信仰的见证!
</GuideStepsItem>
<GuideStepsItem speaker="guide">
太对了!鲁迅和瞿秋白的故事不仅在于《海上述林》的字里行间,这座小楼的三楼也藏着他们为了革命事业并肩战斗的初心。
</GuideStepsItem>
</GuideSteps>
{/* 学习引导 */}
<StudyGuideModal
visible={showGuideModal}
onContinue={() => setShowGuideModal(false)}
ailgn="left"
>
1、场景中出现动态光点的展品表示未浏览,可通过点击光点来了解展品背后的故事,通过滚轮可以放大展品,浏览展品后蓝色动态光点会变为已浏览状态;
{"\n"}
2、在学习过程中会掉落拼图碎片,拼图碎片会自动收入拼图盒中,拼图盒可以点开查看当前拼图收集情况,点击单个拼图可以回到获得拼图的场景再次学习;
{"\n"}
3、所有动态光点的展品全部学习后才能进入下一个场景学习;
</StudyGuideModal>
</div> </div>
); );
}; };
+220 -10
View File
@@ -1,7 +1,18 @@
import { ExhibitOverlay, Image, Title, Cursor } from "@/components"; import {
ExhibitOverlay,
GuideSteps,
GuideStepsItem,
Image,
Title,
Cursor,
StudyGuideModal,
SingleChoiceQuizPanel,
Video,
} from "@/components";
import { useNavigate, useParams } from "react-router-dom"; import { useNavigate, useParams } from "react-router-dom";
import { getBackgroundImage, getStaticImage } from "@/lib/utils"; import { getBackgroundImage, getPublicPath, getStaticImage } from "@/lib/utils";
import { useState } from "react"; import { useState } from "react";
import SuccessAlert from "@/components/layout/alert/success-alert";
// starName 固定为 luxun // starName 固定为 luxun
const starName = "luxun"; const starName = "luxun";
@@ -13,18 +24,50 @@ const exhibitSrcMap: Record<1 | 2 | 3, string> = {
3: "luxun/place4/展品13", 3: "luxun/place4/展品13",
}; };
const quizDefaultError = "请结合刚刚的旁白内容,再思考一下吧!";
const quizExplanations: Record<string, string> = {
A: "陈独秀与鲁迅有思想交往,但他并未在这间“红色庇护所”中避难、与鲁迅夜谈革命议题。",
B: "恭喜你,选对啦。",
C: "李大钊于1927年牺牲,时间上不符合这间客房被用作“红色庇护所”的背景。",
D: "周恩来虽长期在上海从事革命活动,但并未在这里避难并与鲁迅夜谈。",
};
const LuxunPlace5 = () => { const LuxunPlace5 = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const { moduleName } = useParams<{ const { moduleName } = useParams<{
moduleName: string; moduleName: string;
}>(); }>();
// 学习引导弹窗
const [showGuideModal, setShowGuideModal] = useState(true);
const [introGuideVisible, setIntroGuideVisible] = useState(false);
const [introGuideStep, setIntroGuideStep] = useState(0);
const [activeExhibitId, setActiveExhibitId] = useState<1 | 2 | 3 | null>( const [activeExhibitId, setActiveExhibitId] = useState<1 | 2 | 3 | null>(
null null
); );
const [visited, setVisited] = useState<Set<number>>(new Set()); const [visited, setVisited] = useState<Set<number>>(new Set());
// 光标2的题目状态
const [quizVisible, setQuizVisible] = useState(false);
const [quizError, setQuizError] = useState(quizDefaultError);
const [successAlertVisible, setSuccessAlertVisible] = useState(false);
// 光标3的引导对话状态
const [guide3Visible, setGuide3Visible] = useState(false);
const [guide3Step, setGuide3Step] = useState(0);
// 结尾视频与对话
const [showEndingVideo, setShowEndingVideo] = useState(false);
const [endingGuideVisible, setEndingGuideVisible] = useState(false);
const handleOpenExhibit = (index: 1 | 2 | 3) => { const handleOpenExhibit = (index: 1 | 2 | 3) => {
if (index === 3) {
setActiveExhibitId(3);
setGuide3Visible(true);
setGuide3Step(0);
return;
}
setActiveExhibitId(index); setActiveExhibitId(index);
}; };
@@ -83,10 +126,11 @@ const LuxunPlace5 = () => {
{completed && ( {completed && (
<Image <Image
className="absolute bottom-0 right-0" className="absolute bottom-0 right-0"
src={getStaticImage("luxun/place4/离开")} src={getStaticImage("components/place/继续前进")}
button button
onClick={() => { onClick={() => {
navigate(`/${moduleName}/${starName}/home`); // 完成后先播放视频,不立即跳转页面
setShowEndingVideo(true);
}} }}
/> />
)} )}
@@ -94,17 +138,183 @@ const LuxunPlace5 = () => {
<ExhibitOverlay <ExhibitOverlay
visible={activeExhibitId !== null} visible={activeExhibitId !== null}
src={activeExhibitId ? exhibitSrcMap[activeExhibitId] : ""} src={activeExhibitId ? exhibitSrcMap[activeExhibitId] : ""}
description={
activeExhibitId === 1
? '"鲁迅是莱谟斯,是野兽的奶汁所喂养大的,是封建宗法社会的逆子,是绅士阶级的贰臣,而同时也是一些浪漫谛克的革命家的诤友!"'
: activeExhibitId === 2
? '"人生得一知己足矣,斯世当以同怀视之"'
: undefined
}
onClose={() => { onClose={() => {
if (activeExhibitId !== null) { if (activeExhibitId === 2) {
setVisited((prev) => { setActiveExhibitId(null);
const next = new Set(prev); setQuizVisible(true);
next.add(activeExhibitId); return;
return next;
});
} }
if (activeExhibitId === 3) {
// 对话关闭时再标记已读
setActiveExhibitId(null);
return;
}
setVisited((prev) => {
const next = new Set(prev);
if (activeExhibitId !== null) {
next.add(activeExhibitId);
}
return next;
});
setActiveExhibitId(null); setActiveExhibitId(null);
}} }}
/> />
{/* 学习引导结束后的对话 */}
<GuideSteps
visible={introGuideVisible}
onVisibleChange={setIntroGuideVisible}
currentStep={introGuideStep}
onStepChange={setIntroGuideStep}
showMask={false}
highlightSelectors={[]}
zIndex={10000}
>
<GuideStepsItem speaker="student">
你说的是这间小小的客房吗,这里为什么会被称为"红色庇护所"呢?
</GuideStepsItem>
<GuideStepsItem speaker="guide">
你可别小看这间仅6平方米的客房,在白色恐怖笼罩的1933年至1936年,这里是很多共产党人的"安全港湾"。1933年,瞿秋白夫妇第三次到鲁迅家避难,就秘密住进了这里。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
当时反动派到处搜捕共产党人,鲁迅冒着生命危险掩护他们,两人常彻夜长谈。瞿秋白的夫人杨之华回忆道:"秋白和鲁迅在一起,往往感觉夜太短。深刻的友谊的交谈,从政治到文艺,从实际到理论,从希腊到莫斯科,轻松,愉快,活泼。"
</GuideStepsItem>
</GuideSteps>
{/* 光标2的题目 */}
<SingleChoiceQuizPanel
visible={quizVisible}
title="上海鲁迅故居三楼客房被称为“红色庇护所”,以下哪位共产党人曾在此避难并与鲁迅探讨革命议题"
options={[
{ value: "A", label: "陈独秀" },
{ value: "B", label: "瞿秋白" },
{ value: "C", label: "李大钊" },
{ value: "D", label: "周恩来" },
]}
correctAnswer="B"
errorMessage={quizError}
onSubmit={(value, isCorrect) => {
if (isCorrect) {
setQuizError(quizDefaultError);
setQuizVisible(false);
setSuccessAlertVisible(true);
}
}}
onWrongSelect={(value) => {
const explanation = quizExplanations[value];
if (explanation) {
setQuizError(explanation);
return explanation;
}
setQuizError(quizDefaultError);
return quizDefaultError;
}}
zIndex={10001}
/>
{/* 光标2的成功提示 */}
<SuccessAlert
visible={successAlertVisible}
content="1933年,瞿秋白夫妇第三次到鲁迅家避难,就住进了这间仅6平方米的客房,与鲁迅常常彻夜长谈革命与文学。"
onContinue={() => {
setSuccessAlertVisible(false);
setVisited((prev) => {
const next = new Set(prev);
next.add(2);
return next;
});
}}
/>
{/* 光标3的引导对话 */}
<GuideSteps
visible={guide3Visible}
onVisibleChange={(visible) => {
setGuide3Visible(visible);
if (!visible) {
setVisited((prev) => {
const next = new Set(prev);
next.add(3);
return next;
});
}
}}
currentStep={guide3Step}
onStepChange={setGuide3Step}
showMask={false}
highlightSelectors={[]}
zIndex={10000}
>
<GuideStepsItem speaker="student">
除了瞿秋白,还有其他共产党人来过这里吗
</GuideStepsItem>
<GuideStepsItem speaker="guide">
当然!1936年4月,中共中央特派员冯雪峰到上海开展工作,也先住进了这间客房。冯雪峰把红军长征胜利、遵义会议召开的消息告诉鲁迅,还带来了毛泽东同志提出的抗日民族统一战线主张。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
鲁迅听后格外振奋,激动地说"我做一个小兵还是胜任的,用笔!",这句话也成了他晚年投身抗日救亡运动的誓言。
</GuideStepsItem>
</GuideSteps>
{/* 结尾视频播放 */}
{showEndingVideo && (
<Video
src={getPublicPath("video/luxun/luxun_06.mp4")}
className="fixed inset-0 z-[20000]"
autoPlay
playsInline
showSkipButton
onEnded={() => {
setEndingGuideVisible(true);
}}
/>
)}
{/* 视频结束后的结束语对话 */}
<GuideSteps
visible={endingGuideVisible}
onVisibleChange={(visible) => {
setShowEndingVideo(false);
setEndingGuideVisible(visible);
if (!visible && moduleName) {
// 对话结束后返回鲁迅首页
navigate(`/${moduleName}/${starName}/home`);
}
}}
showMask={false}
zIndex={20010}
>
<GuideStepsItem speaker="guide">
我们已经了解了鲁迅居住在上海大陆新村9号时的重要事件。接下来,请你接过先生手中的“笔”,走出上海,跨越时空,追寻他从绍兴三味书屋到上海的创作足迹——解锁每个地标背后的故事,填充文字里的时代线索...
</GuideStepsItem>
<GuideStepsItem speaker="guide">
在互动中,读懂先生笔墨间的温度与力量。
</GuideStepsItem>
</GuideSteps>
{/* 学习引导 */}
<StudyGuideModal
visible={showGuideModal}
onContinue={() => {
setShowGuideModal(false);
setIntroGuideVisible(true);
setIntroGuideStep(0);
}}
ailgn="left"
>
1、场景中出现动态光点的展品表示未浏览,可通过点击光点来了解展品背后的故事,通过滚轮可以放大展品,浏览展品后蓝色动态光点会变为已浏览状态;
{"\n"}
2、在学习过程中会掉落拼图碎片,拼图碎片会自动收入拼图盒中,拼图盒可以点开查看当前拼图收集情况,点击单个拼图可以回到获得拼图的场景再次学习;
{"\n"}
3、所有动态光点的展品全部学习后才能进入下一个场景学习;
</StudyGuideModal>
</div> </div>
); );
}; };
+55 -21
View File
@@ -1,6 +1,6 @@
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { useNavigate, useParams } from "react-router-dom"; import { useNavigate, useParams } from "react-router-dom";
import { Image } from "@/components"; import { GuideSteps, GuideStepsItem, Image } from "@/components";
import SuccessAlert from "@/components/layout/alert/success-alert"; import SuccessAlert from "@/components/layout/alert/success-alert";
import { getBackgroundImage, getStaticImage } from "@/lib/utils"; import { getBackgroundImage, getStaticImage } from "@/lib/utils";
@@ -77,8 +77,13 @@ const LuxunReflectionScene1 = () => {
const [showSuccessAlert, setShowSuccessAlert] = useState(false); const [showSuccessAlert, setShowSuccessAlert] = useState(false);
// 是否全部完成 // 是否全部完成
const [allCompleted, setAllCompleted] = useState(false); const [allCompleted, setAllCompleted] = useState(false);
// 引导对话是否显示
const [guideVisible, setGuideVisible] = useState(true);
const progress = clickedCities.size; const progress = clickedCities.size;
// 当前应当点亮的城市索引(按照 cityConfig 顺序)
const nextCityIndex = progress;
const nextCityId = cityConfig[nextCityIndex]?.id;
const totalTasks = cityConfig.length; const totalTasks = cityConfig.length;
// 检查是否全部完成 // 检查是否全部完成
@@ -90,7 +95,9 @@ const LuxunReflectionScene1 = () => {
}, [progress, totalTasks, allCompleted]); }, [progress, totalTasks, allCompleted]);
const handleCityClick = (cityId: number) => { const handleCityClick = (cityId: number) => {
// 仅允许按照 cityConfig 顺序点击
if (clickedCities.has(cityId)) return; // 已点击过的不再处理 if (clickedCities.has(cityId)) return; // 已点击过的不再处理
if (cityId !== nextCityId) return; // 不是当前应点击的城市则不响应
setClickedCities((prev) => { setClickedCities((prev) => {
const next = new Set(prev); const next = new Set(prev);
@@ -130,6 +137,7 @@ const LuxunReflectionScene1 = () => {
<div className="relative w-full h-full"> <div className="relative w-full h-full">
{cityConfig.map((city) => { {cityConfig.map((city) => {
const isClicked = clickedCities.has(city.id); const isClicked = clickedCities.has(city.id);
const isNext = city.id === nextCityId;
return ( return (
<div <div
key={city.id} key={city.id}
@@ -165,7 +173,8 @@ const LuxunReflectionScene1 = () => {
}} }}
> >
{/* 点一点提示 - 始终显示 */} {/* 点一点提示 - 始终显示 */}
{!isClicked && ( {/* 仅对当前应点击的城市显示点一点提示 */}
{!isClicked && isNext && (
<div className="flex items-center"> <div className="flex items-center">
<img <img
src={getStaticImage( src={getStaticImage(
@@ -174,7 +183,7 @@ const LuxunReflectionScene1 = () => {
alt="点一点" alt="点一点"
/> />
<img <img
className="rotate-y-[180deg] rotate-z-[180deg]" className="animate-finger-bounce scale-y-[-1]"
src={getStaticImage( src={getStaticImage(
`${starName}/reflection/scene1/鲁迅体悟点一点图标` `${starName}/reflection/scene1/鲁迅体悟点一点图标`
)} )}
@@ -231,10 +240,11 @@ const LuxunReflectionScene1 = () => {
</span> </span>
</div> </div>
{/* 任务展示窗口 */} {/* 任务展示窗口 */}
<div className="absolute bottom-0 right-7 w-[995px] h-[728px] font-bold text-6xl space-y-16 px-10 py-10 overflow-y-auto custom-scrollbar"> <div className="absolute bottom-0 right-7 w-[995px] h-[728px] font-bold text-6xl px-10 py-10 ">
{allCompleted {allCompleted ? (
? // 全部完成后显示详细信息 // 全部完成后显示详细信息
cityConfig.map((city) => ( <div className="space-y-10 h-full overflow-y-auto custom-scrollbar">
{cityConfig.map((city) => (
<div key={city.id} className="space-y-4"> <div key={city.id} className="space-y-4">
<div className="text-red-600 text-5xl flex items-end gap-6"> <div className="text-red-600 text-5xl flex items-end gap-6">
<div>{city.location}</div> <div>{city.location}</div>
@@ -246,29 +256,53 @@ const LuxunReflectionScene1 = () => {
{city.description} {city.description}
</div> </div>
</div> </div>
)) ))}
: // 未完成时显示任务列表 </div>
cityConfig.map((city, index) => { ) : (
const isCompleted = clickedCities.has(city.id); <div className="space-y-16">
return ( {
<div // 未完成时显示任务列表
key={city.id} cityConfig.map((city, index) => {
className={isCompleted ? "text-green-600 line-through" : ""} const isCompleted = clickedCities.has(city.id);
> return (
{index + 1}.点击图标{city.location} <div
</div> key={city.id}
); className={
})} isCompleted ? "text-green-600 line-through" : ""
}
>
{index + 1}.点击图标{city.location}
</div>
);
})
}
</div>
)}
</div> </div>
</div> </div>
{/* 成功提示弹窗 */} {/* 成功提示弹窗 */}
<SuccessAlert <SuccessAlert
visible={showSuccessAlert} visible={showSuccessAlert}
title="恭喜你, 完成所有任务!" title="你找到了鲁迅创作的足迹,接下来,填充文字里的故事吧!"
onClose={handleSuccessAlertClose} onClose={handleSuccessAlertClose}
onContinue={handleSuccessAlertClose} onContinue={handleSuccessAlertClose}
/> />
{/* 学习引导对话 */}
<GuideSteps
visible={guideVisible}
onVisibleChange={setGuideVisible}
showMask={false}
highlightSelectors={[]}
zIndex={10000}
>
<GuideStepsItem speaker="guide">
跟着先生的脚步, 解锁文字里的温度与力量!
</GuideStepsItem>
<GuideStepsItem speaker="guide">
请根据年代由远及近的顺序,依“手指提示”点亮先生的创作足迹,解锁更多的故事背景!
</GuideStepsItem>
</GuideSteps>
</div> </div>
); );
}; };
+33 -17
View File
@@ -1,8 +1,7 @@
import { useState, useEffect, useMemo, useCallback } from "react"; import { useState, useEffect, useMemo, useCallback } from "react";
import { useNavigate, useParams } from "react-router-dom"; import { useNavigate, useParams } from "react-router-dom";
import { Image, DragFill } from "@/components"; import { Image, DragFill, GuideSteps, GuideStepsItem } from "@/components";
import type { DragFillResult } from "@/components"; import type { DragFillResult } from "@/components";
import SuccessAlert from "@/components/layout/alert/success-alert";
import ErrorAlert from "@/components/layout/alert/error-alert"; import ErrorAlert from "@/components/layout/alert/error-alert";
import { getBackgroundImage, getStaticImage } from "@/lib/utils"; import { getBackgroundImage, getStaticImage } from "@/lib/utils";
@@ -149,13 +148,15 @@ const LuxunReflectionScene2 = () => {
// DragFill 的值 // DragFill 的值
const [dragFillValue, setDragFillValue] = useState<DragFillResult[]>([]); const [dragFillValue, setDragFillValue] = useState<DragFillResult[]>([]);
// 是否显示成功提示
const [showSuccessAlert, setShowSuccessAlert] = useState(false);
// 是否全部完成 // 是否全部完成
const [allCompleted, setAllCompleted] = useState(false); const [allCompleted, setAllCompleted] = useState(false);
// 错误提示相关状态 // 错误提示相关状态
const [showErrorAlert, setShowErrorAlert] = useState(false); const [showErrorAlert, setShowErrorAlert] = useState(false);
const [errorHint, setErrorHint] = useState(""); const [errorHint, setErrorHint] = useState("");
// 引导对话是否显示
const [guideVisible, setGuideVisible] = useState(true);
// 通关提示对话是否显示
const [completionGuideVisible, setCompletionGuideVisible] = useState(false);
// 转换为 DragFill 的 options // 转换为 DragFill 的 options
const options = useMemo( const options = useMemo(
@@ -243,14 +244,11 @@ const LuxunReflectionScene2 = () => {
useEffect(() => { useEffect(() => {
if (progress === totalTasks && !allCompleted) { if (progress === totalTasks && !allCompleted) {
setAllCompleted(true); setAllCompleted(true);
setShowSuccessAlert(true); // 任务完成后自动显示通关提示对话
setCompletionGuideVisible(true);
} }
}, [progress, totalTasks, allCompleted]); }, [progress, totalTasks, allCompleted]);
const handleSuccessAlertClose = () => {
setShowSuccessAlert(false);
};
const handleErrorAlertClose = () => { const handleErrorAlertClose = () => {
setShowErrorAlert(false); setShowErrorAlert(false);
setErrorHint(""); setErrorHint("");
@@ -518,14 +516,6 @@ const LuxunReflectionScene2 = () => {
</div> </div>
</div> </div>
{/* 成功提示弹窗 */}
<SuccessAlert
visible={showSuccessAlert}
title="恭喜你, 完成所有任务!"
onClose={handleSuccessAlertClose}
onContinue={handleSuccessAlertClose}
/>
{/* 错误提示弹窗 */} {/* 错误提示弹窗 */}
<ErrorAlert <ErrorAlert
visible={showErrorAlert} visible={showErrorAlert}
@@ -533,6 +523,32 @@ const LuxunReflectionScene2 = () => {
onClose={handleErrorAlertClose} onClose={handleErrorAlertClose}
onMaskClick={handleErrorAlertClose} onMaskClick={handleErrorAlertClose}
/> />
{/* 学习引导对话 */}
<GuideSteps
visible={guideVisible}
onVisibleChange={setGuideVisible}
showMask={false}
highlightSelectors={[]}
zIndex={10000}
>
<GuideStepsItem speaker="guide">
这些线索藏在先生的文字里,长按卡片拖到对应地标旁,还原创作故事!
</GuideStepsItem>
</GuideSteps>
{/* 通关提示对话 */}
<GuideSteps
visible={completionGuideVisible}
onVisibleChange={setCompletionGuideVisible}
showMask={false}
highlightSelectors={[]}
zIndex={10000}
>
<GuideStepsItem speaker="guide">
你读懂了鲁迅文字与土地的联结,再深入看看文字里的初心!
</GuideStepsItem>
</GuideSteps>
</div> </div>
); );
}; };
+30 -2
View File
@@ -1,7 +1,7 @@
import { getBackgroundImage, getStaticImage } from "@/lib/utils"; import { getBackgroundImage, getStaticImage } from "@/lib/utils";
import { useState } from "react"; import { useState, useEffect } from "react";
import { useNavigate, useParams } from "react-router-dom"; import { useNavigate, useParams } from "react-router-dom";
import { Image } from "@/components"; import { Image, GuideSteps, GuideStepsItem } from "@/components";
import { SingleChoiceQuizPanel } from "@/components/layout/quiz"; import { SingleChoiceQuizPanel } from "@/components/layout/quiz";
const starName = "luxun"; const starName = "luxun";
@@ -21,11 +21,22 @@ const LuxunReflectionScene3 = () => {
const [completedQuiz1, setCompletedQuiz1] = useState(false); // 题目1是否完成 const [completedQuiz1, setCompletedQuiz1] = useState(false); // 题目1是否完成
const [completedQuiz2, setCompletedQuiz2] = useState(false); // 题目2是否完成 const [completedQuiz2, setCompletedQuiz2] = useState(false); // 题目2是否完成
const [showSummary, setShowSummary] = useState(false); // 是否显示总结 const [showSummary, setShowSummary] = useState(false); // 是否显示总结
const [completionGuideVisible, setCompletionGuideVisible] = useState(false); // 通关提示对话是否显示
const allCompleted = completedQuiz1 && completedQuiz2; const allCompleted = completedQuiz1 && completedQuiz2;
const progress = (completedQuiz1 ? 1 : 0) + (completedQuiz2 ? 1 : 0); const progress = (completedQuiz1 ? 1 : 0) + (completedQuiz2 ? 1 : 0);
const totalTasks = 2; const totalTasks = 2;
// 当显示总结后,延迟1秒显示通关提示对话
useEffect(() => {
if (showSummary) {
const timer = setTimeout(() => {
setCompletionGuideVisible(true);
}, 1000);
return () => clearTimeout(timer);
}
}, [showSummary]);
// 点击"鲁迅1"的处理 // 点击"鲁迅1"的处理
const handleLuxun1Click = () => { const handleLuxun1Click = () => {
setShowTopics(true); setShowTopics(true);
@@ -277,6 +288,23 @@ const LuxunReflectionScene3 = () => {
errorMessage='不是哦,先生的创作从不是为了自己哦!"请回忆先生"弃医从文"的初心。"' errorMessage='不是哦,先生的创作从不是为了自己哦!"请回忆先生"弃医从文"的初心。"'
onSubmit={handleQuiz2Submit} onSubmit={handleQuiz2Submit}
/> />
{/* 通关提示对话 */}
<GuideSteps
visible={completionGuideVisible}
onVisibleChange={(visible) => {
if (!visible) {
setCompletionGuideVisible(false);
}
}}
showMask={false}
highlightSelectors={[]}
zIndex={10000}
>
<GuideStepsItem speaker="guide">
恭喜你读懂了鲁迅的文字与初心,愿这份精神能伴你前行
</GuideStepsItem>
</GuideSteps>
</div> </div>
); );
}; };
+6 -3
View File
@@ -30,6 +30,7 @@ type QuizAnchor = {
options: QuizOption[]; options: QuizOption[];
// 拖拽题专用 // 拖拽题专用
blanks?: { id: string; label: string }[]; blanks?: { id: string; label: string }[];
explanation?: string;
}; };
// 星名固定为 sunzhongshan // 星名固定为 sunzhongshan
@@ -53,8 +54,6 @@ const QUIZ_ANCHORS: QuizAnchor[] = [
value: "B", value: "B",
label: label:
"当时的中国正遭受帝国主义列强的野蛮侵略和封建专制制度的腐朽统治,对于新时代的意义不大", "当时的中国正遭受帝国主义列强的野蛮侵略和封建专制制度的腐朽统治,对于新时代的意义不大",
explanation:
'"振兴中华"需要每一代人的努力,先生当年在上海撰写的《实业计划》里如今已经实现的那些宏伟计划就是我国国家代代相传的结果',
}, },
{ {
value: "C", value: "C",
@@ -68,6 +67,8 @@ const QUIZ_ANCHORS: QuizAnchor[] = [
isCorrect: true, isCorrect: true,
}, },
], ],
explanation:
"“振兴中华”需要每一代人的努力,先生当年在上海撰写的《实业计划》里如今已经实现的那些宏伟计划就是我们国家代代相传的结果。答案B的观点错误",
}, },
{ {
id: "quiz-2", id: "quiz-2",
@@ -234,7 +235,9 @@ const ChenyunInternalization = () => {
label: `${opt.value}. ${opt.label}`, label: `${opt.value}. ${opt.label}`,
}))} }))}
correctAnswer={correctAnswers} correctAnswer={correctAnswers}
errorMessage="再想一想,确保选择了所有正确答案~" errorMessage={
item.explanation || "再想一想,确保选择了所有正确答案~"
}
onSubmit={(values: string[], isCorrect: boolean) => { onSubmit={(values: string[], isCorrect: boolean) => {
if (isCorrect) { if (isCorrect) {
setActiveSuccess(item.id); setActiveSuccess(item.id);