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

+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);
}}