376 lines
14 KiB
TypeScript
376 lines
14 KiB
TypeScript
import { useEffect, useMemo, useRef, useState } from 'react'
|
|
import { useNavigate, useParams } from 'react-router-dom'
|
|
import {
|
|
Image,
|
|
Video,
|
|
type VideoAnchor,
|
|
type VideoRef,
|
|
LetterToNieer,
|
|
Narration,
|
|
EndingOverlay,
|
|
} from '@/components'
|
|
import { getBackgroundImage, getPublicPath, getStaticImage } from '@/lib/utils'
|
|
import { GuideSteps, GuideStepsItem } from '@/components'
|
|
import { SuccessAlert } from '@/components/layout'
|
|
import { MultipleChoiceQuizPanel, SingleChoiceQuizPanel } from '@/components/layout/quiz'
|
|
import { usePuzzleAnimation } from '@/contexts/PuzzleAnimationContext'
|
|
|
|
const starName = 'nieer'
|
|
|
|
type QuizOption = {
|
|
value: string
|
|
label: string
|
|
isCorrect?: boolean
|
|
explanation?: string
|
|
}
|
|
|
|
type QuizAnchor = {
|
|
id: string
|
|
time: number
|
|
type: 'single' | 'multiple'
|
|
title: string
|
|
options: QuizOption[]
|
|
correctAnswer: string | string[]
|
|
successTitle?: string
|
|
}
|
|
|
|
const QUIZ_ANCHORS: QuizAnchor[] = [
|
|
{
|
|
id: 'quiz-1',
|
|
time: 60 * 3,
|
|
type: 'single',
|
|
title: '【单选】《义勇军进行曲》法定管弦乐配器版的创作者是:',
|
|
options: [
|
|
{
|
|
value: 'A',
|
|
label: 'A. 贺绿汀邀请的阿隆·阿甫夏洛穆夫',
|
|
explanation: '这是《义勇军进行曲》的第一个管弦乐配器版,并非法定版哦!请你再想想吧!',
|
|
},
|
|
{ value: 'B', label: 'B. 李焕之', isCorrect: true, explanation: '' },
|
|
{
|
|
value: 'C',
|
|
label: 'C. 聂耳',
|
|
explanation: '聂耳是《义勇军进行曲》的作曲者,并非管弦乐配器版的创作者。请你再想想吧!',
|
|
},
|
|
{
|
|
value: 'D',
|
|
label: 'D. 林超夏',
|
|
explanation: '林超夏完成的管弦乐配器版,并非广为熟知的法定版。请你再想想吧!',
|
|
},
|
|
],
|
|
correctAnswer: 'B',
|
|
},
|
|
{
|
|
id: 'quiz-2',
|
|
time: 60 * 4 + 39, // 4:39
|
|
type: 'multiple',
|
|
title: '【多选】音乐作品要传递时代力量与精神,需具备哪些特质?',
|
|
options: [
|
|
{ value: 'A', label: 'A. 拥有积极向上的音型与坚定有力的节奏,易于传唱与共鸣' },
|
|
{ value: 'B', label: 'B. 贴近民众心声,契合人类共通的真挚情感(如家国情怀、正义坚守)' },
|
|
{ value: 'C', label: 'C. 聚焦时代主题,呼应国家与民族的发展需求' },
|
|
{
|
|
value: 'D',
|
|
label: 'D. 追求复杂华丽的编曲技巧,彰显专业门槛',
|
|
explanation:
|
|
'如《义勇军进行曲》这样的作品,节奏并不复杂,核心在于情感与主题的传递,而非技巧的堆砌。请你再想想吧!',
|
|
},
|
|
],
|
|
correctAnswer: ['A', 'B', 'C'],
|
|
},
|
|
{
|
|
id: 'quiz-3',
|
|
time: 60 * 6 + 31.5, // 6:31
|
|
type: 'multiple',
|
|
title: '【多选】聂耳的音乐作品具有鲜明的“人民性”,主要体现在:',
|
|
options: [
|
|
{ value: 'A', label: 'A. 关注劳苦大众的生活与困境,如《开矿歌》《大路歌》' },
|
|
{ value: 'B', label: 'B. 为少年儿童创作贴合其生活的作品,如《卖报歌》' },
|
|
{ value: 'C', label: 'C. 关注女性群体的境遇,如《梅娘曲》《新女性》《铁蹄下的歌女》' },
|
|
{
|
|
value: 'D',
|
|
label: 'D. 仅围绕抗日救亡主题,不涉及其他社会群体',
|
|
explanation:
|
|
'聂耳的创作既聚焦抗日救亡,也覆盖多个社会群体,体现了广泛的人民性。请你再想想吧!',
|
|
},
|
|
],
|
|
correctAnswer: ['A', 'B', 'C'],
|
|
},
|
|
{
|
|
id: 'quiz-4',
|
|
time: 60 * 7 + 11.5, // 7:11
|
|
type: 'single',
|
|
title:
|
|
'【判断】聂耳在创作中注重挖掘中华优秀传统音乐文化元素,《翠湖春晓》《金蛇狂舞》就是他改编传统音乐的经典作品。',
|
|
options: [
|
|
{
|
|
value: 'A',
|
|
label: 'A. 正确',
|
|
isCorrect: true,
|
|
explanation:
|
|
'这两部作品是聂耳对传统音乐元素的整理、挖掘与再传播,彰显了鲜明的文化传承意识。',
|
|
},
|
|
{
|
|
value: 'B',
|
|
label: 'B. 错误',
|
|
explanation: '聂耳的创作中融入了传统音乐元素,这两部作品正是典型代表哦!请你再想想吧!',
|
|
},
|
|
],
|
|
correctAnswer: 'A',
|
|
},
|
|
{
|
|
id: 'quiz-5',
|
|
time: 60 * 9 + 14.5, // 9:14
|
|
type: 'multiple',
|
|
title: '【多选】聂耳能创作出众多兼具传唱力与思想性的经典作品,源于他身上具有以下宝贵闪光点:',
|
|
options: [
|
|
{ value: 'A', label: 'A. 对新鲜事物的探索精神' },
|
|
{ value: 'B', label: 'B. 关注社会时事的批判思维' },
|
|
{
|
|
value: 'C',
|
|
label: 'C. 只为个人名利创作的追求',
|
|
explanation: '“只为个人名利创作的追求”与聂耳“音乐救国”的初心相悖哦!请你再想想吧!',
|
|
},
|
|
{ value: 'D', label: 'D. 对国家民族未来的深刻思考' },
|
|
],
|
|
correctAnswer: ['A', 'B', 'D'],
|
|
},
|
|
]
|
|
|
|
const NieerInternalization = () => {
|
|
const navigate = useNavigate()
|
|
const { moduleName } = useParams<{ moduleName: string }>()
|
|
|
|
const { triggerPuzzleAnimation } = usePuzzleAnimation()
|
|
|
|
const [narrationVisible, setNarrationVisible] = useState(false)
|
|
const [visible, setVisible] = useState(false)
|
|
const [showVideo, setShowVideo] = useState(false)
|
|
const [showPostNarration, setShowPostNarration] = useState(false)
|
|
const [showLetter, setShowLetter] = useState(false)
|
|
const [showStudyGuide, setShowStudyGuide] = useState(false)
|
|
|
|
const videoRef = useRef<VideoRef>(null)
|
|
|
|
const [activeSuccess, setActiveSuccess] = useState<string | null>(null)
|
|
const [successContent, setSuccessContent] = useState<string>('')
|
|
const [resumeCallback, setResumeCallback] = useState<(() => void) | null>(null)
|
|
|
|
useEffect(() => {
|
|
setNarrationVisible(true)
|
|
}, [])
|
|
|
|
const handleVideoCanPlay = () => {
|
|
if (showVideo && videoRef.current) {
|
|
videoRef.current.play().catch(() => {})
|
|
}
|
|
}
|
|
|
|
const anchors: VideoAnchor[] = useMemo(() => {
|
|
return QUIZ_ANCHORS.map((item) => {
|
|
if (item.type === 'single') {
|
|
const correct = item.correctAnswer as string
|
|
return {
|
|
id: item.id,
|
|
time: item.time,
|
|
once: true,
|
|
afterResume: 'remove',
|
|
render: ({ resume }) => (
|
|
<>
|
|
<SingleChoiceQuizPanel
|
|
visible={activeSuccess !== item.id}
|
|
zIndex={10000}
|
|
title={item.title}
|
|
options={item.options.map((opt) => ({ value: opt.value, label: opt.label }))}
|
|
correctAnswer={correct}
|
|
onWrongSelect={(value: string) => {
|
|
const opt = item.options.find((o) => o.value === value)
|
|
return opt?.explanation || '再思考一下,选择正确答案。'
|
|
}}
|
|
onSubmit={(value: string, isCorrect: boolean) => {
|
|
if (isCorrect) {
|
|
const opt = item.options.find((o) => o.value === value)
|
|
setActiveSuccess(item.id)
|
|
setSuccessContent(opt?.explanation || '')
|
|
setResumeCallback(() => resume)
|
|
}
|
|
}}
|
|
/>
|
|
<SuccessAlert
|
|
visible={activeSuccess === item.id}
|
|
title={item.id === 'quiz-4' ? '恭喜你, 判断正确!' : '恭喜你, 选择正确!'}
|
|
content={successContent}
|
|
onContinue={() => {
|
|
setActiveSuccess(null)
|
|
setSuccessContent('')
|
|
const cb = resumeCallback
|
|
setResumeCallback(null)
|
|
cb?.()
|
|
}}
|
|
/>
|
|
</>
|
|
),
|
|
}
|
|
}
|
|
|
|
const correctAnswers = item.correctAnswer as string[]
|
|
return {
|
|
id: item.id,
|
|
time: item.time,
|
|
once: true,
|
|
afterResume: 'remove',
|
|
render: ({ resume }) => (
|
|
<>
|
|
<MultipleChoiceQuizPanel
|
|
visible={activeSuccess !== item.id}
|
|
zIndex={10000}
|
|
title={item.title}
|
|
options={item.options.map((opt) => ({ value: opt.value, label: opt.label }))}
|
|
correctAnswer={correctAnswers}
|
|
errorMessage={(selectedValues: string[]) => {
|
|
const picked = new Set(selectedValues)
|
|
const wrongOpt = item.options.find((o) => picked.has(o.value) && o.explanation)
|
|
if (wrongOpt?.explanation) return wrongOpt.explanation
|
|
return '你的选择还不够全面哦~请你再想想吧!'
|
|
}}
|
|
onSubmit={(values: string[], isCorrect: boolean) => {
|
|
if (isCorrect) {
|
|
setActiveSuccess(item.id)
|
|
if (item.id === 'quiz-2') {
|
|
triggerPuzzleAnimation(15)
|
|
}
|
|
if (item.id === 'quiz-5') {
|
|
triggerPuzzleAnimation(16)
|
|
}
|
|
setSuccessContent('')
|
|
setResumeCallback(() => resume)
|
|
}
|
|
}}
|
|
/>
|
|
<SuccessAlert
|
|
visible={activeSuccess === item.id}
|
|
content={successContent}
|
|
onContinue={() => {
|
|
setActiveSuccess(null)
|
|
setSuccessContent('')
|
|
const cb = resumeCallback
|
|
setResumeCallback(null)
|
|
cb?.()
|
|
}}
|
|
/>
|
|
</>
|
|
),
|
|
}
|
|
})
|
|
}, [activeSuccess, resumeCallback, successContent, triggerPuzzleAnimation])
|
|
|
|
if (!moduleName) return null
|
|
|
|
return (
|
|
<div
|
|
className="relative h-full w-full bg-black"
|
|
style={{ backgroundImage: getBackgroundImage('components/internalization/bg') }}
|
|
>
|
|
{/* 离开按钮 */}
|
|
<Image
|
|
className="absolute right-8 top-8 z-10"
|
|
src={getStaticImage('components/离开')}
|
|
button
|
|
onClick={() => navigate(`/${moduleName}/${starName}/home`)}
|
|
/>
|
|
|
|
{/* 旁白 */}
|
|
<Narration
|
|
visible={narrationVisible}
|
|
audioChannelId="nieer-internalization-narration"
|
|
audioSrc={getPublicPath('audio/nieer/mp3_nieer_36.mp3')}
|
|
animationSpeed={200}
|
|
onClose={() => {
|
|
setNarrationVisible(false)
|
|
setVisible(true)
|
|
}}
|
|
text="从旧居亭子间的不眠之夜到国歌展示馆的激昂旋律,聂耳以青春为火,以音符为刃,在民族危亡的黑暗中奏响了救亡图存的强音。《义勇军进行曲》从电影主题曲到中华人民共和国国歌,从抗战年代的冲锋号到新时代的复兴曲,跨越近百年时光,依然是凝聚民族力量的精神纽带。今天,站在新时代的舞台上,我们更要读懂聂耳的作品与精神内核,让这份旋律中的力量内化于心、外化于行。"
|
|
/>
|
|
|
|
{/* 视频前引导 */}
|
|
<GuideSteps
|
|
visible={visible}
|
|
audioChannelId="nieer-internalization-guide-pre"
|
|
onVisibleChange={(v) => {
|
|
setVisible(v)
|
|
if (!v) {
|
|
setShowVideo(true)
|
|
handleVideoCanPlay()
|
|
}
|
|
}}
|
|
showMask={false}
|
|
>
|
|
<GuideStepsItem speaker="student" audioSrc={getPublicPath('audio/nieer/mp3_nieer_37.mp3')}>
|
|
小沪,前两个模块中,我循着聂耳的足迹读懂了旋律背后的家国情怀,亲历了国歌跨越时空的动人故事。我还想从音乐这一艺术创作角度出发更多了解聂耳,你能给我讲讲吗?
|
|
</GuideStepsItem>
|
|
<GuideStepsItem speaker="guide" audioSrc={getPublicPath('audio/nieer/mp3_nieer_38.mp3')}>
|
|
讲到音乐,可是很专业的领域了!今天我们有幸邀请到了上海音乐学院党委宣传部副部长,东方乐器博物馆馆长,研究员,肖阳。肖老师会从上海音乐学院对聂耳的研究、与《义勇军进行曲》的渊源的角度告诉你聂耳的音乐作品与时代价值。
|
|
</GuideStepsItem>
|
|
<GuideStepsItem speaker="guide">
|
|
接下来,就让我们跟随肖老师一起,探寻聂耳音乐作品和艺术精神的当代意义吧!
|
|
</GuideStepsItem>
|
|
</GuideSteps>
|
|
|
|
{/* 内化主视频 */}
|
|
{showVideo && (
|
|
<Video
|
|
ref={videoRef}
|
|
src={getPublicPath('video/nieer/nieer_neihua.mp4')}
|
|
className="h-full w-full"
|
|
anchors={anchors}
|
|
playsInline
|
|
controls
|
|
onCanPlay={handleVideoCanPlay}
|
|
onEnded={() => {
|
|
setShowVideo(false)
|
|
setTimeout(() => setShowPostNarration(true), 300)
|
|
}}
|
|
/>
|
|
)}
|
|
|
|
<Narration
|
|
visible={showPostNarration}
|
|
audioChannelId="nieer-internalization-narration-post"
|
|
audioSrc={getPublicPath('audio/nieer/mp3_nieer_39.mp3')}
|
|
animationSpeed={200}
|
|
onClose={() => {
|
|
setShowPostNarration(false)
|
|
setShowLetter(true)
|
|
}}
|
|
text="的确如戏!这份精神,不仅是音乐创作的指引,更是广大青年学子成长路上的精神灯塔,指引大家在专业学习中精益求精,在青春岁月里奋勇前行。此刻,我们仿佛依然站在聂耳旧居的亭子间里,仿佛还能看到先生熬夜打磨旋律的身影,感受到他心中滚烫的家国情怀。现在,就请你静下心来,把对聂耳的崇敬、对国歌精神的体悟和对自己未来的期许,一字一句写成一封信,完成与先生跨越时空的对话吧!"
|
|
/>
|
|
|
|
{/* 写信组件 */}
|
|
<LetterToNieer
|
|
visible={showLetter}
|
|
onClose={() => {
|
|
setShowLetter(false)
|
|
}}
|
|
onAnimationEnd={() => {
|
|
setShowStudyGuide(true)
|
|
}}
|
|
showVideo={true}
|
|
/>
|
|
|
|
{/* 内化结束 */}
|
|
<EndingOverlay
|
|
visible={showStudyGuide}
|
|
audioSrc={getPublicPath('audio/nieer/mp3_nieer_40.mp3')}
|
|
text="恭喜你圆满完成聂耳旧居的学习之旅,请带着研学感悟,前往张乐平故居继续探访吧~"
|
|
animationSpeed={200}
|
|
onContinue={() => {
|
|
setShowStudyGuide(false)
|
|
navigate('/menu')
|
|
}}
|
|
/>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default NieerInternalization
|