feat: 添加连线题和选择题功能,增强互动性

- 在多个组件中引入 MatchQuizPanel 和 SingleChoiceQuizPanel,提升用户参与度。
- 更新 HuangyanpeiPlace1、HuangyanpeiPlace3、LuxunPlace1、LuxunPlace2 组件,增加新的选择题和连线题,丰富学习内容。
- 调整成功提示逻辑,确保用户体验流畅。
- 优化视频播放中的锚点逻辑,提升互动性和学习效果。
This commit is contained in:
zhongyidian committed 2026-02-02 15:41:49 +08:00
1 parent 02a3f00a9b
commit 966b47ede3
12 files changed
+1128 -42

No files matched your search

+1 -1
View File
@@ -247,7 +247,7 @@ const HuangyanpeiInternalization = () => {
} else {
setDragQuizErrors((prev) => ({
...prev,
[item.id]: "再想一想,确保选择的都是中国国产企业/品牌~",
[item.id]: "特斯拉是美国品牌。请你再想想吧!",
}));
}
};
+113 -1
View File
@@ -96,6 +96,14 @@ const HuangyanpeiPlace3 = () => {
// 光标1、2的引导对话状态(在两者都浏览后显示)
const [guide12Visible, setGuide12Visible] = useState(false);
const [guide12Shown, setGuide12Shown] = useState(false);
// 题目3(开群女学)显示状态
const [showQuiz3_12, setShowQuiz3_12] = useState(false);
// 题目3成功提示状态
const [showQuiz3Success, setShowQuiz3Success] = useState(false);
// 题目4(浦东中学)显示状态
const [showQuiz4, setShowQuiz4] = useState(false);
// 题目4成功提示状态
const [showQuiz4Success, setShowQuiz4Success] = useState(false);
const handleOpenExhibit = (index: 1 | 2 | 3) => {
if (index === 3) {
@@ -187,6 +195,50 @@ const HuangyanpeiPlace3 = () => {
}
}, [visited, guide12Shown]);
// 处理guide12Visible变化,对话结束后显示题目3
const handleGuide12VisibleChange = (visible: boolean) => {
setGuide12Visible(visible);
// 当对话关闭时,显示题目3
if (!visible && guide12Shown) {
setShowQuiz3_12(true);
}
};
// 处理题目3提交
const handleQuiz3_12Submit = (value: string, isCorrect: boolean) => {
if (isCorrect) {
setShowQuiz3_12(false);
setShowQuiz3Success(true);
}
};
// 处理题目3成功提示继续
const handleQuiz3SuccessContinue = () => {
setShowQuiz3Success(false);
// 显示题目4
setShowQuiz4(true);
};
// 处理题目4提交
const handleQuiz4Submit = (value: string, isCorrect: boolean) => {
if (isCorrect) {
setShowQuiz4(false);
setShowQuiz4Success(true);
}
};
// 处理题目4成功提示继续
const handleQuiz4SuccessContinue = () => {
setShowQuiz4Success(false);
// 标记展品1和2为已访问,触发继续前进按钮显示
if (!visited.has(1)) {
markExhibitVisited(place, 1);
}
if (!visited.has(2)) {
markExhibitVisited(place, 2);
}
};
const progressCount = visited.size;
const total = 3;
const completed = progressCount >= total;
@@ -373,10 +425,70 @@ const HuangyanpeiPlace3 = () => {
onClose={handleDragQuizSuccessContinue}
/>
{/* 题目3(开群女学) */}
<SingleChoiceQuizPanel
visible={showQuiz3_12}
title="【单选】黄炎培和堂兄黄洪培、堂嫂陆开群一起,策划创建的开创了浦东地区女子教育的先河的学校为:"
options={[
{ value: "A", label: "A. 开群女学" },
{ value: "B", label: "B. 上海女子师范学校" },
{ value: "C", label: "C. 浦东中学" },
]}
correctAnswer="A"
onWrongSelect={(value) => {
switch (value) {
case "B":
return "上海女子师范学校是上海市区的女子教育机构,并非由黄炎培等人在浦东地区创办。请你再想想吧!";
case "C":
return "浦东中学是一所面向男女学生的综合性中学,并非专门的女子学校。请你再想想吧!";
default:
return "请你再想想吧!";
}
}}
onSubmit={handleQuiz3_12Submit}
/>
{/* 题目3成功提示 */}
<SuccessAlert
visible={showQuiz3Success}
onContinue={handleQuiz3SuccessContinue}
onClose={handleQuiz3SuccessContinue}
/>
{/* 题目4(浦东中学) */}
<SingleChoiceQuizPanel
visible={showQuiz4}
title="【单选】由黄炎培担任首任校长的浦东中学是:"
options={[
{ value: "A", label: "A. 近代上海第一所女子中学" },
{ value: "B", label: "B. 近代上海第一所职业技术学校" },
{ value: "C", label: "C. 近代上海第一所完全中学" },
]}
correctAnswer="C"
onWrongSelect={(value) => {
switch (value) {
case "A":
return "浦东中学是一所男女同校的综合性中学,并非女子中学。请你再想想吧!";
case "B":
return "浦东中学以普通中学教育为主,其定位是培养全面发展的人才,并非职业技术学校。请你再想想吧!";
default:
return "请你再想想吧!";
}
}}
onSubmit={handleQuiz4Submit}
/>
{/* 题目4成功提示 */}
<SuccessAlert
visible={showQuiz4Success}
onContinue={handleQuiz4SuccessContinue}
onClose={handleQuiz4SuccessContinue}
/>
{/* 光标1、2的引导对话 */}
<GuideSteps
visible={guide12Visible}
onVisibleChange={setGuide12Visible}
onVisibleChange={handleGuide12VisibleChange}
showMask={false}
zIndex={10000}
>
+59 -5
View File
@@ -41,7 +41,11 @@ const HuangyanpeiPlace5 = () => {
// 展品2视频后的对话引导状态
const [guideVisible, setGuideVisible] = useState(false);
const [guideStep, setGuideStep] = useState(0);
// 题目显示状态
// 题目1(光标1对话后)显示状态
const [showQuiz1, setShowQuiz1] = useState(false);
// 题目1成功提示状态
const [showQuiz1Success, setShowQuiz1Success] = useState(false);
// 题目显示状态(展品2视频后)
const [showQuiz, setShowQuiz] = useState(false);
// 成功提示状态
const [showSuccessAlert, setShowSuccessAlert] = useState(false);
@@ -177,7 +181,15 @@ const HuangyanpeiPlace5 = () => {
}}
onClose={() => {
if (activeExhibitId !== null) {
markExhibitVisited(place, activeExhibitId);
// 如果是展品1关闭,显示题目1
if (activeExhibitId === 1) {
setShowQuiz1(true);
}
// 注意:展品1的已访问标记在题目1成功后处理,这里不标记
// 展品2正常标记
if (activeExhibitId === 2) {
markExhibitVisited(place, activeExhibitId);
}
}
setActiveExhibitId(null);
}}
@@ -188,9 +200,7 @@ const HuangyanpeiPlace5 = () => {
visible={guide1Visible}
currentStep={guide1Step}
onStepChange={setGuide1Step}
onVisibleChange={(visible) => {
setGuide1Visible(visible);
}}
onVisibleChange={setGuide1Visible}
showMask={false}
zIndex={10000}
>
@@ -217,6 +227,50 @@ const HuangyanpeiPlace5 = () => {
</GuideStepsItem>
</GuideSteps>
{/* 题目1(光标1对话后) */}
<SingleChoiceQuizPanel
visible={showQuiz1}
title="【单选】黄炎培先生创建民主政团同盟等组织是为了:"
options={[
{ value: "A", label: "A. 为国家寻求一条和平发展的道路" },
{ value: "B", label: 'B. 支持国民党"攘外必先安内"的政策' },
{ value: "C", label: "C. 扩大个人政治影响力,争夺权力" },
]}
correctAnswer="A"
onWrongSelect={(value) => {
switch (value) {
case "B":
return '国民党政府坚持"攘外必先安内"的政策导致国共关系紧张,黄炎培正是因为反对这种政策、希望推动和平,才发起组建了民主政团同盟。请你再想想吧!';
case "C":
return "黄炎培创建民主政团同盟的出发点是民族大义与国家前途,并非为了个人政治利益。请你再想想吧!";
default:
return "请你再想想吧!";
}
}}
onSubmit={(value, isCorrect) => {
if (isCorrect) {
setShowQuiz1(false);
setShowQuiz1Success(true);
}
}}
zIndex={10001}
/>
{/* 题目1成功提示 */}
<SuccessAlert
visible={showQuiz1Success}
onContinue={() => {
setShowQuiz1Success(false);
// 标记展品1为已访问,修改cursor状态
markExhibitVisited(place, 1);
}}
onClose={() => {
setShowQuiz1Success(false);
// 标记展品1为已访问,修改cursor状态
markExhibitVisited(place, 1);
}}
/>
{/* 光标2的引导对话 */}
<GuideSteps
visible={guideVisible}
+47 -2
View File
@@ -1,5 +1,6 @@
import { useState, useEffect } from "react";
import { Title, Image, Cursor, StudyGuideModal } from "@/components";
import { Title, Image, Cursor, StudyGuideModal, SingleChoiceQuizPanel } from "@/components";
import { SuccessAlert } from "@/components/layout";
import { getBackgroundImage, getPublicPath, getStaticImage } from "@/lib/utils";
import { useNavigate, useParams } from "react-router-dom";
import ChoiceDialog1 from "./scene2/ChoiceDialog1";
@@ -20,6 +21,10 @@ const HuangyanpeiReflectionScene2 = () => {
const [choice2, setChoice2] = useState<"铁工科" | "商科" | null>(null);
const [showCompleteGuide, setShowCompleteGuide] = useState(false);
const [completeGuideShown, setCompleteGuideShown] = useState(false);
// 题目显示状态(ChoiceDialog1关闭后)
const [showQuiz, setShowQuiz] = useState(false);
// 题目成功提示状态
const [showQuizSuccess, setShowQuizSuccess] = useState(false);
// 使用持久化的reflection cursor状态
const scene = "scene2";
@@ -129,7 +134,11 @@ const HuangyanpeiReflectionScene2 = () => {
{/* 第一个选择弹窗 */}
<ChoiceDialog1
visible={showDialog1}
onClose={() => setShowDialog1(false)}
onClose={() => {
setShowDialog1(false);
// 关闭后显示题目
setShowQuiz(true);
}}
onSelect={() => {
markReflectionCursorVisited(scene, 1);
}}
@@ -145,6 +154,42 @@ const HuangyanpeiReflectionScene2 = () => {
}}
/>
{/* 题目(ChoiceDialog1关闭后) */}
<SingleChoiceQuizPanel
visible={showQuiz}
title="【单选】杨斯盛先生近期监工浦东中学建设,有一份上月的工人工钱支出表需要核对,你来告诉我答案吧:"
options={[
{ value: "A", label: "A. 800 两白银" },
{ value: "B", label: "B. 815 两白银" },
{ value: "C", label: "C. 820 两白银" },
{ value: "D", label: "D. 836 两白银" },
]}
correctAnswer="C"
onWrongSelect={() => {
return "是这个数目吗?你要不要再算一下!";
}}
onSubmit={(value, isCorrect) => {
if (isCorrect) {
setShowQuiz(false);
setShowQuizSuccess(true);
}
}}
zIndex={10001}
/>
{/* 题目成功提示 */}
<SuccessAlert
visible={showQuizSuccess}
title=""
content="恭喜你!选择正确。不愧是从川沙小学堂走出来!"
onContinue={() => {
setShowQuizSuccess(false);
}}
onClose={() => {
setShowQuizSuccess(false);
}}
/>
{/* 完成提示 */}
<StudyGuideModal
visible={showCompleteGuide}
+5 -5
View File
@@ -226,7 +226,7 @@ const LuxunInternalization = () => {
if (isCorrect) {
setActiveSuccess(item.id);
setSuccessContent("恭喜你,匹配正确!");
setSuccessContent("");
setResumeCallback(() => resume);
} else {
setDragQuizErrors((prev) => ({
@@ -245,7 +245,7 @@ const LuxunInternalization = () => {
if (isCorrect) {
setActiveSuccess(item.id);
setSuccessContent("恭喜你,匹配正确!");
setSuccessContent("");
setResumeCallback(() => resume);
} else {
setDragQuizErrors((prev) => ({
@@ -276,7 +276,7 @@ const LuxunInternalization = () => {
/>
<SuccessAlert
visible={activeSuccess === item.id}
title="恭喜你, 选择正确!"
// title="恭喜你, 选择正确!"
content={successContent}
onContinue={() => {
setActiveSuccess(null);
@@ -354,7 +354,7 @@ const LuxunInternalization = () => {
/>
<SuccessAlert
visible={activeSuccess === item.id}
title="恭喜你, 选择正确!"
// title="恭喜你, 选择正确!"
content={successContent}
onContinue={() => {
setActiveSuccess(null);
@@ -400,7 +400,7 @@ const LuxunInternalization = () => {
/>
<SuccessAlert
visible={activeSuccess === item.id}
title="恭喜你, 选择正确!"
// title="恭喜你, 选择正确!"
content={successContent}
onContinue={() => {
setActiveSuccess(null);
+73 -6
View File
@@ -7,9 +7,11 @@ import {
Cursor,
StudyGuideModal,
} from "@/components";
import { MatchQuizPanel } from "@/components/layout/quiz";
import { SuccessAlert } from "@/components/layout";
import { useNavigate, useParams } from "react-router-dom";
import { getBackgroundImage, getPublicPath, getStaticImage } from "@/lib/utils";
import { useState, useEffect } from "react";
import { useState } from "react";
import { useCursorController } from "@/hooks/useCursorController";
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
@@ -47,6 +49,10 @@ const LuxunPlace1 = () => {
const [activeExhibitId, setActiveExhibitId] = useState<1 | 2 | null>(null);
// 已浏览展品的 ID 集合
const [visited, setVisited] = useState<Set<number>>(new Set());
// 连线题显示状态(展品1关闭后)
const [showMatchQuiz, setShowMatchQuiz] = useState(false);
// 连线题成功提示状态
const [showMatchQuizSuccess, setShowMatchQuizSuccess] = useState(false);
// Cursor 顺序控制:1 -> 2
const { isEnabled } = useCursorController({
@@ -160,11 +166,17 @@ const LuxunPlace1 = () => {
// }
onClose={() => {
if (activeExhibitId !== null) {
setVisited((prev) => {
const next = new Set(prev);
next.add(activeExhibitId);
return next;
});
// 如果是展品1关闭,显示连线题
if (activeExhibitId === 1) {
setShowMatchQuiz(true);
} else {
// 展品2正常标记为已访问
setVisited((prev) => {
const next = new Set(prev);
next.add(activeExhibitId);
return next;
});
}
}
setActiveExhibitId(null);
}}
@@ -270,6 +282,61 @@ const LuxunPlace1 = () => {
这个过程的确十分曲折。
</GuideStepsItem>
</GuideSteps>
{/* 连线题(展品1关闭后) */}
<MatchQuizPanel
visible={showMatchQuiz}
title="【连线】请将左侧的日期与右侧对应的事件进行连线:"
leftItems={[
{ id: "1", label: "1892-1898年" },
{ id: "2", label: "1919-1923年" },
{ id: "3", label: "1926年9-12月" },
{ id: "4", label: "1927年3-9月" },
]}
rightItems={[
{ id: "a", label: "在绍兴三味书屋求学积淀" },
{ id: "b", label: "于北京八道湾参加新文化运动、酝酿《阿Q正传》" },
{ id: "c", label: "赴厦门大学任教,聚焦底层创作杂文" },
{ id: "d", label: "在广州白云楼整理旧作定稿《朝花夕拾》部分篇章" },
]}
correctMatches={[
{ leftId: "1", rightId: "a" },
{ leftId: "2", rightId: "b" },
{ leftId: "3", rightId: "c" },
{ leftId: "4", rightId: "d" },
]}
errorMessage="连线不正确,请重新尝试!"
onSubmit={(matches, isCorrect) => {
if (isCorrect) {
setShowMatchQuiz(false);
setShowMatchQuizSuccess(true);
}
}}
zIndex={10001}
/>
{/* 连线题成功提示 */}
<SuccessAlert
visible={showMatchQuizSuccess}
onContinue={() => {
setShowMatchQuizSuccess(false);
// 标记展品1为已访问
setVisited((prev) => {
const next = new Set(prev);
next.add(1);
return next;
});
}}
onClose={() => {
setShowMatchQuizSuccess(false);
// 标记展品1为已访问
setVisited((prev) => {
const next = new Set(prev);
next.add(1);
return next;
});
}}
/>
</div>
);
};
+107 -2
View File
@@ -6,11 +6,15 @@ import {
GuideSteps,
GuideStepsItem,
} from "@/components";
import { DragQuizPanel } from "@/components/layout/quiz";
import { SuccessAlert } from "@/components/layout";
import ScaledPortal from "@/components/layout/ScaledPortal";
import { useNavigate, useParams } from "react-router-dom";
import { getBackgroundImage, getPublicPath, getStaticImage } from "@/lib/utils";
import { useState } from "react";
import { useCursorController } from "@/hooks/useCursorController";
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
import type { DragFillResult } from "@/components";
// starName 固定为 luxun
const starName = "luxun";
@@ -57,6 +61,16 @@ const LuxunPlace2 = () => {
// 光标3的引导对话状态
const [guide3Visible, setGuide3Visible] = useState(false);
const [guide3Step, setGuide3Step] = useState(0);
// 拖拽题显示状态(包含那句话的步骤结束后)
const [showDragQuiz, setShowDragQuiz] = useState(false);
// 标记是否已经显示过题目(避免重复显示)
const [quizShown, setQuizShown] = useState(false);
// 拖拽题的值
const [dragQuizValue, setDragQuizValue] = useState<DragFillResult[]>([]);
// 拖拽题错误提示
const [dragQuizError, setDragQuizError] = useState<string>("");
// 拖拽题成功提示状态
const [showDragQuizSuccess, setShowDragQuizSuccess] = useState(false);
const handleOpenExhibit = (index: 1 | 2 | 3) => {
if (index === 1) {
@@ -73,6 +87,7 @@ const LuxunPlace2 = () => {
setActiveExhibitId(3);
setGuide3Visible(true);
setGuide3Step(0);
setQuizShown(false); // 重置题目显示标记
} else {
// 光标3:直接打开
setActiveExhibitId(index);
@@ -99,6 +114,18 @@ const LuxunPlace2 = () => {
setGuide2Visible(visible);
};
// 监听光标3的引导对话步骤变化
const handleGuide3StepChange = (step: number) => {
// 在步骤2(包含那句话的步骤)结束后,进入步骤3前先弹题
if (step === 3 && !quizShown) {
setGuide3Step(2); // 保持在步骤2
setShowDragQuiz(true);
setQuizShown(true);
return;
}
setGuide3Step(step);
};
const progressCount = visited.size;
const total = 3;
const completed = progressCount >= total;
@@ -245,12 +272,12 @@ const LuxunPlace2 = () => {
这是鲁迅的自嘲,他曾说"我不会说绵软的苏白,不会打响亮的京腔,不入调,不入流",所以戏称自己的文章是"南腔北调"。这本书里的《〈守常全集》题记》,赞美了李大钊同志,说"他的遗文却将永驻,因为这是先驱者的遗产,革命史上的丰碑。"
</GuideStepsItem>
</GuideSteps>
\{/* 光标3的引导对话 */}
{/* 光标3的引导对话 */}
<GuideSteps
visible={guide3Visible}
onVisibleChange={setGuide3Visible}
currentStep={guide3Step}
onStepChange={setGuide3Step}
onStepChange={handleGuide3StepChange}
showMask={false}
highlightSelectors={[]}
zIndex={10000}
@@ -274,6 +301,84 @@ const LuxunPlace2 = () => {
文字如利刃般戳破虚假论调,揭露了反动派掩盖自身无能、麻痹民众的阴谋,唤醒国人的民族自信,尽显战斗文字的锋芒与力量。
</GuideStepsItem>
</GuideSteps>
{/* 拖拽题和成功提示(使用 ScaledPortal 保证层级与缩放) */}
<ScaledPortal enabled={showDragQuiz || showDragQuizSuccess} zIndex={10050}>
<>
<DragQuizPanel
visible={showDragQuiz}
title=""
questionPrefix="【拖拽】我们从古以来,就有"
questionSuffix="......这就是中国的脊梁。"
options={[
{ value: "埋头苦干的人", label: "A. 埋头苦干的人" },
{ value: "拼命硬干的人", label: "B. 拼命硬干的人" },
{ value: "为民请命的人", label: "C. 为民请命的人" },
{ value: "舍身求法的人", label: "D. 舍身求法的人" },
]}
blanks={[
{ id: "blank-1", label: "" },
{ id: "blank-2", label: "" },
{ id: "blank-3", label: "" },
{ id: "blank-4", label: "" },
]}
blankSeparators={[",有", ",有", ",有"]}
value={dragQuizValue}
error={dragQuizError}
onChange={(value) => {
setDragQuizValue(value);
setDragQuizError("");
// 检查是否所有空白都已填充
if (value.length < 4) return;
// 验证顺序:必须是 ABCD 顺序
const correctOrder = [
"埋头苦干的人",
"拼命硬干的人",
"为民请命的人",
"舍身求法的人",
];
const isCorrect = value.every((v, index) => {
return v.optionValue === correctOrder[index];
});
if (isCorrect) {
// 答题成功
setShowDragQuiz(false);
setShowDragQuizSuccess(true);
} else {
// 答题失败:顺序不对
setDragQuizError(
"请你按照鲁迅先生《中国人失掉自信力了吗》的原文拖拽哦~"
);
}
}}
zIndex={10000}
/>
<SuccessAlert
visible={showDragQuizSuccess}
onContinue={() => {
setShowDragQuizSuccess(false);
setDragQuizValue([]);
setDragQuizError("");
// 题目完成后,继续对话到步骤3
setGuide3Visible(true);
setGuide3Step(3);
}}
onClose={() => {
setShowDragQuizSuccess(false);
setDragQuizValue([]);
setDragQuizError("");
// 题目完成后,继续对话到步骤3
setGuide3Visible(true);
setGuide3Step(3);
}}
/>
</>
</ScaledPortal>
</div>
);
};
+78 -5
View File
@@ -7,10 +7,12 @@ import {
Cursor,
SingleChoiceQuizPanel,
Video,
type VideoAnchor,
} from "@/components";
import type { VideoRef } from "@/components/video";
import { useNavigate, useParams } from "react-router-dom";
import { getBackgroundImage, getPublicPath, getStaticImage } from "@/lib/utils";
import { useState } from "react";
import { useState, useRef, useMemo } from "react";
import SuccessAlert from "@/components/layout/alert/success-alert";
import { useCursorController } from "@/hooks/useCursorController";
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
@@ -27,10 +29,14 @@ const exhibitSrcMap: Record<1 | 2 | 3, string> = {
const quizDefaultError = "请结合刚刚的旁白内容,再思考一下吧!";
const quizExplanations: Record<string, string> = {
A: "陈独秀与鲁迅有思想交往,但他并未在这间“红色庇护所”中避难、与鲁迅夜谈革命议题。",
B: "恭喜你,选对啦。",
C: "李大钊于1927年牺牲,时间上不符合这间客房被用作“红色庇护所”的背景。",
D: "周恩来虽长期在上海从事革命活动,但并未在这里避难并与鲁迅夜谈。",
// A: "陈独秀与鲁迅有思想交往,但他并未在这间“红色庇护所”中避难、与鲁迅夜谈革命议题。",
// B: "恭喜你,选对啦。",
// C: "李大钊于1927年牺牲,时间上不符合这间客房被用作“红色庇护所”的背景。",
// D: "周恩来虽长期在上海从事革命活动,但并未在这里避难并与鲁迅夜谈。",
A: "请你结合刚刚学习的内容,再思考一下吧!",
B: "恭喜你,选择正确!",
C: "请你结合刚刚学习的内容,再思考一下吧!",
D: "请你结合刚刚学习的内容,再思考一下吧!",
};
const LuxunPlace5 = () => {
@@ -72,6 +78,10 @@ const LuxunPlace5 = () => {
// 结尾视频与对话
const [showEndingVideo, setShowEndingVideo] = useState(false);
const [endingGuideVisible, setEndingGuideVisible] = useState(false);
// 视频锚点相关状态
const [activeVideoSuccess, setActiveVideoSuccess] = useState(false);
const [resumeVideoCallback, setResumeVideoCallback] = useState<(() => void) | null>(null);
const videoRef = useRef<VideoRef>(null);
const handleOpenExhibit = (index: 1 | 2 | 3) => {
if (index === 3) {
@@ -83,6 +93,66 @@ const LuxunPlace5 = () => {
setActiveExhibitId(index);
};
// 视频锚点配置
const videoAnchors: VideoAnchor[] = useMemo(
() => [
{
id: "quiz-anchor",
time: 5,
once: true,
afterResume: "remove",
render: ({ resume }) => (
<>
<SingleChoiceQuizPanel
visible={!activeVideoSuccess}
title='【单选】毛泽东主席称赞鲁迅先生为"最伟大和最英勇的___"。'
options={[
{ value: "A", label: "A. 旗手" },
{ value: "B", label: "B. 作家" },
{ value: "C", label: "C. 战士" },
]}
correctAnswer="A"
onWrongSelect={(value: string) => {
switch (value) {
case "B":
return "这不是毛泽东主席的原话。请你再想想吧!";
case "C":
return "这不是毛泽东主席的原话。请你再想想吧!";
default:
return "请你再想想吧!";
}
}}
onSubmit={(value: string, isCorrect: boolean) => {
if (isCorrect) {
setActiveVideoSuccess(true);
setResumeVideoCallback(() => resume);
}
}}
zIndex={20000}
/>
<SuccessAlert
visible={activeVideoSuccess}
onContinue={() => {
setActiveVideoSuccess(false);
const cb = resumeVideoCallback;
setResumeVideoCallback(null);
cb?.();
}}
onClose={() => {
setActiveVideoSuccess(false);
const cb = resumeVideoCallback;
setResumeVideoCallback(null);
cb?.();
}}
/>
</>
),
},
],
[activeVideoSuccess, resumeVideoCallback]
);
const progressCount = visited.size;
const total = 3;
const completed = progressCount >= total;
@@ -304,11 +374,14 @@ const LuxunPlace5 = () => {
{/* 结尾视频播放 */}
{showEndingVideo && (
<Video
ref={videoRef}
src={getPublicPath("video/luxun/luxun_06.mp4")}
className="fixed inset-0 z-[20000]"
controls
autoPlay
playsInline
showSkipButton
anchors={videoAnchors}
onEnded={() => {
setEndingGuideVisible(true);
}}