Files
shanghai-residences/src/pages/huangyanpei/place/place5.tsx
T
2026-04-30 09:17:31 +08:00

349 lines
14 KiB
TypeScript

import { useState, useRef } from 'react'
import {
ExhibitOverlay,
Title,
Image,
Cursor,
Video,
GuideSteps,
GuideStepsItem,
SingleChoiceQuizPanel,
} from '@/components'
import { SuccessAlert } from '@/components/layout'
import { getBackgroundImage, getStaticImage, getPublicPath } from '@/lib/utils'
import { useNavigate, useParams } from 'react-router-dom'
import type { VideoRef } from '@/components/video'
import { useCursorController } from '@/hooks/useCursorController'
import { usePuzzleAnimation } from '@/contexts/PuzzleAnimationContext'
import { useHuangyanpeiStore, saveHuangyanpeiStore } from '@/store/huangyanpeiStore'
const starName = 'huangyanpei'
const exhibitSrcMap: Record<1 | 2, string> = {
1: 'huangyanpei/place5/7_004',
2: 'huangyanpei/place5/场景5展品2',
}
const HuangyanpeiPlace5 = () => {
const navigate = useNavigate()
const { moduleName } = useParams<{
moduleName: string
}>()
const { triggerPuzzleAnimation } = usePuzzleAnimation()
const [activeExhibitId, setActiveExhibitId] = useState<1 | 2 | null>(null)
const [videoVisible, setVideoVisible] = useState(false)
const videoRef = useRef<VideoRef>(null)
// 光标1的引导对话状态
const [guide1Visible, setGuide1Visible] = useState(false)
const [guide1Step, setGuide1Step] = useState(0)
// 展品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)
// 使用持久化的访问状态
const place = 'place5'
const { markExhibitVisited, getVisitedExhibits, setLastVisitedPlace } = useHuangyanpeiStore()
// 从 store 获取已访问的展品,转换为 Set 用于兼容现有代码
const visitedExhibits = getVisitedExhibits(place)
const visited = new Set(visitedExhibits)
// 光标顺序:1 -> 2(需完成1后解锁2)
const { isEnabled } = useCursorController({
initialEnabled: [1],
dependencies: { 2: [1] },
visited: visitedExhibits, // 传入数组,而不是 Set
})
const handleOpenExhibit = (index: 1 | 2) => {
if (index === 2) {
// 展品2:点击时显示对话,再显示视频
setGuideStep(0)
setGuideVisible(true)
} else {
// 展品1:正常展示展品
setGuide1Step(0)
setGuide1Visible(true)
setActiveExhibitId(index)
}
}
const progressCount = visited.size
const total = 2
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(`huangyanpei/place5/7_001`),
}}
>
<img
src={getStaticImage('huangyanpei/place5/7_003')}
alt=""
className="absolute top-72 right-37"
/>
<Title
title="延安对话"
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}/place4`)
}
}}
/>
{/* 光标1 */}
<Cursor
className="absolute left-35 top-108"
onClick={() => handleOpenExhibit(1)}
read={visited.has(1)}
disabled={!isEnabled(1)}
/>
{/* 光标2 */}
<Cursor
className="absolute right-112 top-108"
onClick={() => handleOpenExhibit(2)}
read={visited.has(2)}
disabled={!isEnabled(2)}
/>
{completed && (
<Image
className="absolute bottom-0 right-4"
src={getStaticImage('components/place/继续前进')}
button
onClick={() => {
setLastVisitedPlace('place6')
saveHuangyanpeiStore()
navigate(`/${moduleName}/${starName}/place6`)
}}
/>
)}
{/* 对话后显示展品2视频播放(全屏) */}
{videoVisible && (
<div className="fixed inset-0 z-9999 bg-black">
<Video
ref={videoRef}
src={getPublicPath('video/huangyanpei/huangyanpei_07.mp4')}
className="w-full h-full"
controls
autoPlay
playsInline
preload="auto"
showSkipButton
skipText="跳过"
onEnded={() => {
// 对话结束后显示题目
setShowQuiz(true)
}}
/>
</div>
)}
<ExhibitOverlay
visible={activeExhibitId !== null}
// description={
// activeExhibitId === 1
// ? '1940年,国民党政府还坚持"攘外必先安内"的政策,让国共两党的关系越来越紧张。黄炎培等有识之士看到这种严峻形势,觉得中间派应该联合起来,为国家寻求一条和平发展的道路。于是他联系了张君励、梁漱溟等爱国同仁,后来又联合了第三党和无党派人士,准备成立统一的同盟。刚好这时发生了震惊中外的"皖南事变",国共矛盾进一步激化,黄炎培心里特别着急,就加紧了筹备工作。1941年3月19日,中国民主政团同盟正式成立,参加成立会的有13人,选出了5位常委,黄炎培当选为第一任主席。1945年,他还创立了民主建国会并担任第一任主委,带领大家积极投入"反内战、保和平"的民主斗争,为国家的和平稳定奔走呼号。'
// : undefined
// }
src={activeExhibitId ? exhibitSrcMap[activeExhibitId] : ''}
onOpen={() => {
if (activeExhibitId === 1) {
triggerPuzzleAnimation(11)
}
}}
onClose={() => {
if (activeExhibitId !== null) {
// 如果是展品1关闭,显示题目1
if (activeExhibitId === 1) {
setShowQuiz1(true)
}
// 注意:展品1的已访问标记在题目1成功后处理,这里不标记
// 展品2正常标记
if (activeExhibitId === 2) {
markExhibitVisited(place, activeExhibitId)
}
}
setActiveExhibitId(null)
}}
/>
{/* 光标1的引导对话 */}
<GuideSteps
visible={guide1Visible}
currentStep={guide1Step}
onStepChange={setGuide1Step}
onVisibleChange={setGuide1Visible}
showMask={false}
zIndex={10000}
>
<GuideStepsItem
speaker="student"
audioSrc={getPublicPath('audio/huangyanpei/mp3_huangyanpei_16.mp3')}
>
抗战胜利后,黄炎培先生除了继续推进职业教育,还参与了政党活动呀?他创建民主政团同盟,是想为国家做些什么呢?
</GuideStepsItem>
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/huangyanpei/mp3_huangyanpei_17.mp3')}
>
这和当时的历史背景分不开。1940年,国民党政府还坚持“攘外必先安内”的政策,让国共两党的关系越来越紧张。黄炎培等有识之士看到这种严峻形势,觉得中间派应该联合起来,为国家寻求一条和平发展的道路。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
于是他联系了张君劢、梁漱溟等爱国同仁,后来又联合了第三党和无党派人士,准备成立统一的同盟。刚好这时发生了震惊中外的“皖南事变”,国共矛盾进一步激化,黄炎培心里特别着急,就加紧了筹备工作。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
1941年3月19日,中国民主政团同盟正式成立,参加成立会的有13人,选出了5位常委,黄炎培当选为第一任主席。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
1945年,他还创立了民主建国会并担任第一任主委,带领大家积极投入“反内战、保和平”的民主斗争,为国家的和平稳定奔走呼号。
</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}
currentStep={guideStep}
onStepChange={setGuideStep}
onVisibleChange={(visible) => {
setGuideVisible(visible)
// 如果对话结束,显示视频
if (!visible) {
setVideoVisible(true)
}
}}
showMask={false}
highlightSelectors={[]}
zIndex={10000}
>
<GuideStepsItem
speaker="student"
audioSrc={getPublicPath('audio/huangyanpei/mp3_huangyanpei_18.mp3')}
>
哇!这就是历史课上学的“窑洞对”!黄炎培先生和毛主席的这次谈话,是不是谈到了“历史周期率”呀?
</GuideStepsItem>
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/huangyanpei/mp3_huangyanpei_19.mp3')}
>
完全正确!1945年7月1日,黄炎培以国民参政员的身份,带着考察团一行六人赴延安考察,中共中央领导人毛泽东、朱德、周恩来等都亲自到机场迎接。7月4日,他和毛泽东主席长谈了十几个小时,这场对话能和诸葛亮的“隆中对”相媲美。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
谈话中,黄炎培提出了一个深刻的问题:“其兴也勃焉,其亡也忽焉”,也就是历代王朝逃不开的“历史周期率”。毛泽东主席当场回答:“我们已经找到新路,我们能跳出这周期律,这条新路就是民主。”
</GuideStepsItem>
<GuideStepsItem speaker="guide">
后来,黄炎培把这段对话写进了《延安归来》这本书,一夜之间就畅销十万册,成为当时最热门的书籍,而“实行民主”也被大家称为破解历史周期率的“初版答案”。
</GuideStepsItem>
</GuideSteps>
{/* 题目 */}
<SingleChoiceQuizPanel
visible={showQuiz}
title={`【单选】《延安归来》中"破解历史周期率"的"初版答案"一夜畅销十万册,其提出的核心对策是:`}
options={[
{ value: 'A', label: 'A. 实行民主' },
{ value: 'B', label: 'B. 发展经济' },
{ value: 'C', label: 'C. 加强党建' },
{ value: 'D', label: 'D. 武装斗争' },
]}
correctAnswer="A"
errorMessage="1945年7月,黄炎培在延安向毛泽东提出“其兴也勃焉,其亡也忽焉”的历史周期率之问。毛泽东回答:“我们已经找到新路,我们能跳出这周期律,这条新路就是民主。”黄炎培将这一对话写入《延安归来》,首次公开提出以“民主”破解历代王朝兴衰循环的命题。该书一夜之间畅销十万册,使“实行民主”成为当时社会各界瞩目的“初版答案”。"
onSubmit={(value, isCorrect) => {
if (isCorrect) {
setVideoVisible(false)
setShowQuiz(false)
setShowSuccessAlert(true)
}
}}
zIndex={10001}
/>
{/* 成功提示 */}
<SuccessAlert
visible={showSuccessAlert}
onContinue={() => {
setShowSuccessAlert(false)
markExhibitVisited(place, 2)
triggerPuzzleAnimation(12)
}}
onClose={() => {
setShowSuccessAlert(false)
}}
/>
</div>
)
}
export default HuangyanpeiPlace5