import { useState, useEffect, useMemo, useCallback, useRef } from 'react' import { AnimatePresence, motion } from 'framer-motion' import { cn, getStaticImage } from '@/lib/utils' import { Image, DragFill } from '@/components' import type { DragFillResult } from '@/components' import { ErrorAlert, SuccessAlert } from '@/components/layout' 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 [mounted, setMounted] = useState(false) const [value, setValue] = useState([]) const [errorVisible, setErrorVisible] = useState(false) const [successVisible, setSuccessVisible] = useState(false) const completedRef = useRef(false) // SSR 兼容 useEffect(() => { setMounted(true) }, []) // 重置游戏状态 useEffect(() => { if (visible) { setValue([]) completedRef.current = false setErrorVisible(false) setSuccessVisible(false) } }, [visible]) // 检查是否完成(3个毛巾都点亮) useEffect(() => { if (value.length === 3 && !completedRef.current) { completedRef.current = true // 显示成功提示 setSuccessVisible(true) } }, [value]) const handleClose = useCallback( (e: React.MouseEvent) => { e.stopPropagation() onClose?.() }, [onClose] ) // 关闭错误提示 const handleErrorClose = useCallback(() => { setErrorVisible(false) }, []) // 关闭成功提示 const handleSuccessContinue = useCallback(() => { setSuccessVisible(false) // 关闭提示后,完成并关闭弹窗 onComplete?.() onClose?.() }, [onComplete, onClose]) // 可选话术选项 const options = useMemo( () => [ { value: 'option1', label: 'A.这是中华职校生产的国货,质量比洋货还好!', }, { value: 'option2', label: 'B.价格便宜,量大从优,诚信经营绝不掺假。', }, { value: 'option3', label: 'C.买国货就是支持实业救国,为国家出一份力!', }, { value: 'option4', label: 'D.我只管赚钱,东西好不好不管哦。', }, ], [] ) // 转换为 DragFill 的 blanks const blanks = useMemo( () => [ { id: 'towel1', label: '毛巾1' }, { id: 'towel2', label: '毛巾2' }, { id: 'towel3', label: '毛巾3' }, ], [] ) // 处理拖拽结果变化 const handleValueChange = useCallback((newValue: DragFillResult[]) => { // 检查是否选择了错误的选项(option4) const hasOption4 = newValue.some((result) => result.optionValue === 'option4') if (hasOption4) { // 显示错误提示 setErrorVisible(true) // 移除 option4,保留其他选项 const filteredValue = newValue.filter((result) => result.optionValue !== 'option4') setValue(filteredValue) } else { // 限制最多3个 if (newValue.length <= 3) { setValue(newValue) } } }, []) // 检查毛巾是否被点亮 const isTowelLit = useCallback( (towelId: string) => { return value.some((result) => result.blankId === towelId) }, [value] ) const getTowelText = useCallback( (towelId: string) => { const result = value.find((item) => item.blankId === towelId) return options.find((option) => option.value === result?.optionValue)?.label }, [options, value] ) // 样式 const overlayStyle = useMemo( () => ({ position: 'fixed', inset: 0, zIndex: 9999, pointerEvents: 'none', }), [] ) if (!mounted) return null const content = ( {visible && (
{/* 背景蒙版 */} {maskSrc && ( )} {/* 返回按钮 */} {onClose && ( 返回 )} {/* 内容区域 */}
{/* 顶部:题目文本和毛巾区域 */}
{/* 题目文本 */}
【拖拽】从以下话术里,挑选三句组成你的推销语,放置在国货毛巾上。
{/* 右侧:毛巾区域 */}
{/* 毛巾堆叠区域 */}
{/* 毛巾1 */}
毛巾1 {getTowelText('towel1') && (
{getTowelText('towel1')}
)}
{/* 毛巾2 */}
毛巾2 {getTowelText('towel2') && (
{getTowelText('towel2')}
)}
{/* 毛巾3 */}
毛巾3 {getTowelText('towel3') && (
{getTowelText('towel3')}
)}
{/* 底部:可选话术区域 - 使用 DragFill */}
答题区
{ // const index = options.findIndex((opt) => opt.value === option.value) return (
{option.label}
) }} renderDragging={(option) => { return (
{option.label}
) }} renderBlank={() => { // 返回隐藏元素,实际的空白区域在页面中通过 data-blank-id 定位 return
}} />
{/* 错误提示 */} {/* 成功提示 */}
)} ) return content } export default TowelSalesDialog