feat:init
This commit is contained in:
1 parent
53d0eeac6b
commit
ae0c050010
494 files changed
+17701
-2318
No files matched your search
@@ -0,0 +1,284 @@
|
||||
import { useMemo, useState } from "react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import {
|
||||
Image,
|
||||
Video,
|
||||
type VideoAnchor,
|
||||
type DragFillResult,
|
||||
} from "@/components";
|
||||
import { getPublicPath, getStaticImage } from "@/lib/utils";
|
||||
import { SuccessAlert, DragQuizPanel } from "@/components/layout";
|
||||
import { MultipleChoiceQuizPanel } from "@/components/layout/quiz";
|
||||
|
||||
type QuizOption = {
|
||||
value: string;
|
||||
label: string;
|
||||
isCorrect?: boolean;
|
||||
explanation?: string; // 错误选项的解释
|
||||
};
|
||||
|
||||
type QuizAnchor = {
|
||||
id: string;
|
||||
time: number;
|
||||
title: string;
|
||||
prompt: string;
|
||||
type: "single" | "multiple" | "drag";
|
||||
options: QuizOption[];
|
||||
// 拖拽题专用
|
||||
blanks?: { id: string; label: string }[];
|
||||
};
|
||||
|
||||
// 星名固定为 chenyun
|
||||
const starName = "chenyun";
|
||||
|
||||
// 锚点数据:可根据实际视频时长调整 time
|
||||
const QUIZ_ANCHORS: QuizAnchor[] = [
|
||||
{
|
||||
id: "quiz-1",
|
||||
time: 12,
|
||||
title: "题目1",
|
||||
prompt: "陈云经济思想的核心内容指的是( )。",
|
||||
type: "multiple",
|
||||
options: [
|
||||
{ value: "A", label: "综合平衡", isCorrect: true },
|
||||
{
|
||||
value: "B",
|
||||
label: "鸟笼比喻",
|
||||
explanation: "鸟笼比喻是正确的处理好计划与市场的关系的一个发展阶段",
|
||||
},
|
||||
{ value: "C", label: "正确的处理好计划与市场的关系", isCorrect: true },
|
||||
{ value: "D", label: "经济建设的目的论", isCorrect: true },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "quiz-2",
|
||||
time: 30,
|
||||
title: "题目2",
|
||||
prompt: "陈云说过以下哪些话能着重体现出他的经济思想。",
|
||||
type: "multiple",
|
||||
options: [
|
||||
{
|
||||
value: "A",
|
||||
label: "不唯上、不唯书、只唯实,交换、比较、反复",
|
||||
isCorrect: true,
|
||||
},
|
||||
{ value: "B", label: "无农不稳,无粮则乱", isCorrect: true },
|
||||
{ value: "C", label: "个人名利淡如水,党的事业重如山。", isCorrect: true },
|
||||
{
|
||||
value: "D",
|
||||
label: "领导机关制定政策,要用百分之九十以上的时间作调查研究工作。",
|
||||
isCorrect: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "quiz-3",
|
||||
time: 48,
|
||||
title: "题目3",
|
||||
prompt: "我们新时代的青年可以从陈云同志身上学习到哪些精神",
|
||||
type: "drag",
|
||||
options: [
|
||||
{ value: "积极的人生态度", label: "积极的人生态度", isCorrect: true },
|
||||
{ value: "刻苦的学习精神", label: "刻苦的学习精神", isCorrect: true },
|
||||
{ value: "朴素的节俭精神", label: "朴素的节俭精神", isCorrect: true },
|
||||
{ value: "实事求是的精神", label: "实事求是的精神", isCorrect: true },
|
||||
],
|
||||
blanks: [
|
||||
{ id: "blank-1", label: "____" },
|
||||
{ id: "blank-2", label: "____" },
|
||||
{ id: "blank-3", label: "____" },
|
||||
{ id: "blank-4", label: "____" },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const ChenyunInternalization = () => {
|
||||
const navigate = useNavigate();
|
||||
const { moduleName } = useParams<{ moduleName: string }>();
|
||||
const [activeSuccess, setActiveSuccess] = useState<string | null>(null);
|
||||
const [successContent, setSuccessContent] = useState<string>("");
|
||||
const [resumeCallback, setResumeCallback] = useState<(() => void) | null>(
|
||||
null
|
||||
);
|
||||
// 拖拽题状态
|
||||
const [dragQuizValues, setDragQuizValues] = useState<
|
||||
Record<string, DragFillResult[]>
|
||||
>({});
|
||||
const [dragQuizErrors, setDragQuizErrors] = useState<Record<string, string>>(
|
||||
{}
|
||||
);
|
||||
|
||||
const anchors: VideoAnchor[] = useMemo(
|
||||
() =>
|
||||
QUIZ_ANCHORS.map((item) => {
|
||||
const correctAnswers = item.options
|
||||
.filter((opt) => opt.isCorrect)
|
||||
.map((opt) => opt.value);
|
||||
|
||||
// 拖拽题处理
|
||||
if (item.type === "drag" && item.blanks) {
|
||||
return {
|
||||
id: item.id,
|
||||
time: item.time,
|
||||
once: true,
|
||||
afterResume: "remove",
|
||||
render: ({ resume }) => {
|
||||
const handleDragChange = (val: DragFillResult[]) => {
|
||||
setDragQuizValues((prev) => ({ ...prev, [item.id]: val }));
|
||||
setDragQuizErrors((prev) => ({ ...prev, [item.id]: "" }));
|
||||
|
||||
if (val.length < correctAnswers.length) return;
|
||||
|
||||
// 检查是否包含了所有正确答案(不要求顺序)
|
||||
const filledAnswers = val.map((v) => v.optionValue);
|
||||
const answerSet = new Set(correctAnswers);
|
||||
const filledSet = new Set(filledAnswers);
|
||||
const isCorrect =
|
||||
answerSet.size === filledSet.size &&
|
||||
[...answerSet].every((answer) => filledSet.has(answer));
|
||||
|
||||
if (isCorrect) {
|
||||
setActiveSuccess(item.id);
|
||||
setSuccessContent("回答正确,请继续探索。");
|
||||
setResumeCallback(() => resume);
|
||||
} else {
|
||||
setDragQuizErrors((prev) => ({
|
||||
...prev,
|
||||
[item.id]: "再想一想,确保选择了所有正确答案~",
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<DragQuizPanel
|
||||
visible={activeSuccess !== item.id}
|
||||
zIndex={10000}
|
||||
title={`${item.title}(拖拽)`}
|
||||
questionPrefix={`${item.prompt}(`}
|
||||
questionSuffix=")?"
|
||||
options={item.options.map((opt) => ({
|
||||
value: opt.value,
|
||||
label: opt.label,
|
||||
}))}
|
||||
blanks={item.blanks || []}
|
||||
value={dragQuizValues[item.id] || []}
|
||||
error={dragQuizErrors[item.id]}
|
||||
onChange={handleDragChange}
|
||||
/>
|
||||
<SuccessAlert
|
||||
visible={activeSuccess === item.id}
|
||||
title="恭喜你, 选择正确!"
|
||||
content={successContent}
|
||||
onContinue={() => {
|
||||
setActiveSuccess(null);
|
||||
setSuccessContent("");
|
||||
setDragQuizValues((prev) => {
|
||||
const next = { ...prev };
|
||||
delete next[item.id];
|
||||
return next;
|
||||
});
|
||||
setDragQuizErrors((prev) => {
|
||||
const next = { ...prev };
|
||||
delete next[item.id];
|
||||
return next;
|
||||
});
|
||||
const cb = resumeCallback;
|
||||
setResumeCallback(null);
|
||||
cb?.();
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// 多选题处理
|
||||
if (item.type === "multiple") {
|
||||
return {
|
||||
id: item.id,
|
||||
time: item.time,
|
||||
once: true,
|
||||
afterResume: "remove",
|
||||
render: ({ resume }) => (
|
||||
<>
|
||||
<MultipleChoiceQuizPanel
|
||||
visible={activeSuccess !== item.id}
|
||||
zIndex={10000}
|
||||
title={`${item.title}(多选题):${item.prompt}`}
|
||||
options={item.options.map((opt) => ({
|
||||
value: opt.value,
|
||||
label: `${opt.value}. ${opt.label}`,
|
||||
}))}
|
||||
correctAnswer={correctAnswers}
|
||||
errorMessage="再思考一下,注意选择所有正确答案。"
|
||||
onSubmit={(values: string[], isCorrect: boolean) => {
|
||||
if (isCorrect) {
|
||||
setActiveSuccess(item.id);
|
||||
setSuccessContent("回答正确,请继续探索。");
|
||||
setResumeCallback(() => resume);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<SuccessAlert
|
||||
visible={activeSuccess === item.id}
|
||||
title="恭喜你, 选择正确!"
|
||||
content={successContent}
|
||||
onContinue={() => {
|
||||
setActiveSuccess(null);
|
||||
setSuccessContent("");
|
||||
const cb = resumeCallback;
|
||||
setResumeCallback(null);
|
||||
cb?.();
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
// 单选题处理(保留原有逻辑,以防万一)
|
||||
return {
|
||||
id: item.id,
|
||||
time: item.time,
|
||||
once: true,
|
||||
afterResume: "remove",
|
||||
render: () => null,
|
||||
};
|
||||
}),
|
||||
[
|
||||
activeSuccess,
|
||||
resumeCallback,
|
||||
successContent,
|
||||
dragQuizValues,
|
||||
dragQuizErrors,
|
||||
]
|
||||
);
|
||||
|
||||
if (!moduleName) return null;
|
||||
|
||||
return (
|
||||
<div className="relative h-full w-full bg-black">
|
||||
{/* 离开按钮 */}
|
||||
<Image
|
||||
className="absolute right-8 top-8 z-10"
|
||||
src={getStaticImage("components/离开")}
|
||||
button
|
||||
onClick={() => navigate(`/${moduleName}/${starName}/home`)}
|
||||
/>
|
||||
|
||||
<Video
|
||||
src={getPublicPath("video/chenyun/chenyun_neihua.mp4")}
|
||||
className="h-full w-full"
|
||||
anchors={anchors}
|
||||
autoPlay
|
||||
muted
|
||||
playsInline
|
||||
controls
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChenyunInternalization;
|
||||
Reference in new issue
Block a user