import { useEffect, useState } from 'react' import { AnimatePresence, motion } from 'framer-motion' import { getStaticImage } from '@/lib/utils' import { Image, Select } from '@/components' import { ErrorAlert } from '@/components/layout' export interface LetterToSunzhongshanProps { /** 是否可见 */ visible?: boolean /** 关闭回调 */ onClose?: () => void /** 提交回调 */ onSubmit?: (values: string[]) => void } /** * LetterToSunzhongshan 组件 - 写给孙中山爷爷的信 * 包含飞入和放大动画 */ const LetterToSunzhongshan = ({ visible = false, onClose, onSubmit, }: LetterToSunzhongshanProps) => { // 下划线可填写内容 const [blankValues, setBlankValues] = useState([]) // 每个空的校验状态:true 表示该空当前不符合要求(为空或超过 10 个字) const [blankErrors, setBlankErrors] = useState([]) // 是否已提交:提交后隐藏编辑信件,展示只读信件 const [submitted, setSubmitted] = useState(false) // ErrorAlert 相关状态 const [showErrorAlert, setShowErrorAlert] = useState(false) const [errorAlertText, setErrorAlertText] = useState('') // 前三个空的正确答案 const BLANK_0_ANSWER = '振兴中华' const BLANK_1_ANSWER = '天下为公' const BLANK_2_ANSWER = '与时俱进' // 最后一个空的正确答案 const LAST_BLANK_ANSWER = '仍需努力' // 前三个空的选项(所有空都用这三个选项) const SELECT_OPTIONS = [ { value: '振兴中华', label: '振兴中华' }, { value: '天下为公', label: '天下为公' }, { value: '与时俱进', label: '与时俱进' }, ] // 校验输入:前三个空必须选择正确答案,最后一个空必须是"仍需努力",其他空不能为空且不超过 10 个字符 const validateBlanks = () => { // 计算占位总数,防止有些没被输入过而是 undefined const placeholderCount = (letterContent.match(/____/g) || []).length const nextErrors: boolean[] = Array(placeholderCount).fill(false) const nextValues: string[] = [...blankValues] let hasError = false for (let i = 0; i < placeholderCount; i++) { const raw = blankValues[i] || '' const v = raw.trim() if (i < 3) { // 前三个空:必须选择正确答案(已在 onChange 中验证,这里只检查是否为空) if (!v) { nextValues[i] = '' nextErrors[i] = true hasError = true } } else if (i === placeholderCount - 1) { // 最后一个空:必须是"仍需努力" if (!v || v !== LAST_BLANK_ANSWER) { nextValues[i] = '' nextErrors[i] = true hasError = true } } else { // 其他空:不能为空,不超过 10 个字 if (!v || v.length > 10) { nextValues[i] = '' nextErrors[i] = true hasError = true } } } setBlankValues(nextValues) setBlankErrors(nextErrors) return !hasError } // 弹窗关闭后重置内容,方便下次重新填写 useEffect(() => { if (!visible) { setSubmitted(false) setBlankValues([]) setBlankErrors([]) setShowErrorAlert(false) setErrorAlertText('') } }, [visible]) // 信件内容模板(根据设计稿) const letterContent = `敬爱的孙中山先生: 您好! 今天,我怀着崇敬的心情走进香山路7号,看着您亲手设计的房屋、勾画的《中国铁路全图》、撰写的《实业计划》手稿,仿佛能看到您为“振兴中华”奔走的身影。在这场跨越时空的对话中,我终于明白,您喊出的“____”,不是遥远的历史,而是我们这代人正在接续的接力;您追求的“____”,不是抽象的理念,而是从服务身边人、做好专业事开始的担当;您秉持的“____”,不是过去的选择,而是面对未来时必须有的勇气。 我是____专业的学生,今后,我会以您“知难行易”的信念,在专业学习中攻克难题,就像您当年为了革命理想屡败屡战;我会以您“心系民众”的情怀,参与____,帮身边人解决实际困难;我更会记住您“适乎世界之潮流”的智慧,紧跟时代步伐,努力用____为民族复兴添砖加瓦。 请您放心,“革命尚未成功,同志____”的嘱托,我们从未忘记;“振兴中华”的初心,我们会用一生践行!` return ( {visible && ( <> {/* 遮罩层 */} {/* 信件容器 */}
e.stopPropagation()} style={{ perspective: '1200px' }} > {/* 编辑态 / 只读态:Y 轴翻转切换 */} {/* 前面:编辑态 */}
{/* 编辑态:信纸背景 */} 信纸 {/* 标题 - 垂直显示在左侧 */}
写给孙中山先生的信
{/* 信件内容区域(可编辑) */}
{(() => { let placeholderIndex = 0 let foundNiHao = false return letterContent.split('\n').map((line, lineIndex) => { // 检测是否已经找到"您好"行 if (line.includes('您好')) { foundNiHao = true } // "您好"行之后的非空行需要首行缩进 const needsIndent = foundNiHao && line.trim() !== '' && !line.includes('您好') const segments = line.split('____') return (
{segments.map((part, partIndex) => { const isLast = partIndex === segments.length - 1 if (isLast) { return {part} } const currentIndex = placeholderIndex++ const isSelectFill = currentIndex < 3 // 前三个空使用 Select return ( {part} {isSelectFill ? ( // 前三个空:下拉选择 { const next = [...blankValues] next[currentIndex] = e.target.value setBlankValues(next) // 用户再次输入时,清除该空的错误高亮 if (blankErrors[currentIndex]) { const nextErrs = [...blankErrors] nextErrs[currentIndex] = false setBlankErrors(nextErrs) } }} /> )} ) })}
) }) })()}
{/* 笔的图片 */}
笔
{/* 提交按钮:切换到只读信件态 */}
可提交状态 { if (!validateBlanks()) return setSubmitted(true) }} button />
{/* 寄信图标(可选,如果需要的话) */} {/*
寄信
*/}
{/* 背面:只读态 */}
{/* 只读态:新背景 */} 信件 信纸 { setSubmitted(false) }} /> {/* 信件内容区域(只读) */}
{(() => { let placeholderIndex = 0 let foundNiHao = false return letterContent.split('\n').map((line, lineIndex) => { if (line.includes('您好')) { foundNiHao = true } const needsIndent = foundNiHao && line.trim() !== '' && !line.includes('您好') const segments = line.split('____') return (
{segments.map((part, partIndex) => { const isLast = partIndex === segments.length - 1 if (isLast) { return {part} } const currentIndex = placeholderIndex++ const value = blankValues[currentIndex] ?? '' return ( {part} {value || '  '} ) })}
) }) })()}
{/* 寄出按钮:关闭窗口 */}
寄出 { onSubmit?.(blankValues) }} button />
{/* 错误提示弹窗 */} setShowErrorAlert(false)} onMaskClick={() => setShowErrorAlert(false)} zIndex={10002} /> )}
) } export default LetterToSunzhongshan