import { useState, useEffect, useMemo, useCallback, useRef } from "react"; import { createPortal } from "react-dom"; import { AnimatePresence, motion } from "framer-motion"; import { cn, getStaticImage } from "@/lib/utils"; import { Image } from "@/components"; import type { DragFillResult } from "@/components"; import { useScale } from "@/contexts/ScaleContext"; export interface TowelSalesDialogProps { /** 是否可见 */ visible?: boolean; /** 关闭回调 */ onClose?: () => void; /** 完成回调 */ onComplete?: () => void; } /** * 毛巾推销语拖拽弹窗 * - 左边显示4个可选话术 * - 右边显示3个毛巾(堆叠显示) * - 拖拽话术到毛巾上,点亮对应的毛巾 * - 3个都点亮后完成 */ const TowelSalesDialog = ({ visible = true, onClose, onComplete, }: TowelSalesDialogProps) => { const maskSrc = getStaticImage("components/蒙版遮罩"); const backSrc = getStaticImage("components/关闭"); // 毛巾图片资源(未点亮) const towel1 = getStaticImage("huangyanpei/reflection/scene4/12_002"); const towel2 = getStaticImage("huangyanpei/reflection/scene4/12_003"); const towel3 = getStaticImage("huangyanpei/reflection/scene4/12_004"); // 毛巾图片资源(点亮) const towel1Lit = getStaticImage("huangyanpei/reflection/scene4/12_002_02"); const towel2Lit = getStaticImage("huangyanpei/reflection/scene4/12_003_02"); const towel3Lit = getStaticImage("huangyanpei/reflection/scene4/12_004_03"); const { scale, offsetX, offsetY, DESIGN_WIDTH, DESIGN_HEIGHT } = useScale(); const [mounted, setMounted] = useState(false); const [value, setValue] = useState([]); const completedRef = useRef(false); // SSR 兼容 useEffect(() => { setMounted(true); }, []); // 重置游戏状态 useEffect(() => { if (visible) { setValue([]); completedRef.current = false; } }, [visible]); // 检查是否完成(3个毛巾都点亮) useEffect(() => { if (value.length === 3 && !completedRef.current) { completedRef.current = true; // 延迟一点时间让用户看到效果,然后完成 setTimeout(() => { onComplete?.(); onClose?.(); }, 500); } }, [value, onComplete, onClose]); const handleClose = useCallback( (e: React.MouseEvent) => { e.stopPropagation(); onClose?.(); }, [onClose] ); // 可选话术选项 const options = useMemo( () => [ { value: "option1", label: "这是中华职校生产的国货,质量比洋货还好!", }, { value: "option2", label: "价格便宜,量大从优,诚信经营绝不掺假", }, { value: "option3", label: "买国货就是支持实业救国,为国家出一份力!", }, { value: "option4", label: "我只管赚钱,东西好不好不管哦", }, ], [] ); // 处理拖拽结果变化 const handleValueChange = useCallback((newValue: DragFillResult[]) => { // 限制最多3个 if (newValue.length <= 3) { setValue(newValue); } }, []); // 检查毛巾是否被点亮 const isTowelLit = useCallback( (towelId: string) => { return value.some((result) => result.blankId === towelId); }, [value] ); // 拖拽悬停状态 const [hoveredTowel, setHoveredTowel] = useState(null); // 样式 const overlayStyle = useMemo( () => ({ position: "fixed", inset: 0, zIndex: 9999, pointerEvents: "none", }), [] ); const scaledContainerStyle = useMemo( () => ({ position: "absolute", width: `${DESIGN_WIDTH}px`, height: `${DESIGN_HEIGHT}px`, left: `${offsetX}px`, top: `${offsetY}px`, transform: `scale(${scale})`, transformOrigin: "top left", overflow: "hidden", }), [scale, offsetX, offsetY, DESIGN_WIDTH, DESIGN_HEIGHT] ); if (!mounted) return null; const content = ( {visible && (
{/* 背景蒙版 */} {maskSrc && ( )} {/* 返回按钮 */} {onClose && ( 返回 )} {/* 内容区域 */}
{/* 左侧:题目和可选话术 */}
{/* 题目文本 */}
题目文本
从以下话术里,挑选3句组成你的推销语 (拖拽至对话框)
{/* 右侧:毛巾区域 */}
{/* 答题区标签 */} {/*
答题区
*/} {/* 毛巾堆叠区域 */}
{/* 毛巾1 */}
{ e.preventDefault(); e.dataTransfer.dropEffect = "move"; if (!isTowelLit("towel1")) { setHoveredTowel("towel1"); } }} onDragLeave={() => { setHoveredTowel(null); }} onDrop={(e) => { e.preventDefault(); setHoveredTowel(null); const optionValue = e.dataTransfer.getData("text/plain"); if (optionValue && !isTowelLit("towel1")) { // 检查是否已经使用过这个选项 const alreadyUsed = value.some( (r) => r.optionValue === optionValue ); if (!alreadyUsed) { handleValueChange([ ...value, { blankId: "towel1", optionValue }, ]); } } }} > 毛巾1
{/* 毛巾2 */}
{ e.preventDefault(); e.dataTransfer.dropEffect = "move"; if (!isTowelLit("towel2")) { setHoveredTowel("towel2"); } }} onDragLeave={() => { setHoveredTowel(null); }} onDrop={(e) => { e.preventDefault(); setHoveredTowel(null); const optionValue = e.dataTransfer.getData("text/plain"); if (optionValue && !isTowelLit("towel2")) { // 检查是否已经使用过这个选项 const alreadyUsed = value.some( (r) => r.optionValue === optionValue ); if (!alreadyUsed) { handleValueChange([ ...value, { blankId: "towel2", optionValue }, ]); } } }} > 毛巾2
{/* 毛巾3 */}
{ e.preventDefault(); e.dataTransfer.dropEffect = "move"; if (!isTowelLit("towel3")) { setHoveredTowel("towel3"); } }} onDragLeave={() => { setHoveredTowel(null); }} onDrop={(e) => { e.preventDefault(); setHoveredTowel(null); const optionValue = e.dataTransfer.getData("text/plain"); if (optionValue && !isTowelLit("towel3")) { // 检查是否已经使用过这个选项 const alreadyUsed = value.some( (r) => r.optionValue === optionValue ); if (!alreadyUsed) { handleValueChange([ ...value, { blankId: "towel3", optionValue }, ]); } } }} > 毛巾3
{/* 可选话术区域 */}
答题区
{options.map((option, index) => { const isUsed = value.some( (r) => r.optionValue === option.value ); return (
{ if (isUsed) { e.preventDefault(); return; } e.dataTransfer.setData( "text/plain", option.value ); }} > {index + 1}. {option.label}
); })}
)}
); return createPortal(content, document.body); }; export default TowelSalesDialog;