205 lines
9.2 KiB
TypeScript
205 lines
9.2 KiB
TypeScript
"use client"
|
||
|
||
import { Card, CardContent } from "@/components/ui/card"
|
||
import AnimatedNumber from "./animated-number"
|
||
import { Users, Gamepad2, CreditCard, ExternalLink, Star, Trophy, Zap, TrendingUp } from "lucide-react"
|
||
import { useEffect, useState, useCallback, useRef } from "react"
|
||
import { useDashboard } from "../_lib/contexts/DashboardContext"
|
||
|
||
export default function StatsPanel() {
|
||
const { config } = useDashboard()
|
||
const yesterdayRevenue = 117200
|
||
const targetRevenue = useRef(Math.floor(yesterdayRevenue * (0.95 + Math.random() * 0.1)))
|
||
|
||
const [data, setData] = useState({
|
||
todayRevenue: 0,
|
||
yesterdayRevenue: yesterdayRevenue,
|
||
totalFans: config.totalFans || 460000,
|
||
activeFans: Math.floor(((config.totalFans || 460000) * (config.activeUserRate ?? 8.5)) / 100),
|
||
championCount: config.championCount || 14,
|
||
totalStars: config.totalStars || 201,
|
||
superIndividualCount: config.superIndividualCount || 1407,
|
||
})
|
||
const [isAnimating, setIsAnimating] = useState(false)
|
||
const stepCountRef = useRef(0)
|
||
|
||
const calculateRevenue = useCallback(() => {
|
||
const now = new Date()
|
||
const startOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate())
|
||
const endOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59)
|
||
|
||
// 计算当天已过去的时间比例
|
||
const dayProgress = (now.getTime() - startOfDay.getTime()) / (endOfDay.getTime() - startOfDay.getTime())
|
||
|
||
// 每5秒增长一次,一天共 17280 次更新机会 (24*60*60/5)
|
||
// 使用缓动函数让增长更自然(开始慢,中间快,结束慢)
|
||
const easeInOut = (t: number) => (t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2)
|
||
const easedProgress = easeInOut(dayProgress)
|
||
|
||
// 当前收益 = 目标收益 * 缓动后的进度
|
||
const currentRevenue = Math.floor(targetRevenue.current * easedProgress)
|
||
|
||
// 添加小幅波动 ±0.5%
|
||
const fluctuation = 0.995 + Math.random() * 0.01
|
||
|
||
return Math.floor(currentRevenue * fluctuation)
|
||
}, [])
|
||
|
||
useEffect(() => {
|
||
// 初始计算
|
||
setData((prev) => ({ ...prev, todayRevenue: calculateRevenue() }))
|
||
|
||
const interval = setInterval(() => {
|
||
setIsAnimating(true)
|
||
stepCountRef.current += 1
|
||
setData((prev) => ({ ...prev, todayRevenue: calculateRevenue() }))
|
||
|
||
// 动画结束后重置状态
|
||
setTimeout(() => {
|
||
setIsAnimating(false)
|
||
}, 300)
|
||
}, 5000)
|
||
|
||
return () => clearInterval(interval)
|
||
}, [calculateRevenue])
|
||
|
||
const handleLinkClick = (url: string) => {
|
||
window.open(url, "_blank", "noopener,noreferrer")
|
||
}
|
||
|
||
const revenueCompare = ((data.todayRevenue / data.yesterdayRevenue) * 100 - 100).toFixed(1)
|
||
const isPositive = Number.parseFloat(revenueCompare) >= 0
|
||
|
||
return (
|
||
<div className="space-y-4">
|
||
{/* 收益统计卡片 */}
|
||
<Card className="wz-glass-card border-border/50 hover:border-white/20 transition-all duration-300 shadow-lg">
|
||
<CardContent className="p-4">
|
||
<div className="flex items-center justify-between">
|
||
<div className="flex items-center">
|
||
<div className="w-12 h-12 bg-white/10 rounded-xl flex items-center justify-center mr-3">
|
||
<CreditCard className="w-6 h-6 text-amber-400/90" />
|
||
</div>
|
||
<div>
|
||
<div className="text-sm text-muted-foreground flex items-center gap-2">
|
||
今日总收益
|
||
<span className="text-[10px] px-1.5 py-0.5 bg-muted rounded text-muted-foreground/70">每5秒更新</span>
|
||
</div>
|
||
<div
|
||
className={`text-2xl font-bold text-foreground flex items-center transition-transform duration-300 ${isAnimating ? "revenue-pulse" : ""}`}
|
||
>
|
||
¥{data.todayRevenue.toLocaleString()}
|
||
<span
|
||
className={`ml-2 text-xs px-2 py-1 rounded-full ${isPositive ? "bg-green-500/20 text-green-400" : "bg-red-500/20 text-red-400"}`}
|
||
>
|
||
{isPositive ? "+" : ""}
|
||
{revenueCompare}%
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div className="mt-4 flex items-center justify-between pt-3 border-t border-border/50">
|
||
<div className="flex items-center">
|
||
<CreditCard className="w-5 h-5 mr-2 text-white/70" />
|
||
<div className="text-sm text-muted-foreground">昨日收益</div>
|
||
<div className="text-lg font-semibold text-foreground ml-2">
|
||
¥{data.yesterdayRevenue.toLocaleString()}
|
||
</div>
|
||
</div>
|
||
<div className="text-xs px-2 py-1 bg-green-500/20 text-green-400 rounded-full flex items-center">
|
||
<TrendingUp className="w-3 h-3 mr-1" />+{config.yearOverYear?.toFixed(1) || "15.2"}%
|
||
</div>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
{/* 粉丝数据卡片 */}
|
||
<Card className="wz-glass-card border-border/50 hover:border-white/20 transition-all duration-300 shadow-lg">
|
||
<CardContent className="p-4">
|
||
<div className="flex items-center justify-between">
|
||
<div className="flex items-center">
|
||
<div className="w-12 h-12 bg-white/10 rounded-xl flex items-center justify-center mr-3">
|
||
<Users className="w-6 h-6 text-white/80" />
|
||
</div>
|
||
<div>
|
||
<div className="text-sm text-muted-foreground">私域粉丝总数</div>
|
||
<div
|
||
className="text-2xl font-bold text-foreground flex items-center cursor-pointer hover:text-blue-400 transition-colors"
|
||
onClick={() => handleLinkClick("https://is-op.vercel.app/devices")}
|
||
>
|
||
<AnimatedNumber value={data.totalFans} formatValue={(val) => (val / 10000).toFixed(1) + "万"} />
|
||
<ExternalLink className="w-4 h-4 ml-2 text-muted-foreground" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div className="mt-4 flex items-center justify-between pt-3 border-t border-border/50">
|
||
<div className="flex items-center">
|
||
<Zap className="w-5 h-5 mr-2 text-green-400" />
|
||
<div>
|
||
<div className="text-sm text-muted-foreground">日活跃粉丝</div>
|
||
<div className="text-lg font-semibold text-foreground">
|
||
<AnimatedNumber value={data.activeFans} formatValue={(val) => (val / 10000).toFixed(1) + "万"} />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div className="text-xs px-2 py-1 bg-green-500/20 text-green-400 rounded-full">
|
||
活跃度 {config.activeUserRate || 8.5}%
|
||
</div>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
{/* 电竞明星数据卡片 */}
|
||
<Card className="wz-glass-card border-border/50 hover:border-white/20 transition-all duration-300 shadow-lg">
|
||
<CardContent className="p-4">
|
||
<div className="space-y-4">
|
||
<div className="flex items-center justify-between p-2 bg-yellow-500/10 rounded-lg">
|
||
<div className="flex items-center">
|
||
<Trophy className="w-5 h-5 mr-2 text-yellow-500" />
|
||
<div className="text-sm text-muted-foreground">全国冠军数量</div>
|
||
</div>
|
||
<div className="text-lg font-bold text-yellow-400">{data.championCount}位</div>
|
||
</div>
|
||
|
||
<div className="flex items-center justify-between p-2 bg-blue-500/10 rounded-lg">
|
||
<div className="flex items-center">
|
||
<Star className="w-5 h-5 mr-2 text-blue-500" />
|
||
<div className="text-sm text-muted-foreground">电竞明星总数</div>
|
||
</div>
|
||
<div className="text-lg font-bold text-blue-400">{data.totalStars}人</div>
|
||
</div>
|
||
|
||
<div className="flex items-center justify-between p-2 bg-emerald-500/10 rounded-lg">
|
||
<div className="flex items-center">
|
||
<Gamepad2 className="w-5 h-5 mr-2 text-emerald-500" />
|
||
<div className="text-sm text-muted-foreground">超级个体数量</div>
|
||
</div>
|
||
<div className="text-lg font-bold text-emerald-400">{data.superIndividualCount}个</div>
|
||
</div>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
{/* 运营团队数据卡片 */}
|
||
<Card className="wz-glass-card border-border/50 hover:border-white/20 transition-all duration-300 shadow-lg">
|
||
<CardContent className="p-4">
|
||
<div className="flex items-center justify-between">
|
||
<div className="flex items-center">
|
||
<div className="w-12 h-12 bg-white/10 rounded-xl flex items-center justify-center mr-3">
|
||
<Users className="w-6 h-6 text-white/80" />
|
||
</div>
|
||
<div>
|
||
<div className="text-sm text-muted-foreground">运营团队人员</div>
|
||
<div className="text-2xl font-bold text-foreground">32人</div>
|
||
</div>
|
||
</div>
|
||
<div className="text-xs px-3 py-1 bg-white/15 text-white/80 rounded-full">专业团队</div>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
</div>
|
||
)
|
||
}
|