import { useEffect, useState } from "react"; import { AnimatePresence, motion } from "framer-motion"; import { getStaticImage } from "@/lib/utils"; import { Image, Select } from "@/components"; import { filterInput } from "@/lib/utils/sensitiveWords"; export interface LetterToHuangyanpeiProps { /** 是否可见 */ visible?: boolean; /** 关闭回调 */ onClose?: () => void; /** 提交回调 */ onSubmit?: (values: string[]) => void; } /** * LetterToHuangyanpei 组件 - 写给黄炎培先生的信 * 包含飞入和放大动画 */ const LetterToHuangyanpei = ({ visible = false, onClose, onSubmit, }: LetterToHuangyanpeiProps) => { // 下划线可填写内容 const [blankValues, setBlankValues] = useState([]); // 每个空的校验状态:true 表示该空当前不符合要求 const [blankErrors, setBlankErrors] = useState([]); // 是否已提交:提交后隐藏编辑信件,展示只读信件 const [submitted, setSubmitted] = useState(false); // 填空类型定义:0=填空1(限填),1=填空2(任填),2=填空3(任填),3=填空4(限填) const BLANK_1_ANSWER = "手脑并用"; const BLANK_4_ANSWER = "有业者乐业"; // 填空1的选项 const BLANK_1_OPTIONS = [ "手脑并用", "有业者乐业", // "知行合一", // "学以致用", // "理论实践" ]; // 填空4的选项 const BLANK_4_OPTIONS = [ "手脑并用", "有业者乐业", // "有业者敬业", // "有业者精业", // "有业者成业", ]; // 校验输入:确保所有填空符合标准 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 = nextValues[i] || ""; const v = raw.trim(); if (i === 0) { // 填空1:限填,必须选择"手脑并用" if (!v || v !== BLANK_1_ANSWER) { nextValues[i] = ""; nextErrors[i] = true; hasError = true; } } else if (i === 1 || i === 2) { // 填空2、3:任填,不能为空,不超过20个字 if (!v || v.length > 20) { nextValues[i] = ""; nextErrors[i] = true; hasError = true; } } else if (i === 3) { // 填空4:限填,必须选择"有业者乐业" if (!v || v !== BLANK_4_ANSWER) { nextValues[i] = ""; nextErrors[i] = true; hasError = true; } } } setBlankValues(nextValues); setBlankErrors(nextErrors); return !hasError; }; // 弹窗关闭后重置内容,方便下次重新填写 useEffect(() => { if (!visible) { setSubmitted(false); setBlankValues([]); setBlankErrors([]); } }, [visible]); // 信件内容模板(根据设计稿) const letterContent = `敬爱的黄炎培先生: 您好! 今天,我循着您的足迹,走进了川沙小学堂的新式课堂,亲历了中华职业学校"手脑并用"的实训时光。那些您为破除旧教育桎梏的奔走、为推广职业教育的坚守、为支援国家危亡的热忱,不再是书本上的文字,而是可感可触的精神力量,深深烙印在我心中。 我终于明白,您喊出的"双手万能、____",不是一句口号,而是我们的立身之本;您践行的"教育救国、实业报国",不是一段历史,而是我们这代人接续的使命;您坚守的"因时求变",不是一种选择,而是我们面对未来的底气。 我是____专业的学生,今后,我会在实训课堂上精益求精,像当年中华职校的学长学姐那样,把技能练到极致;我会在志愿服务中践行担当,用所学知识为身边人、为社会做实事;我更会紧跟时代步伐,努力成为____领域的技能人才,为中国制造升级、为民族产业振兴添砖加瓦! 请您放心,您播下的职教火种,我们会让它在新时代燃烧得更旺;您毕生追求的"无业者有业、____"的愿景,我们会用青春和汗水去实现!`; 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 isLimitedFill = currentIndex === 0 || currentIndex === 3; // 填空1和填空4为限填 const options = currentIndex === 0 ? BLANK_1_OPTIONS : BLANK_4_OPTIONS; return ( {part} {isLimitedFill ? ( // 限填:下拉选择 { const rawValue = e.target.value; // 应用敏感词过滤 const filteredValue = filterInput(rawValue); const next = [...blankValues]; next[currentIndex] = filteredValue; 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 || "  "} ); })}
); }); })()}
{/* 寄出按钮:关闭窗口 */}
寄出 { // 寄出前再次校验,确保所有填空符合标准 if (validateBlanks()) { onSubmit?.(blankValues); } }} button />
)}
); }; export default LetterToHuangyanpei;