"use client"; import { useEffect, useState, useCallback } from "react"; import { createPortal } from "react-dom"; import { AnimatePresence, motion } from "framer-motion"; import { X, Heart, AlertCircle, Check } from "lucide-react"; import type { Artist } from "@/types/artist"; import { cn } from "@/lib/cn"; import { useVoteStore, selectHasVoted } from "@/lib/store"; import Button from "./ui/Button"; import ArtistPortrait from "./cards/ArtistPortrait"; interface VoteModalProps { /** 当前要投票的艺人,传 null 关闭弹窗 */ artist: Artist | null; /** 剩余可投票数(终身 12 - 已投) */ remaining: number; /** 总额度常量 12(用于文案 "X / 12") */ totalQuota: number; /** 关闭弹窗 */ onClose: () => void; /** 确认投票(无 count 参数,固定 1 票) */ onConfirm: (artist: Artist) => void | Promise; } export default function VoteModal({ artist, remaining, totalQuota, onClose, onConfirm, }: VoteModalProps) { const open = artist != null; const [loading, setLoading] = useState(false); const [mounted, setMounted] = useState(false); // 即时判断当前艺人是否已被投过(避免父组件忘传防护) const hasVotedSelector = artist ? selectHasVoted(artist.id) : () => false; const hasVoted = useVoteStore(hasVotedSelector); useEffect(() => setMounted(true), []); // 打开时重置 loading 态 useEffect(() => { if (open) setLoading(false); }, [open]); // ESC 关闭 + body 锁滚 useEffect(() => { if (!open) return; const handler = (e: KeyboardEvent) => { if (e.key === "Escape") onClose(); }; window.addEventListener("keydown", handler); const prev = document.body.style.overflow; document.body.style.overflow = "hidden"; return () => { window.removeEventListener("keydown", handler); document.body.style.overflow = prev; }; }, [open, onClose]); const exhausted = remaining <= 0; const canSubmit = !exhausted && !hasVoted && !loading; const handleConfirm = useCallback(async () => { if (!artist || !canSubmit) return; setLoading(true); try { await onConfirm(artist); } finally { setLoading(false); } }, [artist, canSubmit, onConfirm]); if (!mounted) return null; return createPortal( {open && artist && ( {/* 遮罩 */} {/* 头像 */}
{hasVoted && (
)}
{/* 标题 */}
{hasVoted ? `已为 ${artist.name} 投过票` : exhausted ? "12 票已用完" : `为 ${artist.name} 投票`}
No.{artist.no} · Current Rank #{artist.rank}
{/* 剩余票数显示 */}
你的剩余票数 {remaining}{" "} / {totalQuota}
{/* 规则提示 · 不可撤销警示(仅在可投态显示) */} {!hasVoted && !exhausted && (

投出后不可撤销 · 每位艺人仅能投 1 票

)} {/* 确认按钮 */}
)}
, document.body, ); }