feat: update residence learning flows

Commit non-media application and asset changes while leaving audio, video, and environment files out of version control.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
zhongyidianandClaude Opus 4.7 committed 2026-05-13 10:15:26 +08:00
1 parent 68ba22a33f
commit 6d40e19346
184 files changed
+7663 -2203

No files matched your search

+2 -15
View File
@@ -2,14 +2,12 @@ import { useState } from 'react'
import { getBackgroundImage, getStaticImage } from '@/lib/utils'
import { Image, PuzzleSystem } from '@/components'
import { useNavigate } from 'react-router-dom'
import MenuPuzzleCenter from '@/components/puzzle/MenuPuzzleCenter'
import { DEFAULT_PUZZLE_RESIDENCE } from '@/config/puzzleResidences'
import { usePuzzleStore } from '@/store/puzzleStore'
import { isMenuModuleComplete } from '@/config/progression'
const Menu = () => {
const navigate = useNavigate()
const [isPuzzleCenterVisible, setIsPuzzleCenterVisible] = useState(false)
const [activeResidence, setActiveResidence] = useState<string | null>(null)
const puzzles = usePuzzleStore((state) => state.puzzles)
const isLoaded = usePuzzleStore((state) => state.isLoaded)
@@ -65,17 +63,6 @@ const Menu = () => {
/>
</div>
{/* 顶层碎片管理中心:查看故居导图与进度 */}
<MenuPuzzleCenter
visible={isPuzzleCenterVisible}
onClose={() => setIsPuzzleCenterVisible(false)}
onSelectResidence={(residence) => {
setActiveResidence(residence)
setIsPuzzleCenterVisible(false)
}}
allowPuzzleEntry={false}
/>
{/* 进入具体故居的拼图系统(与故居页面一致的 PuzzleSystem) */}
{activeResidence && (
<PuzzleSystem
@@ -102,7 +89,7 @@ const Menu = () => {
className="relative w-[241px] h-[282px] bg-cover bg-center bg-no-repeat hover:brightness-110 hover:scale-105 transition-all cursor-pointer"
style={{ backgroundImage: getBackgroundImage('menu/模块-通用') }}
key={item.id}
onClick={() => navigate(`/entrance/${encodeURIComponent(item.moduleName)}`)}
onClick={() => navigate(`/${encodeURIComponent(item.moduleName)}`)}
>
<div className="absolute top-15 flex flex-col items-center">
<Image key={item.id} src={getStaticImage(item.image)} />
@@ -132,7 +119,7 @@ const Menu = () => {
<Image
src={getStaticImage('menu/导航地图')}
button
onClick={() => setIsPuzzleCenterVisible(true)}
onClick={() => navigate('/learning-map')}
/>
</div>
</div>
+1 -1
View File
@@ -84,7 +84,7 @@ const Chenyun = () => {
className="absolute right-37 top-22"
src={getStaticImage('components/革命先驱故居-返回')}
button
onClick={() => navigate(`/entrance/${moduleName}`)}
onClick={() => navigate(`/${moduleName}`)}
/>
<div className="absolute right-20 top-75 flex flex-col space-y-10">
+24 -13
View File
@@ -55,7 +55,7 @@ const QUIZ_ANCHORS: QuizAnchor[] = [
value: 'B',
label: '鸟笼比喻',
},
{ value: 'C', label: '正确的处理好计划与市场的关系', isCorrect: true },
{ value: 'C', label: '正确地处理好计划与市场的关系', isCorrect: true },
{ value: 'D', label: '经济建设的目的论', isCorrect: true },
],
explanation: '鸟笼比喻是陈云提出的一个比喻。请你再想想吧!',
@@ -69,14 +69,14 @@ const QUIZ_ANCHORS: QuizAnchor[] = [
options: [
{
value: 'A',
label: '不唯上、不唯书、只唯实,交换、比较、反复',
label: '不唯上、不唯书、只唯实,交换、比较、反复',
isCorrect: true,
},
{ value: 'B', label: '无农不稳,无粮则乱', isCorrect: true },
{ value: 'C', label: '个人名利淡如水,党的事业重如山', isCorrect: true },
{ value: 'B', label: '无农不稳,无粮则乱', isCorrect: true },
{ value: 'C', label: '个人名利淡如水,党的事业重如山', isCorrect: true },
{
value: 'D',
label: '领导机关制定政策,要用百分之九十以上的时间作调查研究工作',
label: '领导机关制定政策,要用百分之九十以上的时间作调查研究工作',
isCorrect: true,
},
],
@@ -378,7 +378,7 @@ const ChenyunInternalization = () => {
<Narration
visible={narrationVisible}
audioChannelId="chenyun-internalization-narration"
audioSrc={getPublicPath('audio/chenyun/mp3_chenyun_36.mp3')}
audioSrc={getPublicPath('audio/chenyun/mp3_chenyun_35_1.mp3')}
onClose={() => {
setNarrationVisible(false)
setVisible(true)
@@ -417,11 +417,14 @@ const ChenyunInternalization = () => {
>
<GuideStepsItem
speaker="student"
audioSrc={getPublicPath('audio/chenyun/mp3_chenyun_36.mp3')}
audioSrc={getPublicPath('audio/chenyun/mp3_chenyun_36_1.mp3')}
>
小沪,跟着你参观陈云纪念馆真是收获满满!看到馆里展示的“米棉之战”史料、全国财经统筹的实物展品,我真切感受到陈云爷爷在建国初期平抑物价、稳定经济的魄力。
</GuideStepsItem>
<GuideStepsItem speaker="student">
<GuideStepsItem
speaker="student"
audioSrc={getPublicPath('audio/chenyun/mp3_chenyun_36_2.mp3')}
>
他用“全国一盘棋”思路化解危机的案例太震撼了,那你能再跟我讲讲,支撑这些成就的核心经济思想是什么吗?
</GuideStepsItem>
<GuideStepsItem
@@ -447,18 +450,26 @@ const ChenyunInternalization = () => {
>
<GuideStepsItem
speaker="student"
audioSrc={getPublicPath('audio/chenyun/mp3_chenyun_38.mp3')}
audioSrc={getPublicPath('audio/chenyun/mp3_chenyun_38_1.mp3')}
>
感谢房老师的讲解!从陈云童年成长的水乡故居,到他主持"银元之战""米棉之战"的史料,再到那台称量粮食的天平、握不住的铅笔头,我真切感受到他"不唯上、不唯书、只唯实"的精神,也懂了他"为老百姓做事"的初心。
感谢房老师的讲解!从陈云童年成长的水乡故居,到他主持“银元之战”“米棉之战”的史料,再到那台称量粮食的天平、握不住的铅笔头,我真切感受到他“不唯上、不唯书、只唯实”的精神,也懂了他“为老百姓做事”的初心。
</GuideStepsItem>
<GuideStepsItem
speaker="student"
audioSrc={getPublicPath('audio/chenyun/mp3_chenyun_38_2.mp3')}
>
这些都让我特别受触动!
</GuideStepsItem>
<GuideStepsItem speaker="student">这些都让我特别受触动!</GuideStepsItem>
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/chenyun/mp3_chenyun_39.mp3')}
audioSrc={getPublicPath('audio/chenyun/mp3_chenyun_39_1.mp3')}
>
你的感悟很深刻!陈云同志用一生诠释了“务实”与“为民”——从破解边区经济困境,到擘画国家工业化蓝图,再到改革开放时力挺宝钢建设,每一步都扎根实际、心系百姓。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/chenyun/mp3_chenyun_39_2.mp3')}
>
你不妨把这份触动凝练成几句心里话,既总结参观的收获,也想想未来该如何践行这份精神,用自己的行动传承这份初心吧!
</GuideStepsItem>
</GuideSteps>
+20 -22
View File
@@ -36,12 +36,11 @@ const ChenyunPlace1 = () => {
// 学习引导弹窗
const [showGuideModal, setShowGuideModal] = useState(true)
// 对话结束后的学习引导弹窗
// 温馨提示弹窗
const [showAfterDialogGuide, setShowAfterDialogGuide] = useState(false)
const [afterDialogGuideShown, setAfterDialogGuideShown] = useState(false)
// 学习说明对话框显示状态
const [showIntroDialog, setShowIntroDialog] = useState(false)
// 引导对话显示状态
// 温馨提示关闭后的场景对话显示状态
const [visible, setVisible] = useState(false)
// 当前激活的展品 ID(只存 ID,不直接存路径,便于作为唯一标识)
const [activeExhibitId, setActiveExhibitId] = useState<1 | 2 | null>(null)
@@ -169,12 +168,12 @@ const ChenyunPlace1 = () => {
}}
/>
{/* 学习引导词 */}
{/* 学习引导 */}
<StudyGuideModal
visible={showGuideModal}
onContinue={() => {
setShowGuideModal(false)
setVisible(true)
setShowAfterDialogGuide(true)
}}
audioSrc={getPublicPath('audio/chenyun/mp3_chenyun_02.mp3')}
ailgn="left"
@@ -198,18 +197,8 @@ const ChenyunPlace1 = () => {
</Overlay>
)}
{/* 场景对话 */}
<GuideSteps
visible={visible}
onVisibleChange={(nextVisible) => {
setVisible(nextVisible)
if (!nextVisible && !afterDialogGuideShown) {
setAfterDialogGuideShown(true)
setShowAfterDialogGuide(true)
}
}}
showMask={false}
>
{/* 温馨提示关闭后的场景对话 */}
<GuideSteps visible={visible} onVisibleChange={setVisible} showMask={false}>
<GuideStepsItem
speaker="student"
audioSrc={getPublicPath('audio/chenyun/mp3_chenyun_03.mp3')}
@@ -218,22 +207,31 @@ const ChenyunPlace1 = () => {
</GuideStepsItem>
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/chenyun/mp3_chenyun_04.mp3')}
audioSrc={getPublicPath('audio/chenyun/mp3_chenyun_04_1.mp3')}
>
这座房子就是陈云故居,位于上海市青浦区练塘镇下塘街95号,是一座坐南朝北、面向河畔的两层楼江南民居。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/chenyun/mp3_chenyun_04_2.mp3')}
>
实际上是陈云舅父母的家,也是少年陈云从4岁至14岁生活的地方。童年的陈云,家庭生活十分不幸。他两岁时父亲病故,四岁时,母亲也悲苦地死去。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/chenyun/mp3_chenyun_04_3.mp3')}
>
父母去世后,由外婆抚养。六岁时,外婆去世,陈云过继给舅父母当养子。陈云从小跟舅父廖文光和舅母廖倪氏一起生活。家里靠种田和做小生意勉强维持生计。
</GuideStepsItem>
</GuideSteps>
{/* 对话结束后的学习引导 */}
{/* 温馨提示 */}
<StudyGuideModal
visible={showAfterDialogGuide}
onContinue={() => setShowAfterDialogGuide(false)}
onContinue={() => {
setShowAfterDialogGuide(false)
setVisible(true)
}}
audioSrc={getPublicPath('audio/chenyun/mp3_chenyun_01.mp3')}
firstLineIndent="2em"
>
+10 -4
View File
@@ -168,11 +168,14 @@ const ChenyunPlace2 = () => {
</GuideStepsItem>
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/chenyun/mp3_chenyun_06.mp3')}
audioSrc={getPublicPath('audio/chenyun/mp3_chenyun_06_1.mp3')}
>
因为家贫,初小读完的陈云便辍学在家。后在舅母亲戚的资助下,陈云才到青浦县立乙种商业学校学习珠算和记账的,他在很短时间里就基本掌握了珠算,还初步学会写账,这为陈云日后从事经济工作打下了基础。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/chenyun/mp3_chenyun_06_2.mp3')}
>
陈云在党内以“铁算盘”著称,他在经济工作中能熟练使用算盘,就得益于少年时在青浦县立乙种商业学校的学习。
</GuideStepsItem>
</GuideSteps>
@@ -195,11 +198,14 @@ const ChenyunPlace2 = () => {
</GuideStepsItem>
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/chenyun/mp3_chenyun_08.mp3')}
audioSrc={getPublicPath('audio/chenyun/mp3_chenyun_08_1.mp3')}
>
是的,陈云在青浦县立乙种商业学校学习了一个多月后,又因经济困难不得不辍学回家。多亏当地知识分子、颜安小学校长杜枢,他免费保荐陈云入颜安小学高小部读书。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/chenyun/mp3_chenyun_08_2.mp3')}
>
颜安小学实行的新式教育开阔了陈云的视野,五四运动后,颜安小学师生积极投入反帝爱国运动,为陈云日后加入中国共产党埋下了思想的种子。
</GuideStepsItem>
</GuideSteps>
+5 -2
View File
@@ -236,11 +236,14 @@ const ChenyunPlace3 = () => {
</GuideStepsItem>
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/chenyun/mp3_chenyun_10.mp3')}
audioSrc={getPublicPath('audio/chenyun/mp3_chenyun_10_1.mp3')}
>
从颜安小学毕业后,陈云进入了商务印书馆当学徒,开始在柜台接待顾客,打珠算、写信封、练包装、理货物、修文具。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/chenyun/mp3_chenyun_10_2.mp3')}
>
陈云的珠算能力在商务印书馆工作期间有了很大提高。由于工作认真负责,三年的学徒期,陈云提前两年就破格到商务印书馆虹口分店(现为1925书局)当店员。
</GuideStepsItem>
</GuideSteps>
+18 -6
View File
@@ -241,14 +241,20 @@ const ChenyunPlace4 = () => {
</GuideStepsItem>
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/chenyun/mp3_chenyun_12.mp3')}
audioSrc={getPublicPath('audio/chenyun/mp3_chenyun_12_1.mp3')}
>
1932年3月,陈云担任中共临时中央政治局常委,兼任全国总工会党团书记。1933年1月,陈云离开上海前往中央苏区首府瑞金,继续参加中共中央和全国总工会的领导工作。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/chenyun/mp3_chenyun_12_2.mp3')}
>
虽然陈云在中央苏区的任务主要是领导工人运动,但是陈云总是能针对一些棘手的经济问题提出较出色的建议。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/chenyun/mp3_chenyun_12_3.mp3')}
>
在中央苏区时期,陈云也关注和积极参与解决了许多经济难题。1934年,在中共六届五中全会上,陈云被选为中央政治局委员、常委,兼任中央白区工作部部长。
</GuideStepsItem>
</GuideSteps>
@@ -271,14 +277,20 @@ const ChenyunPlace4 = () => {
</GuideStepsItem>
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/chenyun/mp3_chenyun_14.mp3')}
audioSrc={getPublicPath('audio/chenyun/mp3_chenyun_14_1.mp3')}
>
中央苏区时期的经济斗争,是陈云经济工作能力的“淬炼场”。陈云分析了“左”倾经济政策的表现、危害以及原因。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/chenyun/mp3_chenyun_14_2.mp3')}
>
他强调,当务之急是重新严格审查各个企业、工厂以及商店、大小作坊的实际承受力和经济支付能力。从实际出发,实事求是地制定符合中央苏维埃区域每一个工厂、企业、作坊发展需求的劳动政策。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/chenyun/mp3_chenyun_14_3.mp3')}
>
陈云提议建立合作社,广泛吸收失业工人加入,多听取工人建议、增强企业管理的民主性,从而更好地建设苏区经济。这些做法为他日后主持全国财经工作积累了宝贵经验。
</GuideStepsItem>
</GuideSteps>
+5 -2
View File
@@ -165,11 +165,14 @@ const ChenyunPlace5 = () => {
</GuideStepsItem>
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/chenyun/mp3_chenyun_16.mp3')}
audioSrc={getPublicPath('audio/chenyun/mp3_chenyun_16_1.mp3')}
>
1944年新春伊始,陈云开始担任西北财经办事处副主任,工作转到财经方面,这是陈云以主要精力从事领导经济工作的开始。当时,陕甘宁边区在国民党严密封锁下,财经状况严峻,边区物价猛涨,老百姓日常生活必需的布匹和粮食十分缺乏。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/chenyun/mp3_chenyun_16_2.mp3')}
>
国民党顽固派封锁边区,企图“不让一粒粮、一尺布进入边区”,同时千方百计阻止边区食盐出口。
</GuideStepsItem>
</GuideSteps>
+5 -2
View File
@@ -134,11 +134,14 @@ const ChenyunPlace6 = () => {
</GuideStepsItem>
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/chenyun/mp3_chenyun_18.mp3')}
audioSrc={getPublicPath('audio/chenyun/mp3_chenyun_18_1.mp3')}
>
是的,新中国刚成立时,中国共产党接管的是一个民穷财尽、物价飞涨的烂摊子。面对财政经济上的严重困难,陈云在1949年受命担任新成立的中央财政经济委员会(简称中财委)主任,主持领导全国财政经济工作。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/chenyun/mp3_chenyun_18_2.mp3')}
>
陈云挂帅后的首仗就是打击投机、平抑物价。通过“银元之战”“两白一黑”战役等几次严峻斗争,只用不到一年时间,就迅速稳定了金融物价。
</GuideStepsItem>
</GuideSteps>
+12 -7
View File
@@ -44,7 +44,7 @@ const QUIZ1_TITLE =
const QUIZ1_ERROR_HINTS: Record<string, string> = {
A: '淮海战役于1949年1月胜利结束,发生在这次“银元之战”前,不存在因果关系。请你再想想吧!',
C: '1949年6月新中国政权尚未建立。请你再想想吧',
C: '1949年6月新中国政权尚未建立。请你再想想吧!',
}
const QUIZ1_FALLBACK_HINT = '再想一想,注意题干中的时间和提示信息哦~'
@@ -305,18 +305,24 @@ const ChenyunPlace7 = () => {
speaker="student"
audioSrc={getPublicPath(`${AUDIO_PREFIX}/mp3_chenyun_19.mp3`)}
>
那"银元之战"是如何取得胜利的呢?
那“银元之战”是如何取得胜利的呢?
</GuideStepsItem>
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath(`${AUDIO_PREFIX}/mp3_chenyun_20.mp3`)}
audioSrc={getPublicPath(`${AUDIO_PREFIX}/mp3_chenyun_20_1.mp3`)}
>
由于通货膨胀,国民党发行的法币、金圆券变成了一堆废纸。当时坊间流传“大街过三道,物价跳三跳”。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath(`${AUDIO_PREFIX}/mp3_chenyun_20_2.mp3`)}
>
有人计算,一百元法币,在一九三七年可买两头牛,一九四五年只能买两个鸡蛋,一九四七年只能买一个煤球,到一九四九年五月连一粒米也买不到。可见,当时通货膨胀多么严重。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath(`${AUDIO_PREFIX}/mp3_chenyun_20_3.mp3`)}
>
打击投机资本,稳定金融物价,陈云代中央起草《中共中央关于打击银元使人民币占领阵地的指示》,提出以强硬手段打击投机势力。
</GuideStepsItem>
</GuideSteps>
@@ -364,7 +370,7 @@ const ChenyunPlace7 = () => {
speaker="student"
audioSrc={getPublicPath(`${AUDIO_PREFIX}/mp3_chenyun_21.mp3`)}
>
那"两白一黑"战役是如何打赢的呢?
那“两白一黑”战役是如何打赢的呢?
</GuideStepsItem>
<GuideStepsItem
speaker="guide"
@@ -395,7 +401,6 @@ const ChenyunPlace7 = () => {
title="恭喜你, 选择正确!"
content={successContent}
onClose={handleSuccessDismiss}
onContinue={handleSuccessDismiss}
/>
</div>
)}
+14 -5
View File
@@ -189,11 +189,14 @@ const ChenyunPlace8 = () => {
</GuideStepsItem>
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/chenyun/mp3_chenyun_26.mp3')}
audioSrc={getPublicPath('audio/chenyun/mp3_chenyun_26_1.mp3')}
>
不错,随着大规模经济建设的展开,全国粮、棉、油等主要农产品严重紧缺,供求矛盾尖锐。陈云经过调查研究,反复比较,权衡利弊得失,终于下决心采用在农村征购,在城市配售的解决粮食问题的办法,也就是后来所说的“统购统销”。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/chenyun/mp3_chenyun_26_2.mp3')}
>
陈云说:“只有采用这种办法,才能保证我国人民生活日益增长的需要,才能制止投机活动,保证市场物价的稳定,才能使发展国民经济第一个五年计划得以顺利地进行。”
</GuideStepsItem>
</GuideSteps>
@@ -231,14 +234,20 @@ const ChenyunPlace8 = () => {
</GuideStepsItem>
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/chenyun/mp3_chenyun_24.mp3')}
audioSrc={getPublicPath('audio/chenyun/mp3_chenyun_24_1.mp3')}
>
一九五四年,陈云的主要工作之一,就是主持编制第一个五年计划纲要。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/chenyun/mp3_chenyun_24_2.mp3')}
>
这次编制工作是在前四次基础上进行的,陈云提出计划编制工作中要注意几个问题:首先是工业投资金额,其次是市场供应和人民生活,第三是农业发展速度,这三个问题对编制计划有指导意义。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/chenyun/mp3_chenyun_24_3.mp3')}
>
第一个五年计划纲要草案的重点是优先发展重工业,特别是156项重点工程。当时陈云重点抓重工业建设和粮食保障,通过“统购统销”稳定粮价,为重点工程项目筑牢根基,亲手铺就了我国工业化的起步之路。
</GuideStepsItem>
</GuideSteps>
+9 -3
View File
@@ -147,14 +147,20 @@ const ChenyunPlace9 = () => {
>
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/chenyun/mp3_chenyun_27.mp3')}
audioSrc={getPublicPath('audio/chenyun/mp3_chenyun_27_1.mp3')}
>
我们已经沿着陈云的足迹,从练塘水乡走到改革开放的前沿,读懂了他“适合老百姓的要求,做了一点事”的初心。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/chenyun/mp3_chenyun_27_2.mp3')}
>
接下来,请你跨越时空,走进1949年刚解放时期的上海,参与到当时那场轰轰烈烈的“银元之战”和“米棉之战”之中去。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/chenyun/mp3_chenyun_27_3.mp3')}
>
在实践决策中体会陈云“多谋善断”“敢闯务实”的卓越智慧与战略魄力,感受我国集中力量办大事的制度优势如何在经济战场中彰显威力。
</GuideStepsItem>
</GuideSteps>
@@ -9,7 +9,7 @@ import type { ClueData } from '../clue/types'
// 银元之战视频配置
export const YINYUAN_VIDEO_CONFIG = {
src: getPublicPath('video/chenyun/chenyun_15.mp4'),
narration: `1949年5月,上海解放,此时上海经济秩序混乱,一些资本家甚至说“共产党是军事一百分,政治八十分,财经打零分。“上海经济旧势力利用人们长期以来形成的担心钞票贬值的心理,大肆贩卖银元,掀起投机风潮。上海证券交易所是投机商的大本营,而大街小巷的银元贩子却更多。原来的一块银元值100人民币,到了6月4日已经涨到1100元,而涨势仍在继续。事实上,银元已经是上海市场的主要货币。有些商号只认银元,把人民币排斥到市场之外。上海,这个全国的经济心脏,能否在新政权手中恢复跳动?一场没有硝烟的经济战争即将打响。面对这硝烟弥漫的经济战线,陈云同志亲自参与指导了同旧经济势力间的“银元之战。你将化身陈云同志的特派员,直面这场“银元之战”。"`,
narration: `1949年5月,上海解放,此时上海经济秩序混乱,一些资本家甚至说“共产党是军事一百分,政治八十分,财经打零分。“上海经济旧势力利用人们长期以来形成的担心钞票贬值的心理,大肆贩卖银元,掀起投机风潮。上海证券交易所是投机商的大本营,而大街小巷的银元贩子却更多。原来的一块银元值100人民币,到了6月4日已经涨到1100元,而涨势仍在继续。事实上,银元已经是上海市场的主要货币。有些商号只认银元,把人民币排斥到市场之外。上海,这个全国的经济心脏,能否在新政权手中恢复跳动?一场没有硝烟的经济战争即将打响。面对这硝烟弥漫的经济战线,陈云同志亲自参与指导了同旧经济势力间的“银元之战”。”你将化身陈云同志的特派员,直面这场“银元之战”。`,
alert: {
title: '第一幕:“银元之战”--人民币的阵地争夺战',
subtitle: '上海目前出现大肆贩卖银元的情况,请你了解情况!',
@@ -36,10 +36,10 @@ export const YINYUAN_CLUE_DATA: Record<
title: '线索二',
content: (
<div className="flex flex-col items-start text-left gap-2 px-14">
<div className="mb-8">
<div className="mb-8 indent-14">
南京路的先施、永安、大新、新新等四大公司率先拒收人民币,甚至用银元标价。
</div>
<div>其他商户纷纷仿效,人民币在市场交易中地位下降。</div>
<div className="indent-14">其他商户纷纷仿效,人民币在市场交易中地位下降。</div>
</div>
),
},
+34 -4
View File
@@ -2,7 +2,7 @@
* 陈云反思页面主入口
* 整合银元之战和米棉之战两个模块
*/
import { Image, StudyGuideModal } from '@/components'
import { Image, StudyGuideModal, Video } from '@/components'
import ErrorAlert from '@/components/layout/alert/error-alert'
import TitleAlert from '@/components/layout/alert/title-alert'
import ClueOverlay from '@/pages/chenyun/reflection/clue'
@@ -63,10 +63,10 @@ const ChenyunReflection = () => {
/** 是否显示线索弹窗 */
const [showClueOverlay, setShowClueOverlay] = useState(false)
const [showOpeningVideo, setShowOpeningVideo] = useState(true)
/** 欢迎语弹窗(使用 StudyGuideModal 展示入口引导) */
const [showWelcomeGuide, setShowWelcomeGuide] = useState(
() => !useChenyunStore.getState().isYinyuanSolved
)
const [showWelcomeGuide, setShowWelcomeGuide] = useState(false)
/** 第一幕银元之战 - 学习引导弹窗 */
const [showYinyuanGuide, setShowYinyuanGuide] = useState(false)
@@ -219,6 +219,36 @@ const ChenyunReflection = () => {
backgroundImage: getBackgroundImage(`chenyun/reflection/底图`),
}}
>
{showOpeningVideo && (
<div className="fixed inset-0 z-10000 w-full h-full bg-black">
<Video
src={getPublicPath('video/reflection/chenyun_tiwu_00.mp4')}
autoPlay
playsInline
showSkipButton
skipText="跳过"
onEnded={() => {
setShowOpeningVideo(false)
if (!useChenyunStore.getState().isYinyuanSolved) {
setShowWelcomeGuide(true)
}
}}
onSkip={() => {
setShowOpeningVideo(false)
if (!useChenyunStore.getState().isYinyuanSolved) {
setShowWelcomeGuide(true)
}
}}
onError={() => {
setShowOpeningVideo(false)
if (!useChenyunStore.getState().isYinyuanSolved) {
setShowWelcomeGuide(true)
}
}}
/>
</div>
)}
{/* 离开按钮 */}
{expandedFile == null && (
<Image
@@ -139,7 +139,7 @@ export const YinyuanBattle = ({
// 关闭多选题1,显示旁白文字(图2)
state.setShowQuiz1(false)
state.setNarration2Text(
`六月五日,上海政府抛出十万银元,力图以银元制服银元,使价格回跌。全市举行了"反对银元投机,保障人民生活"的游行和宣传。上海市军委会主任陈毅在全市集会上警告投机奸商"赶快洗手不干,否则勿谓言之不预"。但这两手收效仍微。十万银元被投机商人一吸而空,六月七日银元每元的价格涨到一千八百元人民币。陈云同志判断:"单纯的经济手段已不足以震慑敌人,必须辅以坚定的行政与法律手段。"`
`六月五日,上海政府抛出十万银元,力图以银元制服银元,使价格回跌。全市举行了“反对银元投机,保障人民生活”的游行和宣传。上海市军委会主任陈毅在全市集会上警告投机奸商“赶快洗手不干,否则勿谓言之不预”。但这两手收效仍微。十万银元被投机商人一吸而空,六月七日银元每元的价格涨到一千八百元人民币。陈云同志判断:“单纯的经济手段已不足以震慑敌人,必须辅以坚定的行政与法律手段。”`
)
state.setShowNarration2(true)
}
@@ -77,7 +77,7 @@ export const YinyuanGuides = ({
<ExhibitOverlay
visible={showTrendChartDetail}
src="chenyun/reflection/银元价格走势图"
description={`以上海为中心,代表"人民币信用"的绿色区域迅速覆盖全市,并向外辐射。`}
description={`以上海为中心,代表“人民币信用”的绿色区域迅速覆盖全市,并向外辐射。`}
onClick={onTrendChartClick}
onClose={onTrendChartDetailClose}
/>
@@ -47,17 +47,17 @@ export const YinyuanQuizzes = ({
{/* 多选题1 - 点击引导图标后显示 */}
<MultipleChoiceQuizPanel
visible={showQuiz1}
title="【多选】面对银元投机狂潮,你认为首要突破口是? "
title="【多选】面对银元投机狂潮,你认为首要突破口是?"
options={[
{ value: 'A', label: 'A. 政治攻势(警告、约谈)' },
{ value: 'B', label: 'B. 经济手段(抛售银元,以银元打银元)' },
{ value: 'C', label: 'C. 舆论动员(宣传人民币信用,发动群众)' },
{ value: 'B', label: 'B. 经济手段(抛售银元,以银元打银元)' },
{ value: 'C', label: 'C. 舆论动员(宣传人民币信用,发动群众)' },
]}
correctAnswer={['A', 'B', 'C']}
errorMessage="单一措施效果有限,请尝试综合施策。"
errorMessage="单一措施效果有限,请尝试综合施策。"
minSelections={1}
maxSelections={3}
minSelectionsErrorMessage="单一措施效果有限,请尝试综合施策。"
minSelectionsErrorMessage="单一措施效果有限,请尝试综合施策。"
submitText="提交"
onSubmit={onQuiz1Submit}
onError={(error) => {
@@ -77,17 +77,17 @@ export const YinyuanQuizzes = ({
{/* 多选题2(图3)- 关闭旁白后显示 */}
<MultipleChoiceQuizPanel
visible={showQuiz2}
title="【多选】面对投机商的猖狂反扑,下一步应采取哪些决定性措施?"
title="【多选】面对投机商的猖狂反扑,下一步应采取哪些决定性措施?"
options={[
{ value: 'A', label: 'A. 加大银元抛售量,耗尽投机资本' },
{ value: 'B', label: 'B. 查封投机核心场所,逮捕首要分子' },
{ value: 'C', label: 'C. 颁布法令,严禁银元计价流通' },
{ value: 'A', label: 'A. 加大银元抛售量,耗尽投机资本' },
{ value: 'B', label: 'B. 查封投机核心场所,逮捕首要分子' },
{ value: 'C', label: 'C. 颁布法令,严禁银元计价流通' },
]}
correctAnswer={['A', 'B', 'C']}
errorMessage="单一措施效果有限,请尝试综合施策。"
errorMessage="单一措施效果有限,请尝试综合施策。"
minSelections={1}
maxSelections={3}
minSelectionsErrorMessage="单一措施效果有限,请尝试综合施策。"
minSelectionsErrorMessage="单一措施效果有限,请尝试综合施策。"
submitText="提交"
onSubmit={onQuiz2Submit}
onError={(error) => {
@@ -98,8 +98,8 @@ export const YinyuanQuizzes = ({
{/* 成功提示框 - 多选题2答对后显示 */}
<SuccessAlert
visible={showSuccessAlert}
title="恭喜你, 选择正确!"
content="陈云同志的意见是:三管齐下,但核心是查封投机核心场所,逮捕首要分子。擒贼先擒王,打掉指挥部。"
title="恭喜你,选择正确!"
content="陈云同志的意见是:三管齐下,但核心是查封投机核心场所,逮捕首要分子。擒贼先擒王,打掉指挥部。"
onContinue={onSuccessAlertContinue}
/>
</>
+2 -2
View File
@@ -1,5 +1,5 @@
import { getBackgroundImage, getStaticImage } from '@/lib/utils'
import { useNavigate, useParams } from 'react-router-dom'
import { Navigate, useNavigate, useParams } from 'react-router-dom'
import { Image } from '@/components'
import { isResidenceStarted, isResidenceUnlocked } from '@/config/progression'
import { usePuzzleStore } from '@/store/puzzleStore'
@@ -124,7 +124,7 @@ const Entrance = () => {
// const decodedModuleName = decodeURIComponent(moduleName || "");
const { backgroundImage, Star } = EntranceBackgroundMap[moduleName as ModuleName] || {}
if (!Star) {
return null
return <Navigate to="/menu" replace />
}
const { Star1, Star2 } = Star
const isStar1Unlocked = isResidenceUnlocked(Star1.starName, puzzles)
+1 -1
View File
@@ -79,7 +79,7 @@ const Huangyanpei = () => {
className="absolute right-37 top-22"
src={getStaticImage('components/革命先驱故居-返回')}
button
onClick={() => navigate(`/entrance/${moduleName}`)}
onClick={() => navigate(`/${moduleName}`)}
/>
<div className="absolute right-20 top-75 flex flex-col space-y-10">
+7 -4
View File
@@ -141,7 +141,7 @@ const QUIZ_ANCHORS: QuizAnchor[] = [
},
{
value: 'D',
label: '在川沙小学堂采取"半天读书、半天做手工"的学习模式',
label: '在川沙小学堂采取“半天读书、半天做手工”的学习模式',
isCorrect: true,
},
],
@@ -152,7 +152,7 @@ const QUIZ_ANCHORS: QuizAnchor[] = [
time: 369, // 6:09
title: '',
prompt:
'【拖拽】当年黄炎培先生有推广国货的爱国情怀,如今我们的"国货"已经自强自立,请将其中的国产企业/品牌放置到空格处',
'【拖拽】当年黄炎培先生有推广国货的爱国情怀,如今我们的“国货”已经自强自立,请将其中的国产企业/品牌放置到空格处',
type: 'drag',
options: [
{ value: '特斯拉', label: '特斯拉' },
@@ -488,11 +488,14 @@ const HuangyanpeiInternalization = () => {
>
<GuideStepsItem
speaker="student"
audioSrc={getPublicPath('audio/huangyanpei/mp3_huangyanpei_28.mp3')}
audioSrc={getPublicPath('audio/huangyanpei/mp3_huangyanpei_28_1.mp3')}
>
小沪,刚刚结束的“小职成长记”太有意思了!我变成百年前的川沙少年选课程、做手工、设计国货推销方案,好像真的走进了黄炎培先生的职教世界。
</GuideStepsItem>
<GuideStepsItem speaker="student">
<GuideStepsItem
speaker="student"
audioSrc={getPublicPath('audio/huangyanpei/mp3_huangyanpei_28_2.mp3')}
>
但我还有一个疑问:先生当年倡导的“手脑并用、工学结合”,放在我们现在的中职学习中,到底有什么样的意义呢?
</GuideStepsItem>
<GuideStepsItem
+18 -23
View File
@@ -31,10 +31,9 @@ const HuangyanpeiPlace1 = () => {
// 学习引导弹窗
const [showGuideModal, setShowGuideModal] = useState(true)
// 对话结束后的学习引导弹窗
// 温馨提示弹窗
const [showAfterDialogGuide, setShowAfterDialogGuide] = useState(false)
const [afterDialogGuideShown, setAfterDialogGuideShown] = useState(false)
// 引导对话显示状态
// 温馨提示关闭后的引导对话显示状态
const [visible, setVisible] = useState(false)
// 当前激活的展品 ID(只存 ID,不直接存路径,便于作为唯一标识)
const [activeExhibitId, setActiveExhibitId] = useState<1 | null>(null)
@@ -123,12 +122,12 @@ const HuangyanpeiPlace1 = () => {
}}
/>
{/* 学习引导词 */}
{/* 学习引导 */}
<StudyGuideModal
visible={showGuideModal}
onContinue={() => {
setShowGuideModal(false)
setVisible(true)
setShowAfterDialogGuide(true)
}}
audioSrc={getPublicPath('audio/huangyanpei/mp3_huangyanpei_02.mp3')}
ailgn="left"
@@ -140,44 +139,40 @@ const HuangyanpeiPlace1 = () => {
3.所有动态光点的展品全部学习后才能进入下一个场景学习。
</StudyGuideModal>
{/* 引导 */}
<GuideSteps
visible={visible}
onVisibleChange={(nextVisible) => {
setVisible(nextVisible)
if (!nextVisible && !afterDialogGuideShown) {
setAfterDialogGuideShown(true)
setShowAfterDialogGuide(true)
}
}}
showMask={false}
>
{/* 温馨提示关闭后的引导对话 */}
<GuideSteps visible={visible} onVisibleChange={setVisible} showMask={false}>
<GuideStepsItem
speaker="student"
audioSrc={getPublicPath('audio/huangyanpei/mp3_huangyanpei_03.mp3')}
>
小沪,这栋古色古香的江南大院,就是黄炎培先生从小生活、读书的地方吗
小沪,这栋古色古香的江南大院,就是黄炎培先生从小生活、读书的地方吗?
</GuideStepsItem>
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/huangyanpei/mp3_huangyanpei_04.mp3')}
audioSrc={getPublicPath('audio/huangyanpei/mp3_huangyanpei_04_1.mp3')}
>
没错,1878年10月1日,黄炎培先生就出生在当时江苏省川沙县城关镇(现上海市浦东新区川沙镇)的这处“内史第”,宅子原是他外祖父沈树镛家,也是他后来所说的“生于斯、读于斯、长于斯”之地。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/huangyanpei/mp3_huangyanpei_04_2.mp3')}
>
1890年,川沙创办“观澜书院”,12岁的黄炎培以第一名的成绩考入,每天提着母亲亲手缝制的布书包,往返十里路求学,一点都不叫苦。1893年他去上海应县考,松江府复试名列前茅;1899年,在松江府试中更是以第一名的成绩中了秀才。
</GuideStepsItem>
</GuideSteps>
{/* 对话结束后的学习引导 */}
{/* 温馨提示 */}
<StudyGuideModal
visible={showAfterDialogGuide}
onContinue={() => setShowAfterDialogGuide(false)}
onContinue={() => {
setShowAfterDialogGuide(false)
setVisible(true)
}}
audioSrc={getPublicPath('audio/huangyanpei/mp3_huangyanpei_01.mp3')}
firstLineIndent="2em"
>
同学你好,欢迎来到“走进故居·对话”学习模块!{'\n'}
此刻你正站在黄炎培先生故居门前,接下来就跟着时光的脉络,拆解黄先生人生里五段滚烫的旅程:思想启蒙、教育实践、创立职教、延安对话、职教传薪。准备好一起走进这段炽热的岁月了吗?
此刻你正站在黄炎培先生故居门前,接下来就跟着时光的脉络,拆解黄先生人生里5段滚烫的旅程:思想启蒙、教育实践、创立职教、延安对话、职教传薪。准备好一起走进这段炽热的岁月了吗?
</StudyGuideModal>
</div>
)
+9 -3
View File
@@ -152,14 +152,20 @@ const HuangyanpeiPlace2 = () => {
</GuideStepsItem>
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/huangyanpei/mp3_huangyanpei_06.mp3')}
audioSrc={getPublicPath('audio/huangyanpei/mp3_huangyanpei_06_1.mp3')}
>
你说得特别对!1901年,黄炎培先生考入南洋公学(今上海交通大学)特班,当时的老师正是蔡元培先生。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/huangyanpei/mp3_huangyanpei_06_2.mp3')}
>
蔡先生一句“中国教育之病,在于学与术分离”,让他瞬间醍醐灌顶,明白了教育不能只谈书本、脱离实际。1902年,黄炎培去南京参加江南乡试,成功中了举人;
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/huangyanpei/mp3_huangyanpei_06_3.mp3')}
>
可就在这一年,南洋公学发生了著名的“墨水瓶风潮”,他毅然跟着蔡元培先生退学,转入蔡先生创办的中国教育会,正式开启了自己“教育救国”的实践。
</GuideStepsItem>
</GuideSteps>
+44 -37
View File
@@ -27,7 +27,7 @@ const exhibitSrcMap: Record<1 | 2 | 3, string> = {
// 常量定义
const FIRST_QUIZ_SUCCESS_CONTENT =
'1903年,黄炎培将"观澜书院"改制为"川沙小学堂",自任校长,率先打破传统经学模式,在课程表中首次列入算术、格致(近代自然科学)、体操和乡土调查四门新学科,并实行"半日授课、半日实习"的教学方式,开创了近代中国小学融入科学、体育与实践教育的新风。'
'1903年,黄炎培将“观澜书院”改制为“川沙小学堂”,自任校长,率先打破传统经学模式,在课程表中首次列入算术、格致(近代自然科学)、体操和乡土调查四门新学科,并实行“半日授课、半日实习”的教学方式,开创了近代中国小学融入科学、体育与实践教育的新风。'
const DRAG_QUIZ_SUCCESS_CONTENT = '恭喜你,选择正确!'
@@ -349,14 +349,20 @@ const HuangyanpeiPlace3 = () => {
</GuideStepsItem>
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/huangyanpei/mp3_huangyanpei_08.mp3')}
audioSrc={getPublicPath('audio/huangyanpei/mp3_huangyanpei_08_1.mp3')}
>
太有共鸣啦!1903年,24岁的黄炎培先生和好友张志鹤等人一起,把原来的“观澜书院”改成了“川沙小学堂”,他自己当校长。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/huangyanpei/mp3_huangyanpei_08_2.mp3')}
>
这所学校的课程表可太有突破性了——破天荒出现了算术、格致(也就是咱们现在说的自然科学)、体操、乡土调查这些新学科,还实行“半天读书、半天做手工”的模式,特别注重实践。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/huangyanpei/mp3_huangyanpei_08_3.mp3')}
>
当时办学经费特别紧张,没钱买课本,他就带头刻蜡版油印;缺体育器材,就带着学生用毛竹做单杠,完全是凭着一腔热血办学呢!这也是中国最早的“半日授课、半日实习”雏形。
</GuideStepsItem>
</GuideSteps>
@@ -364,7 +370,7 @@ const HuangyanpeiPlace3 = () => {
{/* 光标3的题目 */}
<SingleChoiceQuizPanel
visible={showQuiz3}
title='【单选】黄炎培1903年把川沙"观澜书院"改成"川沙小学堂"后,课程表首次出现下列哪组学科?'
title="【单选】黄炎培1903年把川沙“观澜书院”改成“川沙小学堂”后,课程表首次出现下列哪组学科?"
options={[
{ value: 'A', label: 'A. 经学、策论、八股文' },
{
@@ -375,17 +381,16 @@ const HuangyanpeiPlace3 = () => {
{ value: 'D', label: 'D. 绘画、声乐、舞蹈、戏剧' },
]}
correctAnswer="B"
errorMessage='1903年,黄炎培将"观澜书院"改制为"川沙小学堂",自任校长,率先打破传统经学模式,在课程表中首次列入算术、格致(近代自然科学)、体操和乡土调查四门新学科,并实行"半日授课、半日实习"的教学方式,开创了近代中国小学融入科学、体育与实践教育的新风。'
onWrongSelect={(value) => {
switch (value) {
case 'A':
return '经学、策论、八股文是传统书院的旧式课程,而川沙小学堂正是要打破这种传统模式。请你再想想吧!'
return '经学、策论、八股文是传统书院的旧式课程,而川沙小学堂正是要打破这种传统模式。请你再想想吧!'
case 'C':
return '外语、逻辑、演讲、社交这些科目虽然也很重要,但不是川沙小学堂首次引入的突破性学科。请你再想想吧!'
return '外语、逻辑、演讲、社交这些科目虽然也很重要,但不是川沙小学堂首次引入的突破性学科。请你再想想吧!'
case 'D':
return '绘画、声乐、舞蹈、戏剧属于艺术类课程,不是川沙小学堂在1903年首次开设的突破性学科。请你再想想吧!'
return '绘画、声乐、舞蹈、戏剧属于艺术类课程,不是川沙小学堂在1903年首次开设的突破性学科。请你再想想吧!'
default:
return '这不是川沙小学堂开设的具有突破性的学科。请你再想想吧!'
return '这不是川沙小学堂开设的具有突破性的学科。请你再想想吧!'
}
}}
onSubmit={handleQuiz3Submit}
@@ -395,7 +400,6 @@ const HuangyanpeiPlace3 = () => {
<SuccessAlert
visible={showSuccessAlert && !showDragQuiz && !successAlertContent}
content={FIRST_QUIZ_SUCCESS_CONTENT}
onContinue={handleFirstQuizSuccessContinue}
onClose={handleFirstQuizSuccessContinue}
/>
@@ -404,8 +408,8 @@ const HuangyanpeiPlace3 = () => {
visible={showDragQuiz}
zIndex={10000}
title=""
questionPrefix='【拖拽】黄炎培在"川沙小学堂"实行"半天'
questionSuffix="的模式,特别注重实践。"
questionPrefix="【拖拽】黄炎培在川沙小学堂实行半天"
questionSuffix="的模式,特别注重实践。"
options={DRAG_QUIZ_OPTIONS}
blanks={DRAG_QUIZ_BLANKS}
blankSeparators={['、']}
@@ -418,14 +422,13 @@ const HuangyanpeiPlace3 = () => {
<SuccessAlert
visible={showSuccessAlert && !showDragQuiz && !!successAlertContent}
// content={successAlertContent}
onContinue={handleDragQuizSuccessContinue}
onClose={handleDragQuizSuccessContinue}
/>
{/* 题目3(开群女学) */}
<SingleChoiceQuizPanel
visible={showQuiz3_12}
title="【单选】黄炎培和堂兄黄洪培、堂嫂陆开群一起,策划创建的开创了浦东地区女子教育的先河的学校为:"
title="【单选】黄炎培和堂兄黄洪培、堂嫂陆开群一起,策划创建的开创了浦东地区女子教育的先河的学校为:"
options={[
{ value: 'A', label: 'A. 开群女学' },
{ value: 'B', label: 'B. 上海女子师范学校' },
@@ -435,22 +438,18 @@ const HuangyanpeiPlace3 = () => {
onWrongSelect={(value) => {
switch (value) {
case 'B':
return '上海女子师范学校是上海市区的女子教育机构,并非由黄炎培等人在浦东地区创办。请你再想想吧!'
return '上海女子师范学校是上海市区的女子教育机构,并非由黄炎培等人在浦东地区创办。请你再想想吧!'
case 'C':
return '浦东中学是一所面向男女学生的综合性中学,并非专门的女子学校。请你再想想吧!'
return '浦东中学是一所面向男女学生的综合性中学,并非专门的女子学校。请你再想想吧!'
default:
return '请你再想想吧!'
return '请你再想想吧!'
}
}}
onSubmit={handleQuiz3_12Submit}
/>
{/* 题目3成功提示 */}
<SuccessAlert
visible={showQuiz3Success}
onContinue={handleQuiz3SuccessContinue}
onClose={handleQuiz3SuccessContinue}
/>
<SuccessAlert visible={showQuiz3Success} onClose={handleQuiz3SuccessContinue} />
{/* 题目4(浦东中学) */}
<SingleChoiceQuizPanel
@@ -465,22 +464,18 @@ const HuangyanpeiPlace3 = () => {
onWrongSelect={(value) => {
switch (value) {
case 'A':
return '浦东中学是一所男女同校的综合性中学,并非女子中学。请你再想想吧!'
return '浦东中学是一所男女同校的综合性中学,并非女子中学。请你再想想吧!'
case 'B':
return '浦东中学以普通中学教育为主,其定位是培养全面发展的人才,并非职业技术学校。请你再想想吧!'
return '浦东中学以普通中学教育为主,其定位是培养全面发展的人才,并非职业技术学校。请你再想想吧!'
default:
return '请你再想想吧!'
return '请你再想想吧!'
}
}}
onSubmit={handleQuiz4Submit}
/>
{/* 题目4成功提示 */}
<SuccessAlert
visible={showQuiz4Success}
onContinue={handleQuiz4SuccessContinue}
onClose={handleQuiz4SuccessContinue}
/>
<SuccessAlert visible={showQuiz4Success} onClose={handleQuiz4SuccessContinue} />
{/* 光标1、2的引导对话 */}
<GuideSteps
@@ -493,24 +488,36 @@ const HuangyanpeiPlace3 = () => {
speaker="student"
audioSrc={getPublicPath('audio/huangyanpei/mp3_huangyanpei_09.mp3')}
>
原来当年还有专门的女子学校呀?在那个年代,女孩子能上学是不是特别不容易?后来的浦东中学也是黄炎培先生办的吗?
原来当年还有专门的女子学校呀?在那个年代,女孩子能上学是不是特别不容易?后来的浦东中学也是黄炎培先生办的吗?
</GuideStepsItem>
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/huangyanpei/mp3_huangyanpei_10.mp3')}
audioSrc={getPublicPath('audio/huangyanpei/mp3_huangyanpei_10_1.mp3')}
>
你观察得特别仔细!川沙小学堂开学没多久,黄炎培就和堂兄黄洪培、堂嫂陆开群一起,策划创建了“开群女学”——这所学校专门面向社会招收女学生,还用全力支持办学、腾出自家住房的陆开群名字命名。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/huangyanpei/mp3_huangyanpei_10_2.mp3')}
>
这所学校直接开创了浦东地区女子教育的先河,让很多女孩子有了读书的机会。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/huangyanpei/mp3_huangyanpei_10_3.mp3')}
>
到了1906年,上海营造业宗师杨斯盛捐资30万两白银,在浦东六里桥购地40多亩创办浦东中学,特意邀请黄炎培担任首任校长。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/huangyanpei/mp3_huangyanpei_10_4.mp3')}
>
校舍是黄炎培亲自设计的,杨斯盛先生亲自监工,建成后有能容纳千人的大礼堂和两座教学楼,是近代上海第一所完全中学呢!
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/huangyanpei/mp3_huangyanpei_10_5.mp3')}
>
1912年,黄炎培担任江苏省署教育司司长,任职期间一直倡导实用主义教育,强调教育要和生产劳动结合起来,这也为后来的职业教育打下了坚实基础。
</GuideStepsItem>
</GuideSteps>
+24 -12
View File
@@ -240,17 +240,26 @@ const HuangyanpeiPlace4 = () => {
</GuideStepsItem>
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/huangyanpei/mp3_huangyanpei_14.mp3')}
audioSrc={getPublicPath('audio/huangyanpei/mp3_huangyanpei_14_1.mp3')}
>
没错!黄炎培先生一直怀揣着“教育救国”的理想,职业教育从来都和国家命运紧紧连在一起。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/huangyanpei/mp3_huangyanpei_14_2.mp3')}
>
1932年“一・二八”事变爆发,黄炎培立刻组织了抗日救国研究会,一边支援前线输送军需,一边维持地方秩序、稳定金融市场,还创办了“一・二八”残废院,专门收容受伤战士和无家可归的难民。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/huangyanpei/mp3_huangyanpei_14_3.mp3')}
>
“八・一三”抗战爆发后,他担任上海抗战后援会主席,组织民众设难民所、医疗队和后方医院,还成立了青年战地服务团,冒着枪林弹雨为前线军队服务。他自己不顾日机轰炸的危险,九次往返南京与上海之间,协助政府募集救国公债。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/huangyanpei/mp3_huangyanpei_14_4.mp3')}
>
正是这样的全民抗敌,粉碎了日军“一个月灭亡中国”的狂妄企图。
</GuideStepsItem>
</GuideSteps>
@@ -273,18 +282,24 @@ const HuangyanpeiPlace4 = () => {
speaker="student"
audioSrc={getPublicPath('audio/huangyanpei/mp3_huangyanpei_11.mp3')}
>
职业教育这个词是黄炎培先生创造的吗?
那职业教育这个词是黄炎培先生创造的吗?
</GuideStepsItem>
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/huangyanpei/mp3_huangyanpei_12.mp3')}
audioSrc={getPublicPath('audio/huangyanpei/mp3_huangyanpei_12_1.mp3')}
>
这个词不是他创造的,但体系是他建立的。1917年5月6日,黄炎培联合蔡元培、张謇等48位有识之士,在上海成立了“中华职业教育社”,并发表了《中华职业教育社宣言》。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/huangyanpei/mp3_huangyanpei_12_2.mp3')}
>
1918年,他在上海陆家浜创办中华职业学校,开设铁工、木工、珐琅、纽扣四科,招生广告明确写着“免学费、包分配”。首批招收78人,还推出了响亮的口号“双手万能、手脑并用”。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/huangyanpei/mp3_huangyanpei_12_3.mp3')}
>
这所学校特别厉害,教室和车间连在一起,学生可以一边学理论,一边练技能。学校铁工厂制作的台钳,后来还成了“国货精品”,甚至在巴拿马万国博览会上拿过金奖呢!
</GuideStepsItem>
</GuideSteps>
@@ -327,7 +342,7 @@ const HuangyanpeiPlace4 = () => {
{/* 题目 */}
<SingleChoiceQuizPanel
visible={showQuiz}
title={`【单选】中华职业学校首批招生78人,凭借(\xa0\xa0\xa0\xa0)口号和“免学费、包分配”的优待,开创了中国近代职业教育的先河。`}
title={`【单选】中华职业学校首批招生78人,凭借____口号和“免学费、包分配”的优待,开创了中国近代职业教育的先河。`}
options={[
{ value: 'A', label: 'A. 学而优则仕' },
{ value: 'B', label: 'B. 双手万能、手脑并用' },
@@ -343,9 +358,6 @@ const HuangyanpeiPlace4 = () => {
{/* 成功提示 */}
<SuccessAlert
visible={showSuccessAlert}
onContinue={() => {
setShowSuccessAlert(false)
}}
onClose={() => {
setShowSuccessAlert(false)
}}
+31 -24
View File
@@ -212,17 +212,26 @@ const HuangyanpeiPlace5 = () => {
</GuideStepsItem>
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/huangyanpei/mp3_huangyanpei_17.mp3')}
audioSrc={getPublicPath('audio/huangyanpei/mp3_huangyanpei_17_1.mp3')}
>
这和当时的历史背景分不开。1940年,国民党政府还坚持“攘外必先安内”的政策,让国共两党的关系越来越紧张。黄炎培等有识之士看到这种严峻形势,觉得中间派应该联合起来,为国家寻求一条和平发展的道路。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/huangyanpei/mp3_huangyanpei_17_2.mp3')}
>
于是他联系了张君劢、梁漱溟等爱国同仁,后来又联合了第三党和无党派人士,准备成立统一的同盟。刚好这时发生了震惊中外的“皖南事变”,国共矛盾进一步激化,黄炎培心里特别着急,就加紧了筹备工作。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/huangyanpei/mp3_huangyanpei_17_3.mp3')}
>
1941年3月19日,中国民主政团同盟正式成立,参加成立会的有13人,选出了5位常委,黄炎培当选为第一任主席。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/huangyanpei/mp3_huangyanpei_17_4.mp3')}
>
1945年,他还创立了民主建国会并担任第一任主委,带领大家积极投入“反内战、保和平”的民主斗争,为国家的和平稳定奔走呼号。
</GuideStepsItem>
</GuideSteps>
@@ -230,21 +239,21 @@ const HuangyanpeiPlace5 = () => {
{/* 题目1(光标1对话后) */}
<SingleChoiceQuizPanel
visible={showQuiz1}
title="【单选】黄炎培先生创建民主政团同盟等组织是为了:"
title="【单选】黄炎培先生创建民主政团同盟等组织是为了:"
options={[
{ value: 'A', label: 'A. 为国家寻求一条和平发展的道路' },
{ value: 'B', label: 'B. 支持国民党"攘外必先安内"的政策' },
{ value: 'C', label: 'C. 扩大个人政治影响力,争夺权力' },
{ value: 'B', label: 'B. 支持国民党“攘外必先安内”的政策' },
{ value: 'C', label: 'C. 扩大个人政治影响力,争夺权力' },
]}
correctAnswer="A"
onWrongSelect={(value) => {
switch (value) {
case 'B':
return '国民党政府坚持"攘外必先安内"的政策导致国共关系紧张,黄炎培正是因为反对这种政策、希望推动和平,才发起组建了民主政团同盟。请你再想想吧!'
return '国民党政府坚持“攘外必先安内”的政策导致国共关系紧张,黄炎培正是因为反对这种政策、希望推动和平,才发起组建了民主政团同盟。请你再想想吧!'
case 'C':
return '黄炎培创建民主政团同盟的出发点是民族大义与国家前途,并非为了个人政治利益。请你再想想吧!'
return '黄炎培创建民主政团同盟的出发点是民族大义与国家前途,并非为了个人政治利益。请你再想想吧!'
default:
return '请你再想想吧!'
return '请你再想想吧!'
}
}}
onSubmit={(value, isCorrect) => {
@@ -259,11 +268,6 @@ const HuangyanpeiPlace5 = () => {
{/* 题目1成功提示 */}
<SuccessAlert
visible={showQuiz1Success}
onContinue={() => {
setShowQuiz1Success(false)
// 标记展品1为已访问,修改cursor状态
markExhibitVisited(place, 1)
}}
onClose={() => {
setShowQuiz1Success(false)
// 标记展品1为已访问,修改cursor状态
@@ -295,14 +299,20 @@ const HuangyanpeiPlace5 = () => {
</GuideStepsItem>
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/huangyanpei/mp3_huangyanpei_19.mp3')}
audioSrc={getPublicPath('audio/huangyanpei/mp3_huangyanpei_19_1.mp3')}
>
完全正确!1945年7月1日,黄炎培以国民参政员的身份,带着考察团一行六人赴延安考察,中共中央领导人毛泽东、朱德、周恩来等都亲自到机场迎接。7月4日,他和毛泽东主席长谈了十几个小时,这场对话能和诸葛亮的“隆中对”相媲美。
完全正确!1945年7月1日,黄炎培以国民参政员的身份,带着考察团一行六人赴延安考察,中共中央领导人毛泽东、朱德、周恩来等都亲自到机场迎接。7月4日,他和毛泽东主席长谈了一个下午,这场对话能和诸葛亮的“隆中对”相媲美。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/huangyanpei/mp3_huangyanpei_19_2.mp3')}
>
谈话中,黄炎培提出了一个深刻的问题:“其兴也勃焉,其亡也忽焉”,也就是历代王朝逃不开的“历史周期率”。毛泽东主席当场回答:“我们已经找到新路,我们能跳出这周期律,这条新路就是民主。”
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/huangyanpei/mp3_huangyanpei_19_3.mp3')}
>
后来,黄炎培把这段对话写进了《延安归来》这本书,一夜之间就畅销十万册,成为当时最热门的书籍,而“实行民主”也被大家称为破解历史周期率的“初版答案”。
</GuideStepsItem>
</GuideSteps>
@@ -310,7 +320,7 @@ const HuangyanpeiPlace5 = () => {
{/* 题目 */}
<SingleChoiceQuizPanel
visible={showQuiz}
title={`【单选】《延安归来》中"破解历史周期率"的"初版答案"一夜畅销十万册,其提出的核心对策是:`}
title={`【单选】《延安归来》中“破解历史周期率”的“初版答案”一夜畅销十万册,其提出的核心对策是:`}
options={[
{ value: 'A', label: 'A. 实行民主' },
{ value: 'B', label: 'B. 发展经济' },
@@ -332,14 +342,11 @@ const HuangyanpeiPlace5 = () => {
{/* 成功提示 */}
<SuccessAlert
visible={showSuccessAlert}
onContinue={() => {
onClose={() => {
setShowSuccessAlert(false)
markExhibitVisited(place, 2)
triggerPuzzleAnimation(12)
}}
onClose={() => {
setShowSuccessAlert(false)
}}
/>
</div>
)
+16 -10
View File
@@ -94,12 +94,14 @@ const HuangyanpeiPlace6 = () => {
read={visited.has(1)}
/>
{Leave && (
{completed && (
<Image
className="absolute bottom-0 right-4"
src={getStaticImage('components/place/离开场景')}
button
onClick={() => {
saveHuangyanpeiStore()
setCourseTimeProgress(10)
navigate(`/${moduleName}/${starName}/home`)
}}
/>
@@ -136,19 +138,25 @@ const HuangyanpeiPlace6 = () => {
speaker="student"
audioSrc={getPublicPath('audio/huangyanpei/mp3_huangyanpei_20.mp3')}
>
新中国成立后,黄炎培先生还继续从事教育工作吗?我们现在的中职教育体系,是不是也有他当年的理念呀?
新中国成立后,黄炎培先生还继续从事教育工作吗?我们现在的中职教育体系,是不是也有他当年的理念呀?
</GuideStepsItem>
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/huangyanpei/mp3_huangyanpei_21.mp3')}
audioSrc={getPublicPath('audio/huangyanpei/mp3_huangyanpei_21_1.mp3')}
>
当然啦!1949年10月19日,71岁的黄炎培出任政务院副总理兼轻工业部部长,依然没有放下他毕生追求的职业教育事业。他把中华职教社的骨干力量全部调到北京,亲自起草《关于改革学制的决定》。
当然啦!1949年10月19日,71岁的黄炎培出任政务院副总理兼轻工业部部长,依然没有放下他毕生追求的职业教育事业。他把中华职教社的骨干力量全部调到北京,亲自起草《关于改革学制的决定》。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
第一次把“中等专业学校”正式纳入国家学制,让职业教育有了明确的国家定位。1952年,他又推动建立了上海机械学校、南京航空学校等五所中专,这些学校为国家"一五"计划输送了6.3万名技术工人,为工业发展提供了坚实的人才支撑。
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/huangyanpei/mp3_huangyanpei_21_2.mp3')}
>
第一次把”中等专业学校”正式纳入国家学制,让职业教育有了明确的国家定位。1952年,他又推动建立了上海机械学校、南京航空学校等五所中专,这些学校为国家”一五”计划输送了6.3万名技术工人,为工业发展提供了坚实的人才支撑。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
黄炎培先生曾说过:“我要把最后一滴血,滴在职业教育的讲台上。”他用一生践行了这句话,而我们现在中职教育强调的“工学结合、德技并修”,正是对他“手脑并用、注重实践”理念的传承和发展。
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/huangyanpei/mp3_huangyanpei_21_3.mp3')}
>
黄炎培先生曾说过:”我要把最后一滴血,滴在职业教育的讲台上。”他用一生践行了这句话,而我们现在中职教育强调的”工学结合、德技并修”,正是对他”手脑并用、注重实践”理念的传承和发展。
</GuideStepsItem>
</GuideSteps>
@@ -161,8 +169,6 @@ const HuangyanpeiPlace6 = () => {
// 标记为已显示过,防止再次弹出
setHasShownEnding(true)
setLeave(true)
setCourseTimeProgress(10)
saveHuangyanpeiStore()
}}
animationSpeed={200}
audioSrc={getPublicPath('audio/huangyanpei/mp3_huangyanpei_22.mp3')}
@@ -49,15 +49,15 @@ const QuizDialog = ({ visible = true, onClose, onComplete }: QuizDialogProps) =>
() => [
{
value: 'option1',
label: '加入义勇军(军事训练)',
label: 'A. 加入义勇军(军事训练)',
},
{
value: 'option2',
label: '制作军需物资',
label: 'B. 制作军需物资',
},
{
value: 'option3',
label: '宣传抗日募捐',
label: 'C. 宣传抗日募捐',
},
],
[]
@@ -121,7 +121,7 @@ const QuizDialog = ({ visible = true, onClose, onComplete }: QuizDialogProps) =>
<div className="flex-1">
<div className="text-5xl font-bold mb-6 -ml-[26px]">【单选】</div>
<div className="text-4xl leading-relaxed">
国难当头,黄先生说“职教救国”,中华职校的师生都行动起来了!你要加入哪支爱国队伍?
国难当头,黄先生说“职教救国”,中华职校的师生都行动起来了!你要加入哪支爱国队伍?
</div>
</div>
@@ -148,7 +148,7 @@ const QuizDialog = ({ visible = true, onClose, onComplete }: QuizDialogProps) =>
setSuccessVisible(true)
}}
>
{index + 1}. {option.label}
{option.label}
</div>
))}
</div>
@@ -105,9 +105,9 @@ export const QuestionModal = ({
{/* 游戏内容 */}
<div className="relative z-10 w-full h-full flex flex-col ">
{/* 标题 */}
<div className="mt-28 mb-4 text-center px-80">
<p className="text-5xl text-white mt-10 leading-tight">
黄先生说:“中国教育之病,在于学与术分离”!你看,这里有两套课程表,选一套开启学习吧。
<div className="mt-28 mb-4 text-center px-35">
<p className="text-5xl text-white mt-10 leading-18">
为打破中国教育学与术分离之病,黄先生在川沙小学堂开始了他的教育实践。你看,这里有两套课程表,选一套开启学习吧。
</p>
</div>
@@ -128,16 +128,15 @@ export const QuestionModal = ({
{/* 成功提示弹窗 */}
<SuccessAlert
visible={showSuccessAlert}
title="选对啦!"
content="这就是黄先生打破旧教育的创新,学这些实用知识,才能真正帮到家乡。"
onContinue={handleSuccessAlertClose}
title="选对啦!"
content="这就是黄先生打破旧教育的创新,学这些实用知识,才能真正帮到家乡。"
onClose={handleSuccessAlertClose}
/>
{/* 错误提示弹窗 */}
<ErrorAlert
visible={showErrorAlert}
text="哎呀选错啦!黄先生最反对只啃书本脱离实际的教育,旧课程学不到真本事,快试试另一边的新式课程吧。"
text="哎呀选错啦!黄先生最反对只啃书本脱离实际的教育,旧课程学不到真本事,快试试另一边的新式课程吧。"
onClose={handleErrorAlertClose}
/>
</div>
+28 -3
View File
@@ -1,5 +1,5 @@
import { useState, useCallback, useMemo } from 'react'
import { Title, Image, Cursor, StudyGuideModal } from '@/components'
import { Title, Image, Cursor, StudyGuideModal, Video } from '@/components'
import { BambooPuzzleGame, QuestionModal } from './components'
import { getBackgroundImage, getPublicPath, getStaticImage } from '@/lib/utils'
import { useNavigate, useParams } from 'react-router-dom'
@@ -14,7 +14,8 @@ const HuangyanpeiReflectionScene1 = () => {
moduleName: string
}>()
const [showGuideModal, setShowGuideModal] = useState(true)
const [showOpeningVideo, setShowOpeningVideo] = useState(true)
const [showGuideModal, setShowGuideModal] = useState(false)
const [showQuestionModal, setShowQuestionModal] = useState(false)
const [showGame, setShowGame] = useState(false)
const [showNextGuide, setShowNextGuide] = useState(false)
@@ -60,8 +61,32 @@ const HuangyanpeiReflectionScene1 = () => {
backgroundImage: getBackgroundImage(`huangyanpei/reflection/scene1/9_001`),
}}
>
{showOpeningVideo && (
<div className="fixed inset-0 z-10000 w-full h-full bg-black">
<Video
src={getPublicPath('video/reflection/huangyanpei_tiwu_00.mp4')}
autoPlay
playsInline
showSkipButton
skipText="跳过"
onEnded={() => {
setShowOpeningVideo(false)
setShowGuideModal(true)
}}
onSkip={() => {
setShowOpeningVideo(false)
setShowGuideModal(true)
}}
onError={() => {
setShowOpeningVideo(false)
setShowGuideModal(true)
}}
/>
</div>
)}
<Title
title="跨越时空"
title="川沙小学堂"
subtitle={subtitle}
subtitleClassName={subtitleClassName}
className="absolute top-10 left-1/2 -translate-x-1/2"
+1 -4
View File
@@ -135,7 +135,7 @@ const HuangyanpeiReflectionScene2 = () => {
}}
>
<Title
title="跨越时空"
title="川沙小学堂"
subtitle={subtitle}
subtitleClassName={subtitleClassName}
className="absolute top-10 left-1/2 -translate-x-1/2"
@@ -235,9 +235,6 @@ const HuangyanpeiReflectionScene2 = () => {
visible={showQuizSuccess}
title=""
content="恭喜你!选择正确。不愧是从川沙小学堂走出来!"
onContinue={() => {
setShowQuizSuccess(false)
}}
onClose={() => {
setShowQuizSuccess(false)
}}
@@ -201,10 +201,6 @@ const ChoiceDialog1 = ({ visible = true, onClose, onSelect }: ChoiceDialog1Props
visible={showSuccessAlert}
title="任务1 正确反馈"
content="答对啦!黄先生说“教育要为国家培养实干人才”,浦东中学就是要教大家学真本事,不是只为当官。"
onContinue={() => {
setShowSuccessAlert(false)
onClose?.()
}}
onClose={() => {
setShowSuccessAlert(false)
onClose?.()
@@ -220,10 +216,6 @@ const ChoiceDialog1 = ({ visible = true, onClose, onSelect }: ChoiceDialog1Props
<SuccessAlert
visible={showSuccessAlert}
content="答对啦!黄先生说“教育要为国家培养实干人才”,浦东中学就是要教大家学真本事,不是只为当官。"
onContinue={() => {
setShowSuccessAlert(false)
onClose?.()
}}
onClose={() => {
setShowSuccessAlert(false)
onClose?.()
@@ -108,8 +108,9 @@ const ChoiceDialog2 = ({ visible = true, onClose, onSelect }: ChoiceDialog2Props
visible={showGuide}
onContinue={() => setShowGuide(false)}
verticalAlign="center"
firstLineIndent="2em"
>
在浦东中学,你可以选未来的方向啦!不管选哪个,黄先生都会支持,因为“三百六十行,行行出状元”!
在浦东中学,你可以选未来的方向啦!不管选哪个,黄先生都会支持,因为“三百六十行,行行出状元”!
</StudyGuideModal>
{/* 返回按钮 */}
@@ -175,7 +176,6 @@ const ChoiceDialog2 = ({ visible = true, onClose, onSelect }: ChoiceDialog2Props
visible={showSuccessAlert}
title="选得好!"
content="不管是做工匠还是做买卖,学好本事就能报效国家,这个选择会影响你在中华职业学校的专业哦!"
onContinue={handleSuccessAlertContinue}
onClose={handleSuccessAlertContinue}
/>
</div>
@@ -114,7 +114,7 @@ const Scene2QuizDialog = ({ visible = true, onClose, onComplete }: Scene2QuizDia
<div className="flex-1">
<div className="text-5xl font-bold mb-6 -ml-[26px]">【单选】</div>
<div className="text-4xl leading-relaxed">
杨斯盛先生近期监工浦东中学建设,有一份上月的工人工钱支出表需要核对,你来告诉我答案吧?
杨斯盛先生近期监工浦东中学建设,有一份上月的工人工钱支出表需要核对,你来告诉我答案吧?
</div>
</div>
+29 -16
View File
@@ -10,6 +10,7 @@ import { QuizDialog } from './components'
import { useHuangyanpeiStore } from '@/store/huangyanpeiStore'
import { setCourseTimeProgress } from '@/lib/utils/api-common'
import { useCursorController } from '@/hooks/useCursorController'
import { saveHuangyanpeiStore } from '@/store/huangyanpeiStore'
const starName = 'huangyanpei'
@@ -66,7 +67,7 @@ const HuangyanpeiReflectionScene3 = () => {
}}
>
<Title
title="跨越时空"
title="川沙小学堂"
subtitle={subtitle}
subtitleClassName={subtitleClassName}
className="absolute top-10 left-1/2 -translate-x-1/2"
@@ -84,15 +85,15 @@ const HuangyanpeiReflectionScene3 = () => {
<Image
className="absolute left-5 top-5"
src={getStaticImage('huangyanpei/reflection/components/喇叭1')}
button
onClick={() => {
if (isEnabled(3)) {
setShowQuizDialog(true)
} else {
setShowErrorAlertForQuiz(true)
}
}}
aria-disabled={!isEnabled(3)}
// button
// onClick={() => {
// if (isEnabled(3)) {
// setShowQuizDialog(true)
// } else {
// setShowErrorAlertForQuiz(true)
// }
// }}
// aria-disabled={!isEnabled(3)}
/>
{/* 第一段引导 */}
@@ -129,6 +130,20 @@ const HuangyanpeiReflectionScene3 = () => {
disabled={!isEnabled(2)}
/>
{/* 喇叭光标 */}
<Cursor
className="absolute left-31 top-9"
onClick={() => {
if (isEnabled(3)) {
setShowQuizDialog(true)
} else {
setShowErrorAlertForQuiz(true)
}
}}
read={visited.has(3)}
disabled={!isEnabled(3)}
/>
{/* 组装台钳互动弹窗 */}
<ViseAssemblyDialog
visible={showDialog}
@@ -143,7 +158,7 @@ const HuangyanpeiReflectionScene3 = () => {
{/* 多选题答题框 */}
<MultipleChoiceQuizPanel
visible={showQuiz}
title="【多选】 制作台钳需要哪些工具?"
title="【多选】制作台钳需要哪些工具?"
options={[
{ value: 'A', label: 'A. 锯子' },
{ value: 'B', label: 'B. 毛笔' },
@@ -173,13 +188,10 @@ const HuangyanpeiReflectionScene3 = () => {
<SuccessAlert
visible={showSuccessAlert}
title="满分!"
content="黄先生说“手脑并用”,懂了理论,也要动手实践。"
onContinue={() => {
setShowSuccessAlert(false)
markReflectionCursorVisited(scene, 2)
}}
content="黄先生说“手脑并用”,懂了理论,也要动手实践。"
onClose={() => {
setShowSuccessAlert(false)
markReflectionCursorVisited(scene, 2)
}}
/>
@@ -227,6 +239,7 @@ const HuangyanpeiReflectionScene3 = () => {
showSkipButton
onEnded={() => {
setShowVideo(false)
saveHuangyanpeiStore()
setCourseTimeProgress(11)
navigate(`/${moduleName}/${starName}/home`)
}}
+22 -26
View File
@@ -11,6 +11,7 @@ import { usePuzzleAnimation } from '@/contexts/PuzzleAnimationContext'
import { useHuangyanpeiStore } from '@/store/huangyanpeiStore'
import { setCourseTimeProgress } from '@/lib/utils/api-common'
import { useCursorController } from '@/hooks/useCursorController'
import { saveHuangyanpeiStore } from '@/store/huangyanpeiStore'
const starName = 'huangyanpei'
@@ -30,7 +31,6 @@ const HuangyanpeiReflectionScene4 = () => {
const videoRef = useRef<VideoRef>(null)
const [showGuide1, setShowGuide1] = useState(true)
const [showErrorAlertForQuiz, setShowErrorAlertForQuiz] = useState(false)
const [showSpeakerGuide, setShowSpeakerGuide] = useState(false)
// 使用持久化的reflection cursor状态
const scene = 'scene4'
@@ -49,16 +49,8 @@ const HuangyanpeiReflectionScene4 = () => {
//喇叭考题弹窗
const [showQuizDialog, setShowQuizDialog] = useState(false)
// 处理成功提示的继续和关闭
const handleSuccessAlertContinue = useCallback(() => {
setShowSuccessAlert(false)
// 更新visited状态
markReflectionCursorVisited(scene, 1)
}, [markReflectionCursorVisited, scene])
const handleSuccessAlertClose = useCallback(() => {
setShowSuccessAlert(false)
// 更新visited状态
markReflectionCursorVisited(scene, 1)
}, [markReflectionCursorVisited, scene])
@@ -82,7 +74,7 @@ const HuangyanpeiReflectionScene4 = () => {
}}
>
<Title
title="跨越时空"
title="川沙小学堂"
subtitle={subtitle}
subtitleClassName={subtitleClassName}
className="absolute top-10 left-1/2 -translate-x-1/2"
@@ -91,7 +83,20 @@ const HuangyanpeiReflectionScene4 = () => {
<Image
className="absolute left-5 top-5"
src={getStaticImage('huangyanpei/reflection/components/喇叭2')}
button
// button
// onClick={() => {
// if (isEnabled(3)) {
// setShowQuizDialog(true)
// } else {
// setShowErrorAlertForQuiz(true)
// }
// }}
// aria-disabled={!isEnabled(3)}
/>
{/* 喇叭光标 */}
<Cursor
className="absolute left-31 top-9"
onClick={() => {
if (isEnabled(3)) {
setShowQuizDialog(true)
@@ -99,7 +104,8 @@ const HuangyanpeiReflectionScene4 = () => {
setShowErrorAlertForQuiz(true)
}
}}
aria-disabled={!isEnabled(3)}
read={visited.has(3)}
disabled={!isEnabled(3)}
/>
<Image
@@ -134,7 +140,7 @@ const HuangyanpeiReflectionScene4 = () => {
{/* 选择题1:中华职校商科的核心教学目标 */}
<MultipleChoiceQuizPanel
visible={showQuiz1}
title={`【多选】中华职校商科的核心教学目标是什么? `}
title={`【多选】中华职校商科的核心教学目标是什么?`}
options={[
{
value: 'A',
@@ -169,8 +175,7 @@ const HuangyanpeiReflectionScene4 = () => {
<SuccessAlert
visible={showSuccessAlert}
title="满分!"
content="诚信是经商之本, 推广国货是商科学生的责任, 这正是黄先生的办学初衷。"
onContinue={handleSuccessAlertContinue}
content="诚信是经商之本,推广国货是商科学生的责任,这正是黄先生的办学初衷。"
onClose={handleSuccessAlertClose}
/>
@@ -181,8 +186,6 @@ const HuangyanpeiReflectionScene4 = () => {
onComplete={() => {
// 完成拖拽后更新visited状态
markReflectionCursorVisited(scene, 2)
setShowSpeakerGuide(true)
triggerPuzzleAnimation(15)
}}
/>
@@ -209,7 +212,7 @@ const HuangyanpeiReflectionScene4 = () => {
>
终于到中华职业学校啦!这里是中国近代职教的摇篮,“免学费、包分配”,还能学真手艺,快来练就一技之长。
{'\n'}
现在要完成你的第一个任务:推销中华职校生产的“国货精品”——手工纽扣和纯棉毛巾!当年学生们就是带着这些国货,走街串巷做推销呢!
现在要完成你的第一个任务:推销中华职校生产的“国货精品”——纯棉毛巾!当年学生们就是带着这些国货,走街串巷做推销呢!
</StudyGuideModal>
{showVideo && (
@@ -222,6 +225,7 @@ const HuangyanpeiReflectionScene4 = () => {
playsInline
onEnded={() => {
setShowVideo(false)
saveHuangyanpeiStore()
setCourseTimeProgress(11)
navigate(`/${moduleName}/${starName}/home`)
}}
@@ -237,14 +241,6 @@ const HuangyanpeiReflectionScene4 = () => {
}}
/>
<StudyGuideModal
visible={showSpeakerGuide}
onContinue={() => setShowSpeakerGuide(false)}
verticalAlign="center"
>
请点击左上角的喇叭。
</StudyGuideModal>
{/* 错误提示:请先完成前两个任务 */}
<ErrorAlert
visible={showErrorAlertForQuiz}
@@ -91,19 +91,19 @@ const TowelSalesDialog = ({ visible = true, onClose, onComplete }: TowelSalesDia
() => [
{
value: 'option1',
label: '这是中华职校生产的国货,质量比洋货还好!',
label: 'A.这是中华职校生产的国货,质量比洋货还好!',
},
{
value: 'option2',
label: '价格便宜,量大从优,诚信经营绝不掺假',
label: 'B.价格便宜,量大从优,诚信经营绝不掺假',
},
{
value: 'option3',
label: '买国货就是支持实业救国,为国家出一份力!',
label: 'C.买国货就是支持实业救国,为国家出一份力!',
},
{
value: 'option4',
label: '我只管赚钱,东西好不好不管哦',
label: 'D.我只管赚钱,东西好不好不管哦',
},
],
[]
@@ -290,7 +290,7 @@ const TowelSalesDialog = ({ visible = true, onClose, onComplete }: TowelSalesDia
showSubmitButton={false}
className="w-full"
renderOption={(option, isUsed) => {
const index = options.findIndex((opt) => opt.value === option.value)
// const index = options.findIndex((opt) => opt.value === option.value)
return (
<div
className={cn(
@@ -300,7 +300,7 @@ const TowelSalesDialog = ({ visible = true, onClose, onComplete }: TowelSalesDia
: 'border-[#79471f] bg-black/20 text-white cursor-move hover:border-[#f0be75] hover:shadow-[0_12px_40px_rgba(240,190,117,0.7)]'
)}
>
{index + 1}. {option.label}
{option.label}
</div>
)
}}
@@ -331,8 +331,8 @@ const TowelSalesDialog = ({ visible = true, onClose, onComplete }: TowelSalesDia
{/* 成功提示 */}
<SuccessAlert
title="恭喜你, 推销语设计成功!"
content="既突出了国货优势,又传递了爱国情怀,当年的商科学生也是这么做的"
title="恭喜你, 推销语设计成功!"
content="既突出了国货优势,又传递了爱国情怀,当年的商科学生也是这么做的。"
visible={successVisible}
onContinue={handleSuccessContinue}
/>
+108
View File
@@ -0,0 +1,108 @@
import { useMemo } from 'react'
import { useNavigate } from 'react-router-dom'
import { getBackgroundImage, getStaticImage } from '@/lib/utils'
import { Image } from '@/components'
import { usePuzzleStore } from '@/store/puzzleStore'
const residences = [
{ key: 'maozedong', name: '毛泽东', left: '30%', top: '35%' },
{ key: 'sunzhongshan', name: '孙中山', left: '12%', top: '51%' },
{ key: 'luxun', name: '鲁迅', left: '26.3%', top: '74%' },
{ key: 'huangyanpei', name: '黄炎培', left: '44%', top: '67%' },
{ key: 'nieer', name: '聂耳', left: '62%', top: '60%' },
{ key: 'zhangleping', name: '张乐平', left: '73%', top: '33%' },
{ key: 'rongdesheng', name: '荣德生', left: '90%', top: '45%' },
{ key: 'chenyun', name: '陈云', left: '80%', top: '76%' },
]
interface HexagonProgressBarProps {
progress: number
}
const HexagonProgressBar = ({ progress }: HexagonProgressBarProps) => {
const percent = Math.min(100, Math.max(0, progress))
const text = percent >= 100 ? '进度100%' : `进度${Math.round(percent)}%`
const color = percent >= 100 ? '#90B86B' : percent >= 50 ? '#EEB362' : '#E0E0E0'
const hex = 'polygon(0% 50%, 3% 0%, 97% 0%, 100% 50%, 97% 100%, 3% 100%)'
return (
<div className="relative" style={{ width: '260px', height: '50px' }}>
<div className="absolute inset-0" style={{ clipPath: hex, backgroundColor: 'white' }} />
<div className="absolute" style={{ left: 4, top: 4, right: 4, bottom: 4, clipPath: hex }}>
<div className="absolute inset-0" style={{ backgroundColor: '#757575' }} />
{percent > 0 && (
<div
className="absolute left-0 top-0 bottom-0 overflow-hidden"
style={{ width: `${percent}%` }}
>
<div className="absolute inset-0" style={{ backgroundColor: color }} />
</div>
)}
</div>
<div
className="absolute inset-0 flex items-center justify-center text-white text-2xl font-bold pointer-events-none"
style={{ textShadow: '0 2px 4px rgba(0,0,0,0.6), 0 1px 2px rgba(0,0,0,0.8)', zIndex: 10 }}
>
{text}
</div>
</div>
)
}
const LearningMap = () => {
const navigate = useNavigate()
const getPuzzleCollectProgress = usePuzzleStore((s) => s.getPuzzleCollectProgress)
const progressMap = useMemo(
() => Object.fromEntries(residences.map((r) => [r.key, getPuzzleCollectProgress(r.key)])),
[getPuzzleCollectProgress]
)
return (
<div
className="relative w-full h-full bg-cover bg-center bg-no-repeat"
style={{ backgroundImage: getBackgroundImage('learning-map/底图') }}
>
<img
className="absolute top-84 left-55"
src={getStaticImage('learning-map/箭头指引')}
alt=""
/>
<Image
className="absolute right-8 top-8 z-10"
src={getStaticImage('components/关闭')}
alt="关闭"
button
onClick={() => navigate('/menu')}
/>
<div className="absolute inset-0">
{residences.map(({ key, name, left, top }) => (
<div
key={key}
className="absolute -translate-x-1/2 -translate-y-1/2"
style={{ left, top }}
>
<Image src={getStaticImage(`learning-map/${name}`)} alt={name} />
<div className="mb-4 flex justify-center">
<HexagonProgressBar progress={progressMap[key] ?? 0} />
</div>
<div className="flex justify-center gap-6">
<Image
src={getStaticImage('learning-map/追光入口')}
alt="追光"
button
onClick={() => navigate(`/map/spotlight/${key}`)}
/>
</div>
</div>
))}
</div>
</div>
)
}
export default LearningMap
+87
View File
@@ -0,0 +1,87 @@
import { useState } from 'react'
import { Navigate, useNavigate, useParams } from 'react-router-dom'
import { Image } from '@/components'
import { getStaticImage } from '@/lib/utils'
const residenceNameMap: Record<string, string> = {
maozedong: '毛泽东',
sunzhongshan: '孙中山',
luxun: '鲁迅',
huangyanpei: '黄炎培',
nieer: '聂耳',
zhangleping: '张乐平',
rongdesheng: '荣德生',
chenyun: '陈云',
}
const SpotlightMap = () => {
const navigate = useNavigate()
const { residence } = useParams()
const [showDetail, setShowDetail] = useState(false)
const residenceName = residence ? residenceNameMap[residence] : undefined
if (!residenceName) {
return <Navigate to="/learning-map" replace />
}
return (
<div className="relative w-full h-full bg-black">
<Image
className="absolute right-8 top-8 z-10"
src={getStaticImage('components/返回')}
alt="返回"
button
onClick={() => navigate('/learning-map')}
/>
<div className="flex h-full w-full items-center justify-center">
<img
draggable={false}
className="max-h-full max-w-full object-contain"
src={getStaticImage(`learning-map/spotlight/追光${residenceName}`)}
alt={`${residenceName}追光`}
/>
</div>
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2">
<div className="relative w-218 group">
<div
className="absolute left-30 -top-90 cursor-pointer w-160 h-210"
onClick={() => setShowDetail(true)}
></div>
<Image
className="pointer-events-none transition-transform duration-200 group-hover:scale-110"
src={getStaticImage('learning-map/spotlight/点击')}
alt="详情"
/>
</div>
</div>
{showDetail && (
<div className="fixed inset-0 z-20 bg-black">
<Image
className="absolute right-8 top-8 z-10"
src={getStaticImage('components/关闭')}
alt="关闭"
button
onClick={(e) => {
e.stopPropagation()
setShowDetail(false)
}}
/>
<div className="flex h-full w-full items-center justify-center">
<img
draggable={false}
className="max-h-full max-w-full object-contain"
src={getStaticImage(`learning-map/spotlight/${residenceName}详情`)}
alt={`${residenceName}详情`}
/>
</div>
</div>
)}
</div>
)
}
export default SpotlightMap
+2 -2
View File
@@ -85,7 +85,7 @@ const Luxun = () => {
className="absolute right-37 top-22"
src={getStaticImage('components/革命先驱故居-返回')}
button
onClick={() => navigate(`/entrance/${moduleName}`)}
onClick={() => navigate(`/${moduleName}`)}
/>
<div className="absolute right-20 top-75 flex flex-col space-y-10">
@@ -106,7 +106,7 @@ const Luxun = () => {
src={getStaticImage('star/二-解锁')}
onClick={() => {
if (!reflectionUnlocked) return
navigate(`/${moduleName}/${starName}/${reflectionRoute || 'reflection'}`)
navigate(`/${moduleName}/${starName}/reflection`)
}}
/>
{/* 内化 */}
+8 -6
View File
@@ -79,7 +79,7 @@ const QUIZ_ANCHORS: QuizAnchor[] = [
},
{
id: 'quiz-2',
time: 60 * 5 + 2, // 5:02
time: 60 * 5 + 3, // 5:02
title: '',
prompt: '【多选】对于鼓励和培养青年一代,鲁迅先生的做法是:',
type: 'multiple',
@@ -107,7 +107,7 @@ const QUIZ_ANCHORS: QuizAnchor[] = [
value: 'A',
label: '寄意寒星荃不察,我以我血荐轩辕',
isCorrect: true,
explanation: '本句出自鲁迅的《自题小像》',
explanation: '本句出自鲁迅的《自题小像》。',
},
{
value: 'B',
@@ -141,12 +141,12 @@ const QUIZ_ANCHORS: QuizAnchor[] = [
blankPrefixes: [',如', '如'],
blankSeparators: [''],
options: [
{ value: '七首', label: '七首' },
{ value: '匕首', label: '匕首' },
{ value: '投枪', label: '投枪' },
{ value: '清泉', label: '清泉' },
],
correctAnswersMap: {
'blank-1': '七首',
'blank-1': '匕首',
'blank-2': '投枪',
},
explanation: '“清泉”不是鲁迅文章的意象。请你再想想吧!',
@@ -473,10 +473,12 @@ const LuxunInternalization = () => {
<Narration
visible={showNarration}
audioSrc={getPublicPath('audio/luxun/mp3_luxun_35.mp3')}
text={`走进上海鲁迅故居的红砖小楼,解锁了先生从绍兴到上海的文学足迹,我们读懂了他以笔为刃的战斗姿态,也见证了他与进步人士的革命情谊。那些针砭时弊的杂文、借古讽今的篇章,还有藏在6平方米客房里的红色坚守,都在诉说着先生的热忱。1907年,鲁迅先生在《文化偏至论》里写到:“是故将生存两间,角逐列国是务,其首在立人,人立而后凡事举;若其道术,乃必尊个性而张精神。”`}
animationSpeed={200}
text={`走进上海鲁迅故居的红砖小楼,解锁了先生从绍兴到上海的文学足迹,我们读懂了他以笔为刃的战斗姿态,也见证了他与进步人士的革命情谊。那些针砭时弊的杂文、借古讽今的篇章,还有藏在6平方米客房里的红色坚守,都在诉说着先生的热忱。1907年,鲁迅先生在《文化偏至论》里写到:“是故将生存两间,角逐列国是务,其首在立人,人立而后凡事举;若其道术,乃必尊个性而张精神。”先生认为,中华民族要屹立于世界民族之林,“首在立人”,“立人”思想从此贯穿在鲁迅先生的人生和作品中。`}
onClose={() => {
setShowNarration(false)
setShowVideo(true)
setVisible(true)
// setShowVideo(true)
// handleVideoCanPlay()
}}
/>
+13 -19
View File
@@ -217,15 +217,18 @@ const LuxunPlace1 = () => {
zIndex={10000}
>
<GuideStepsItem speaker="student" audioSrc={getPublicPath('audio/luxun/mp3_luxun_03.mp3')}>
小沪!我知道鲁迅是1933年搬到大陆新村9号的,那在这之前他为什么会选择来到上海,又有哪些思想转变呢?
小沪,我知道鲁迅是1933年搬到大陆新村9号的,那在这之前他为什么会选择来到上海,又有哪些思想转变呢?
</GuideStepsItem>
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/luxun/mp3_luxun_04.mp3')}>
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/luxun/mp3_luxun_04_1.mp3')}>
你问得很关键!从1892-1898年少年时期在绍兴三味书屋求学积淀,到1919-1923年于北京八道湾参加新文化运动、酝酿《阿Q正传》,再到1926年9-12月赴厦门大学任教期间聚焦底层创作杂文。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
1927年3-9月广州白云楼整理旧作定稿《朝花夕拾》部分篇章,鲁迅的创作足迹跨越南北,而1927年是鲁迅思想的重要转折点,这一年他在广州亲历了“四一二”反革命政变,亲眼看见进步青年和革命志士倒在血泊中,曾经对“温和改革”的幻想彻底破灭。
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/luxun/mp3_luxun_04_2.mp3')}>
1927年3-9月广州白云楼整理旧作定稿《朝花夕拾》部分篇章,鲁迅的创作足迹跨越南北。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/luxun/mp3_luxun_04_3.mp3')}>
而1927年是鲁迅思想的重要转折点。这一年他在广州亲历了“四一二”反革命政变,亲眼看见进步青年和革命志士倒在血泊中,曾经对“温和改革”的幻想彻底破灭。
</GuideStepsItem>
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/luxun/mp3_luxun_04_4.mp3')}>
此前他写下《狂人日记》《阿Q正传》批判封建礼教,参加语丝社针砭时弊,但始终在“彷徨”中摸索救国之路。广州的血雨腥风,让他认清了反动派的真面目。
</GuideStepsItem>
</GuideSteps>
@@ -255,14 +258,14 @@ const LuxunPlace1 = () => {
<GuideStepsItem speaker="student" audioSrc={getPublicPath('audio/luxun/mp3_luxun_05.mp3')}>
这张照片也是鲁迅在上海期间的住所吗?
</GuideStepsItem>
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/luxun/mp3_luxun_06.mp3')}>
不错!1927年10月,鲁迅带着许广平来到上海,先住进了虹口区横浜路景云里。在景云里的两年半,他结识了冯雪峰等共产党员,参与发起中国左翼作家联盟,从"独战的英雄"转变为革命文艺战线的旗手。
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/luxun/mp3_luxun_06_1.mp3')}>
不错!1927年10月,鲁迅带着许广平来到上海,先住进了虹口区横浜路景云里。在景云里的两年半,他结识了冯雪峰等共产党员,参与发起中国左翼作家联盟,从“独战的英雄”转变为革命文艺战线的旗手。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/luxun/mp3_luxun_06_2.mp3')}>
这段时间他写下《三闲集》《二心集》,表达他在当时历史条件下针对社会现象、文化问题和论战的看法与社会批判,此间还翻译了大量马列文艺理论著作,为此后的战斗打下了坚实思想基础。
</GuideStepsItem>
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/luxun/mp3_luxun_07.mp3')}>
鲁迅在发起成立左联后,为躲避国民党当局监视,1930年5月,与许广平、周海婴迁居到拉摩斯公寓。1933年4月,受战火纷扰影响,为安全及健康计,同时也为了更方便开展革命文化工作,鲁迅以"内山书店职员"的名义,搬到了大陆新村9号。
鲁迅在发起成立左联后,为躲避国民党当局监视,1930年5月,与许广平、周海婴迁居到拉摩斯公寓。1933年4月,受战火纷扰影响,为安全及健康计,同时也为了更方便开展革命文化工作,鲁迅以“内山书店职员”的名义,搬到了大陆新村9号。
</GuideStepsItem>
<GuideStepsItem speaker="student" audioSrc={getPublicPath('audio/luxun/mp3_luxun_08.mp3')}>
这个过程的确十分曲折。
@@ -283,7 +286,7 @@ const LuxunPlace1 = () => {
{ id: 'b', label: '于北京八道湾参加新文化运动、酝酿《阿Q正传》' },
{ id: 'a', label: '在绍兴三味书屋求学积淀' },
{ id: 'd', label: '在广州白云楼整理旧作定稿《朝花夕拾》部分篇章' },
{ id: 'c', label: '赴厦门大学任教,聚焦底层创作杂文' },
{ id: 'c', label: '赴厦门大学任教,聚焦底层创作杂文' },
]}
correctMatches={[
{ leftId: '1', rightId: 'a' },
@@ -305,11 +308,6 @@ const LuxunPlace1 = () => {
<SuccessAlert
title="恭喜你,匹配正确!"
visible={showMatchQuizSuccess}
onContinue={() => {
setShowMatchQuizSuccess(false)
// 标记展品1为已访问
markExhibitVisited(place, 1)
}}
onClose={() => {
setShowMatchQuizSuccess(false)
// 标记展品1为已访问
@@ -350,10 +348,6 @@ const LuxunPlace1 = () => {
<SuccessAlert
title="恭喜你,选择正确!"
visible={showExhibit2QuizSuccess}
onContinue={() => {
setShowExhibit2QuizSuccess(false)
markExhibitVisited(place, 2)
}}
onClose={() => {
setShowExhibit2QuizSuccess(false)
markExhibitVisited(place, 2)
+9 -17
View File
@@ -229,12 +229,12 @@ const LuxunPlace2 = () => {
zIndex={10000}
>
<GuideStepsItem speaker="student" audioSrc={getPublicPath('audio/luxun/mp3_luxun_09.mp3')}>
小沪,这里的《且介亭杂文》就是鲁迅先生在大陆新村9号创作的吧,有人将先生的杂文评价为"匕首与投枪",《且介亭杂文》中有具体体现吗?
小沪,这里的《且介亭杂文》就是鲁迅先生在大陆新村9号创作的吧,有人将先生的杂文评价为“匕首与投枪”,《且介亭杂文》中有具体体现吗?
</GuideStepsItem>
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/luxun/mp3_luxun_10.mp3')}>
当然有!20世纪30年代,国民党"文化围剿"愈烈,进步思想遭严控,鲁迅在大陆新村9号创作出《且介亭杂文》等集子。这些杂文聚焦文化乱象与社会沉疴,篇幅精炼却直击要害,像匕首般刺穿虚伪表象,似投枪般直戳问题核心。
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/luxun/mp3_luxun_10_1.mp3')}>
当然有!20世纪30年代,国民党“文化围剿”愈烈,进步思想遭严控,鲁迅在大陆新村9号创作出《且介亭杂文》等集子。这些杂文聚焦文化乱象与社会沉疴,篇幅精炼却直击要害,像匕首般刺穿虚伪表象,似投枪般直戳问题核心。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/luxun/mp3_luxun_10_2.mp3')}>
用锋利文字与黑暗势力对峙,成为那个时代最具战斗力的思想武器,“匕首与投枪”的赞誉恰是其战斗性的生动写照。
</GuideStepsItem>
</GuideSteps>
@@ -249,10 +249,10 @@ const LuxunPlace2 = () => {
zIndex={10000}
>
<GuideStepsItem speaker="student" audioSrc={getPublicPath('audio/luxun/mp3_luxun_13.mp3')}>
那《南腔北调集》这个名字又有什么深意呢?
那《南腔北调集》这个名字又有什么深意呢?
</GuideStepsItem>
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/luxun/mp3_luxun_14.mp3')}>
这是鲁迅的自嘲,他曾说“我不会说绵软的苏白,不会打响亮的京腔,不入调,不入流”,所以戏称自己的文章是“南腔北调”。这本书里的《〈守常全集》题记》,赞美了李大钊同志,说“他的遗文却将永驻,因为这是先驱者的遗产,革命史上的丰碑。”
这是鲁迅的自嘲,他曾说“我不会说绵软的苏白,不会打响亮的京腔,不入调,不入流”,所以戏称自己的文章是“南腔北调”。这本书里的《〈守常全集〉题记》,赞美了李大钊同志,说“他的遗文却将永驻,因为这是先驱者的遗产,革命史上的丰碑。”
</GuideStepsItem>
</GuideSteps>
{/* 光标3的引导对话 */}
@@ -268,13 +268,13 @@ const LuxunPlace2 = () => {
<GuideStepsItem speaker="student" audioSrc={getPublicPath('audio/luxun/mp3_luxun_11.mp3')}>
我们初中学的《中国人失掉自信力了吗》就是收录于《且介亭杂文》中的!
</GuideStepsItem>
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/luxun/mp3_luxun_12.mp3')}>
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/luxun/mp3_luxun_12_1.mp3')}>
那你应该更有体会!这篇《中国人失掉自信力了吗》是将“匕首与投枪”的特质发挥到极致。当时国民党当局接连丧权辱国,一些文人却散布“中国人失掉自信力”的悲观论调,迎合反动派的卖国行径。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/luxun/mp3_luxun_12_2.mp3')}>
鲁迅直接驳斥这种谬论,一针见血指出:“我们从古以来,就有埋头苦干的人,有拼命硬干的人,有为民请命的人,有舍身求法的人……这就是中国的脊梁。”
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/luxun/mp3_luxun_12_3.mp3')}>
文字如利刃般戳破虚假论调,揭露了反动派掩盖自身无能、麻痹民众的阴谋,唤醒国人的民族自信,尽显战斗文字的锋芒与力量。
</GuideStepsItem>
</GuideSteps>
@@ -330,14 +330,6 @@ const LuxunPlace2 = () => {
<SuccessAlert
visible={showDragQuizSuccess}
onContinue={() => {
setShowDragQuizSuccess(false)
setDragQuizValue([])
setDragQuizError('')
// 题目完成后,继续对话到步骤3
setGuide3Visible(true)
setGuide3Step(3)
}}
onClose={() => {
setShowDragQuizSuccess(false)
setDragQuizValue([])
+8 -8
View File
@@ -286,10 +286,10 @@ const LuxunPlace3 = () => {
zIndex={10000}
>
<GuideStepsItem speaker="student" audioSrc={getPublicPath('audio/luxun/mp3_luxun_15.mp3')}>
小沪,书架上还有《故事新编》,这不是历史小说吗?鲁迅为什么要在这个时期写古代故事呢?
小沪,书架上还有《故事新编》,这不是历史小说吗?鲁迅为什么要在这个时期写古代故事呢?
</GuideStepsItem>
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/luxun/mp3_luxun_16.mp3')}>
《故事新编》是鲁迅酝酿十几年的作品,最终在大陆新村9号完成定稿。表面上写的是神话传说和历史典故,实则句句都在影射现实。当时反动派妄图用复古思想麻痹民众,鲁迅就用"故事新编"的方式,借古讽今,唤醒民众。
《故事新编》是鲁迅酝酿十几年的作品,最终在大陆新村9号完成定稿。表面上写的是神话传说和历史典故,实则句句都在影射现实。当时反动派妄图用复古思想麻痹民众,鲁迅就用“故事新编”的方式,借古讽今,唤醒民众。
</GuideStepsItem>
</GuideSteps>
@@ -311,10 +311,10 @@ const LuxunPlace3 = () => {
<GuideStepsItem speaker="student" audioSrc={getPublicPath('audio/luxun/mp3_luxun_17.mp3')}>
这本书里面还收录了《理水》,是大禹治水的故事吗?
</GuideStepsItem>
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/luxun/mp3_luxun_18.mp3')}>
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/luxun/mp3_luxun_18_1.mp3')}>
不错,文章写的是大禹治水“三过家门而不入”的上古传说,实则藏着对现实的尖锐针砭。当时反动派统治腐朽,面对内忧外患只会粉饰太平,还有一群空谈理论、脱离实际的“学者”混淆视听。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/luxun/mp3_luxun_18_2.mp3')}>
大禹带领民众跋山涉水、实地勘察,摒弃“堵”的旧法改用“疏”的良方,历经千辛万苦终平水患。这哪里是写古代治水?分明是在赞颂脚踏实地、敢破敢立的实干精神,讽刺那些庸碌无为的当权者与空谈家,用远古的正义之光映照现实的黑暗。
</GuideStepsItem>
</GuideSteps>
@@ -387,10 +387,10 @@ const LuxunPlace3 = () => {
zIndex={10000}
>
<GuideStepsItem speaker="student" audioSrc={getPublicPath('audio/luxun/mp3_luxun_19.mp3')}>
我还看到书架上有很多翻译作品,这也是他在这儿完成的吗?
我还看到书架上有很多翻译作品,这也是他在这儿完成的吗?
</GuideStepsItem>
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/luxun/mp3_luxun_20.mp3')}>
没错!鲁迅在这儿翻译了《死魂灵》《表》等大量外国作品。他说“没有拿来的,人不能自成为新人”,就是想通过翻译,为中国文艺引进新思想、新方法,让更多人看到世界的真相,找到救国的道路。
没错!鲁迅在这儿翻译了《死魂灵》《表》等大量外国作品。他说“没有拿来的,人不能自成为新人”,就是想通过翻译,为中国文艺引进新思想、新方法,让更多人看到世界的真相,找到救国的道路。
</GuideStepsItem>
</GuideSteps>
@@ -475,10 +475,10 @@ const LuxunPlace3 = () => {
zIndex={10000}
>
<GuideStepsItem speaker="student" audioSrc={getPublicPath('audio/luxun/mp3_luxun_24.mp3')}>
原来这本书不仅是译文集,更是两人革命友谊和共同信仰的见证!
原来这本书不仅是译文集,更是两人革命友谊和共同信仰的见证!
</GuideStepsItem>
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/luxun/mp3_luxun_25.mp3')}>
太对了!鲁迅和瞿秋白的故事不仅在于《海上述林》的字里行间,这座小楼的三楼也藏着他们为了革命事业并肩战斗的初心。
太对了!鲁迅和瞿秋白的故事不仅在于《海上述林》的字里行间,这座小楼的三楼也藏着他们为了革命事业并肩战斗的初心。
</GuideStepsItem>
</GuideSteps>
</div>
+21 -20
View File
@@ -57,7 +57,7 @@ const LuxunPlace5 = () => {
// 使用持久化的访问状态
const place = 'place4'
const { markExhibitVisited, getVisitedExhibits, setLastVisitedPlace } = useLuxunStore()
const { markExhibitVisited, getVisitedExhibits } = useLuxunStore()
// 从 store 获取已访问的展品,转换为 Set 用于兼容现有代码
const visitedExhibits = getVisitedExhibits(place)
@@ -122,11 +122,11 @@ const LuxunPlace5 = () => {
onWrongSelect={(value: string) => {
switch (value) {
case 'B':
return '这不是毛泽东主席的原话。请你再想想吧!'
return '这不是毛泽东主席的原话。请你再想想吧!'
case 'C':
return '这不是毛泽东主席的原话。请你再想想吧!'
return '这不是毛泽东主席的原话。请你再想想吧!'
default:
return '请你再想想吧!'
return '请你再想想吧!'
}
}}
onSubmit={(value: string, isCorrect: boolean) => {
@@ -140,12 +140,6 @@ const LuxunPlace5 = () => {
<SuccessAlert
visible={activeVideoSuccess}
onContinue={() => {
setActiveVideoSuccess(false)
const cb = resumeVideoCallback
setResumeVideoCallback(null)
cb?.()
}}
onClose={() => {
setActiveVideoSuccess(false)
const cb = resumeVideoCallback
@@ -220,7 +214,6 @@ const LuxunPlace5 = () => {
button
onClick={() => {
// 完成后先播放视频,不立即跳转页面
saveLuxunStore()
setShowEndingVideo(true)
}}
/>
@@ -229,6 +222,13 @@ const LuxunPlace5 = () => {
<ExhibitOverlay
visible={activeExhibitId !== null}
src={activeExhibitId ? exhibitSrcMap[activeExhibitId] : ''}
audioSrc={
activeExhibitId == 1
? getPublicPath(`audio/luxun/mp3_luxun_39.mp3`)
: activeExhibitId == 2
? getPublicPath(`audio/luxun/mp3_luxun_40.mp3`)
: undefined
}
onOpen={() => {
if (activeExhibitId === 1) {
triggerPuzzleAnimation(11)
@@ -276,12 +276,12 @@ const LuxunPlace5 = () => {
zIndex={10000}
>
<GuideStepsItem speaker="student" audioSrc={getPublicPath('audio/luxun/mp3_luxun_26.mp3')}>
你说的是这间小小的客房吗,这里为什么会被称为“红色庇护所”呢?
你说的是这间小小的客房吗,这里为什么会被称为“红色庇护所”呢?
</GuideStepsItem>
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/luxun/mp3_luxun_27.mp3')}>
你可别小看这间仅6平方米的客房,在白色恐怖笼罩的1933年至1936年,这里是很多共产党人的"安全港湾"。1933年,瞿秋白夫妇第三次到鲁迅家避难,就秘密住进了这里。
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/luxun/mp3_luxun_27_1.mp3')}>
你可别小看这间仅6平方米的客房,在白色恐怖笼罩的1933年至1936年,这里是很多共产党人的“安全港湾”。1933年,瞿秋白夫妇第三次到鲁迅家避难,就秘密住进了这里。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/luxun/mp3_luxun_27_2.mp3')}>
当时反动派到处搜捕共产党人,鲁迅冒着生命危险掩护他们,两人常彻夜长谈。瞿秋白的夫人杨之华回忆道:“秋白和鲁迅在一起,往往感觉夜太短。深刻的友谊的交谈,从政治到文艺,从实际到理论,从希腊到莫斯科,轻松,愉快,活泼。”
</GuideStepsItem>
</GuideSteps>
@@ -345,10 +345,10 @@ const LuxunPlace5 = () => {
<GuideStepsItem speaker="student" audioSrc={getPublicPath('audio/luxun/mp3_luxun_28.mp3')}>
除了瞿秋白,还有其他共产党人来过这里吗
</GuideStepsItem>
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/luxun/mp3_luxun_29.mp3')}>
当然!1936年4月,中共中央特派员冯雪峰到上海开展工作,也先住进了这间客房。冯雪峰把红军长征胜利、遵义会议召开的消息告诉鲁迅,还带来了毛泽东同志提出的抗日民族统一战线主张。
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/luxun/mp3_luxun_29_1.mp3')}>
当然!1936年4月,中共中央特派员冯雪峰到上海开展工作,也住进了这间客房。冯雪峰把红军长征胜利、遵义会议召开的消息告诉鲁迅,还带来了毛泽东同志提出的抗日民族统一战线主张。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/luxun/mp3_luxun_29_2.mp3')}>
鲁迅听后格外振奋,激动地说“我做一个小兵还是胜任的,用笔!”,这句话也成了他晚年投身抗日救亡运动的誓言。
</GuideStepsItem>
</GuideSteps>
@@ -378,6 +378,7 @@ const LuxunPlace5 = () => {
setEndingGuideVisible(visible)
if (!visible && moduleName) {
// 对话结束后返回鲁迅首页
saveLuxunStore()
setCourseTimeProgress(7)
navigate(`/${moduleName}/${starName}/home`)
}
@@ -385,10 +386,10 @@ const LuxunPlace5 = () => {
showMask={false}
zIndex={20010}
>
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/luxun/mp3_luxun_30.mp3')}>
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/luxun/mp3_luxun_30_1.mp3')}>
我们已经了解了鲁迅居住在上海大陆新村9号时的重要事件。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/luxun/mp3_luxun_30_2.mp3')}>
接下来,请你接过先生手中的“笔”,走出上海,跨越时空,追寻他从绍兴三味书屋到上海的创作足迹——解锁每个地标背后的故事,填充文字里的时代线索,在互动中,读懂先生笔墨间的温度与力量。
</GuideStepsItem>
</GuideSteps>
+31 -5
View File
@@ -1,6 +1,6 @@
import { useState, useEffect } from 'react'
import { useNavigate, useParams } from 'react-router-dom'
import { Image, StudyGuideModal } from '@/components'
import { useLocation, useNavigate, useParams } from 'react-router-dom'
import { Image, StudyGuideModal, Video } from '@/components'
import SuccessAlert from '@/components/layout/alert/success-alert'
import { getBackgroundImage, getPublicPath, getStaticImage } from '@/lib/utils'
import { usePuzzleAnimation } from '@/contexts/PuzzleAnimationContext'
@@ -66,6 +66,9 @@ const cityConfig = [
const LuxunReflectionScene1 = () => {
const navigate = useNavigate()
const location = useLocation()
const fromReflectionSubpage = !!(location.state as { fromReflectionSubpage?: boolean } | null)
?.fromReflectionSubpage
const { moduleName } = useParams<{
moduleName: string
}>()
@@ -82,8 +85,9 @@ const LuxunReflectionScene1 = () => {
const [currentCityAlertId, setCurrentCityAlertId] = useState<number | null>(null)
// 是否全部完成
const [allCompleted, setAllCompleted] = useState(false)
const [showOpeningVideo, setShowOpeningVideo] = useState(!fromReflectionSubpage)
// 学习引导对话是否显示
const [showGuideModal, setShowGuideModal] = useState(true)
const [showGuideModal, setShowGuideModal] = useState(false)
const progress = clickedCities.size
// 当前应当点亮的城市索引(按照 cityConfig 顺序)
@@ -141,6 +145,30 @@ const LuxunReflectionScene1 = () => {
backgroundImage: getBackgroundImage(`${starName}/reflection/scene1/底图`),
}}
>
{showOpeningVideo && (
<div className="fixed inset-0 z-10000 w-full h-full bg-black">
<Video
src={getPublicPath('video/reflection/luxun_tiwu_00.mp4')}
autoPlay
playsInline
showSkipButton
skipText="跳过"
onEnded={() => {
setShowOpeningVideo(false)
setShowGuideModal(true)
}}
onSkip={() => {
setShowOpeningVideo(false)
setShowGuideModal(true)
}}
onError={() => {
setShowOpeningVideo(false)
setShowGuideModal(true)
}}
/>
</div>
)}
{/* 离开按钮 */}
<Image
className="absolute right-5 top-5 z-10"
@@ -296,7 +324,6 @@ const LuxunReflectionScene1 = () => {
// title={`你找到了${currentCity.location}!`}
content={`${currentCity.time},\n${currentCity.description}`}
onClose={handleCityAlertClose}
onContinue={handleCityAlertClose}
/>
)}
@@ -305,7 +332,6 @@ const LuxunReflectionScene1 = () => {
visible={showSuccessAlert}
title="你找到了鲁迅创作的足迹,接下来,填充文字里的故事吧!"
onClose={handleSuccessAlertClose}
onContinue={handleSuccessAlertClose}
/>
{/* 学习引导对话 */}
{/* <GuideSteps
+5 -4
View File
@@ -310,7 +310,9 @@ const LuxunReflectionScene2 = () => {
button
onClick={() => {
if (moduleName && starName) {
navigate(`/${moduleName}/${starName}/reflection`)
navigate(`/${moduleName}/${starName}/reflection`, {
state: { fromReflectionSubpage: true },
})
}
}}
/>
@@ -544,7 +546,6 @@ const LuxunReflectionScene2 = () => {
visible={showSuccessAlert}
title={successMessage}
onClose={handleSuccessAlertClose}
onContinue={handleSuccessAlertClose}
/>
{/* 学习引导对话 */}
@@ -557,7 +558,7 @@ const LuxunReflectionScene2 = () => {
audioChannelId="luxun-reflection-scene2-guide"
>
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/luxun/mp3_luxun_32.mp3')}>
这些线索藏在先生的文字里,长按卡片拖到对应地标旁,还原创作故事!
这些线索藏在先生的文字里,长按卡片拖到对应地标旁,还原创作故事!
</GuideStepsItem>
</GuideSteps>
@@ -571,7 +572,7 @@ const LuxunReflectionScene2 = () => {
audioChannelId="luxun-reflection-scene2-completion-guide"
>
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/luxun/mp3_luxun_32_02.mp3')}>
你读懂了鲁迅文字与土地的联结,再深入看看文字里的初心!
你读懂了鲁迅文字与土地的联结,再深入看看文字里的初心!
</GuideStepsItem>
</GuideSteps>
</div>
+5 -7
View File
@@ -6,6 +6,7 @@ import { SingleChoiceQuizPanel } from '@/components/layout/quiz'
import SuccessAlert from '@/components/layout/alert/success-alert'
import { usePuzzleAnimation } from '@/contexts/PuzzleAnimationContext'
import { setCourseTimeProgress } from '@/lib/utils/api-common'
import { saveLuxunStore } from '@/store/luxunStore'
const starName = 'luxun'
@@ -53,7 +54,7 @@ const LuxunReflectionScene3 = () => {
}, 500)
return () => clearTimeout(timer)
}
}, [showSummary])
}, [showSummary, triggerPuzzleAnimation])
// 点击"鲁迅1"的处理
const handleLuxun1Click = () => {
@@ -340,6 +341,7 @@ const LuxunReflectionScene3 = () => {
src={getStaticImage('components/place/离开场景')}
button
onClick={() => {
saveLuxunStore()
setCourseTimeProgress(8)
navigate(`/${moduleName}/${starName}/home`)
}}
@@ -377,7 +379,7 @@ const LuxunReflectionScene3 = () => {
visible={showQuiz3}
title={'【单选】先生辗转多地创作,核心追求是:'}
options={[
{ value: 'A', label: 'A. 唤醒国人,改变社会' },
{ value: 'A', label: 'A. 唤醒国人,改变社会' },
{ value: 'B', label: 'B. 只为个人成名' },
]}
correctAnswer="A"
@@ -394,10 +396,6 @@ const LuxunReflectionScene3 = () => {
setShowSuccessAlert(false)
setSuccessMessage('')
}}
onContinue={() => {
setShowSuccessAlert(false)
setSuccessMessage('')
}}
/>
{/* 通关提示对话 */}
@@ -414,7 +412,7 @@ const LuxunReflectionScene3 = () => {
zIndex={10000}
>
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/luxun/mp3_luxun_33_02.mp3')}>
恭喜你读懂了鲁迅的文字与初心,愿这份精神能伴你前行!
恭喜你读懂了鲁迅的文字与初心,愿这份精神能伴你前行!
</GuideStepsItem>
</GuideSteps>
+1 -1
View File
@@ -40,7 +40,7 @@ const Maozedong = () => {
className="absolute right-37 top-22"
src={getStaticImage('components/革命先驱故居-返回')}
button
onClick={() => navigate(`/entrance/revolutionary`)}
onClick={() => navigate(`/revolutionary`)}
/>
<div className="absolute right-20 top-75 flex flex-col space-y-10">
+3 -1
View File
@@ -15,6 +15,7 @@ import { getBackgroundImage, getPublicPath, getStaticImage } from '@/lib/utils'
import { GuideSteps, GuideStepsItem } from '@/components'
import { BasketQuizPanel, DragQuizPanel, SuccessAlert } from '@/components/layout'
import { usePuzzleAnimation } from '@/contexts/PuzzleAnimationContext'
import { setCourseTimeProgress } from '@/lib/utils/api-common'
const starName = 'maozedong'
@@ -386,7 +387,7 @@ const MaozedongInternalization = () => {
{/* 过渡语 */}
<Narration
visible={showTransition}
audioSrc={getPublicPath('audio/maozedong/mp3_mzd_27.mp3')}
audioSrc={getPublicPath('audio/maozedong/mp3_mzd_28.mp3')}
onClose={() => {
setShowTransition(false)
setIntroDialogVisible(true)
@@ -512,6 +513,7 @@ const MaozedongInternalization = () => {
animationSpeed={200}
onContinue={() => {
setShowCompleted(false)
setCourseTimeProgress(3)
navigate('/menu')
}}
/>
+3 -3
View File
@@ -168,10 +168,10 @@ const MaozedongPlace1 = () => {
speaker="student"
audioSrc={getPublicPath('audio/maozedong/mp3_mzd_03.mp3')}
>
那参加中共一大时,毛主席对未来有什么想法吗?
那参加中共一大时,毛主席对未来有什么想法吗?
</GuideStepsItem>
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/maozedong/mp3_mzd_04.mp3')}>
当时全国只有50多个党员,毛主席所在的湖南也才几个。但他觉得,重要的是把马克思主义和中国的实际情况结合起来。就像种田,种子再好,也得看土壤——中国的“土壤”就是农民占大多数,这是和苏联不一样的地方。
当时全国只有50多个党员,毛主席所在的湖南也才几个。但他觉得,重要的是把马克思主义和中国的实际情况结合起来。就像种田,种子再好,也得看土壤——中国的”土壤”就是农民占大多数,这是和苏联不一样的地方。
</GuideStepsItem>
</GuideSteps>
@@ -307,7 +307,7 @@ const MaozedongPlace1 = () => {
firstLineIndent="2em"
>
{`同学你好,欢迎来到“走进故居·对话”学习模块!
此刻你正处在毛泽东旧居陈列馆里,接下来请跟着时光的脚步,一同回望毛泽东主席人生中4段风云激荡的岁月:摸索方向 确立信仰、播撒红色火种、吹响合作号角、唤起工农群众。准备好推开这扇门,走进那段滚烫的峥嵘岁月了吗?`}
此刻你正站在上海毛泽东旧居陈列馆的门口,接下来请跟着时光的脚步,一同回望毛泽东主席人生中4段风云激荡的岁月:摸索方向·确立信仰、播撒红色火种、吹响合作号角、唤起工农群众。准备好推开这扇门,走进那段滚烫的峥嵘岁月了吗?`}
</StudyGuideModal>
</div>
)
+10 -5
View File
@@ -235,8 +235,7 @@ const MaozedongPlace2 = () => {
speaker="student"
audioSrc={getPublicPath('audio/maozedong/mp3_mzd_05.mp3')}
>
中共一大定了“推翻资产阶级政权”的目标,但中共二大提
出了“反帝反封建的民主革命纲领”,为什么会有这样的转变呢?
中共一大定了“推翻资产阶级政权”的目标,但中共二大提出了“反帝反封建的民主革命纲领”,为什么会有这样的转变呢?
</GuideStepsItem>
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/maozedong/mp3_mzd_06.mp3')}>
中共一大时我们中国共产党刚成立,像刚学走路的孩子,
@@ -247,12 +246,18 @@ const MaozedongPlace2 = () => {
speaker="student"
audioSrc={getPublicPath('audio/maozedong/mp3_mzd_07.mp3')}
>
“眼前的敌人”?
“眼前的敌人”?
</GuideStepsItem>
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/maozedong/mp3_mzd_08.mp3')}>
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/maozedong/mp3_mzd_08_1.mp3')}
>
对!想想看1922年长沙的码头工人罢工,安源的路矿工人罢工,他们的敌人是谁?是外国资本家,是军阀。中共二大时毛主席虽然没有出席,但完全赞同“最低纲领”——打倒军阀、推翻帝国主义压迫,实现民族独立和人民民主。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/maozedong/mp3_mzd_08_2.mp3')}
>
这就像盖房子,得先把地基打好;等打倒了军阀和帝国主义,再谈社会主义也不迟。
</GuideStepsItem>
</GuideSteps>
+17 -7
View File
@@ -11,7 +11,7 @@ import {
} from '@/components'
import { useNavigate, useParams } from 'react-router-dom'
import { getBackgroundImage, getStaticImage, getPublicPath } from '@/lib/utils'
import { useEffect, useMemo, useRef, useState } from 'react'
import { useMemo, useRef, useState } from 'react'
import Cursor from '@/components/cursor'
import { useCursorController } from '@/hooks/useCursorController'
import SuccessAlert from '@/components/layout/alert/success-alert'
@@ -49,8 +49,9 @@ const MaozedongPlace3 = () => {
[getVisited]
)
const [visited, setVisited] = useState<Set<1 | 2 | 3>>(() => new Set(persistedVisited))
// 入场旁白
// 入场旁白(当前页面生命周期内仅展示一次)
const [narrationVisible, setNarrationVisible] = useState(false)
const narrationShownRef = useRef(false)
// 光标3对应的对话状态
const [guide3Visible, setGuide3Visible] = useState(false)
@@ -87,11 +88,15 @@ const MaozedongPlace3 = () => {
const completed = progressCount >= total
const subtitle = completed ? '已完成' : `${progressCount}/${total}`
useEffect(() => {
if (visited.has(1) && visited.has(2)) {
// 关闭展品1/2时,如果另一个已学且旁白未展示过,则弹出
const tryShowNarration = (justVisitedId: 1 | 2) => {
if (narrationShownRef.current) return
const otherId = justVisitedId === 1 ? 2 : 1
if (visited.has(otherId)) {
narrationShownRef.current = true
setNarrationVisible(true)
}
}, [visited])
}
if (!moduleName) return null
@@ -105,8 +110,10 @@ const MaozedongPlace3 = () => {
<Narration
visible={narrationVisible}
onClose={() => setNarrationVisible(false)}
animationSpeed={200}
audioSrc={getPublicPath('audio/maozedong/mp3_mzd_12.mp3')}
text={`本党(国民党)为革命党,凡有利于革命的可采用,有害于革命的即应摒弃。——(毛泽东在国民党“一大”上的发言)`}
text="本党(国民党)为革命党,凡有利于革命的可采用,有害于革命的即应摒弃。"
attribution="——毛泽东在国民党“一大”上的发言"
/>
{/* 标题 */}
@@ -221,6 +228,9 @@ const MaozedongPlace3 = () => {
saveMaozedongStore()
return next
})
if (currentId === 1 || currentId === 2) {
tryShowNarration(currentId)
}
}
setActiveExhibitId(null)
}}
@@ -295,7 +305,7 @@ const MaozedongPlace3 = () => {
/>
<SuccessAlert
content="你理解了中共三大和国民党合作的核心内容及历史意义"
content="你理解了中共三大和国民党合作的核心内容及历史意义。"
visible={successAlert3Visible}
onContinue={() => {
setSuccessAlert3Visible(false)
+41 -26
View File
@@ -18,6 +18,7 @@ import SuccessAlert from '@/components/layout/alert/success-alert'
import ScaledPortal from '@/components/layout/ScaledPortal'
import { useMaozedongStore, saveMaozedongStore } from '@/store/maozedongStore'
import { usePuzzleAnimation } from '@/contexts/PuzzleAnimationContext'
import { setCourseTimeProgress } from '@/lib/utils/api-common'
// starName 固定为 maozedong
const starName = 'maozedong'
@@ -149,6 +150,8 @@ const MaozedongPlace4 = () => {
button
onClick={() => {
if (moduleName && starName) {
saveMaozedongStore()
setCourseTimeProgress(1)
navigate(`/${moduleName}/${starName}/home`)
}
}}
@@ -242,13 +245,18 @@ const MaozedongPlace4 = () => {
>
看来中共四大明确提出了“无产阶级领导权”和“工农联盟”,这和毛主席的实践有关吗?
</GuideStepsItem>
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/maozedong/mp3_mzd_16.mp3')}>
太有关系了!中共三大后毛主席到上海工作了一段时间,看到工人运动轰轰烈烈,
但农民还是“沉默的大多数”。
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/maozedong/mp3_mzd_16_1.mp3')}
>
太有关系了!中共三大后毛主席到上海工作了一段时间,看到工人运动轰轰烈烈,但农民还是“沉默的大多数”。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
后来毛主席回湖南,专门跑了湘潭、湘乡、衡山、
醴陵、长沙五县,发现农民不仅能革命,而且革命性极强——他们被剥削得最狠, 反抗起来也最坚决!
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/maozedong/mp3_mzd_16_2.mp3')}
>
后来毛主席回湖南,专门跑了湘潭、湘乡、衡山、醴陵、长沙五县,发现农民不仅能革命,而且革命性极强——他们被剥削得最狠,
反抗起来也最坚决!
</GuideStepsItem>
</GuideSteps>
@@ -271,8 +279,7 @@ const MaozedongPlace4 = () => {
那中共四大为什么召开呢?
</GuideStepsItem>
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/maozedong/mp3_mzd_14.mp3')}>
因为此前中共三大虽然确定了国共合作的方针,但未明确解决
“谁领导革命”的问题。我们必须要明确“谁领导革命”“依靠谁革命” 这两个基本问题。
因为此前中共三大虽然确定了国共合作的方针,但未明确解决“谁领导革命”的问题。我们必须要明确“谁领导革命”“依靠谁革命”这两个基本问题。
</GuideStepsItem>
</GuideSteps>
@@ -294,13 +301,17 @@ const MaozedongPlace4 = () => {
>
所以毛主席觉得,光靠工人不够,得把农民拉进革命队伍?
</GuideStepsItem>
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/maozedong/mp3_mzd_18.mp3')}>
对!无产阶级要领导革命,必须联合农民——他们是工人阶级的“天然盟友”。
四大虽然还没有明确提出“农村包围城市”,但已经意识到农民问题的重要性。
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/maozedong/mp3_mzd_18_01.mp3')}
>
对!无产阶级要领导革命,必须联合农民——他们是工人阶级的“天然盟友”。四大虽然还没有明确提出“农村包围城市”,但已经意识到农民问题的重要性。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
后来毛主席常说:“国民革命的中心问题,就是农民问题,一切都要靠农民问题的解决。”
这句话的种子,就是在四大前后埋下的。
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/maozedong/mp3_mzd_18_02.mp3')}
>
后来毛主席常说:“国民革命的中心问题,就是农民问题,一切都要靠农民问题的解决。”这句话的种子,就是在四大前后埋下的。
</GuideStepsItem>
</GuideSteps>
@@ -323,20 +334,24 @@ const MaozedongPlace4 = () => {
speaker="student"
audioSrc={getPublicPath('audio/maozedong/mp3_mzd_19.mp3')}
>
为有牺牲多壮志,敢教日月换新天。小沪,通过您的讲解,
我明白了中共一大到四大是在艰难探索中不断前进的——从建党的理想,
到制定民主革命纲领;从国共合作,到发现农民的力量。这些探索对我们今天有什么启示呢?
为有牺牲多壮志,敢教日月换新天。小沪,通过您的讲解,我明白了中共一大到四大是在艰难探索中不断前进的——从建党的理想,到制定民主革命纲领;从国共合作,到发现农民的力量。这些探索对我们今天有什么启示呢?
</GuideStepsItem>
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/maozedong/mp3_mzd_20.mp3')}>
同学问到了根子上!革命就像爬山,一开始可能看不清山顶的样子,
但只要沿着正确的方向,一步一步扎扎实实地走,总会离目标越来越近。
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/maozedong/mp3_mzd_20_1.mp3')}
>
同学问到了根子上!革命就像爬山,一开始可能看不清山顶的样子,但只要沿着正确的方向,一步一步扎扎实实地走,总会离目标越来越近。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
你们现在学历史,不能只记年代和事件,更要理解“为什么”——
为什么马克思主义要和中国实际相结合?为什么农民是革命的重要力量?
把这些道理想通了,将来不管做什么事,都能脚踏实地。
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/maozedong/mp3_mzd_20_2.mp3')}
>
你们现在学历史,不能只记年代和事件,更要理解“为什么”——为什么马克思主义要和中国实际相结合?为什么农民是革命的重要力量?把这些道理想通了,将来不管做什么事,都能脚踏实地。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/maozedong/mp3_mzd_20_3.mp3')}
>
“实事求是”这四个字,是我们党一路走来的法宝,希望你也能记住。
</GuideStepsItem>
</GuideSteps>
@@ -456,7 +471,7 @@ const MaozedongPlace4 = () => {
<SingleChoiceQuizPanel
visible={quizVisible && quizStep === 1}
zIndex={10000}
title="【单选】中共四大召开的时间是( )"
title="【单选】中共四大召开的时间是:"
options={[
{ value: 'A', label: 'A.1923年6月' },
{ value: 'B', label: 'B.1924年1月' },
+5 -19
View File
@@ -10,8 +10,10 @@ import { SuccessAlert } from '@/components/layout'
import { usePuzzleAnimation } from '@/contexts/PuzzleAnimationContext'
import { cn, getStaticImage } from '@/lib/utils'
import { getPublicPath } from '@/lib/utils'
import { setCourseTimeProgress } from '@/lib/utils/api-common'
import { useCallback, useMemo, useRef, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { saveMaozedongStore } from '@/store/maozedongStore'
// 光标组件 - 带旋转动画
// Safari 兼容:使用 onTouchEnd 和 -webkit-tap-highlight-color
@@ -38,7 +40,7 @@ const Cursor = ({ className, onClick }: { className?: string; onClick: () => voi
const firstQuizOptions = [
{
value: 'A',
label: 'A. 基层情况对决策至关重要,关乎无产阶级领导权、工农联盟议题能否贴合实际',
label: 'A. 优先高层协商',
},
{
value: 'B',
@@ -104,12 +106,6 @@ const FirstQuizCursor = ({ resume, unmute, onCorrect }: QuizCursorProps) => {
<SuccessAlert
visible={showSuccessAlert}
content="基于对史实的理解,毛主席的选择应当是深入基层、收集真实情况,再推进各项筹备与行动。"
onContinue={() => {
if (hasResumedRef.current) return
hasResumedRef.current = true
setShowSuccessAlert(false)
resume()
}}
onClose={() => {
if (hasResumedRef.current) return
hasResumedRef.current = true
@@ -152,12 +148,6 @@ const SecondQuizCursor = ({ resume, unmute, onCorrect }: QuizCursorProps) => {
visible={showSuccessAlert}
title="恭喜你,选择正确!"
content="当时毛泽东采用动员方式,以“钟英”为代号发出《关于召开四大致各地党组织的信》和《关于召开四大的通知》,在通知中阐明意义,各地党组织积极响应,为四大议题积累丰富实践依据,推动筹备深入。"
onContinue={() => {
if (hasResumedRef.current) return
hasResumedRef.current = true
setShowSuccessAlert(false)
resume()
}}
onClose={() => {
if (hasResumedRef.current) return
hasResumedRef.current = true
@@ -199,12 +189,6 @@ const EndingQuizCursor = ({ resume, unmute }: Omit<QuizCursorProps, 'onCorrect'>
visible={showSuccessAlert}
title="恭喜你,选择正确!"
content="毛泽东回湘后,深入农村,了解农民疾苦与诉求,为后续《湖南农民运动考察报告》奠基,让四大工农联盟思想在实践中持续深化,开始了领导工农运动的新征程。"
onContinue={() => {
if (hasResumedRef.current) return
hasResumedRef.current = true
setShowSuccessAlert(false)
resume()
}}
onClose={() => {
if (hasResumedRef.current) return
hasResumedRef.current = true
@@ -506,6 +490,8 @@ const Home = () => {
text="毛泽东在中共四大筹备期间的三次重要抉择,均锚定真实实践、扎根基层,为四大首次明确无产阶级领导权、工农联盟筑牢基础。虽因健康未参会,但其筹备工作与后续调研,如纽带般连接理论与实践,让党的革命理念在历史浪潮中,朝着正确方向坚定前行,成为早期共产党人探索革命道路的生动注脚,影响深远,照耀后世奋进征程。"
onContinue={() => {
setEndingVisible(false)
saveMaozedongStore()
setCourseTimeProgress(2)
navigate(`/${moduleName}/maozedong/home`)
}}
/>
+1 -1
View File
@@ -38,7 +38,7 @@ const NieerHome = () => {
className="absolute right-37 top-22"
src={getStaticImage('components/革命先驱故居-返回')}
button
onClick={() => navigate(`/entrance/${moduleName}`)}
onClick={() => navigate(`/${moduleName}`)}
/>
<div className="absolute right-20 top-75 flex flex-col space-y-10">
+8 -6
View File
@@ -14,6 +14,7 @@ import { GuideSteps, GuideStepsItem } from '@/components'
import { SuccessAlert } from '@/components/layout'
import { MultipleChoiceQuizPanel, SingleChoiceQuizPanel } from '@/components/layout/quiz'
import { usePuzzleAnimation } from '@/contexts/PuzzleAnimationContext'
import { setCourseTimeProgress } from '@/lib/utils/api-common'
const starName = 'nieer'
@@ -62,7 +63,7 @@ const QUIZ_ANCHORS: QuizAnchor[] = [
},
{
id: 'quiz-2',
time: 60 * 4 + 39, // 4:39
time: 60 * 4 + 39.2, // 4:39
type: 'multiple',
title: '【多选】音乐作品要传递时代力量与精神,需具备哪些特质?',
options: [
@@ -80,7 +81,7 @@ const QUIZ_ANCHORS: QuizAnchor[] = [
},
{
id: 'quiz-3',
time: 60 * 6 + 31.5, // 6:31
time: 60 * 6 + 31.2, // 6:31
type: 'multiple',
title: '【多选】聂耳的音乐作品具有鲜明的“人民性”,主要体现在:',
options: [
@@ -345,13 +346,13 @@ const NieerInternalization = () => {
zIndex={60}
audioChannelId="nieer-internalization-narration-post"
>
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/nieer/mp3_nieer_39.mp3')}>
的确如戏!这份精神,不仅是音乐创作的指引,更是广大青年学子成长路上的精神灯塔,指引大家在专业学习中精益求精,在青春岁月里奋勇前行。
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/nieer/mp3_nieer_39_1.mp3')}>
的确如此!这份精神,不仅是音乐创作的指引,更是广大青年学子成长路上的精神灯塔,指引大家在专业学习中精益求精,在青春岁月里奋勇前行。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/nieer/mp3_nieer_39_2.mp3')}>
此刻,我们仿佛依然站在聂耳旧居的亭子间里,仿佛还能看到先生熬夜打磨旋律的身影,感受到他心中滚烫的家国情怀。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/nieer/mp3_nieer_39_3.mp3')}>
现在,就请你静下心来,把对聂耳的崇敬、对国歌精神的体悟和对自己未来的期许,一字一句写成一封信,完成与先生跨越时空的对话吧!
</GuideStepsItem>
</GuideSteps>
@@ -376,6 +377,7 @@ const NieerInternalization = () => {
animationSpeed={200}
onContinue={() => {
setShowStudyGuide(false)
setCourseTimeProgress(15)
navigate('/menu')
}}
/>
+8 -22
View File
@@ -215,13 +215,13 @@ const NieerPlace1 = () => {
<GuideStepsItem speaker="student" audioSrc={getPublicPath('audio/nieer/mp3_nieer_05.mp3')}>
这里提到聂耳发表了《中国歌舞短论》,批评脱离民众的音乐,这是为什么呢?
</GuideStepsItem>
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/nieer/mp3_nieer_06.mp3')}>
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/nieer/mp3_nieer_06_1.mp3')}>
你观察得很细致!这时的聂耳在明月歌舞剧社做练习生,社长李锦晖看中了他的潜力,虽然他当时小提琴水平不算专业,还是录取了他,还安排首席小提琴手专门辅导他。聂耳特别刻苦,每天至少练7个小时小提琴,很快就成长为首席小提琴手。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/nieer/mp3_nieer_06_2.mp3')}>
但好景不长,1931年九·一八事变后,全国抗日救亡浪潮高涨,可还存在很多脱离现实的靡靡之音,这让满怀爱国热忱的聂耳特别愤懑。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/nieer/mp3_nieer_06_3.mp3')}>
1932年,他以“黑天使”为笔名在《电影艺术》杂志刊登《中国歌舞短论》,猛烈评击“软豆腐”式的音乐,提出我们需要“真刀真枪的硬功夫”,主张音乐要贴近民众、为抗日救亡服务。后来他离开了歌舞剧社。
</GuideStepsItem>
</GuideSteps>
@@ -257,16 +257,16 @@ const NieerPlace1 = () => {
<GuideStepsItem speaker="student" audioSrc={getPublicPath('audio/nieer/mp3_nieer_03.mp3')}>
小沪,聂耳是在九·一八事变爆发后来到的上海吗?他为什么来上海呢?
</GuideStepsItem>
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/nieer/mp3_nieer_04.mp3')}>
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/nieer/mp3_nieer_04_1.mp3')}>
九·一八事变爆发前,聂耳就已经来到上海了,核心原因是躲避迫害。聂耳中学就读的云南省立第一师范学校是昆明学生运动的中心,在那里他接触到了马克思主义进步思想,还秘密加入了中国共产主义青年团。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/nieer/mp3_nieer_04_2.mp3')}>
也因为他积极参加学生运动,1930年被国民党当局列入了抓捕黑名单,在家人帮助下,他顶替哥哥的身份逃离昆明来到上海。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/nieer/mp3_nieer_04_3.mp3')}>
来到上海后,1931年九·一八事变爆发,继而1932年一·二八事变爆发,聂耳亲眼看见了日军进犯上海的暴行,也目睹了上海民众踊跃投身抗日的热烈场景。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/nieer/mp3_nieer_04_4.mp3')}>
这些亲身经历让他深刻意识到,音乐不能只是娱乐,更能成为唤醒民众、凝聚抗战士气的“武器”。正是这份在上海的所见所感,让他坚定了“音乐救国”的方向,也为《义勇军进行曲》注入了源于现实的痛感与抗争决心。
</GuideStepsItem>
</GuideSteps>
@@ -298,11 +298,6 @@ const NieerPlace1 = () => {
{/* 光标2题目成功提示:成功后点亮 cursor2,并进入后置对话 */}
<SuccessAlert
visible={successAlert2Visible}
title="恭喜你, 选择正确!"
onContinue={() => {
setSuccessAlert2Visible(false)
setVideo2Step(1)
}}
onClose={() => {
setSuccessAlert2Visible(false)
setVideo2Step(1)
@@ -326,7 +321,6 @@ const NieerPlace1 = () => {
return
}
markExhibitVisited(place, 2)
saveNieerStore()
setVideo2Step(null)
}}
onSkip={() => {
@@ -335,7 +329,6 @@ const NieerPlace1 = () => {
return
}
markExhibitVisited(place, 2)
saveNieerStore()
setVideo2Step(null)
}}
/>
@@ -369,16 +362,9 @@ const NieerPlace1 = () => {
{/* 光标1题目成功提示:成功后点亮 cursor1 */}
<SuccessAlert
visible={successAlert1Visible}
title="恭喜你, 选择正确!"
onContinue={() => {
setSuccessAlert1Visible(false)
markExhibitVisited(place, 1)
saveNieerStore()
}}
onClose={() => {
setSuccessAlert1Visible(false)
markExhibitVisited(place, 1)
saveNieerStore()
}}
/>
@@ -407,7 +393,7 @@ const NieerPlace1 = () => {
firstLineIndent="2em"
>
同学你好,欢迎来到“走进故居·对话”学习模块!{'\n'}
此刻你正位立于国歌展示馆的入口,请随时光的脚步,在聂耳作品的“时光声浪”中聆听五段炽热的时代回响:民族危亡的怒吼声、时代呼唤的最强音、民族解放的冲锋号、强我中华的进行曲、伟大复兴的主旋律。你准备好跟随聂耳的音符,踏上这段炽热征程了吗?
此刻你正位立于国歌展示馆的入口,请随时光的脚步,在聂耳作品的“时光声浪”中聆听5段炽热的时代回响:民族危亡的怒吼声、时代呼唤的最强音、民族解放的冲锋号、强我中华的进行曲、伟大复兴的主旋律。你准备好跟随聂耳的音符,踏上这段炽热征程了吗?
</StudyGuideModal>
</div>
)
+6 -9
View File
@@ -138,7 +138,6 @@ const NieerPlace2 = () => {
}
if (closingId !== null) {
markExhibitVisited(place, closingId)
saveNieerStore()
}
}}
/>
@@ -156,10 +155,10 @@ const NieerPlace2 = () => {
<GuideStepsItem speaker="student" audioSrc={getPublicPath('audio/nieer/mp3_nieer_09.mp3')}>
这盒《风云儿女》的胶片太有年代感了!聂耳是怎么和《风云儿女》这部电影结缘,并给这部电影谱主题歌《义勇军进行曲》的啊?
</GuideStepsItem>
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/nieer/mp3_nieer_10.mp3')}>
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/nieer/mp3_nieer_10_1.mp3')}>
1935年的时候,日本帝国主义已经把侵略的魔爪伸向华北,中华民族到了生死存亡的关键时刻,全国人民都渴望有能凝聚斗志的精神力量。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/nieer/mp3_nieer_10_2.mp3')}>
左翼文艺界想通过拍摄《风云儿女》这部抗日电影,让更多人看清侵略者的野心,唤醒大家的抗争意识,而电影的主题歌必须得有“振聋发聩”的力量。
</GuideStepsItem>
</GuideSteps>
@@ -174,16 +173,16 @@ const NieerPlace2 = () => {
highlightSelectors={[]}
zIndex={10001}
>
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/nieer/mp3_nieer_11.mp3')}>
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/nieer/mp3_nieer_11_1.mp3')}>
1935年2月,田汉创作了《风云儿女》剧本梗概,写下《义勇军进行曲》歌词后不久被捕,田汉的战友辗转把歌词转交给左翼剧联,聂耳得知后第一时间就主动申请完成曲创作。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/nieer/mp3_nieer_11_2.mp3')}>
他之所以这么积极,一方面是和田汉的革命情谊——两人都是左翼文艺运动的核心力量,早就约定要一起用文艺为抗日救亡服务;
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/nieer/mp3_nieer_11_3.mp3')}>
更重要的是他对时代的感知,之前创作《开矿歌》《卖报歌》时,他就深入底层了解民众的苦难,清楚地知道大家需要什么样的声音。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/nieer/mp3_nieer_11_4.mp3')}>
他当时说“这歌词太有力量了,我一定要把它谱成能让全中国人民都唱起来的歌”,这份信心来自他对民众心声的把握,也来自对救国使命的坚守。
</GuideStepsItem>
</GuideSteps>
@@ -197,12 +196,10 @@ const NieerPlace2 = () => {
showSkipButton
onEnded={() => {
markExhibitVisited(place, 2)
saveNieerStore()
setShowVideo2(false)
}}
onSkip={() => {
markExhibitVisited(place, 2)
saveNieerStore()
setShowVideo2(false)
}}
/>
+8 -17
View File
@@ -194,16 +194,16 @@ const NieerPlace3 = () => {
<GuideStepsItem speaker="student" audioSrc={getPublicPath('audio/nieer/mp3_nieer_12.mp3')}>
这份聂耳手写的《义勇军进行曲》的曲谱太珍贵了,在当时那么动乱的年代,他是怎么创作出这么激昂的旋律的?
</GuideStepsItem>
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/nieer/mp3_nieer_13.mp3')}>
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/nieer/mp3_nieer_13_1.mp3')}>
1935年3月,聂耳在上海霞飞路(现淮海中路1258号聂耳旧居)完成了《义勇军进行曲》的曲谱初稿的创作。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/nieer/mp3_nieer_13_2.mp3')}>
当时他住的是一个亭子间,那里又小又暗,连一张像样的桌子都没有,为了找到最贴合歌词的旋律,他每天天不亮就起来哼唱,用筷子敲着桌子打节拍,有时候灵感来了,半夜还会起来修改,邻居都能听到他的歌声。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/nieer/mp3_nieer_13_3.mp3')}>
草稿上的修改痕迹,藏着他的用心——比如他反复调整音符的节奏,把原本偏舒缓的旋律改得更急促、更有冲击力,就是想让旋律能跟上战士冲锋的步伐;还优化了歌词和旋律的搭配,让每一句都能唱得铿锵有力。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/nieer/mp3_nieer_13_4.mp3')}>
他还专门去电通公司唱给同事听,把工人、学生、战士的反馈都记下来,一点点打磨。
</GuideStepsItem>
</GuideSteps>
@@ -221,13 +221,13 @@ const NieerPlace3 = () => {
<GuideStepsItem speaker="student" audioSrc={getPublicPath('audio/nieer/mp3_nieer_14.mp3')}>
原来初稿就倾注了这么多心血!他当时就是用这把小提琴创作的吗?
</GuideStepsItem>
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/nieer/mp3_nieer_15.mp3')}>
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/nieer/mp3_nieer_15_1.mp3')}>
这把小提琴其实是演员赵丹在电影《聂耳》中饰演聂耳时用的道具,不是聂耳本人的。你看到的抱着小提琴的聂耳的照片的拍摄地点已经是在海外了。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/nieer/mp3_nieer_15_2.mp3')}>
当时他因为创作了太多进步歌曲,被国民党当局列入了黑名单,继续留在上海会有生命危险,因此他受党的指示前往海外暂避。但他心里一直惦记着谱曲任务,到海外后没敢耽误,立刻投入修改。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/nieer/mp3_nieer_15_3.mp3')}>
虽然身在海外,但他通过海外华人社团、抗日刊物,一直关注着国内的抗日浪潮,还收集了海外华人传唱抗日歌曲的反馈,这些都成了他修改的重要参考。
</GuideStepsItem>
</GuideSteps>
@@ -252,7 +252,6 @@ const NieerPlace3 = () => {
if (id === 1) {
markExhibitVisited(place, 1)
saveNieerStore()
return
}
@@ -267,7 +266,6 @@ const NieerPlace3 = () => {
if (id === 1) {
markExhibitVisited(place, 1)
saveNieerStore()
return
}
@@ -282,7 +280,7 @@ const NieerPlace3 = () => {
<SingleChoiceQuizPanel
visible={quiz3Visible}
zIndex={10001}
title="【单选】聂耳是主动请缨为《风云儿女》电影创作的主题曲是:"
title="【单选】聂耳主动请缨为《风云儿女》电影创作的主题曲是:"
options={[
{ value: 'A', label: 'A.义勇军进行曲' },
{
@@ -316,16 +314,9 @@ const NieerPlace3 = () => {
{/* 光标3题目成功提示:成功后点亮 cursor3 */}
<SuccessAlert
visible={successAlert3Visible}
title="恭喜你, 选择正确!"
onContinue={() => {
setSuccessAlert3Visible(false)
markExhibitVisited(place, 3)
saveNieerStore()
}}
onClose={() => {
setSuccessAlert3Visible(false)
markExhibitVisited(place, 3)
saveNieerStore()
}}
/>
</div>
+4 -8
View File
@@ -117,9 +117,6 @@ const NieerPlace4 = () => {
if (activeExhibitId === 2) {
triggerPuzzleAnimation(8)
}
if (activeExhibitId === 1) {
triggerPuzzleAnimation(9)
}
}}
onClose={() => {
const closingId = activeExhibitId
@@ -128,7 +125,6 @@ const NieerPlace4 = () => {
setGuide1Visible(false)
if (closingId !== null) {
markExhibitVisited(place, closingId)
saveNieerStore()
}
}}
/>
@@ -146,10 +142,10 @@ const NieerPlace4 = () => {
<GuideStepsItem speaker="student" audioSrc={getPublicPath('audio/nieer/mp3_nieer_16.mp3')}>
随着《风云儿女》的上映,田汉作词、聂耳谱曲的《义勇军进行曲》真如聂耳所期待的一样,传遍了全国吧!
</GuideStepsItem>
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/nieer/mp3_nieer_17.mp3')}>
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/nieer/mp3_nieer_17_1.mp3')}>
1935年《风云儿女》公映后,这首歌就跟着影片走进了全国各地的影院,看完电影的观众都忍不住跟着哼唱。当时的百代唱片公司还专门录制了这首歌的唱片,在街头巷尾的唱片店、收音机里循环播放。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/nieer/mp3_nieer_17_2.mp3')}>
因为它朗朗上口,哪怕是不识字的老百姓,教几遍就能学会,很快就成了街头巷尾、田间地头都能听到的旋律,真正做到了家喻户晓。
</GuideStepsItem>
</GuideSteps>
@@ -167,10 +163,10 @@ const NieerPlace4 = () => {
<GuideStepsItem speaker="student" audioSrc={getPublicPath('audio/nieer/mp3_nieer_18.mp3')}>
这张指挥千人大合唱的照片好震撼!除了日常传唱,这首歌在抗战时期还发挥了哪些特别的作用呢?
</GuideStepsItem>
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/nieer/mp3_nieer_19.mp3')}>
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/nieer/mp3_nieer_19_1.mp3')}>
它的作用可太大了!你看这张1936年的照片,刘良模先生在上海南市公共体育场指挥数千人齐唱,就连前来镇压的警察都被感染,跟着一起唱。抗战期间,抗日军队的行军路上、根据地的文艺宣传队里、城市的救亡集会中,都能听到这首歌的声音。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/nieer/mp3_nieer_19_2.mp3')}>
它就像冲锋号一样,每次响起都能凝聚起大家的斗志,让战士们更勇敢地奔赴战场,让民众们更坚定地支持抗战,成为整个民族团结抗敌的精神纽带,而它也不再只是文艺作品,更是凝聚了中国人民的抗战士气,成了抗日救亡运动的精神旗帜。
</GuideStepsItem>
</GuideSteps>
+42 -62
View File
@@ -70,10 +70,10 @@ const NieerPlace5 = () => {
const subtitle = completed ? '已完成' : `${progressCount}/${total}`
const { isEnabled } = useCursorController({
initialEnabled: [2],
initialEnabled: [1],
dependencies: {
1: [2],
3: [1],
2: [1],
3: [2],
},
visited: visitedExhibits,
})
@@ -96,14 +96,15 @@ const NieerPlace5 = () => {
const handleOpenExhibit = (id: 1 | 2 | 3) => {
setActiveExhibitId(id)
// 光标1显示guide2的内容(选定国歌时间线),光标2显示guide1的内容(宪法修正案)
if (id === 1) {
setGuide1Step(0)
setGuide1Visible(true)
setGuide2Step(0)
setGuide2Visible(true)
}
if (id === 2) {
setGuide2Step(0)
setGuide2Visible(true)
setGuide1Step(0)
setGuide1Visible(true)
}
if (id === 3) {
@@ -169,13 +170,13 @@ const NieerPlace5 = () => {
src={activeExhibitId ? exhibitSrcMap[activeExhibitId] : ''}
onOpen={() => {
if (activeExhibitId === 1) {
triggerPuzzleAnimation(10)
triggerPuzzleAnimation(9)
}
if (activeExhibitId === 2) {
triggerPuzzleAnimation(11)
triggerPuzzleAnimation(10)
}
if (activeExhibitId === 3) {
triggerPuzzleAnimation(12)
triggerPuzzleAnimation(11)
}
}}
onClose={() => {
@@ -185,11 +186,11 @@ const NieerPlace5 = () => {
setGuide2Visible(false)
setGuide3Visible(false)
if (closingId == 2) {
markExhibitVisited(place, 2)
if (closingId == 1) {
markExhibitVisited(place, 1)
}
// 展品1关闭后完成光标1,并触发光标2的两道题
if (closingId === 1) {
// 展品2关闭后触发光标2的两道题
if (closingId === 2) {
saveNieerStore()
setQuiz2_1Visible(true)
}
@@ -199,28 +200,7 @@ const NieerPlace5 = () => {
}}
/>
{/* 光标1对话(图1) */}
<GuideSteps
visible={guide1Visible}
currentStep={guide1Step}
onStepChange={setGuide1Step}
onVisibleChange={setGuide1Visible}
showMask={false}
highlightSelectors={[]}
zIndex={10001}
>
<GuideStepsItem speaker="student" audioSrc={getPublicPath('audio/nieer/mp3_nieer_22.mp3')}>
那2004年的宪法修正案又对国歌做了什么重要规定呢?为什么要把它写入宪法呀?
</GuideStepsItem>
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/nieer/mp3_nieer_23.mp3')}>
2004年3月14日,第十届全国人民代表大会二次会议通过宪法修正案,明确规定“中华人民共和国国歌是《义勇军进行曲》”,这让国歌的地位有了根本大法的保障。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
把它写入宪法,是因为这首歌不仅记录了民族救亡的历史,更凝聚了中华民族的精神气节。从抗战时期的冲锋号到新时代的强国音,它早已成为国家尊严的象征,写入宪法能更好地培育全民的爱国意识和国家观念。
</GuideStepsItem>
</GuideSteps>
{/* 光标2对话(图2) */}
{/* 光标1对话(图1) - 选定国歌时间线 */}
<GuideSteps
visible={guide2Visible}
currentStep={guide2Step}
@@ -233,14 +213,35 @@ const NieerPlace5 = () => {
<GuideStepsItem speaker="student" audioSrc={getPublicPath('audio/nieer/mp3_nieer_20.mp3')}>
小沪,从抗日战歌到正式被确立为国歌,中间经历了多久呀?《义勇军进行曲》第一次作为国歌奏响是在什么时候呢?
</GuideStepsItem>
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/nieer/mp3_nieer_21.mp3')}>
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/nieer/mp3_nieer_21_1.mp3')}>
时间跨度还真不短呢!1949年9月27日,中国人民政治协商会议第一届全体会议决定,在国歌未正式制定前,以《义勇军进行曲》作为代国歌。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/nieer/mp3_nieer_21_2.mp3')}>
同年10月1日开国大典上,它第一次在天安门广场作为国歌庄严奏响,见证了新中国的诞生。直到1982年12月4日,第五届全国人民代表大会第五次会议才正式将它定为中华人民共和国国歌,完成了从代国歌到正式国歌的跨越。
</GuideStepsItem>
</GuideSteps>
{/* 光标2对话(图2) - 宪法修正案 */}
<GuideSteps
visible={guide1Visible}
currentStep={guide1Step}
onStepChange={setGuide1Step}
onVisibleChange={setGuide1Visible}
showMask={false}
highlightSelectors={[]}
zIndex={10001}
>
<GuideStepsItem speaker="student" audioSrc={getPublicPath('audio/nieer/mp3_nieer_22.mp3')}>
那2004年的宪法修正案又对国歌做了什么重要规定呢?为什么要把它写入宪法呀?
</GuideStepsItem>
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/nieer/mp3_nieer_23_1.mp3')}>
2004年3月14日,第十届全国人民代表大会二次会议通过宪法修正案,明确规定“中华人民共和国国歌是《义勇军进行曲》”,这让国歌的地位有了根本大法的保障。
</GuideStepsItem>
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/nieer/mp3_nieer_23_2.mp3')}>
把它写入宪法,是因为这首歌不仅记录了民族救亡的历史,更凝聚了中华民族的精神气节。从抗战时期的冲锋号到新时代的强国音,它早已成为国家尊严的象征,写入宪法能更好地培育全民的爱国意识和国家观念。
</GuideStepsItem>
</GuideSteps>
{/* 光标2题目1(图3:选择题) - 错两次弹窗提示 */}
<SingleChoiceQuizPanel
visible={quiz2_1Visible}
@@ -276,11 +277,6 @@ const NieerPlace5 = () => {
<SuccessAlert
visible={success2_1Visible}
title="恭喜你, 选择正确!"
onContinue={() => {
setSuccess2_1Visible(false)
setQuiz2_2Visible(true)
}}
onClose={() => {
setSuccess2_1Visible(false)
setQuiz2_2Visible(true)
@@ -315,15 +311,9 @@ const NieerPlace5 = () => {
<SuccessAlert
visible={success2_2Visible}
onContinue={() => {
setSuccess2_2Visible(false)
markExhibitVisited(place, 1)
saveNieerStore()
}}
onClose={() => {
setSuccess2_2Visible(false)
markExhibitVisited(place, 1)
saveNieerStore()
markExhibitVisited(place, 2)
}}
/>
@@ -346,7 +336,7 @@ const NieerPlace5 = () => {
<MatchQuizPanel
visible={quiz3_1Visible}
zIndex={10001}
title="【连线】请将左侧《义勇军进行曲》的关键事件与右侧时间进行匹配"
title="【连线】请将左侧《义勇军进行曲》的关键事件与右侧时间进行匹配:"
leftItems={[
{
id: 'e1982',
@@ -413,11 +403,7 @@ const NieerPlace5 = () => {
<SuccessAlert
visible={success3_1Visible}
title="恭喜你, 匹配正确!"
onContinue={() => {
setSuccess3_1Visible(false)
setQuiz3_2Visible(true)
}}
title="恭喜你, 匹配正确!"
onClose={() => {
setSuccess3_1Visible(false)
setQuiz3_2Visible(true)
@@ -446,15 +432,9 @@ const NieerPlace5 = () => {
<SuccessAlert
title="恭喜你,判断正确!"
visible={success3_2Visible}
onContinue={() => {
setSuccess3_2Visible(false)
markExhibitVisited(place, 3)
saveNieerStore()
}}
onClose={() => {
setSuccess3_2Visible(false)
markExhibitVisited(place, 3)
saveNieerStore()
}}
/>
</div>
+10 -5
View File
@@ -13,6 +13,7 @@ import { useNavigate, useParams } from 'react-router-dom'
import { useNieerStore, saveNieerStore } from '@/store/nieerStore'
import { useCursorController } from '@/hooks/useCursorController'
import { usePuzzleAnimation } from '@/contexts/PuzzleAnimationContext'
import { setCourseTimeProgress } from '@/lib/utils/api-common'
const NieerPlace6 = () => {
const navigate = useNavigate()
@@ -102,7 +103,11 @@ const NieerPlace6 = () => {
className="absolute bottom-0 right-0"
src={getStaticImage('components/place/离开场景')}
button
onClick={() => navigate(`/${moduleName}/${starName}/home`)}
onClick={() => {
saveNieerStore()
setCourseTimeProgress(13)
navigate(`/${moduleName}/${starName}/home`)
}}
/>
)}
@@ -111,10 +116,10 @@ const NieerPlace6 = () => {
src={activeExhibitId ? exhibitSrcMap[activeExhibitId] : ''}
onOpen={() => {
if (activeExhibitId === 1) {
triggerPuzzleAnimation(14)
triggerPuzzleAnimation(13)
}
if (activeExhibitId === 2) {
triggerPuzzleAnimation(13)
triggerPuzzleAnimation(12)
}
}}
onClose={() => {
@@ -151,10 +156,10 @@ const NieerPlace6 = () => {
<GuideStepsItem speaker="student" audioSrc={getPublicPath('audio/nieer/mp3_nieer_25.mp3')}>
小沪,这些不同场景下奏响国歌的画面太让人感动了!在和平年代,这首歌还有什么特别的意义呢?
</GuideStepsItem>
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/nieer/mp3_nieer_26.mp3')}>
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/nieer/mp3_nieer_26_1.mp3')}>
和平年代里,它的精神力量依然不减。你看这张中国女排奥运夺金的照片,国歌奏响时,是民族自豪的宣告;神舟飞船升空时的国歌,是航天人攻坚克难的礼赞。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/nieer/mp3_nieer_26_2.mp3')}>
它就像一根精神纽带,无论在高光时刻还是艰难关头,都能凝聚起全国人民的力量,提醒我们不忘历史、砥砺前行。
</GuideStepsItem>
</GuideSteps>
+5 -1
View File
@@ -49,7 +49,11 @@ const NieerReflectionBox = () => {
className="absolute right-6 top-6 z-10"
src={getStaticImage('components/返回')}
alt="返回"
onClick={() => navigate(`/${moduleName}/${starName}/reflection`)}
onClick={() =>
navigate(`/${moduleName}/${starName}/reflection`, {
state: { fromReflectionSubpage: true },
})
}
/>
<div className="absolute inset-0">
+64 -16
View File
@@ -1,9 +1,9 @@
import { EndingOverlay, Image, StudyGuideModal } from '@/components'
import { EndingOverlay, Image, StudyGuideModal, Video } from '@/components'
import { cn, getBackgroundImage, getPublicPath, getStaticImage } from '@/lib/utils'
import { useEffect, useMemo, useRef, useState } from 'react'
import { useNavigate, useParams } from 'react-router-dom'
import { useLocation, useNavigate, useParams } from 'react-router-dom'
import { LOBBY_COLUMNS, LOBBY_LAYOUT, LOBBY_TIMELINE } from './lobby.config'
import { useNieerStore } from '@/store/nieerStore'
import { saveNieerStore, useNieerStore } from '@/store/nieerStore'
import { useDialogReducer } from './useDialogReducer'
import DialogLayer from './DialogLayer'
import {
@@ -15,11 +15,16 @@ import {
getUnlockVideoSrcForScene,
getUnlockedByIdMap,
} from './reflectionProgress'
import { usePuzzleAnimation } from '@/contexts/PuzzleAnimationContext'
import { setCourseTimeProgress } from '@/lib/utils/api-common'
const starName = 'nieer'
const NieerReflection = () => {
const navigate = useNavigate()
const location = useLocation()
const fromReflectionSubpage = !!(location.state as { fromReflectionSubpage?: boolean } | null)
?.fromReflectionSubpage
const { moduleName } = useParams<{ moduleName: string }>()
const debugLobby = new URLSearchParams(window.location.search).get('debugLobby') === '1'
const visitedReflectionCursors = useNieerStore((state) => state.visitedReflectionCursors)
@@ -27,7 +32,14 @@ const NieerReflection = () => {
const setReflectionWork = useNieerStore((state) => state.setReflectionWork)
const hasSeenReflectionGuide = useNieerStore((state) => state.hasSeenReflectionGuide)
const markReflectionGuideSeen = useNieerStore((state) => state.markReflectionGuideSeen)
const showGuideModal = !hasSeenReflectionGuide
const hasSeenBoxHint = useNieerStore((state) => state.hasSeenBoxHint)
const markBoxHintSeen = useNieerStore((state) => state.markBoxHintSeen)
const [showOpeningVideo, setShowOpeningVideo] = useState(!fromReflectionSubpage)
const showGuideModal = !showOpeningVideo && !hasSeenReflectionGuide
const showBoxHint = !showOpeningVideo && !showGuideModal && !hasSeenBoxHint
// 获取碎片动画触发方法
const { triggerPuzzleAnimation } = usePuzzleAnimation()
const {
state: dialogState,
@@ -59,10 +71,11 @@ const NieerReflection = () => {
dialogState.mode === 'idle' &&
allPlacesCompleted
) {
triggerPuzzleAnimation(14)
setEndingVisible(true)
}
prevDialogModeRef.current = dialogState.mode
}, [dialogState.mode, allPlacesCompleted])
}, [dialogState.mode, allPlacesCompleted, triggerPuzzleAnimation])
const handleResultShare = (sceneId: number, payload?: Record<string, unknown>) => {
handleShare({
@@ -78,6 +91,21 @@ const NieerReflection = () => {
className="w-full h-full relative bg-cover bg-center bg-no-repeat overflow-hidden"
style={{ backgroundImage: getBackgroundImage(`${starName}/reflection/lobby/场景首页底图`) }}
>
{showOpeningVideo && (
<div className="fixed inset-0 z-10000 w-full h-full bg-black">
<Video
src={getPublicPath('video/reflection/nieer_tiwu_00.mp4')}
autoPlay
playsInline
showSkipButton
skipText="跳过"
onEnded={() => setShowOpeningVideo(false)}
onSkip={() => setShowOpeningVideo(false)}
onError={() => setShowOpeningVideo(false)}
/>
</div>
)}
<Image
className="absolute right-6 top-5 z-30"
src={getStaticImage('components/返回')}
@@ -198,7 +226,7 @@ const NieerReflection = () => {
</div>
<div
className="absolute left-0 bottom-0 w-full h-[201px] z-20 flex justify-center items-center pt-20 gap-10"
className="absolute left-0 bottom-0 w-full h-[201px] z-1 flex justify-center items-center pt-20 gap-10 pointer-events-none"
style={{
backgroundImage: getBackgroundImage(`${starName}/reflection/lobby/footer`),
}}
@@ -227,13 +255,30 @@ const NieerReflection = () => {
</div>
</div>
<Image
button
src={getStaticImage(`${starName}/reflection/lobby/icon/旅行箱`)}
className="absolute right-0 bottom-0 z-30"
alt="旅行箱"
onClick={() => navigate(`/${moduleName}/${starName}/reflection/box`)}
/>
<div className="absolute right-0 bottom-0 z-30">
{showBoxHint && (
<>
<div className="absolute -left-85 -top-45 flex flex-col items-center bg-[#3a2a1a]/85 rounded-xl px-8 py-5 border-2 border-[#fcc419] animate-pulse">
<span className="text-4xl text-white font-bold [text-shadow:_0_0_10px_rgba(252,196,25,0.8)] whitespace-nowrap">
点击「领取旅行箱」
</span>
<br />
<span className="text-3xl text-[#fcc419] font-bold [text-shadow:_0_0_10px_rgba(0,0,0,0.8)] whitespace-nowrap">
旅行箱会记录你每段旅行的成果哦!
</span>
</div>
</>
)}
<Image
button
src={getStaticImage(`${starName}/reflection/lobby/icon/旅行箱`)}
alt="旅行箱"
onClick={() => {
markBoxHintSeen()
navigate(`/${moduleName}/${starName}/reflection/box`)
}}
/>
</div>
{debugLobby && (
<div
@@ -260,9 +305,10 @@ const NieerReflection = () => {
audioSrc={getPublicPath('audio/nieer/mp3_nieer_28.mp3')}
firstLineIndent="2em"
>
{`同学你好,欢迎来到“跨越时空·体悟”学习模块!
请登陆“时空回溯・亲历时代”体验仓,下一站:1936年,上海。
请带上你的旅行包,穿越时空解锁6个国歌故事,集齐精神徽章,绘制属于你的国歌精神图谱吧~时空门已开,请出发吧!`}
{`同学你好,欢迎来到”跨越时空·体悟”学习模块!
请登陆”时空回溯・亲历时代”体验仓,下一站:1936年,上海。
请带上你的旅行包,穿越时空解锁6个国歌故事,集齐精神徽章,绘制属于你的国歌精神图谱吧~
别忘了右下角的旅行箱哦,它会记录你每段旅行的成果!时空门已开,请出发吧!`}
</StudyGuideModal>
<EndingOverlay
@@ -272,6 +318,8 @@ const NieerReflection = () => {
text={`恭喜你完成所有时空旅行!成功绘制“国歌精神图谱”,解锁6个跨越时空的国歌故事~国歌的故事还在继续,它的精神藏在每一次唱响、每一次坚守中。愿你带着这份精神力量,做国歌的传承者、爱国的践行者!`}
onContinue={() => {
setEndingVisible(false)
saveNieerStore()
setCourseTimeProgress(14)
navigate(`/${moduleName}/${starName}/home`)
}}
/>
+1 -1
View File
@@ -38,7 +38,7 @@ const RongdeshengHome = () => {
className="absolute right-37 top-22"
src={getStaticImage('components/革命先驱故居-返回')}
button
onClick={() => navigate(`/entrance/${moduleName}`)}
onClick={() => navigate(`/${moduleName}`)}
/>
<div className="absolute right-20 top-75 flex flex-col space-y-10">
+31 -8
View File
@@ -16,6 +16,8 @@ import {
import { SuccessAlert } from '@/components/layout'
import { MultipleChoiceQuizPanel, SingleChoiceQuizPanel } from '@/components/layout/quiz'
import { getBackgroundImage, getPublicPath, getStaticImage } from '@/lib/utils'
import { usePuzzleAnimation } from '@/contexts/PuzzleAnimationContext'
import { setCourseTimeProgress } from '@/lib/utils/api-common'
const starName = 'rongdesheng'
@@ -73,7 +75,7 @@ const QUIZ_ANCHORS: QuizAnchor[] = [
},
{
id: 'quiz-2',
time: 60 + 51,
time: 60 + 52,
type: 'single',
title: '【单选】荣德生的企业虽多次遭战火摧毁,但始终浴火重生,这体现了他哪种宝贵品质?',
options: [
@@ -133,7 +135,7 @@ const QUIZ_ANCHORS: QuizAnchor[] = [
},
{
id: 'quiz-5',
time: 5 * 60 + 22,
time: 5 * 60 + 23,
type: 'single',
title:
'【单选】荣德生先生提出的“崇俭立国”主张,以及造桥铺路、创办学校等善举,体现了他怎样的财富观?',
@@ -160,6 +162,9 @@ const RongdeshengInternalization = () => {
const navigate = useNavigate()
const { moduleName } = useParams<{ moduleName: string }>()
// 获取碎片动画触发方法
const { triggerPuzzleAnimation } = usePuzzleAnimation()
const [dragQuizValues, setDragQuizValues] = useState<Record<string, DragFillResult[]>>({})
const [dragQuizErrors, setDragQuizErrors] = useState<Record<string, string>>({})
const [narrationVisible, setNarrationVisible] = useState(true)
@@ -303,6 +308,10 @@ const RongdeshengInternalization = () => {
setActiveSuccess(item.id)
setSuccessContent(option?.explanation || item.successContent || '')
setResumeCallback(() => resume)
// 答题成功后触发碎片获取动画
// quiz-2 -> 碎片15(坚韧不拔),quiz-5 -> 碎片16(社会责任担当)
if (item.id === 'quiz-2') triggerPuzzleAnimation(15)
if (item.id === 'quiz-5') triggerPuzzleAnimation(16)
}
}}
/>
@@ -369,7 +378,14 @@ const RongdeshengInternalization = () => {
),
}
})
}, [activeSuccess, dragQuizErrors, dragQuizValues, resumeCallback, successContent])
}, [
activeSuccess,
dragQuizErrors,
dragQuizValues,
resumeCallback,
successContent,
triggerPuzzleAnimation,
])
if (!moduleName) return null
@@ -411,18 +427,21 @@ const RongdeshengInternalization = () => {
>
<GuideStepsItem
speaker="student"
audioSrc={getPublicPath('audio/rongdesheng/mp3_rongdesheng_33.mp3')}
audioSrc={getPublicPath('audio/rongdesheng/mp3_rongdesheng_33_1.mp3')}
>
小沪,跟着你走完荣德生旧居的学习之旅,我真的被这位民族实业家的一生打动了!
</GuideStepsItem>
<GuideStepsItem speaker="student">
<GuideStepsItem
speaker="student"
audioSrc={getPublicPath('audio/rongdesheng/mp3_rongdesheng_33_2.mp3')}
>
从钱庄学徒到“面粉大王”“纺织巨子”,从抗战守节到坚守大陆留存工业火种,他用一生践行实业救国的初心,那在新时代,我们该如何理解和传承荣先生的这份实业精神呢?
</GuideStepsItem>
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/rongdesheng/mp3_rongdesheng_34.mp3')}
>
针对你的这个问题,今天我们有幸邀请到了上海社会科学院经济研究所博士后,经济史研究室研究人员徐晶老师。关于荣德生先生的实业精神与新时代的传承,我想徐老师会给我们带来深刻的解读!
针对你的这个问题,今天我们有幸邀请到了上海社会科学院经济研究所博士后,经济史研究室研究人员徐昂老师。关于荣德生先生的实业精神与新时代的传承,我想徐老师会给我们带来深刻的解读!
</GuideStepsItem>
</GuideSteps>
@@ -455,11 +474,14 @@ const RongdeshengInternalization = () => {
>
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/rongdesheng/mp3_rongdesheng_35.mp3')}
audioSrc={getPublicPath('audio/rongdesheng/mp3_rongdesheng_35_1.mp3')}
>
荣德生先生用一生诠释了“实业救国、经济报国”的初心——从钱庄学徒到实业巨子,他扎根民族大地,以面粉和棉纱筑牢国家经济根基;从战火守节到坚守大陆,他始终与国家同呼吸、共命运;
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/rongdesheng/mp3_rongdesheng_35_2.mp3')}
>
从创办学校到造桥铺路,他用责任与担当诠释了企业家的社会价值。你不妨把这份触动凝练成几句心里话,既总结学习的收获,也想想未来该如何践行这份实业精神,用自己的行动传承这份初心吧!
</GuideStepsItem>
</GuideSteps>
@@ -482,6 +504,7 @@ const RongdeshengInternalization = () => {
animationSpeed={200}
onContinue={() => {
setShowEnding(false)
setCourseTimeProgress(21)
navigate('/menu')
}}
/>
+46 -30
View File
@@ -10,9 +10,10 @@ import {
import Cursor from '@/components/cursor'
import { useCursorController } from '@/hooks/useCursorController'
import { getBackgroundImage, getPublicPath, getStaticImage } from '@/lib/utils'
import { useMemo, useState } from 'react'
import { useState } from 'react'
import { useNavigate, useParams } from 'react-router-dom'
import { useRongdeshengStore, saveRongdeshengStore } from '@/store/rongdeshengStore'
import { usePuzzleAnimation } from '@/contexts/PuzzleAnimationContext'
const starName = 'rongdesheng'
const place = 'place1'
@@ -23,11 +24,12 @@ const RongdeshengPlace1 = () => {
const { markExhibitVisited, getVisitedExhibits, setLastVisitedPlace } = useRongdeshengStore()
const persistedVisited = useMemo(
() => new Set(getVisitedExhibits(place) as (1 | 2)[]),
[getVisitedExhibits]
)
const [visited, setVisited] = useState<Set<1 | 2>>(() => new Set(persistedVisited))
// 获取碎片动画触发方法
const { triggerPuzzleAnimation } = usePuzzleAnimation()
// 从 store 获取已访问的展品,转换为 Set 用于兼容现有代码
const visitedExhibits = getVisitedExhibits(place)
const visited = new Set(visitedExhibits)
const total = 2
const progressCount = visited.size
@@ -79,16 +81,6 @@ const RongdeshengPlace1 = () => {
setGuide1Visible(false)
}
const markVisited = (id: 1 | 2) => {
setVisited((prev) => {
const next = new Set(prev)
next.add(id)
markExhibitVisited(place, id)
saveRongdeshengStore()
return next
})
}
return (
<div
className="w-full h-full bg-cover bg-center bg-no-repeat"
@@ -97,7 +89,7 @@ const RongdeshengPlace1 = () => {
}}
>
<Title
title="金融启蒙·资本助基"
title="金融启蒙·资本筑基"
subtitle={subtitle}
className="absolute top-10 left-1/2 -translate-x-1/2"
/>
@@ -139,9 +131,21 @@ const RongdeshengPlace1 = () => {
<ExhibitOverlay
visible={activeExhibitId !== null}
onOpen={() => {
if (activeExhibitId === 1) {
triggerPuzzleAnimation(1)
}
if (activeExhibitId === 2) {
triggerPuzzleAnimation(2)
}
}}
src={activeExhibitId ? exhibitSrcMap[activeExhibitId] : ''}
onClose={() => {
setActiveExhibitId(null)
if (activeExhibitId === 2) {
setQuizVisible(true)
return
}
}}
/>
@@ -151,7 +155,7 @@ const RongdeshengPlace1 = () => {
onVisibleChange={(nextVisible) => {
setGuide1Visible(nextVisible)
if (!nextVisible) {
markVisited(1)
markExhibitVisited(place, 1)
}
}}
currentStep={guide1Step}
@@ -168,14 +172,20 @@ const RongdeshengPlace1 = () => {
</GuideStepsItem>
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/rongdesheng/mp3_rongdesheng_04.mp3')}
audioSrc={getPublicPath('audio/rongdesheng/mp3_rongdesheng_04_1.mp3')}
>
如你所言,1875年,荣德生先生出生于无锡的商贾之家。凭借着殷实的家境,荣先生从小便由老先生教学,九岁的时候被送到私塾念书,毕业以后,就正式开始学习经商的道路。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/rongdesheng/mp3_rongdesheng_04_2.mp3')}
>
想要做生意,第一要走南闯北,第二要胆识过人。所以1889年,十几岁的荣先生离开无锡老家,来到上海通顺钱庄学做生意。当时的钱庄学徒要从打扫、记账、珠算等基础活干起,每天天不亮就起床,深夜才休息。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/rongdesheng/mp3_rongdesheng_04_3.mp3')}
>
荣先生格外刻苦,不仅熟练掌握了珠算、记账、汇兑等核心技能,还学会了察言观色、与人打交道的本事,也从繁重琐碎的钱业学习中逐渐培养起了稳健踏实的办事风格,为他后来创办自己的广生钱庄打下了坚实基础。
</GuideStepsItem>
</GuideSteps>
@@ -185,9 +195,6 @@ const RongdeshengPlace1 = () => {
visible={guide2Visible}
onVisibleChange={(nextVisible) => {
setGuide2Visible(nextVisible)
if (!nextVisible) {
setQuizVisible(true)
}
}}
currentStep={guide2Step}
onStepChange={setGuide2Step}
@@ -203,17 +210,26 @@ const RongdeshengPlace1 = () => {
</GuideStepsItem>
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/rongdesheng/mp3_rongdesheng_06.mp3')}
audioSrc={getPublicPath('audio/rongdesheng/mp3_rongdesheng_06_1.mp3')}
>
1896年,荣德生先生与兄长荣宗敬和父亲与人合伙在上海开设广生钱庄,后在无锡设立分庄,这是荣氏兄弟携手创业的起点。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/rongdesheng/mp3_rongdesheng_06_2.mp3')}
>
广生钱庄开展的“收茧款项”就是他们的创新之举——利用钱庄的汇兑优势,在上海与无锡之间开展蚕茧收购业务,把无锡的蚕茧运到上海销售,再将货款通过钱庄汇回,赚取了可观的利润。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/rongdesheng/mp3_rongdesheng_06_3.mp3')}
>
广生钱庄后来转为荣家独资经营,成为荣家资本原始积累的重要来源,就像一座桥梁,连接起他后来的实业梦想。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/rongdesheng/mp3_rongdesheng_06_4.mp3')}
>
不过可惜的是,因为关联商号卷入投机活动失败,广生钱庄最终歇业了,但这段金融从业经历让荣德生摸清了市场规律,积累了宝贵的管理经验,为之后创办面粉厂、纺织厂埋下了伏笔。
</GuideStepsItem>
</GuideSteps>
@@ -238,7 +254,7 @@ const RongdeshengPlace1 = () => {
successAlertProps={{
title: '恭喜你,选择正确!',
content:
'1896年广生钱庄创办,荣宗敬任上海总庄经理,荣德生任无锡分庄经理,这是荣氏兄弟携手创业的起点',
'1896年广生钱庄创办,荣宗敬任上海总庄经理,荣德生任无锡分庄经理,这是荣氏兄弟携手创业的起点。',
}}
onSubmit={(_value, isCorrect) => {
if (!isCorrect) return
@@ -246,7 +262,7 @@ const RongdeshengPlace1 = () => {
}}
onSuccessContinue={() => {
setQuizVisible(false)
markVisited(2)
markExhibitVisited(place, 2)
}}
/>
+48 -32
View File
@@ -13,6 +13,7 @@ import { getBackgroundImage, getPublicPath, getStaticImage } from '@/lib/utils'
import { useMemo, useState } from 'react'
import { useNavigate, useParams } from 'react-router-dom'
import { saveRongdeshengStore, useRongdeshengStore } from '@/store/rongdeshengStore'
import { usePuzzleAnimation } from '@/contexts/PuzzleAnimationContext'
const starName = 'rongdesheng'
const place = 'place3'
@@ -23,11 +24,12 @@ const RongdeshengPlace3 = () => {
const { markExhibitVisited, getVisitedExhibits, setLastVisitedPlace } = useRongdeshengStore()
const persistedVisited = useMemo(
() => new Set(getVisitedExhibits(place) as (1 | 2 | 3)[]),
[getVisitedExhibits]
)
const [visited, setVisited] = useState<Set<1 | 2 | 3>>(() => new Set(persistedVisited))
// 获取碎片动画触发方法
const { triggerPuzzleAnimation } = usePuzzleAnimation()
// 从 store 获取已访问的展品,转换为 Set 用于兼容现有代码
const visitedExhibits = getVisitedExhibits(place)
const visited = new Set(visitedExhibits)
const total = 3
const progressCount = visited.size
@@ -144,6 +146,14 @@ const RongdeshengPlace3 = () => {
<ExhibitOverlay
visible={activeExhibitId !== null}
src={activeExhibitId ? exhibitSrcMap[activeExhibitId] : ''}
onOpen={() => {
if (activeExhibitId === 1) {
triggerPuzzleAnimation(8)
}
if (activeExhibitId === 2) {
triggerPuzzleAnimation(9)
}
}}
onClose={() => {
if (activeExhibitId === null) return
const closingId = activeExhibitId
@@ -151,13 +161,13 @@ const RongdeshengPlace3 = () => {
// 展品1、2:关闭展品后直接完成
if (closingId === 1 || closingId === 2) {
setVisited((prev) => {
const next = new Set(prev)
next.add(closingId)
markExhibitVisited(place, closingId)
saveRongdeshengStore()
return next
})
markExhibitVisited(place, closingId)
// 触发碎片获取动画(展品1 -> 碎片8,展品2 -> 碎片9)
// if (closingId === 1) {
// triggerPuzzleAnimation(8)
// } else if (closingId === 2) {
// triggerPuzzleAnimation(9)
// }
}
if (closingId === 1) {
@@ -200,14 +210,20 @@ const RongdeshengPlace3 = () => {
</GuideStepsItem>
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/rongdesheng/mp3_rongdesheng_16.mp3')}
audioSrc={getPublicPath('audio/rongdesheng/mp3_rongdesheng_16_1.mp3')}
>
1937年抗日战争全面爆发,日军的炮火轰炸了荣氏在上海、无锡的多家工厂,设备被毁,生产停滞。为了保住民族工业的火种,支援抗战,荣德生做出了艰难的决定——将工厂内迁。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/rongdesheng/mp3_rongdesheng_16_2.mp3')}
>
迁厂要从上海、无锡出发,穿越日军的封锁线,沿着崎岖的道路前往重庆、成都等地,路途遥远且危险重重。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/rongdesheng/mp3_rongdesheng_16_3.mp3')}
>
很多设备在运输过程中遭到损坏,不少工人也付出了生命的代价,但他们始终没有放弃,因为荣德生知道,后方的生产关乎前线的军需和民众的生计。
</GuideStepsItem>
</GuideSteps>
@@ -229,11 +245,14 @@ const RongdeshengPlace3 = () => {
</GuideStepsItem>
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/rongdesheng/mp3_rongdesheng_18.mp3')}
audioSrc={getPublicPath('audio/rongdesheng/mp3_rongdesheng_18_1.mp3')}
>
是的!内迁后的重庆申新纱厂、茂新面粉厂等,在简陋的条件下迅速恢复生产。虽然产量不及战前,但从未中断。生产的面粉、棉纱,一部分供应给前线的抗日军队,制作军装、被褥、急救包;另一部分供应给后方民众,保障基本生活需求。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/rongdesheng/mp3_rongdesheng_18_2.mp3')}
>
这篇报道,就见证了荣氏企业用实业支援抗战的壮举,为抗日战争的物资供应提供了有力支持,成为民族救亡运动中不可或缺的力量。
</GuideStepsItem>
</GuideSteps>
@@ -255,11 +274,14 @@ const RongdeshengPlace3 = () => {
</GuideStepsItem>
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/rongdesheng/mp3_rongdesheng_20.mp3')}
audioSrc={getPublicPath('audio/rongdesheng/mp3_rongdesheng_20_1.mp3')}
>
这发生于1941年太平洋战争爆发、上海公共租界沦陷之后。日军以经济提携为名,通过行政命令强迫中国企业交由日本公司或个人管理,企图将上海的民族工业纳入其战时经济体系。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/rongdesheng/mp3_rongdesheng_20_2.mp3')}
>
日商丰田纱厂多次上门威逼利诱,想收购上海申新一、八厂;汪伪政权的外交部长也出面邀谈,企图逼迫他妥协。但荣德生始终坚守民族底线,不合作、不妥协。
</GuideStepsItem>
</GuideSteps>
@@ -282,7 +304,7 @@ const RongdeshengPlace3 = () => {
speaker="guide"
audioSrc={getPublicPath('audio/rongdesheng/mp3_rongdesheng_21.mp3')}
>
生死存亡之际,他用行动诠释了“民族气节”四个字。即便工厂被占据,也绝不与侵略者合作。
在那个生死存亡的时刻,他用自己的行动诠释了“民族气节”四个字,即便工厂被日军占据,也绝不与侵略者合作。
</GuideStepsItem>
</GuideSteps>
@@ -315,10 +337,10 @@ const RongdeshengPlace3 = () => {
<SingleChoiceQuizPanel
visible={quiz3Visible}
zIndex={10001}
title="【单选】面对威逼利诱与占领压力,荣德生坚持的原则最能体现为:"
title="【单选】荣德生先生题写“心正思无邪 意诚言必中”,背后体现的核心精神是:"
options={[
{ value: 'A', label: 'A. 民族气节,不合作、不妥协' },
{ value: 'B', label: 'B. 权宜之计,暂时让渡控制权' },
{ value: 'A', label: 'A. 面对日军威逼利诱,坚守民族气节绝不妥协' },
{ value: 'B', label: 'B. 告诫自己做好企业管理,把控产品品质' },
]}
correctAnswer="A"
errorMessage="这句题字是荣德生在民族危亡之际,表达坚守民族底线的心声,并非单纯针对企业管理。请你再想想吧!"
@@ -332,15 +354,9 @@ const RongdeshengPlace3 = () => {
onSuccessContinue={() => {
setQuiz3Visible(false)
// 展品3:答题成功后才算完成,更新 cursor 状态
setVisited((prev) => {
const next = new Set(prev)
if (!next.has(3)) {
next.add(3)
markExhibitVisited(place, 3)
saveRongdeshengStore()
}
return next
})
markExhibitVisited(place, 3)
// 触发碎片10获取动画(展品3 -> 碎片10)
triggerPuzzleAnimation(10)
}}
/>
</div>
+35 -1
View File
@@ -12,6 +12,7 @@ import { VideoBubbles } from './components/VideoBubbles'
import { HINT_AUDIOS, HINT_TEXTS } from './constants'
import { MODULES, getSceneTitle } from './modules.data'
import type { ModuleId, ModuleState, VideoPlayingState } from './types'
import { setCourseTimeProgress } from '@/lib/utils/api-common'
const RongdeshengReflection = () => {
const navigate = useNavigate()
@@ -28,8 +29,10 @@ const RongdeshengReflection = () => {
const markReflectionFinalCompleted = useRongdeshengStore((s) => s.markReflectionFinalCompleted)
const markReflectionGuideShown = useRongdeshengStore((s) => s.markReflectionGuideShown)
const [showOpeningVideo, setShowOpeningVideo] = useState(true)
// 学习引导弹窗:第一次进入时显示
const [showGuide, setShowGuide] = useState(!persistedGuideShown)
const [showGuide, setShowGuide] = useState(false)
// 当前选中的模块ID:默认选中第一个未完成的模块(断点续学)
const [currentModuleId, setCurrentModuleId] = useState<ModuleId>(() => {
@@ -260,6 +263,36 @@ const RongdeshengReflection = () => {
),
}}
>
{showOpeningVideo && (
<div className="fixed inset-0 z-10000 w-full h-full bg-black">
<Video
src={getPublicPath('video/reflection/rongdesheng_tiwu_00.mp4')}
autoPlay
playsInline
showSkipButton
skipText="跳过"
onEnded={() => {
setShowOpeningVideo(false)
if (!persistedGuideShown) {
setShowGuide(true)
}
}}
onSkip={() => {
setShowOpeningVideo(false)
if (!persistedGuideShown) {
setShowGuide(true)
}
}}
onError={() => {
setShowOpeningVideo(false)
if (!persistedGuideShown) {
setShowGuide(true)
}
}}
/>
</div>
)}
{/* 返回按钮 */}
<Image
className="absolute right-5 top-5 z-10"
@@ -337,6 +370,7 @@ const RongdeshengReflection = () => {
finalVideoStage={finalVideoStage}
onStart={() => setFinalVideoStage('playing')}
onVideoEnded={() => {
setCourseTimeProgress(20)
setFinalVideoStage('ended')
markReflectionFinalCompleted()
}}
@@ -1,3 +1,4 @@
import { getPublicPath } from '@/lib/utils'
import type { Module } from './types'
export const MODULES: Module[] = [
@@ -6,24 +7,34 @@ export const MODULES: Module[] = [
title: '建厂拓业',
backgroundImage: 'rongdesheng/reflection/场景1选中',
question:
'1915年,荣氏兄弟计划在上海创办申新纱厂,却面临三大难题:租界建厂受列强限制、资金短缺、原料运输不便。上海作为远东商贸中心,机遇与挑战并存,如何在这里建起属于中国人的纱厂?',
'1915年,荣氏兄弟计划在上海创办申新纱厂,却面临三大难题:租界建厂受列强限制、资金短缺、原料运输不便。上海作为远东商贸中心,机遇与挑战并存,如何在这里建起属于中国人的纱厂?',
options: [
{ id: 'opt1-1', text: '创办申新纱厂', isCorrect: true, videoResourceId: 'video1-1' },
{ id: 'opt1-2', text: '引进国外先进设备', isCorrect: true, videoResourceId: 'video1-2' },
{ id: 'opt1-3', text: '建立工会组织', isCorrect: false },
{
id: 'opt1-1',
text: '选址上海杨树浦(临黄浦江,便于原料运输)',
isCorrect: true,
videoResourceId: 'video1-1',
},
{
id: 'opt1-2',
text: '联合无锡帮华商与实业同仁招股筹款',
isCorrect: true,
videoResourceId: 'video1-2',
},
{ id: 'opt1-3', text: '依附租界洋行,换取建厂便利与资金支持', isCorrect: false },
],
videoResources: [
{
id: 'video1-1',
thumbnail: 'rongdesheng/reflection/视频1-1',
videoSrc: '/video/rongdesheng/rongdesheng_2_1.mp4',
videoSrc: getPublicPath('video/rongdesheng/rongdesheng_2_1.mp4'),
position: { top: '16%', left: '8%' },
flipY: false,
},
{
id: 'video1-2',
thumbnail: 'rongdesheng/reflection/视频1-2',
videoSrc: '/video/rongdesheng/rongdesheng_2_2.mp4',
videoSrc: getPublicPath('video/rongdesheng/rongdesheng_2_2.mp4'),
position: { top: '25%', left: '29%' },
flipY: true,
},
@@ -32,7 +43,7 @@ export const MODULES: Module[] = [
'择址临港、凝聚华商力量,申新在上海扎根生长,成为民族工业冲破列强封锁的一面旗帜!',
resultImage: 'rongdesheng/reflection/场景1结算',
wrongHint:
'荣氏兄弟坚守"实业救国"初心,拒绝洋行干涉企业经营,不愿民族产业沦为外资附庸!试试其他方案吧~',
'荣氏兄弟坚守“实业救国”初心,拒绝洋行干涉企业经营,不愿民族产业沦为外资附庸!试试其他方案吧~',
audio: { wrongHint: '' },
},
{
@@ -60,13 +71,13 @@ export const MODULES: Module[] = [
{
id: 'video2-1',
thumbnail: 'rongdesheng/reflection/视频2-1',
videoSrc: '/video/rongdesheng/rongdesheng_2_3.mp4',
videoSrc: getPublicPath('video/rongdesheng/rongdesheng_2_3.mp4'),
position: { top: '14%', left: '11%' },
},
{
id: 'video2-2',
thumbnail: 'rongdesheng/reflection/视频2-2',
videoSrc: '/video/rongdesheng/rongdesheng_2_4.mp4',
videoSrc: getPublicPath('video/rongdesheng/rongdesheng_2_4.mp4'),
position: { top: '21%', left: '22.2%' },
flipY: true,
},
@@ -103,13 +114,13 @@ export const MODULES: Module[] = [
{
id: 'video3-1',
thumbnail: 'rongdesheng/reflection/视频3-1',
videoSrc: '/video/rongdesheng/rongdesheng_2_5.mp4',
videoSrc: getPublicPath('video/rongdesheng/rongdesheng_2_5.mp4'),
position: { top: '15%', left: '44%' },
},
{
id: 'video3-2',
thumbnail: 'rongdesheng/reflection/视频3-2',
videoSrc: '/video/rongdesheng/rongdesheng_2_6.mp4',
videoSrc: getPublicPath('video/rongdesheng/rongdesheng_2_6.mp4'),
position: { bottom: '50%', left: '55%' },
flipY: true,
},
@@ -144,14 +155,14 @@ export const MODULES: Module[] = [
{
id: 'video4-1',
thumbnail: 'rongdesheng/reflection/视频4-1',
videoSrc: '/video/rongdesheng/rongdesheng_2_7.mp4',
videoSrc: getPublicPath('video/rongdesheng/rongdesheng_2_7.mp4'),
position: { top: '42%', left: '41%' },
flipY: true,
},
{
id: 'video4-2',
thumbnail: 'rongdesheng/reflection/视频4-2',
videoSrc: '/video/rongdesheng/rongdesheng_2_8.mp4',
videoSrc: getPublicPath('video/rongdesheng/rongdesheng_2_8.mp4'),
position: { bottom: '26%', right: '44%' },
},
],
@@ -189,13 +200,13 @@ export const MODULES: Module[] = [
{
id: 'video5-1',
thumbnail: 'rongdesheng/reflection/视频5-1',
videoSrc: '/video/rongdesheng/rongdesheng_2_10.mp4',
videoSrc: getPublicPath('video/rongdesheng/rongdesheng_2_10.mp4'),
position: { top: '25%', left: '42%' },
},
{
id: 'video5-2',
thumbnail: 'rongdesheng/reflection/视频5-2',
videoSrc: '/video/rongdesheng/rongdesheng_2_9.mp4',
videoSrc: getPublicPath('video/rongdesheng/rongdesheng_2_9.mp4'),
position: { bottom: '50%', left: '55%' },
flipY: true,
},
@@ -209,7 +220,7 @@ export const MODULES: Module[] = [
},
]
export const FINAL_VIDEO_SRC = '/video/rongdesheng/rongdesheng_2_11.mp4'
export const FINAL_VIDEO_SRC = getPublicPath('video/rongdesheng/rongdesheng_2_11.mp4')
const SCENE_NUMBER_MAP: Record<Module['id'], string> = {
scene1: '一',
+1 -1
View File
@@ -91,7 +91,7 @@ const Sunzhongshan = () => {
className="absolute right-37 top-22"
src={getStaticImage('components/革命先驱故居-返回')}
button
onClick={() => navigate(`/entrance/${moduleName}`)}
onClick={() => navigate(`/${moduleName}`)}
/>
<div className="absolute right-20 top-75 flex flex-col space-y-10">
+3 -3
View File
@@ -52,8 +52,8 @@ const QUIZ_ANCHORS: QuizAnchor[] = [
time: 60 + 14, //5.52分钟
title: '题目1',
prompt:
'【拖拽】习近平总书记在纪念孙中山先生诞辰150周年大会上指出: "中国共产党人是孙中山先生革命事业最坚定的',
promptSuffix: '、最忠实的继承者。"',
'【拖拽】习近平总书记在纪念孙中山先生诞辰150周年大会上指出: “中国共产党人是孙中山先生革命事业最坚定的',
promptSuffix: '、最忠实的继承者。”',
type: 'drag',
blanks: [
{ id: 'blank-1', label: '最坚定的' },
@@ -478,7 +478,7 @@ const SunzhongshanInternalization = () => {
<Narration
visible={showNarration}
audioChannelId="sunzhongshan-internalization-narration"
text="从翠亨村的农家院落到影响中国的革命征程,孙中山先生以'亟拯斯民于水火,切扶大厦之将倾'的担当,在近代中国的黑暗中点燃了民主共和的火种。他留下的不仅是推翻帝制、建立共和的历史功绩,更有'振兴中华'的初心、'天下为公'的情怀、'与时俱进'的智慧。今天,我们站在故居的青砖黛瓦间,不仅要回望他为民族独立、人民幸福奋斗的一生,更要让这份精神穿越时空,成为新时代青年砥砺前行的力量。"
text="从翠亨村的农家院落到影响中国的革命征程,孙中山先生以“亟拯斯民于水火,切扶大厦之将倾”的担当,在近代中国的黑暗中点燃了民主共和的火种。他留下的不仅是推翻帝制、建立共和的历史功绩,更有“振兴中华”的初心、“天下为公”的情怀、“与时俱进”的智慧。今天,我们站在故居的青砖黛瓦间,不仅要回望他为民族独立、人民幸福奋斗的一生,更要让这份精神穿越时空,成为新时代青年砥砺前行的力量。"
audioSrc={getPublicPath('audio/sunzhongshan/mp3_sunzhongshan_55.mp3')}
animationSpeed={200}
onClose={() => {
+103 -101
View File
@@ -13,9 +13,10 @@ import { useNavigate, useParams } from 'react-router-dom'
import { getBackgroundImage, getPublicPath, getStaticImage } from '@/lib/utils'
import { useState, useRef } from 'react'
import Cursor from '@/components/cursor'
import { SuccessAlert, DragQuizPanel } from '@/components/layout'
import { SuccessAlert, DragQuizPanel, ScaledPortal } from '@/components/layout'
import { MultipleChoiceQuizPanel, SelectFillQuizPanel } from '@/components/layout/quiz'
import { useCursorController } from '@/hooks/useCursorController'
import { useAudioManager } from '@/hooks/useAudioManager'
import { usePuzzleAnimation } from '@/contexts/PuzzleAnimationContext'
import { useSunzhongshanStore, saveSunzhongshanStore } from '@/store/sunzhongshanStore'
@@ -85,14 +86,19 @@ const SunzhongshanPlace1 = () => {
const navigate = useNavigate()
const { moduleName } = useParams<{ moduleName: string }>()
// 用于在触发视频前停止引导对话音频(避免音画重叠)
const { stop: stopGuideAudio } = useAudioManager({
mode: 'channel',
channelId: 'guide-steps-default',
})
// 拼图碎片掉落动画
const { triggerPuzzleAnimation } = usePuzzleAnimation()
// ===== 进入页面的两段引导(首段引导 → 顶层对话 → 后续引导) =====
const [showGuideModal, setShowGuideModal] = useState(true) // 进入即弹出的学习指南
const [topDialogVisible, setTopDialogVisible] = useState(false) // 学习指南关闭后展示的开场对话
const [showAfterDialogGuide, setShowAfterDialogGuide] = useState(false) // 开场对话结束后弹出的二段指南
const [afterDialogGuideShown, setAfterDialogGuideShown] = useState(false) // 二段指南只展示一次
const [showAfterDialogGuide, setShowAfterDialogGuide] = useState(false) // 温馨提示弹窗
// ===== 当前激活展品(仅存 ID,路径通过 map 查询) =====
const [activeExhibitId, setActiveExhibitId] = useState<ExhibitId | null>(null)
@@ -172,7 +178,7 @@ const SunzhongshanPlace1 = () => {
* - 展品 1:关闭后弹出多选题,答对后才标记为已访问
* - 展品 2:关闭后弹出衔接对话;由 GuideSteps 的 onVisibleChange 标记访问
* - 展品 3:关闭后播放衔接视频,视频结束后标记已访问
* - 展品 4:关闭后播放视频 → 拖拽题 → 成功提示,结束后标记已访问
* - 展品 4:对话结束播放视频 → 拖拽题 → 成功提示;关闭展品后标记已访问
* - 展品 5:直接关闭即可,由 GuideSteps 的 onVisibleChange 标记访问
* 默认流程(展品 2 / 5 走到此):标记访问 + 清空 activeExhibitId
*/
@@ -195,9 +201,7 @@ const SunzhongshanPlace1 = () => {
return
case 4:
guide4.close()
setActiveExhibitId(null)
setShowVideo4(true)
return
break
case 5:
guide5.close()
break // 走默认流程
@@ -280,13 +284,13 @@ const SunzhongshanPlace1 = () => {
/>
{/* ====================================================================
顶层引导:学习指南 → 开场对话 → 二段学习指南
顶层引导:学习指南 → 学习引导 → 开场对话
==================================================================== */}
<StudyGuideModal
visible={showGuideModal}
onContinue={() => {
setShowGuideModal(false)
setTopDialogVisible(true)
setShowAfterDialogGuide(true)
}}
audioSrc={audio('02')}
ailgn="left"
@@ -298,16 +302,22 @@ const SunzhongshanPlace1 = () => {
3.所有动态光点的展品全部学习后才能进入下一个场景学习。
</StudyGuideModal>
<StudyGuideModal
visible={showAfterDialogGuide}
onContinue={() => {
setShowAfterDialogGuide(false)
setTopDialogVisible(true)
}}
audioSrc={audio('01')}
firstLineIndent="2em"
>
{`同学你好,欢迎来到“走进故居·对话”学习模块!
此刻你正置身于孙中山故居堂屋,接下来请随时光的脚步,一同走进他人生中4段激情燃烧的岁月:立志救国·推翻帝制、讨袁护法·捍卫共和、与时俱进·探索新路、抱病北上·鞠躬尽瘁。做好准备了吗?开始探索这段波澜壮阔的历史征程吧!`}
</StudyGuideModal>
<GuideSteps
visible={topDialogVisible}
onVisibleChange={(nextVisible) => {
setTopDialogVisible(nextVisible)
// 开场对话结束后,仅首次触发后续引导
if (!nextVisible && !afterDialogGuideShown) {
setAfterDialogGuideShown(true)
setShowAfterDialogGuide(true)
}
}}
onVisibleChange={setTopDialogVisible}
showMask={false}
highlightSelectors={[]}
>
@@ -319,16 +329,6 @@ const SunzhongshanPlace1 = () => {
</GuideStepsItem>
</GuideSteps>
<StudyGuideModal
visible={showAfterDialogGuide}
onContinue={() => setShowAfterDialogGuide(false)}
audioSrc={audio('01')}
firstLineIndent="2em"
>
{`同学你好,欢迎来到“走进故居·对话”学习模块!
此刻你正置身于孙中山故居堂屋,接下来请随时光的脚步,一同走进他人生中四段激情燃烧的岁月:立志救国·推翻帝制、讨袁护法·捍卫共和、与时俱进·探索新路、抱病北上·鞠躬尽瘁。做好准备了吗?开始探索这段波澜壮阔的历史征程吧!`}
</StudyGuideModal>
{/* ====================================================================
光标 2:四大寇合影 —— 关闭后追加一段衔接对话
==================================================================== */}
@@ -349,7 +349,7 @@ const SunzhongshanPlace1 = () => {
“革命四杰”?
</GuideStepsItem>
<GuideStepsItem speaker="guide" audioSrc={audio('06')}>
是呀,这是孙中山先生在香港西医书院就读之时与同学的留影。孙先生1887年入学,其间广泛阅读西方自然科学和社会政治学说书籍。常与同学陈少白、尤列、杨鹤龄一起畅谈革命,抨击时政,被戏称为“四大寇”。
是呀,这是孙中山先生在香港西医书院就读之时与同学的留影。孙先生1887年入学,其间广泛阅读西方自然科学和社会政治学说书籍。常与同学陈少白、尤列、杨鹤龄一起畅谈革命,抨击时政,被戏称为“四大寇”。
</GuideStepsItem>
</GuideSteps>
@@ -405,28 +405,35 @@ const SunzhongshanPlace1 = () => {
<GuideSteps
visible={guide4.visible}
currentStep={guide4.step}
onStepChange={guide4.setStep}
onStepChange={(step) => {
if (step === 2) {
stopGuideAudio()
setShowVideo4(true)
return
}
guide4.setStep(step)
}}
onVisibleChange={guide4.setVisible}
showMask={false}
highlightSelectors={[]}
zIndex={10000}
>
<GuideStepsItem speaker="student" audioSrc={audio('13')}>
哇!这张《申报》好震撼,“中华民国万岁”“孙大总统万岁”,就连发行日期写的都是“中华民国元年元月二日”!
哇!这张《申报》好震撼,“中华民国万岁”“孙大总统万岁”,就连发行日期写的都是“中华民国元年元月二日”!
</GuideStepsItem>
<GuideStepsItem speaker="guide" audioSrc={audio('14')}>
没错!资产阶级民主革命的尝试走过了风雨兼程的十余年,这一过程中孙中山先生一直在探寻救国道路,1905年孙先生在亲自撰写的《民报》发刊词中明确提出了三民主义,即民族、民权、民生这一资产阶级民主革命的政纲。
{`没错!资产阶级民主革命的尝试走过了风雨兼程的十余年,这一过程中孙中山先生一直在探寻救国道路,1905年孙先生在亲自撰写的《<民报>发刊词》中明确提出了三民主义,即民族、民权、民生这一资产阶级民主革命的政纲。`}
</GuideStepsItem>
<GuideStepsItem speaker="guide" audioSrc={audio('15')}>
这些尝试终于在武昌起义过后迎来了回响。1911年年末,湖南、广东等14个省和上海纷纷宣布脱离清政府独立。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem speaker="guide" audioSrc={audio('16')}>
1912年1月1日上午,上海有一万多人聚集在沪宁车站,欢送孙先生前去南京就任临时大总统。当天夜里11点,孙先生在原清朝两江总督府宣誓就职,新的共和政体就此诞生。彼时仍属农历辛亥年,因此历史上将这一系列事件称为“辛亥革命”。
</GuideStepsItem>
</GuideSteps>
{/* 光标 4 衔接的视频:播放结束 → 进入拖拽题 */}
{showVideo4 && (
<ScaledPortal show={showVideo4} zIndex={20000}>
<Video
ref={video4Ref}
src={getPublicPath('video/sunzhongshan/sunzhongshan_02.mp4')}
@@ -439,60 +446,63 @@ const SunzhongshanPlace1 = () => {
setDragQuiz4Visible(true)
}}
/>
)}
</ScaledPortal>
{/* 光标 4 的拖拽题:要求三个空白填入 民族 / 民权 / 民生(顺序不限) */}
<DragQuizPanel
visible={dragQuiz4Visible}
zIndex={10000}
title=""
questionPrefix="【拖拽】三民主义是孙中山所倡导的民主革命纲领,是其民主思想的精髓和高度概括,具体包括"
questionSuffix="。"
options={[
{ value: '民族', label: '民族' },
{ value: '民权', label: '民权' },
{ value: '民生', label: '民生' },
{ value: '民主', label: '民主' },
]}
blanks={[
{ id: 'blank1', label: '____' },
{ id: 'blank2', label: '____' },
{ id: 'blank3', label: '____' },
]}
value={dragQuiz4Value}
error={dragQuiz4Error}
onChange={(value) => {
setDragQuiz4Value(value)
setDragQuiz4Error('')
// 三个空白未填满前不参与判定
if (value.length < 3) return
// 集合判等:忽略顺序,三个值需恰好等于 民族 / 民权 / 民生
const correctAnswers = ['民族', '民权', '民生']
const filledAnswers = value.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) {
setDragQuiz4Visible(false)
setSuccessAlert4Visible(true)
} else {
setDragQuiz4Error(
'三民主义是"民族主义""民权主义""民生主义"的合称,是孙中山在进行了一系列挽救民族危亡、探索国家近代化建设的尝试后,从经验中概括提炼而成的理论成果。三民主义形成后,又指导了进一步的革命实践并得到了新的发展。它是中国革命的宝贵精神遗产。'
)
}
}}
/>
{/* 光标 4 答对后的成功提示:关闭时标记展品 4 已访问 */}
<SuccessAlert
visible={successAlert4Visible}
onClose={() => {
setSuccessAlert4Visible(false)
markExhibitVisited(place, 4)
}}
/>
{/* 光标 4 的拖拽题:通过 ScaledPortal 渲染到 body,避免被 ExhibitOverlay 遮挡 */}
<ScaledPortal show={dragQuiz4Visible || successAlert4Visible} zIndex={10000}>
<DragQuizPanel
visible={dragQuiz4Visible}
zIndex={10000}
title=""
questionPrefix="【拖拽】三民主义是孙中山所倡导的民主革命纲领,是其民主思想的精髓和高度概括,具体包括"
questionSuffix="。"
options={[
{ value: '民族', label: '民族' },
{ value: '民权', label: '民权' },
{ value: '民生', label: '民生' },
{ value: '民主', label: '民主' },
]}
blanks={[
{ id: 'blank1', label: '____' },
{ id: 'blank2', label: '____' },
{ id: 'blank3', label: '____' },
]}
value={dragQuiz4Value}
error={dragQuiz4Error}
onChange={(value) => {
setDragQuiz4Value(value)
setDragQuiz4Error('')
// 三个空白未填满前不参与判定
if (value.length < 3) return
// 集合判等:忽略顺序,三个值需恰好等于 民族 / 民权 / 民生
const correctAnswers = ['民族', '民权', '民生']
const filledAnswers = value.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) {
setDragQuiz4Visible(false)
setDragQuiz4Value([])
setDragQuiz4Error('')
setSuccessAlert4Visible(true)
} else {
setDragQuiz4Error(
'三民主义是“民族主义”“民权主义”“民生主义”的合称,是孙中山在进行了一系列挽救民族危亡、探索国家近代化建设的尝试后,从经验中概括提炼而成的理论成果。三民主义形成后,又指导了进一步的革命实践并得到了新的发展。它是中国革命的宝贵精神遗产。'
)
}
}}
/>
{/* 光标 4 答对后的成功提示:关闭后切到下一句对话 */}
<SuccessAlert
visible={successAlert4Visible}
onClose={() => {
setSuccessAlert4Visible(false)
guide4.setStep(2)
}}
/>
</ScaledPortal>
{/* 光标 3 关闭后衔接的视频:播放结束后展示填空题(答对后才标记展品 3 已访问) */}
{showVideo4Second && (
@@ -535,10 +545,6 @@ const SunzhongshanPlace1 = () => {
<SuccessAlert
visible={successAlert3Visible}
title="恭喜你, 选择正确!"
onContinue={() => {
setSuccessAlert3Visible(false)
markExhibitVisited(place, 3)
}}
onClose={() => {
setSuccessAlert3Visible(false)
markExhibitVisited(place, 3)
@@ -557,13 +563,13 @@ const SunzhongshanPlace1 = () => {
zIndex={10000}
>
<GuideStepsItem speaker="student" audioSrc={audio('16')}>
孙中山先生领导的辛亥革命推动了中国社会的变革,可是这张《民立报》让我有点不明白,孙先生刚当上临时大总统,为什么又很快宣布“解职”了呀?
孙中山先生领导的辛亥革命推动了中国社会的变革,可是这张《民立报》让我有点不明白,孙先生刚当上临时大总统,为什么又很快宣布“解职”了呀?
</GuideStepsItem>
<GuideStepsItem speaker="guide" audioSrc={audio('17')}>
<GuideStepsItem speaker="guide" audioSrc={audio('17_1')}>
中华民国的建立有个重要背景——“南北议和”。辛亥革命中宣告独立的省份大多集中在中国南方,负隅顽抗的清政府将最后希望寄托在主导北洋军阀势力的袁世凯身上。在列强支持下,袁世凯率军南下。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
他一边以武力威胁革命势力,一边又迎合革命党人急于完成统一的愿望,诱使其开展和议。当时,革命党人力量还不够强。为避免国家陷入前途未卜的内战,孙中山先生发表声明:只要清帝退位,袁世凯赞成共和,就推举他做大总统。
<GuideStepsItem speaker="guide" audioSrc={audio('17_2')}>
他一边以武力威胁革命势力,一边又迎合革命党人急于完成统一的愿望,诱使其开展和议。当时,革命党人力量还不够强。为避免国家陷入前途未卜的内战,孙中山先生发表声明:只要清帝退位,袁世凯赞成共和,就推举他做大总统。
</GuideStepsItem>
</GuideSteps>
@@ -580,7 +586,7 @@ const SunzhongshanPlace1 = () => {
zIndex={10000}
>
<GuideStepsItem speaker="student" audioSrc={audio('18')}>
这算不算一种"妥协"呀?
这算不算一种“妥协”呀?
</GuideStepsItem>
<GuideStepsItem speaker="guide" audioSrc={audio('19')}>
可以说,这是当时客观条件限制下的一种无奈妥协。因为革命党人的首要目标是推翻帝制,在武装力量不足以对抗北洋军阀的情况下,只能暂时信任袁世凯,希望他能维护共和。你看旁边的“南北议和”纪念章和纪念墨盒,就是当时这段历史的见证。
@@ -600,8 +606,8 @@ const SunzhongshanPlace1 = () => {
title="【多选】关于辛亥革命的意义,下列表述正确的有:"
options={[
{ value: '1', label: 'A.结束了中国两千多年的君主专制制度' },
{ value: '2', label: 'B.传播了民主共和理念,推动了中华民族思想解放' },
{ value: '3', label: 'C.冲破了封建主义藩篱,打击了帝国主义在华势力' },
{ value: '2', label: 'B.传播了民主共和理念,推动了中华民族思想解放' },
{ value: '3', label: 'C.冲破了封建主义藩篱,打击了帝国主义在华势力' },
{ value: '4', label: 'D.完成了民族独立、人民解放的历史任务' },
]}
correctAnswer={['1', '2', '3']}
@@ -618,10 +624,6 @@ const SunzhongshanPlace1 = () => {
<SuccessAlert
visible={successAlert1Visible}
title="恭喜你, 选择正确!"
onContinue={() => {
setSuccessAlert1Visible(false)
markExhibitVisited(place, 1)
}}
onClose={() => {
setSuccessAlert1Visible(false)
markExhibitVisited(place, 1)
+17 -13
View File
@@ -246,15 +246,18 @@ const SunzhongshanPlace2 = () => {
speaker="student"
audioSrc={getPublicPath('audio/sunzhongshan/mp3_sunzhongshan_22.mp3')}
>
这张大大的路线图好复杂,上面画满了铁路线,这是孙中山先生规划的吗?
这张大大的路线图好复杂,上面画满了铁路线,这是孙中山先生规划的吗?
</GuideStepsItem>
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/sunzhongshan/mp3_sunzhongshan_23.mp3')}
audioSrc={getPublicPath('audio/sunzhongshan/mp3_sunzhongshan_23_1.mp3')}
>
对!这是中国铁路总公司的路线图。孙中山先生辞去临时大总统后,致力于国家的实业建设。他认为“交通为实业之母,铁路又为交通之母”。要让国家强大,首先要发展实业,投身铁路规划与建设就是其中一项重要实践。
对!这是中国铁路总公司的路线图。孙中山先生辞去临时大总统后,致力于国家的实业建设。他认为“交通为实业之母,铁路又为交通之母”。要让国家强大,首先要发展实业,投身铁路规划与建设就是其中一项重要实践。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/sunzhongshan/mp3_sunzhongshan_23_2.mp3')}
>
旁边这本《实业计划》中译稿手稿,就是他对中国实业发展的整体构想,里面不仅有铁路,还有港口、工厂等规划。这个计划也是孙先生在上海居住期间完成的。
</GuideStepsItem>
</GuideSteps>
@@ -314,8 +317,8 @@ const SunzhongshanPlace2 = () => {
visible={dragQuiz1Visible}
zIndex={10000}
title=""
questionPrefix="【拖拽】《实业计划》全书共二十余万字,孙中山在其中强调了现代"
questionSuffix="的建设蓝图。三者相辅相成,前业筑舍,阶级收合,这三个问题的方法。"
questionPrefix="【拖拽】《实业计划》全书共二十余万字,孙中山在其中擘画了现代"
questionSuffix="的建设蓝图。旨在提供解决国际战争、商业战争、阶级战争这三大问题的方法。"
options={[
{ value: '工业', label: '工业' },
{ value: '交通', label: '交通' },
@@ -342,7 +345,7 @@ const SunzhongshanPlace2 = () => {
[...answerSet].every((answer) => filledSet.has(answer))
if (isCorrect) {
setDragQuiz1Visible(false)
setSuccessAlert1Content('恭喜你,选择正确!')
setSuccessAlert1Content('')
setSuccessAlert1Visible(true)
setPendingQuiz1('complete')
} else {
@@ -356,7 +359,6 @@ const SunzhongshanPlace2 = () => {
<SuccessAlert
visible={successAlert1Visible}
content={successAlert1Content}
onContinue={handleSuccessAlert1Close}
onClose={handleSuccessAlert1Close}
/>
@@ -376,15 +378,18 @@ const SunzhongshanPlace2 = () => {
speaker="student"
audioSrc={getPublicPath('audio/sunzhongshan/mp3_sunzhongshan_24.mp3')}
>
真是一个宏伟的计划,可是在内忧外患的时局下,实现起来一定很艰难吧?
真是一个宏伟的计划,可是在内忧外患的时局下,实现起来一定很艰难吧?
</GuideStepsItem>
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/sunzhongshan/mp3_sunzhongshan_25.mp3')}
audioSrc={getPublicPath('audio/sunzhongshan/mp3_sunzhongshan_25_1.mp3')}
>
是的,1913年3月“宋教仁案”将国民党和袁世凯方面的矛盾推向高峰。不愿坐视袁世凯窃取辛亥革命果实后独揽大权,革命党人发动了“二次革命”。可惜革命失败,孙中山先生被迫流亡海外。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/sunzhongshan/mp3_sunzhongshan_25_2.mp3')}
>
袁世凯趁机撤销了他“筹办全国铁路全权”的职务,把铁路公司的事权交给了北京政府交通部,公司就这样解散了。
</GuideStepsItem>
</GuideSteps>
@@ -408,7 +413,7 @@ const SunzhongshanPlace2 = () => {
<SingleChoiceQuizPanel
visible={singleQuiz2Visible}
zIndex={10000}
title="【单选】护法运动,又称护法战争。第一次护法战争,1917年7月至1918年5月21日孙中山发动的维护《中华民国临时约法》和国会的军事行动。"
title="【单选】护法运动,又称护法战争、第一次护法战争,1917年7月至1918年5月21日孙中山发动的维护、反对段祺瑞独裁统治的斗争。"
options={[
{ value: 'A', label: 'A. 中华民国临时政府组织大纲' },
{ value: 'B', label: 'B. 中华民国临时约法' },
@@ -431,7 +436,6 @@ const SunzhongshanPlace2 = () => {
<SuccessAlert
visible={successAlert2Visible}
content={successAlert2Content}
onContinue={handleSuccessAlert2Close}
onClose={handleSuccessAlert2Close}
/>
</div>
+26 -19
View File
@@ -230,21 +230,24 @@ const SunzhongshanPlace3 = () => {
speaker="guide"
audioSrc={getPublicPath('audio/sunzhongshan/mp3_sunzhongshan_37.mp3')}
>
这封信的收件人是胡适。信里提到,孙中山先生经历了陈炯明叛变事件回到上海后,态度变得很冷静,不再坚持之前的护法主张,反而提出“收军权于中央”“结合民主的联合战线,与反动派决裂”等。
这封信的收件人是胡适。信里提到,孙中山先生经历了陈炯明叛变事件回到上海后,态度变得很冷静,不再坚持之前的护法主张,反而提出”收军权于中央””结合民主的联合战线,与反动派决裂”等。
</GuideStepsItem>
<GuideStepsItem
speaker="student"
audioSrc={getPublicPath('audio/sunzhongshan/mp3_sunzhongshan_38.mp3')}
>
孙先生与李大钊先生进行了深入的交谈。他在寻找新的合作力量吗?
孙先生与李大钊先生进行了深入的交谈。他在寻找新的合作力量吗?
</GuideStepsItem>
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/sunzhongshan/mp3_sunzhongshan_39.mp3')}
audioSrc={getPublicPath('audio/sunzhongshan/mp3_sunzhongshan_39_1.mp3')}
>
这个力量就是新生的中国共产党。孙中山先生向共产党人释放了巨大的诚意。除了与李大钊的频繁接触,他还在上海会见了共产国际代表马林,明确表示愿意和苏俄合作;邀请鲍罗庭来中国担任国民党组织教练员;
这个力量就是新生的中国共产党。孙中山先生向共产党人释放了巨大的诚意。除了与李大钊的频繁接触,他还在上海会见了共产国际代表马林,明确表示愿意和苏俄合作;邀请鲍罗庭来中国担任国民党组织教练员;
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/sunzhongshan/mp3_sunzhongshan_39_2.mp3')}
>
与陈独秀多次沟通,在共产国际与中共的建议和帮助下改组国民党,为国民党一大的召开做准备。
</GuideStepsItem>
</GuideSteps>
@@ -265,15 +268,18 @@ const SunzhongshanPlace3 = () => {
speaker="student"
audioSrc={getPublicPath('audio/sunzhongshan/mp3_sunzhongshan_40.mp3')}
>
我记得在毛泽东旧居陈列馆中学到,国共第一次合作正式形成的标志就是国民党一大,这就是当时的大会宣言吗?
我记得在毛泽东旧居陈列馆中学到,国共第一次合作正式形成的标志就是国民党一大,这就是当时的大会宣言吗?
</GuideStepsItem>
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/sunzhongshan/mp3_sunzhongshan_41.mp3')}
audioSrc={getPublicPath('audio/sunzhongshan/mp3_sunzhongshan_41_1.mp3')}
>
完全正确!1924年1月20日,中国国民党第一次全国代表大会在广东高师礼堂开幕,孙中山先生以国民党总理的身份担任大会主席,主持开幕式。此次大会共有165名代表出席,其中共产党员代表超过20人,包括李大钊、毛泽东等中共早期领导人。
完全正确!1924年1月20日,中国国民党第一次全国代表大会在广东高师礼堂开幕,孙中山先生以国民党总理的身份担任大会主席,主持开幕式。此次大会共有165名代表出席,其中共产党员代表超过20人,包括李大钊、毛泽东等中共早期领导人。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/sunzhongshan/mp3_sunzhongshan_41_2.mp3')}
>
会议上,孙先生重新解释了三民主义,事实上确立了联俄、联共、扶助农工的三大政策。第一次国共合作正式形成后,轰轰烈烈的国民革命也就此展开。
</GuideStepsItem>
</GuideSteps>
@@ -283,7 +289,7 @@ const SunzhongshanPlace3 = () => {
<Video
ref={video2Ref}
src={getPublicPath('video/sunzhongshan/sunzhongshan_08.mp4')}
className="fixed inset-0 z-[20000]"
className="fixed inset-0 z-20000"
autoPlay
playsInline
showSkipButton
@@ -340,11 +346,6 @@ const SunzhongshanPlace3 = () => {
{/* 光标2的成功提示 */}
<SuccessAlert
visible={successAlert2Visible}
onContinue={() => {
setSuccessAlert2Visible(false)
// 修改cursor状态
markExhibitVisited(place, 2)
}}
onClose={() => {
setSuccessAlert2Visible(false)
// 修改cursor状态
@@ -372,11 +373,14 @@ const SunzhongshanPlace3 = () => {
</GuideStepsItem>
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/sunzhongshan/mp3_sunzhongshan_35.mp3')}
audioSrc={getPublicPath('audio/sunzhongshan/mp3_sunzhongshan_35_1.mp3')}
>
旁边这本《星期评论》创刊于五四运动后。孙中山先生在五四运动中看到了群众的力量和工人、学生们的热情。更觉顺应时代潮流,传播新文化、介绍马克思主义、鼓励工人运动的重要性。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/sunzhongshan/mp3_sunzhongshan_35_2.mp3')}
>
这份进步刊物成为一扇窗,让国人,也让孙先生自己看到了民族、民主革命的新转机。
</GuideStepsItem>
</GuideSteps>
@@ -401,11 +405,14 @@ const SunzhongshanPlace3 = () => {
</GuideStepsItem>
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/sunzhongshan/mp3_sunzhongshan_33.mp3')}
audioSrc={getPublicPath('audio/sunzhongshan/mp3_sunzhongshan_33_1.mp3')}
>
不仅关注,而且还很早。1912年10月14日-16日,孙中山先生连续三日在上海三马路(今汉口路)中华大戏院发表关于社会主义学说的演讲,吸引了众多听众。这就是演说后经由他本人修订出版的小册子。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/sunzhongshan/mp3_sunzhongshan_33_2.mp3')}
>
后来俄国十月革命胜利,上海《民国日报》最先在中国报道了这个消息。
</GuideStepsItem>
</GuideSteps>
+12 -8
View File
@@ -125,12 +125,10 @@ const SunzhongshanPlace4 = () => {
{completed && (
<Image
className="absolute bottom-0 right-140"
src={getStaticImage('components/place/l-前进')}
className="absolute bottom-0 right-0"
src={getStaticImage('components/place/离开场景')}
button
onClick={() => {
const { setLastVisitedPlace } = useSunzhongshanStore.getState()
setLastVisitedPlace('place4')
setCourseTimeProgress(4)
saveSunzhongshanStore()
navigate(`/${moduleName}/${starName}`)
@@ -235,14 +233,20 @@ const SunzhongshanPlace4 = () => {
</GuideStepsItem>
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/sunzhongshan/mp3_sunzhongshan_45.mp3')}
audioSrc={getPublicPath('audio/sunzhongshan/mp3_sunzhongshan_45_1.mp3')}
>
没错。1924年10月,冯玉祥将军发动“北京政变”,推翻了曹锟政府,邀请孙中山先生北上共商国是,希望能实现国家和平统一。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
孙先生认为“这是一个好机会”,于是11月13日从广州出发北上,途经上海、日本、天津等地,一路宣传反帝、反封建主张。孙先生抵达上海时,外滩码头上聚集了前来欢迎的各界代表、群众四千多人。
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/sunzhongshan/mp3_sunzhongshan_45_2.mp3')}
>
孙先生认为“这是一个好机会”,于是11月13日从广州出发北上,途经上海、天津等地,一路宣传反帝、反封建主张。孙先生抵达上海时,外滩码头上聚集了前来欢迎的各界代表、群众四千多人。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/sunzhongshan/mp3_sunzhongshan_45_3.mp3')}
>
大家关心国家前途命运,拥护孙先生,认同他在《北上宣言》中提出的“废除不平等条约”“召开国民会议”等主张,真心希望结束军阀割据,国家实现统一,民族实现独立。
</GuideStepsItem>
</GuideSteps>
+29 -3
View File
@@ -1,6 +1,6 @@
import { useState, useEffect } from 'react'
import { useNavigate, useParams } from 'react-router-dom'
import { Image, StudyGuideModal } from '@/components'
import { useLocation, useNavigate, useParams } from 'react-router-dom'
import { Image, StudyGuideModal, Video } from '@/components'
import { getBackgroundImage, getPublicPath, getStaticImage } from '@/lib/utils'
import { useSunzhongshanStore, saveSunzhongshanStore } from '@/store/sunzhongshanStore'
@@ -9,11 +9,15 @@ const GUIDE_STORAGE_KEY = `${starName}-reflection-guide-shown`
const SunzhongshanReflection = () => {
const navigate = useNavigate()
const location = useLocation()
const fromReflectionSubpage = !!(location.state as { fromReflectionSubpage?: boolean } | null)
?.fromReflectionSubpage
const { moduleName } = useParams<{
moduleName: string
}>()
const [selectedScene, setSelectedScene] = useState<number | null>(null)
const [showOpeningVideo, setShowOpeningVideo] = useState(!fromReflectionSubpage)
const [showGuide, setShowGuide] = useState(false)
const [sceneCompleted, setSceneCompleted] = useState<{
scene1: boolean
@@ -59,7 +63,7 @@ const SunzhongshanReflection = () => {
useEffect(() => {
const hasShownGuide = getReflectionFlag(GUIDE_STORAGE_KEY)
if (!hasShownGuide) {
setShowGuide(true)
setShowGuide(!showOpeningVideo)
}
}, [getReflectionFlag])
@@ -78,6 +82,13 @@ const SunzhongshanReflection = () => {
}
}
const handleOpeningVideoDone = () => {
setShowOpeningVideo(false)
if (!getReflectionFlag(GUIDE_STORAGE_KEY)) {
setShowGuide(true)
}
}
const handleGuideClose = () => {
setShowGuide(false)
setReflectionFlag(GUIDE_STORAGE_KEY, true)
@@ -100,6 +111,21 @@ const SunzhongshanReflection = () => {
backgroundImage: getBackgroundImage(`${starName}/reflection/home/背景`),
}}
>
{showOpeningVideo && (
<div className="fixed inset-0 z-10000 w-full h-full bg-black">
<Video
src={getPublicPath('video/reflection/sunzhongshan_tiwu_00.mp4')}
autoPlay
playsInline
showSkipButton
skipText="跳过"
onEnded={handleOpeningVideoDone}
onSkip={handleOpeningVideoDone}
onError={handleOpeningVideoDone}
/>
</div>
)}
{/* 关闭按钮 */}
<Image
className="absolute right-0 top-0 z-10"
+50 -57
View File
@@ -76,8 +76,9 @@ const SunzhongshanReflectionScene1 = () => {
const [showClueHint, setShowClueHint] = useState(false)
const [showClueBook, setShowClueBook] = useState(false)
const [showSuccessAlert, setShowSuccessAlert] = useState(false)
const [successAlertContent, setSuccessAlertContent] = useState('')
const [showClueSuccessAlert, setShowClueSuccessAlert] = useState(false)
const [clueSuccessAlertContent, setClueSuccessAlertContent] = useState('')
const [showQuizSuccessAlert, setShowQuizSuccessAlert] = useState(false)
const [showVideo, setShowVideo] = useState(true)
const [showGuide, setShowGuide] = useState(false)
const [showQuiz1, setShowQuiz1] = useState(false)
@@ -104,8 +105,8 @@ const SunzhongshanReflectionScene1 = () => {
const handleClueClick = (clueId: string, clueName: string) => {
// 如果已收集,不重复收集
if (isClueCollected(clueId)) {
setSuccessAlertContent('该线索已收集!')
setShowSuccessAlert(true)
setClueSuccessAlertContent('该线索已收集!')
setShowClueSuccessAlert(true)
return
}
@@ -124,8 +125,8 @@ const SunzhongshanReflectionScene1 = () => {
duration: 1200,
onComplete: () => {
// 动画完成后的回调
setSuccessAlertContent('获得1条线索,已获得线索可在线索簿中查看!')
setShowSuccessAlert(true)
setClueSuccessAlertContent('获得1条线索,已获得线索可在线索簿中查看!')
setShowClueSuccessAlert(true)
},
})
}
@@ -138,8 +139,8 @@ const SunzhongshanReflectionScene1 = () => {
// 如果线索1已收集,显示提示
if (isClueCollected(clue1Id)) {
setSuccessAlertContent('该线索已收集!')
setShowSuccessAlert(true)
setClueSuccessAlertContent('该线索已收集!')
setShowClueSuccessAlert(true)
return
}
@@ -158,8 +159,8 @@ const SunzhongshanReflectionScene1 = () => {
duration: 1200,
onComplete: () => {
// 动画完成后显示成功提示
setSuccessAlertContent('获得该线索!快去线索簿查看详细信息吧!')
setShowSuccessAlert(true)
setClueSuccessAlertContent('获得该线索!快去线索簿查看详细信息吧!')
setShowClueSuccessAlert(true)
},
})
}
@@ -204,8 +205,7 @@ const SunzhongshanReflectionScene1 = () => {
const handleQuiz1Submit = (_value: string, isCorrect: boolean) => {
if (isCorrect) {
setShowQuiz1(false)
setSuccessAlertContent('回答正确!')
setShowSuccessAlert(true)
setShowQuizSuccessAlert(true)
setPendingQuiz('quiz2')
}
}
@@ -214,8 +214,7 @@ const SunzhongshanReflectionScene1 = () => {
const handleQuiz2Submit = (_value: string, isCorrect: boolean) => {
if (isCorrect) {
setShowQuiz2(false)
setSuccessAlertContent('回答正确!')
setShowSuccessAlert(true)
setShowQuizSuccessAlert(true)
setPendingQuiz('quiz3')
}
}
@@ -224,13 +223,32 @@ const SunzhongshanReflectionScene1 = () => {
const handleQuiz3Submit = (_value: string, isCorrect: boolean) => {
if (isCorrect) {
setShowQuiz3(false)
setSuccessAlertContent('回答正确!')
setShowSuccessAlert(true)
setShowQuizSuccessAlert(true)
// 设置待显示的完成提示
setPendingQuiz('complete')
}
}
const handleClueSuccessAlertClose = () => {
setShowClueSuccessAlert(false)
setClueSuccessAlertContent('')
}
const handleQuizSuccessAlertClose = () => {
setShowQuizSuccessAlert(false)
if (pendingQuiz === 'quiz2') {
setShowQuiz2(true)
setPendingQuiz(null)
} else if (pendingQuiz === 'quiz3') {
setShowQuiz3(true)
setPendingQuiz(null)
} else if (pendingQuiz === 'complete') {
triggerPuzzleAnimation(12)
setShowCompleteGuide(true)
setPendingQuiz(null)
}
}
// 处理完成提示关闭
const handleCompleteGuideClose = () => {
setShowCompleteGuide(false)
@@ -303,7 +321,9 @@ const SunzhongshanReflectionScene1 = () => {
button
onClick={() => {
if (moduleName && starName) {
navigate(`/${moduleName}/${starName}/reflection`)
navigate(`/${moduleName}/${starName}/reflection`, {
state: { fromReflectionSubpage: true },
})
}
}}
/>
@@ -399,49 +419,22 @@ const SunzhongshanReflectionScene1 = () => {
getClueHintImage={getClueHintImage}
/>
{/* 成功提示弹窗 */}
{/* 获取碎片提示弹窗 */}
<SuccessAlert
visible={showSuccessAlert}
title="恭喜你!"
content={successAlertContent}
visible={showClueSuccessAlert}
title="恭喜你!"
content={clueSuccessAlertContent}
audioSrc={
successAlertContent === '获得1条线索,已获得线索可在线索簿中查看!'
clueSuccessAlertContent === '获得1条线索,已获得线索可在线索簿中查看!'
? getPublicPath('audio/sunzhongshan/mp3_sunzhongshan_49.mp3')
: undefined
}
onContinue={() => {
setShowSuccessAlert(false)
setSuccessAlertContent('')
// 检查是否有待显示的题目或完成提示
if (pendingQuiz === 'quiz2') {
setShowQuiz2(true)
setPendingQuiz(null)
} else if (pendingQuiz === 'quiz3') {
setShowQuiz3(true)
setPendingQuiz(null)
} else if (pendingQuiz === 'complete') {
triggerPuzzleAnimation(12)
setShowCompleteGuide(true)
setPendingQuiz(null)
}
}}
onClose={() => {
setShowSuccessAlert(false)
setSuccessAlertContent('')
// 检查是否有待显示的题目或完成提示
if (pendingQuiz === 'quiz2') {
setShowQuiz2(true)
setPendingQuiz(null)
} else if (pendingQuiz === 'quiz3') {
setShowQuiz3(true)
setPendingQuiz(null)
} else if (pendingQuiz === 'complete') {
setShowCompleteGuide(true)
setPendingQuiz(null)
}
}}
onClose={handleClueSuccessAlertClose}
/>
{/* 答题成功提示弹窗 */}
<SuccessAlert visible={showQuizSuccessAlert} onClose={handleQuizSuccessAlertClose} />
{/* 学习引导弹窗 */}
<StudyGuideModal
visible={showGuide}
@@ -459,13 +452,13 @@ const SunzhongshanReflectionScene1 = () => {
{/* 题目1:今天的日期是? */}
<SingleChoiceQuizPanel
visible={showQuiz1}
title="【单选】今天的日期是?"
title="【单选】今天的日期是?"
options={[
{ value: 'A', label: 'A.1919年5月5日' },
{ value: 'B', label: 'B.1921年5月5日' },
]}
correctAnswer="B"
errorMessage="回答错误,请你再仔细思考一下吧。"
errorMessage="回答错误,请你再仔细思考一下吧。"
onSubmit={handleQuiz1Submit}
zIndex={10001}
/>
@@ -473,7 +466,7 @@ const SunzhongshanReflectionScene1 = () => {
{/* 题目2:今天的日记写什么呢? */}
<SingleChoiceQuizPanel
visible={showQuiz2}
title="【单选】今天的日记写什么呢?"
title="【单选】今天的日记写什么呢?"
options={[
{
value: 'A',
@@ -495,7 +488,7 @@ const SunzhongshanReflectionScene1 = () => {
{/* 题目3:作为五四运动的见证者、参与者,你觉得中国革命有望通过怎样的方式实现? */}
<SingleChoiceQuizPanel
visible={showQuiz3}
title="【单选】作为五四运动的见证者、参与者,你觉得中国革命有望通过怎样的方式实现?"
title="【单选】作为五四运动的见证者、参与者,你觉得中国革命有望通过怎样的方式实现?"
options={[
{
value: 'A',
+49 -56
View File
@@ -86,8 +86,9 @@ const SunzhongshanReflectionScene2 = () => {
const [showClueHint, setShowClueHint] = useState(false)
const [showClueBook, setShowClueBook] = useState(false)
const [showSuccessAlert, setShowSuccessAlert] = useState(false)
const [successAlertContent, setSuccessAlertContent] = useState('')
const [showClueSuccessAlert, setShowClueSuccessAlert] = useState(false)
const [clueSuccessAlertContent, setClueSuccessAlertContent] = useState('')
const [showQuizSuccessAlert, setShowQuizSuccessAlert] = useState(false)
const [showVideo, setShowVideo] = useState(true)
const [showGuide, setShowGuide] = useState(false)
const [showQuiz1, setShowQuiz1] = useState(false)
@@ -114,8 +115,8 @@ const SunzhongshanReflectionScene2 = () => {
const handleClueClick = (clueId: string, clueName: string) => {
// 如果已收集,不重复收集
if (isClueCollected(clueId)) {
setSuccessAlertContent('该线索已收集!')
setShowSuccessAlert(true)
setClueSuccessAlertContent('该线索已收集!')
setShowClueSuccessAlert(true)
return
}
@@ -134,8 +135,8 @@ const SunzhongshanReflectionScene2 = () => {
duration: 1200,
onComplete: () => {
// 动画完成后的回调
setSuccessAlertContent('获得1条线索,已获得线索可在线索簿中查看!')
setShowSuccessAlert(true)
setClueSuccessAlertContent('获得1条线索,已获得线索可在线索簿中查看!')
setShowClueSuccessAlert(true)
},
})
}
@@ -148,8 +149,8 @@ const SunzhongshanReflectionScene2 = () => {
// 如果线索1已收集,显示提示
if (isClueCollected(clue1Id)) {
setSuccessAlertContent('该线索已收集!')
setShowSuccessAlert(true)
setClueSuccessAlertContent('该线索已收集!')
setShowClueSuccessAlert(true)
return
}
@@ -168,8 +169,8 @@ const SunzhongshanReflectionScene2 = () => {
duration: 1200,
onComplete: () => {
// 动画完成后显示成功提示
setSuccessAlertContent('获得1条线索,已获得线索可在线索簿中查看!')
setShowSuccessAlert(true)
setClueSuccessAlertContent('获得1条线索,已获得线索可在线索簿中查看!')
setShowClueSuccessAlert(true)
},
})
}
@@ -214,8 +215,7 @@ const SunzhongshanReflectionScene2 = () => {
const handleQuiz1Submit = (_value: string, isCorrect: boolean) => {
if (isCorrect) {
setShowQuiz1(false)
setSuccessAlertContent('回答正确!')
setShowSuccessAlert(true)
setShowQuizSuccessAlert(true)
setPendingQuiz('quiz2')
}
}
@@ -224,8 +224,7 @@ const SunzhongshanReflectionScene2 = () => {
const handleQuiz2Submit = (_value: string, isCorrect: boolean) => {
if (isCorrect) {
setShowQuiz2(false)
setSuccessAlertContent('回答正确!')
setShowSuccessAlert(true)
setShowQuizSuccessAlert(true)
setPendingQuiz('quiz3')
}
}
@@ -234,13 +233,32 @@ const SunzhongshanReflectionScene2 = () => {
const handleQuiz3Submit = (_value: string, isCorrect: boolean) => {
if (isCorrect) {
setShowQuiz3(false)
setSuccessAlertContent('回答正确!')
setShowSuccessAlert(true)
setShowQuizSuccessAlert(true)
// 设置待显示的完成提示
setPendingQuiz('complete')
}
}
const handleClueSuccessAlertClose = () => {
setShowClueSuccessAlert(false)
setClueSuccessAlertContent('')
}
const handleQuizSuccessAlertClose = () => {
setShowQuizSuccessAlert(false)
if (pendingQuiz === 'quiz2') {
setShowQuiz2(true)
setPendingQuiz(null)
} else if (pendingQuiz === 'quiz3') {
setShowQuiz3(true)
setPendingQuiz(null)
} else if (pendingQuiz === 'complete') {
triggerPuzzleAnimation(13)
setShowCompleteGuide(true)
setPendingQuiz(null)
}
}
// 处理完成提示关闭
const handleCompleteGuideClose = () => {
setShowCompleteGuide(false)
@@ -317,7 +335,9 @@ const SunzhongshanReflectionScene2 = () => {
button
onClick={() => {
if (moduleName && starName) {
navigate(`/${moduleName}/${starName}/reflection`)
navigate(`/${moduleName}/${starName}/reflection`, {
state: { fromReflectionSubpage: true },
})
}
}}
/>
@@ -413,49 +433,22 @@ const SunzhongshanReflectionScene2 = () => {
getClueHintImage={getClueHintImage}
/>
{/* 成功提示弹窗 */}
{/* 获取碎片提示弹窗 */}
<SuccessAlert
visible={showSuccessAlert}
title="恭喜你!"
content={successAlertContent}
visible={showClueSuccessAlert}
title="恭喜你!"
content={clueSuccessAlertContent}
audioSrc={
successAlertContent === '获得1条线索,已获得线索可在线索簿中查看!'
clueSuccessAlertContent === '获得1条线索,已获得线索可在线索簿中查看!'
? getPublicPath('audio/sunzhongshan/mp3_sunzhongshan_49.mp3')
: undefined
}
onContinue={() => {
setShowSuccessAlert(false)
setSuccessAlertContent('')
// 检查是否有待显示的题目或完成提示
if (pendingQuiz === 'quiz2') {
setShowQuiz2(true)
setPendingQuiz(null)
} else if (pendingQuiz === 'quiz3') {
setShowQuiz3(true)
setPendingQuiz(null)
} else if (pendingQuiz === 'complete') {
triggerPuzzleAnimation(13)
setShowCompleteGuide(true)
setPendingQuiz(null)
}
}}
onClose={() => {
setShowSuccessAlert(false)
setSuccessAlertContent('')
// 检查是否有待显示的题目或完成提示
if (pendingQuiz === 'quiz2') {
setShowQuiz2(true)
setPendingQuiz(null)
} else if (pendingQuiz === 'quiz3') {
setShowQuiz3(true)
setPendingQuiz(null)
} else if (pendingQuiz === 'complete') {
setShowCompleteGuide(true)
setPendingQuiz(null)
}
}}
onClose={handleClueSuccessAlertClose}
/>
{/* 答题成功提示弹窗 */}
<SuccessAlert visible={showQuizSuccessAlert} onClose={handleQuizSuccessAlertClose} />
{/* 学习引导弹窗 */}
<StudyGuideModal
visible={showGuide}
@@ -476,8 +469,8 @@ const SunzhongshanReflectionScene2 = () => {
visible={showQuiz1}
title="【单选】当下无产阶级工人运动面对的敌人主要有哪些?"
options={[
{ value: 'A', label: 'A: 地主和旧官僚' },
{ value: 'B', label: 'B: 军阀势力和帝国主义列强' },
{ value: 'A', label: 'A.地主和旧官僚' },
{ value: 'B', label: 'B.军阀势力和帝国主义列强' },
]}
correctAnswer="B"
errorMessage="回答错误,请你再仔细思考一下吧。"
@@ -510,7 +503,7 @@ const SunzhongshanReflectionScene2 = () => {
{/* 题目3:当下无产阶级工人运动面临困境,你觉得谁可以成为工人们的朋友? */}
<SingleChoiceQuizPanel
visible={showQuiz3}
title="【单选】当下无产阶级工人运动面临困境,你觉得谁可以成为工人们的朋友?"
title="【单选】当下无产阶级工人运动面临困境,你觉得谁可以成为工人们的朋友?"
options={[
{
value: 'A',
+52 -59
View File
@@ -78,8 +78,9 @@ const SunzhongshanReflectionScene3 = () => {
const [showClueHint, setShowClueHint] = useState(false)
const [showClueBook, setShowClueBook] = useState(false)
const [showSuccessAlert, setShowSuccessAlert] = useState(false)
const [successAlertContent, setSuccessAlertContent] = useState('')
const [showClueSuccessAlert, setShowClueSuccessAlert] = useState(false)
const [clueSuccessAlertContent, setClueSuccessAlertContent] = useState('')
const [showQuizSuccessAlert, setShowQuizSuccessAlert] = useState(false)
const [showErrorAlert, setShowErrorAlert] = useState(false)
const [errorAlertContent, setErrorAlertContent] = useState('')
const [showVideo, setShowVideo] = useState(true)
@@ -112,8 +113,8 @@ const SunzhongshanReflectionScene3 = () => {
const handleClueClick = (clueId: string, clueName: string) => {
// 如果已收集,不重复收集
if (isClueCollected(clueId)) {
setSuccessAlertContent('该线索已收集!')
setShowSuccessAlert(true)
setClueSuccessAlertContent('该线索已收集!')
setShowClueSuccessAlert(true)
return
}
@@ -132,8 +133,8 @@ const SunzhongshanReflectionScene3 = () => {
duration: 1200,
onComplete: () => {
// 动画完成后的回调
setSuccessAlertContent('获得1条线索,已获得线索可在线索簿中查看!')
setShowSuccessAlert(true)
setClueSuccessAlertContent('获得1条线索,已获得线索可在线索簿中查看!')
setShowClueSuccessAlert(true)
// 如果是在"窃窃私语"场景收集了线索1,显示 Cursor
if (!isMainScene && clueId === `${sceneId}/clue1`) {
@@ -160,8 +161,8 @@ const SunzhongshanReflectionScene3 = () => {
// 如果线索4已收集,显示提示
if (isClueCollected(clue4Id)) {
setSuccessAlertContent('该线索已收集!')
setShowSuccessAlert(true)
setClueSuccessAlertContent('该线索已收集!')
setShowClueSuccessAlert(true)
return
}
@@ -180,8 +181,8 @@ const SunzhongshanReflectionScene3 = () => {
duration: 1200,
onComplete: () => {
// 动画完成后显示成功提示
setSuccessAlertContent('获得1条线索,已获得线索可在线索簿中查看!')
setShowSuccessAlert(true)
setClueSuccessAlertContent('获得1条线索,已获得线索可在线索簿中查看!')
setShowClueSuccessAlert(true)
},
})
}
@@ -226,8 +227,7 @@ const SunzhongshanReflectionScene3 = () => {
const handleQuiz1Submit = (_value: string, isCorrect: boolean) => {
if (isCorrect) {
setShowQuiz1(false)
setSuccessAlertContent('回答正确!')
setShowSuccessAlert(true)
setShowQuizSuccessAlert(true)
setPendingQuiz('quiz2')
}
}
@@ -236,8 +236,7 @@ const SunzhongshanReflectionScene3 = () => {
const handleQuiz2Submit = (_value: string, isCorrect: boolean) => {
if (isCorrect) {
setShowQuiz2(false)
setSuccessAlertContent('回答正确!')
setShowSuccessAlert(true)
setShowQuizSuccessAlert(true)
setPendingQuiz('quiz3')
}
}
@@ -246,12 +245,31 @@ const SunzhongshanReflectionScene3 = () => {
const handleQuiz3Submit = (_value: string, _isCorrect: boolean) => {
// 题目3的两个选项都正确,所以总是成功
setShowQuiz3(false)
setSuccessAlertContent('回答正确!')
setShowSuccessAlert(true)
setShowQuizSuccessAlert(true)
// 设置待显示的完成提示
setPendingQuiz('complete')
}
const handleClueSuccessAlertClose = () => {
setShowClueSuccessAlert(false)
setClueSuccessAlertContent('')
}
const handleQuizSuccessAlertClose = () => {
setShowQuizSuccessAlert(false)
if (pendingQuiz === 'quiz2') {
setShowQuiz2(true)
setPendingQuiz(null)
} else if (pendingQuiz === 'quiz3') {
setShowQuiz3(true)
setPendingQuiz(null)
} else if (pendingQuiz === 'complete') {
triggerPuzzleAnimation(14)
setShowCompleteGuide(true)
setPendingQuiz(null)
}
}
// 处理完成提示关闭
const handleCompleteGuideClose = () => {
setShowCompleteGuide(false)
@@ -260,8 +278,8 @@ const SunzhongshanReflectionScene3 = () => {
if (moduleName && starName) {
const { setReflectionRoute } = useSunzhongshanStore.getState()
setReflectionRoute('reflection3')
setCourseTimeProgress(5)
saveSunzhongshanStore()
setCourseTimeProgress(5)
navigate(`/${moduleName}/${starName}/home`)
}
}
@@ -349,7 +367,9 @@ const SunzhongshanReflectionScene3 = () => {
button
onClick={() => {
if (moduleName && starName) {
navigate(`/${moduleName}/${starName}/reflection`)
navigate(`/${moduleName}/${starName}/reflection`, {
state: { fromReflectionSubpage: true },
})
}
}}
/>
@@ -459,49 +479,22 @@ const SunzhongshanReflectionScene3 = () => {
getClueHintImage={getClueHintImage}
/>
{/* 成功提示弹窗 */}
{/* 获取碎片提示弹窗 */}
<SuccessAlert
visible={showSuccessAlert}
title="恭喜你!"
content={successAlertContent}
visible={showClueSuccessAlert}
title="恭喜你!"
content={clueSuccessAlertContent}
audioSrc={
successAlertContent === '获得1条线索,已获得线索可在线索簿中查看!'
clueSuccessAlertContent === '获得1条线索,已获得线索可在线索簿中查看!'
? getPublicPath('audio/sunzhongshan/mp3_sunzhongshan_49.mp3')
: undefined
}
onContinue={() => {
setShowSuccessAlert(false)
setSuccessAlertContent('')
// 检查是否有待显示的题目或完成提示
if (pendingQuiz === 'quiz2') {
setShowQuiz2(true)
setPendingQuiz(null)
} else if (pendingQuiz === 'quiz3') {
setShowQuiz3(true)
setPendingQuiz(null)
} else if (pendingQuiz === 'complete') {
triggerPuzzleAnimation(14)
setShowCompleteGuide(true)
setPendingQuiz(null)
}
}}
onClose={() => {
setShowSuccessAlert(false)
setSuccessAlertContent('')
// 检查是否有待显示的题目或完成提示
if (pendingQuiz === 'quiz2') {
setShowQuiz2(true)
setPendingQuiz(null)
} else if (pendingQuiz === 'quiz3') {
setShowQuiz3(true)
setPendingQuiz(null)
} else if (pendingQuiz === 'complete') {
setShowCompleteGuide(true)
setPendingQuiz(null)
}
}}
onClose={handleClueSuccessAlertClose}
/>
{/* 答题成功提示弹窗 */}
<SuccessAlert visible={showQuizSuccessAlert} onClose={handleQuizSuccessAlertClose} />
{/* 错误提示弹窗 */}
<ErrorAlert
visible={showErrorAlert}
@@ -528,7 +521,7 @@ const SunzhongshanReflectionScene3 = () => {
{/* 题目1:冯玉祥发动"北京政变"后邀请孙中山北上共商国是,各界对此事的看法如何? */}
<SingleChoiceQuizPanel
visible={showQuiz1}
title='【单选】冯玉祥发动"北京政变"后邀请孙中山北上共商国是,各界对此事的看法如何?'
title="【单选】冯玉祥发动“北京政变”后邀请孙中山北上共商国是,各界对此事的看法如何?"
options={[
{ value: 'A', label: 'A.意见高度一致,认为和平统一已经十拿九稳' },
{ value: 'B', label: 'B.期待、信任为主,亦有担忧和谨慎的探讨' },
@@ -542,7 +535,7 @@ const SunzhongshanReflectionScene3 = () => {
{/* 题目2:毅然北上赴会的孙中山先生,为争取国家和平统一作出了哪些努力? */}
<SingleChoiceQuizPanel
visible={showQuiz2}
title="【单选】毅然北上赴会的孙中山先生,为争取国家和平统一作出了哪些努力?"
title="【单选】毅然北上赴会的孙中山先生,为争取国家和平统一作出了哪些努力?"
options={[
{
value: 'A',
@@ -560,15 +553,15 @@ const SunzhongshanReflectionScene3 = () => {
zIndex={10001}
/>
{/* 题目3:今天的日记写什么呢? */}
{/* 题目3:今天的日记写什么呢? */}
<SingleChoiceQuizPanel
visible={showQuiz3}
title="【单选】今天的日记写什么呢?"
title="【单选】今天的日记写什么呢?"
options={[
{
value: 'A',
label:
'A.当前情形,反对军阀势力进而反对列强压迫已成为国共两党乃至各界共识。只是这一目标应当如何达成呢?今年上半年就听说黄埔军校在全国招生的消息,大批青年踊跃报考。可见组建军队,武力北伐是一个公认的选项。然下半年直系军阀倒台,冯玉祥电邀孙先生北上主政,便又打开了和平统一的一线契机,如能实现,亦是应了国人的期盼。今日孙先生行程抵沪,马不停蹄接见各界人士,宣讲北上主张,我辈见之无不钦佩其鞠躬尽瘁之心。前路未明,探索不止,希望国民共同努力,早日迎来和平民主的新天地。',
'A.当前情形,反对军阀势力进而反对列强压迫已成为国共两党乃至各界共识。只是这一目标应当如何达成呢?今年上半年就听说黄埔军校在全国招生的消息,大批青年踊跃报考。可见组建军队,武力北伐是一个公认的选项。然下半年直系军阀倒台,冯玉祥电邀孙先生北上主政,便又打开了和平统一的一线契机,如能实现,亦是应了国人的期盼。今日孙先生行程抵沪,马不停蹄接见各界人士,宣讲北上主张,我辈见之无不钦佩其鞠躬尽瘁之心。前路未明,探索不止,希望国民共同努力,早日迎来和平民主的新天地。',
},
{
value: 'B',
+1 -1
View File
@@ -40,7 +40,7 @@ const ZhanglepingHome = () => {
className="absolute right-37 top-22"
src={getStaticImage('components/革命先驱故居-返回')}
button
onClick={() => navigate(`/entrance/${moduleName}`)}
onClick={() => navigate(`/${moduleName}`)}
/>
<div className="absolute right-20 top-75 flex flex-col space-y-10">
+9 -4
View File
@@ -197,6 +197,9 @@ const ZhanglepingInternalization = () => {
if (isCorrect) {
const opt = item.options.find((o) => o.value === value)
setActiveSuccess(item.id)
if (item.id === 'quiz-3') {
triggerPuzzleAnimation(15)
}
setSuccessContent(opt?.explanation || '恭喜你,选择正确!')
setResumeCallback(() => resume)
}
@@ -295,7 +298,7 @@ const ZhanglepingInternalization = () => {
'先生善于捕捉核心细节展现人物特质,化繁为简的场景刻画让漫画更具感染力,这正是“以少胜多”艺术境界的体现。'
)
} else if (item.id === 'quiz-4') {
triggerPuzzleAnimation(15)
triggerPuzzleAnimation(16)
setSuccessContent(
'张乐平先生坚持艺术扎根生活,以简洁笔墨刻画时代,用作品传递温暖与正义,这些都是他的核心创作理念。'
)
@@ -440,7 +443,6 @@ const ZhanglepingInternalization = () => {
onVisibleChange={(v) => {
setPostGuideVisible(v)
if (!v) {
triggerPuzzleAnimation(16)
setShowLetter(true)
}
}}
@@ -449,11 +451,14 @@ const ZhanglepingInternalization = () => {
>
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/zhangleping/mp3_zhangleping_34.mp3')}
audioSrc={getPublicPath('audio/zhangleping/mp3_zhangleping_34_1.mp3')}
>
张乐平先生的精神,不是挂在墙上的口号,而是“源于生活”的真诚、“化繁为简”的智慧、“向善向美”的坚守。这份精神,藏在三毛的三根头发里,藏在先生的笔墨春秋中,更藏在我们每一个人的日常行动里。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/zhangleping/mp3_zhangleping_34_2.mp3')}
>
现在,就让我们站在这座承载着温暖与力量的故居中,把对先生的崇敬、对精神的体悟,浓缩进你想对先生说的话中吧!
</GuideStepsItem>
</GuideSteps>
+13 -4
View File
@@ -172,17 +172,26 @@ const ZhanglepingPlace1 = () => {
>
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/zhangleping/mp3_zhangleping_05.mp3')}
audioSrc={getPublicPath('audio/zhangleping/mp3_zhangleping_05_1.mp3')}
>
你看这里的绣品,就源自张先生的母亲。她能字画、工女红,尤擅剪纸、刺绣。张先生自幼耳濡目染,受到启蒙,在小学老师陆寅生指导下,表现出不凡的绘画才能。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/zhangleping/mp3_zhangleping_05_2.mp3')}
>
因为有绘画的陪伴,张先生的童年还是过得非常愉快的,正如你看到的《童年的回忆》这幅画。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/zhangleping/mp3_zhangleping_05_3.mp3')}
>
但是因为家境贫寒,张先生15岁就只身来到南汇万祥镇,在木料行当学徒,其间他在老板的纸堆上偷偷画画,哪怕被责打也不放弃,还用芦条在沙滩上画巨龙,巨龙里蕴藏的就是他“以画报国”的初心。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/zhangleping/mp3_zhangleping_05_4.mp3')}
>
由此可见,艺术从不是温室里的花朵,而是在生活的土壤中倔强生长的种子。
</GuideStepsItem>
</GuideSteps>
+10 -4
View File
@@ -184,11 +184,14 @@ const ZhanglepingPlace2 = () => {
</GuideStepsItem>
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/zhangleping/mp3_zhangleping_09.mp3')}
audioSrc={getPublicPath('audio/zhangleping/mp3_zhangleping_09_1.mp3')}
>
这就是张乐平先生画作在当时发挥的最重要的作用!他的幅幅作品犹如刺刀钢枪,揭露日军暴行,抨击汉奸敌伪的卖国嘴脸,直至抗战的最后胜利。那时他常说“漫画是流动的宣传队”。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/zhangleping/mp3_zhangleping_09_2.mp3')}
>
他的漫画还被译成多国文字,印成传单,空投敌占区,宣传正义,呼唤良知,这正是“笔铸刀枪”的力量!
</GuideStepsItem>
</GuideSteps>
@@ -217,11 +220,14 @@ const ZhanglepingPlace2 = () => {
</GuideStepsItem>
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/zhangleping/mp3_zhangleping_07.mp3')}
audioSrc={getPublicPath('audio/zhangleping/mp3_zhangleping_07_1.mp3')}
>
这是个很好的问题!张乐平先生的画笔在不同的历史时期发挥着不同作用,比如抗日战争时是“刀枪”,揭露日寇暴行;解放战争时是“镜子”,映照社会现实;新中国成立后是“园丁”,为万千儿童种下真善美的种子。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/zhangleping/mp3_zhangleping_07_2.mp3')}
>
六十年笔墨不辍,他笔下不只是漫画故事,更是陪着一代代中国人长大的心灵成长书。张先生用画笔为我们带来了对社会深刻的洞察和对普通民众深切的关怀。
</GuideStepsItem>
</GuideSteps>
+9 -3
View File
@@ -180,14 +180,20 @@ const ZhanglepingPlace3 = () => {
</GuideStepsItem>
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/zhangleping/mp3_zhangleping_13.mp3')}
audioSrc={getPublicPath('audio/zhangleping/mp3_zhangleping_13_1.mp3')}
>
确实如此。这幅名叫《向日葵》的画选自于1950—1965年的《三毛日记》。像这样的“新三毛”形象再次发生重要转变是自1949年新中国成立之后,三毛戴上红领巾,成为新中国的少先队员。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/zhangleping/mp3_zhangleping_13_2.mp3')}
>
这一变化不仅标志着角色自身的成长,更是整个中国社会由战乱走向安定、由贫弱迈向新生的时代缩影。三毛那三根标志性的头发,仿佛穿越时光的见证者,记录下一个民族从山河破碎到国泰民安的巨大变迁。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/zhangleping/mp3_zhangleping_13_3.mp3')}
>
他的每一次蜕变,不仅是一个漫画人物的成长,更是一个国家与一代人的共同记忆。
</GuideStepsItem>
</GuideSteps>
+7 -4
View File
@@ -87,7 +87,7 @@ const ZhanglepingPlace4 = () => {
button
onClick={() => {
if (moduleName && starName) {
navigate(`/${moduleName}/${starName}/home`)
navigate(`/${moduleName}/${starName}/place3`)
}
}}
/>
@@ -140,11 +140,14 @@ const ZhanglepingPlace4 = () => {
</GuideStepsItem>
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/zhangleping/mp3_zhangleping_15.mp3')}
audioSrc={getPublicPath('audio/zhangleping/mp3_zhangleping_15_1.mp3')}
>
张乐平先生将其一生都献给了中国漫画和儿童事业。他说:“大树是从苗苗长大的,对苗苗我们要注意浇灌。”新中国成立后,张先生像“园丁”一样,经常深入学校、少年宫等辅导孩子们画画。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/zhangleping/mp3_zhangleping_15_2.mp3')}
>
他说:“一看到孩子,我心里就乐。和他们在一起,我忘乎所以。我别无他技,只有把为孩子服务,当作报国的手段。”张先生为儿童作画,与孩子交流,从中汲取灵感与素材,不断创作出儿童喜爱的漫画作品,为万千儿童种下真善美的种子。
</GuideStepsItem>
</GuideSteps>
@@ -173,7 +176,7 @@ const ZhanglepingPlace4 = () => {
<SingleChoiceQuizPanel
visible={quizVisible && quizStep === 1}
zIndex={10000}
title="【单选】新中国成立后,张乐平先生的画笔成为“园丁”,他的作品主要起到了以下哪种作用"
title="【单选】新中国成立后,张乐平先生的画笔成为“园丁”,他的作品主要起到了以下哪种作用?"
options={[
{
value: 'A',
+14 -12
View File
@@ -176,17 +176,26 @@ const ZhanglepingPlace5 = () => {
</GuideStepsItem>
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/zhangleping/mp3_zhangleping_17.mp3')}
audioSrc={getPublicPath('audio/zhangleping/mp3_zhangleping_17_1.mp3')}
>
是呀!这面汇聚了五十二位著名漫画家创作的瓷板画墙,堪称中国漫画界一次意义非凡的“集体致敬”,每一位参与者都以自己独特的艺术语言,重新诠释了三毛与张乐平先生之间的故事。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/zhangleping/mp3_zhangleping_17_2.mp3')}
>
这些作品风格各异,有的充满诙谐趣味,有的流淌着温暖深情,但共同诉说着一个深刻的主题——张乐平先生作为漫画泰斗的巨大行业影响力。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/zhangleping/mp3_zhangleping_17_3.mp3')}
>
他创办的《漫画世界》不仅是一本刊物,更成为培育漫画新人的重要园地,为中国漫画界输送了大量杰出人才。正因如此,他在同行中赢得了“平民大师”的美誉,这不仅是对其艺术成就的肯定,更是对其人格魅力的褒奖。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
<GuideStepsItem
speaker="guide"
audioSrc={getPublicPath('audio/zhangleping/mp3_zhangleping_17_4.mp3')}
>
这些画作中流淌的是承载着跨越半个世纪的深厚情谊与中国漫画事业的代际传承,更是用漫画传递真善美的中国漫画精神的重要见证。
</GuideStepsItem>
</GuideSteps>
@@ -210,15 +219,8 @@ const ZhanglepingPlace5 = () => {
markExhibitVisited(place, activeExhibitId)
}
if (activeExhibitId === 3) {
triggerPuzzleAnimation(14).then(() => {
setEndingVisible(true)
// setEndingShown(true)
})
// setTimeout(() => {
// setEndingVisible(true)
setEndingVisible(true)
// setEndingShown(true)
// }, 1000)
}
setActiveExhibitId(null)
+34 -4
View File
@@ -1,6 +1,6 @@
import { useEffect, useMemo, useState } from 'react'
import { useNavigate, useParams } from 'react-router-dom'
import { Image, StudyGuideModal } from '@/components'
import { useLocation, useNavigate, useParams } from 'react-router-dom'
import { Image, StudyGuideModal, Video } from '@/components'
import { getBackgroundImage, getPublicPath, getStaticImage } from '@/lib/utils'
import { useZhanglepingStore, saveZhanglepingStore } from '@/store/zhanglepingStore'
@@ -57,7 +57,11 @@ const sceneConfig: SceneConfig[] = [
const ZhanglepingReflection = () => {
const navigate = useNavigate()
const location = useLocation()
const fromReflectionSubpage = !!(location.state as { fromReflectionSubpage?: boolean } | null)
?.fromReflectionSubpage
const { moduleName } = useParams<{ moduleName: string }>()
const [showOpeningVideo, setShowOpeningVideo] = useState(!fromReflectionSubpage)
const [showGuideModal, setShowGuideModal] = useState(false)
const { getVisitedReflectionCursors, setReflectionRoute, hasSeenStudyGuide, markStudyGuideSeen } =
@@ -66,9 +70,9 @@ const ZhanglepingReflection = () => {
// 只在第一次进入时显示引导窗口
useEffect(() => {
if (!hasSeenStudyGuide) {
setShowGuideModal(true)
setShowGuideModal(!showOpeningVideo)
}
}, [hasSeenStudyGuide])
}, [hasSeenStudyGuide, showOpeningVideo])
const sceneProgress = useMemo<Record<SceneId, number>>(() => {
const getCount = (sceneId: SceneId) => getVisitedReflectionCursors(`scene${sceneId}`).length
@@ -87,6 +91,8 @@ const ZhanglepingReflection = () => {
return sceneProgress[sceneId] >= cfg.totalFragments
}
const allFragmentsCompleted = sceneConfig.every((scene) => isSceneCompleted(scene.id))
// 场景逐步解锁:1 默认解锁;N 需要 N-1 完成
const isSceneUnlocked = (sceneId: SceneId) => {
if (sceneId === 1) return true
@@ -106,6 +112,21 @@ const ZhanglepingReflection = () => {
return (
<>
{showOpeningVideo && (
<div className="fixed inset-0 z-10000 w-full h-full bg-black">
<Video
src={getPublicPath('video/reflection/zhangleping_tiwu_00.mp4')}
autoPlay
playsInline
showSkipButton
skipText="跳过"
onEnded={() => setShowOpeningVideo(false)}
onSkip={() => setShowOpeningVideo(false)}
onError={() => setShowOpeningVideo(false)}
/>
</div>
)}
{/* 学习引导窗口 */}
<StudyGuideModal
visible={showGuideModal}
@@ -192,6 +213,15 @@ const ZhanglepingReflection = () => {
)
})}
{allFragmentsCompleted && (
<Image
className="absolute bottom-0 right-0 z-10"
src={getStaticImage('components/place/离开场景')}
button
onClick={() => navigate(`/${moduleName}/${starName}/home`)}
/>
)}
{/* 碎片收集 */}
<div className="absolute right-0 top-[27%] w-[24%] h-[68%]">
{/* 5 个碎片位(按效果图纵向排列,可继续微调坐标) */}
+9 -5
View File
@@ -21,9 +21,9 @@ type ExhibitId = 1 | 2
type FragmentId = 3
// 第二阶段:唯一正确物品(其余都算“选择题错误”)
const CORRECT_DRAG_OPTION = 'drag-1'
const CORRECT_DRAG_OPTION = 'drag-3'
const DRAG_ERROR_HINT =
'这些物品虽然与绘画有关,但并不是解锁记忆碎片的关键。试试找找与“眼里看到的真东西”相关的物品吧!'
'这个物品虽然与绘画有关,但并不是解锁记忆碎片的关键。试试找找与“眼里看到的真东西”相关的物品吧!'
const PERSISTENT_DRAG_HINT = '拖拽关联的物品至虚框处解锁记忆碎片'
const DRAG_ERROR_HINT_DURATION_MS = 3200
const DRAG_ERROR_HINT_RESET_DELAY_MS = 3300
@@ -43,16 +43,19 @@ const DRAG_ITEMS = [
id: 'drag-1',
className: 'absolute left-[21%] top-[66.5%]',
src: 'zhangleping/reflection/scene1/场景1拖拽1',
name: '漫画书',
},
{
id: 'drag-2',
className: 'absolute right-[28%] bottom-[26%]',
src: 'zhangleping/reflection/scene1/场景1拖拽2',
name: '笔',
},
{
id: 'drag-3',
className: 'absolute right-[22%] bottom-[14%]',
src: 'zhangleping/reflection/scene1/场景1拖拽3',
name: '手绢',
},
] as const
@@ -152,7 +155,8 @@ const ZhanglepingReflectionScene1 = () => {
[hint, isDragPhase]
)
const handleLeave = () => navigate(`/${moduleName}/${STAR_NAME}/reflection`)
const handleLeave = () =>
navigate(`/${moduleName}/${STAR_NAME}/reflection`, { state: { fromReflectionSubpage: true } })
const openExhibit = (id: ExhibitId) => {
setActiveExhibit(id)
setShowExhibit1Dialogue(false)
@@ -235,7 +239,7 @@ const ZhanglepingReflectionScene1 = () => {
return (
<div className={`${cfg.className}`} style={{ opacity: isUsed ? 0 : 1 }}>
<Image src={getStaticImage(cfg.src)} />
{!isUsed && !isDragging && (
{!isUsed && !isDragging && option.value !== 'drag-2' && (
<div
className={cn(
'pointer-events-none absolute left-1/2 -translate-x-1/2 -bottom-10 z-10',
@@ -392,7 +396,7 @@ const ZhanglepingReflectionScene1 = () => {
zIndex={10000}
>
<SanmaoDialogueItem audioSrc={getPublicPath('audio/zhangleping/mp3_zhangleping_22.mp3')}>
原来爷爷用画笔记录普通人的生活!这才创造出“我”
原来爷爷用画笔记录普通人的生活!这才创造出“我”。
</SanmaoDialogueItem>
</SanmaoDialogueSteps>
</div>
+45 -6
View File
@@ -1,4 +1,4 @@
import { useCallback, useMemo, useState } from 'react'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { useNavigate, useParams } from 'react-router-dom'
import { Image, Title, Cursor, ExhibitFlipOverlay, DragFill, SceneHint } from '@/components'
import type { DragFillResult } from '@/components'
@@ -6,6 +6,7 @@ import { cn, getBackgroundImage, getPublicPath, getStaticImage } from '@/lib/uti
import { useZhanglepingStore, saveZhanglepingStore } from '@/store/zhanglepingStore'
import { SanmaoDialogueItem, SanmaoDialogueSteps } from '@/pages/zhangleping/components'
import { useCursorController } from '@/hooks'
import { useAudioManager } from '@/hooks/useAudioManager'
import { useSceneHintController } from './useSceneHintController'
const STAR_NAME = 'zhangleping'
@@ -27,11 +28,11 @@ const exhibitAssets: Record<ExhibitId, { src: string }> = {
const exhibitDialogues: Record<ExhibitId, Record<string, string>> = {
1: {
text: '1937年打仗了,爷爷画了我当兵的故事,因为他说大家要一起打鬼子!',
text: '1937年打仗了,爷爷画了我当兵的故事,因为他说大家要一起打鬼子!',
audioSrc: getPublicPath('audio/zhangleping/mp3_zhangleping_23.mp3'),
},
2: {
text: '我在抗战中成为一名小战士,才真正理解"团结就是力量"。',
text: '我在抗战中成为一名小战士,才真正理解“团结就是力量”。',
audioSrc: getPublicPath('audio/zhangleping/mp3_zhangleping_24.mp3'),
},
}
@@ -126,9 +127,17 @@ const ZhanglepingReflectionScene2 = () => {
const isDragPhase = resourcesCompleted && !fragmentCompleted
const [showRepairOverlay, setShowRepairOverlay] = useState(false)
const [showFragmentOverlay, setShowFragmentOverlay] = useState(false)
const [fragmentUnlockHintPhase, setFragmentUnlockHintPhase] = useState<
'none' | 'story' | 'success'
>(fragmentCompleted ? 'success' : 'none')
const [successHintKey, setSuccessHintKey] = useState(0)
const hint = useSceneHintController(isDragPhase, PERSISTENT_DRAG_HINT)
const fragmentUnlockAudioSrc = getPublicPath('audio/zhangleping/mp3_zhangleping_36.mp3')
const { currentSrc, isPlaying, play, stop } = useAudioManager({
mode: 'channel',
channelId: 'zhangleping-fragment-2-hint',
})
// 获取指定释放区域已放置的物品
const getBlankFilledItem = useCallback(
@@ -182,10 +191,32 @@ const ZhanglepingReflectionScene2 = () => {
const handleCloseFragmentOverlay = useCallback(() => {
setShowFragmentOverlay(false)
markVisitedAndSave(3 as FragmentId)
setFragmentUnlockHintPhase('story')
setSuccessHintKey((k) => k + 1)
}, [markVisitedAndSave])
const handleLeave = () => navigate(`/${moduleName}/${STAR_NAME}/reflection`)
useEffect(() => {
if (fragmentUnlockHintPhase !== 'story') return
play(fragmentUnlockAudioSrc)
}, [fragmentUnlockAudioSrc, fragmentUnlockHintPhase, play])
useEffect(() => {
if (fragmentUnlockHintPhase !== 'story') return
if (currentSrc !== fragmentUnlockAudioSrc || isPlaying) return
setFragmentUnlockHintPhase('success')
setSuccessHintKey((k) => k + 1)
}, [currentSrc, fragmentUnlockAudioSrc, fragmentUnlockHintPhase, isPlaying])
useEffect(() => {
return () => {
if (currentSrc === fragmentUnlockAudioSrc) stop()
}
}, [currentSrc, fragmentUnlockAudioSrc, stop])
const handleLeave = () =>
navigate(`/${moduleName}/${STAR_NAME}/reflection`, { state: { fromReflectionSubpage: true } })
const openExhibit = (id: ExhibitId) => {
setActiveExhibit(id)
@@ -228,11 +259,19 @@ const ZhanglepingReflectionScene2 = () => {
)}
{/* 场景提示:碎片已解锁 */}
{!showRepairOverlay && !showFragmentOverlay && fragmentCompleted && (
{!showRepairOverlay && !showFragmentOverlay && fragmentUnlockHintPhase === 'story' && (
<SceneHint when triggerKey={successHintKey} persistent offset={{ x: -20 }}>
抗战烽火·记忆碎片已解锁
战乱中的三毛,和所有人一起守护家园。
</SceneHint>
)}
{!showRepairOverlay &&
!showFragmentOverlay &&
fragmentCompleted &&
fragmentUnlockHintPhase === 'success' && (
<SceneHint when triggerKey={successHintKey} persistent offset={{ x: -20 }}>
抗战烽火·记忆碎片已解锁
</SceneHint>
)}
{/* 场景提示:拖拽阶段常驻/错误提示 */}
{!showRepairOverlay &&
!showFragmentOverlay &&
+5 -4
View File
@@ -36,9 +36,9 @@ const exhibitDialogues: Record<ExhibitId, Record<string, string>> = {
}
// 第二阶段:点击常量
const CORRECT_CLICK_ITEM = 'click-2'
const CORRECT_CLICK_ITEM = 'click-3'
const CLICK_ERROR_HINT =
'这些物品虽然与三毛有关,但并不是解锁记忆碎片的关键。试试找找与“苦难里还有善良”相关的物品吧!'
'这个物品虽然与三毛有关,但并不是解锁记忆碎片的关键。试试找找与“苦难里还有善良”相关的物品吧!'
const PERSISTENT_CLICK_HINT = '点击正确的物品解锁记忆碎片'
const CLICK_ERROR_HINT_DURATION_MS = 3200
const CLICK_ERROR_HINT_RESET_DELAY_MS = 3300
@@ -143,7 +143,8 @@ const ZhanglepingReflectionScene3 = () => {
setSuccessHintKey((k) => k + 1)
}, [markVisitedAndSave])
const handleLeave = () => navigate(`/${moduleName}/${STAR_NAME}/reflection`)
const handleLeave = () =>
navigate(`/${moduleName}/${STAR_NAME}/reflection`, { state: { fromReflectionSubpage: true } })
const openExhibit = (id: ExhibitId) => {
setActiveExhibit(id)
@@ -338,7 +339,7 @@ const ZhanglepingReflectionScene3 = () => {
zIndex={10000}
>
<SanmaoDialogueItem audioSrc={getPublicPath('audio/zhangleping/mp3_zhangleping_27.mp3')}>
流浪的三毛,从来没忘记帮助别人,苦难里还有善良!
流浪的三毛,从来没忘记帮助别人,苦难里还有善良!
</SanmaoDialogueItem>
</SanmaoDialogueSteps>
+3 -2
View File
@@ -170,7 +170,8 @@ const ZhanglepingReflectionScene4 = () => {
setSuccessHintKey((k) => k + 1)
}, [markVisitedAndSave])
const handleLeave = () => navigate(`/${moduleName}/${STAR_NAME}/reflection`)
const handleLeave = () =>
navigate(`/${moduleName}/${STAR_NAME}/reflection`, { state: { fromReflectionSubpage: true } })
const openExhibit = (id: ExhibitId) => {
setActiveExhibit(id)
@@ -393,7 +394,7 @@ const ZhanglepingReflectionScene4 = () => {
zIndex={10000}
>
<SanmaoDialogueItem audioSrc={getPublicPath('audio/zhangleping/mp3_zhangleping_28.mp3')}>
1949年以后,我有了新衣服,可以上学了!爷爷说,现在的小朋友不用流浪啦!所以爷爷又创作出了爱科学的我,并在新中国第一份综合性少儿期刊《儿童时代》上连载。
1949年以后,我有了新衣服,可以上学了!爷爷说,现在的小朋友不用流浪啦!所以爷爷又创作出了爱科学的我,并在新中国第一份综合性少儿期刊《儿童时代》上连载。
</SanmaoDialogueItem>
</SanmaoDialogueSteps>
)}
+10 -2
View File
@@ -14,6 +14,7 @@ import { cn, getBackgroundImage, getStaticImage } from '@/lib/utils'
import { downloadDataUrlImage } from '@/lib/utils/sensitiveWords'
import { useZhanglepingStore, saveZhanglepingStore } from '@/store/zhanglepingStore'
import { useSceneHintController } from './useSceneHintController'
import { usePuzzleAnimation } from '@/contexts/PuzzleAnimationContext'
const starName = 'zhangleping'
@@ -80,6 +81,8 @@ const ZhanglepingReflectionScene5 = () => {
hasDrawArtwork,
} = useZhanglepingStore()
const { triggerPuzzleAnimation } = usePuzzleAnimation()
// 根据 store 中碎片是否已解锁决定初始子场景
const fragmentAlreadyUnlocked = getVisitedReflectionCursors(scene).includes(1)
const [subScene, setSubScene] = useState<1 | 2 | 3>(fragmentAlreadyUnlocked ? 3 : 1)
@@ -164,9 +167,10 @@ const ZhanglepingReflectionScene5 = () => {
const handleCloseFragmentOverlay = useCallback(() => {
setShowFragmentOverlay(false)
setAllPlaced(true)
triggerPuzzleAnimation(14)
// 短暂展示插入碎片后效果,然后进入画板场景
setTimeout(() => setSubScene(3), 800)
}, [])
}, [triggerPuzzleAnimation])
const handleDownloadArtwork = useCallback(async () => {
if (!hasSubmittedArtwork) return
@@ -201,7 +205,11 @@ const ZhanglepingReflectionScene5 = () => {
className="absolute right-5 top-5 z-10"
src={getStaticImage('components/返回')}
button
onClick={() => navigate(`/${moduleName}/${starName}/reflection`)}
onClick={() =>
navigate(`/${moduleName}/${starName}/reflection`, {
state: { fromReflectionSubpage: true },
})
}
/>
)}