"use client"; import React, { useState, useEffect, useRef } from "react"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { FerrisWheel, Trophy, X } from "lucide-react"; import { toast } from "sonner"; const COLORS = [ "#f87171", "#fb923c", "#fbbf24", "#a3e635", "#34d399", "#22d3ee", "#818cf8", "#e879f9" ]; export default function WheelPage() { const canvasRef = useRef(null); const [items, setItems] = useState(["今晚吃火锅", "去看电影", "写代码", "早点睡觉", "打游戏", "喝奶茶"]); const [newItem, setNewItem] = useState(""); const [isSpinning, setIsSpinning] = useState(false); const [winner, setWinner] = useState(null); // Animation Refs const rotationRef = useRef(0); const speedRef = useRef(0); const requestRef = useRef(0); // Draw Wheel const drawLine = (ctx: CanvasRenderingContext2D, center: number, radius: number) => { ctx.clearRect(0, 0, center * 2, center * 2); const total = items.length; const arc = (2 * Math.PI) / total; // Draw Slices for (let i = 0; i < total; i++) { const angle = rotationRef.current + i * arc; ctx.beginPath(); ctx.fillStyle = COLORS[i % COLORS.length]; ctx.moveTo(center, center); ctx.arc(center, center, radius, angle, angle + arc); ctx.lineTo(center, center); ctx.fill(); ctx.stroke(); // Text ctx.save(); ctx.translate(center, center); ctx.rotate(angle + arc / 2); ctx.textAlign = "right"; ctx.fillStyle = "#fff"; ctx.font = "bold 14px Arial"; ctx.fillText(items[i], radius - 20, 5); ctx.restore(); } // Draw Pointer ctx.beginPath(); ctx.fillStyle = "white"; ctx.moveTo(center + radius - 10, center); ctx.lineTo(center + radius + 15, center - 10); ctx.lineTo(center + radius + 15, center + 10); ctx.fill(); ctx.strokeStyle = "#333"; ctx.stroke(); // Center Circle ctx.beginPath(); ctx.arc(center, center, 20, 0, 2 * Math.PI); ctx.fillStyle = "white"; ctx.fill(); ctx.stroke(); }; const animate = () => { if (speedRef.current > 0.001) { speedRef.current *= 0.985; // Friction rotationRef.current += speedRef.current; rotationRef.current %= 2 * Math.PI; const canvas = canvasRef.current; if (canvas) { const ctx = canvas.getContext("2d"); if (ctx) drawLine(ctx, 200, 180); } requestRef.current = requestAnimationFrame(animate); } else { setIsSpinning(false); cancelAnimationFrame(requestRef.current); // Calculate Winner const total = items.length; const arc = (2 * Math.PI) / total; // Pointer is at 0 degrees (right side), wheel rotates clockwise // Effective angle is (2PI - current_rotation) % 2PI // But we need to account for slice index // Let's simplify: // Angle of slice i is: current_rot + i * arc // We want to know which slice covers angle 0 // (current_rot + i * arc) % 2PI needs to enclose 0 (or 2PI) let currentRot = rotationRef.current % (2 * Math.PI); if (currentRot < 0) currentRot += 2 * Math.PI; // normalize // The pointer is at 0 radians (right). // A slice i spans from [currentRot + i*arc] to [currentRot + (i+1)*arc] // We find i such that this range includes 0 or 2PI. // Actually simpler: Which index i corresponds to angle 0? // angle_i_start = currentRot + i * arc // We want angle_i_start <= 2PI*k <= angle_i_end // Let's invert: what angle corresponds to pointer (which is at 0 relative to canvas) // relative to wheel 0? -> -currentRot let pointerAngle = (0 - currentRot); if (pointerAngle < 0) pointerAngle += 2 * Math.PI; const winningIndex = Math.floor(pointerAngle / arc); const winItem = items[winningIndex % total]; setWinner(winItem); toast.success(`结果揭晓:${winItem}!`); } }; const spin = () => { if (items.length < 2) { toast.error("至少需要两个选项"); return; } if (isSpinning) return; setIsSpinning(true); setWinner(null); speedRef.current = Math.random() * 0.3 + 0.4; // Initial speed animate(); }; useEffect(() => { const canvas = canvasRef.current; if (canvas) { const ctx = canvas.getContext("2d"); if (ctx) { canvas.width = 400; canvas.height = 400; drawLine(ctx, 200, 180); } } }, [items]); const addItem = () => { if (newItem.trim()) { setItems([...items, newItem.trim()]); setNewItem(""); } }; const removeItem = (idx: number) => { setItems(items.filter((_, i) => i !== idx)); }; const clearItems = () => { if (confirm("确定要清空所有选项吗?")) setItems([]); } return (

大转盘抽奖

帮你在多个选项中做出随机决定

{/* Center Button Override just in case */}
{winner && (

恭喜选中

{winner}

)}
选项列表 ({items.length})
setNewItem(e.target.value)} onKeyDown={(e) => e.key === "Enter" && addItem()} placeholder="输入新选项..." />
{items.length === 0 && (
暂无选项,请添加
)} {items.map((item, idx) => (
{item}
))}
); }