Files
wzdj/app/admin/page.tsx
2026-04-11 17:15:11 +08:00

304 lines
13 KiB
TypeScript

"use client"
import { useState, useEffect } from "react"
import { useRouter } from "next/navigation"
import {
Users,
Radio,
FileText,
TrendingUp,
UserCheck,
CreditCard,
Gamepad2,
Wallet,
BarChart3,
PieChart,
Activity,
} from "lucide-react"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Badge } from "@/components/ui/badge"
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import { getDashboard } from "@/lib/api/admin"
interface DashboardStats {
totalUsers: number
totalStreamers: number
totalOrders: number
totalRevenue: number
todayNewUsers: number
todayOrders: number
activeStreamers: number
pendingPawns: number
}
export default function AdminDashboard() {
const router = useRouter()
const [stats, setStats] = useState<DashboardStats>({
totalUsers: 0,
totalStreamers: 0,
totalOrders: 0,
totalRevenue: 0,
todayNewUsers: 0,
todayOrders: 0,
activeStreamers: 0,
pendingPawns: 0,
})
const [recentUsers, setRecentUsers] = useState<{ id?: string; name?: string; avatar?: string; phone?: string; createdAt?: string }[]>([])
const [recentOrders, setRecentOrders] = useState<{ orderNo: string; itemName: string; amount: number; createdAt: string }[]>([])
useEffect(() => {
loadDashboardData()
}, [])
const loadDashboardData = async () => {
try {
const data = await getDashboard()
if (!data) return
setStats(data.stats)
setRecentUsers(data.recentUsers)
setRecentOrders(data.recentOrders)
} catch (error) {
console.error("Failed to load dashboard data:", error)
}
}
return (
<div className="max-w-[1600px] mx-auto space-y-6">
<div>
<h1 className="text-2xl font-bold text-gray-900"></h1>
<p className="text-sm text-gray-500 mt-1"> · </p>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-4 gap-4">
<Card className="admin-card-stat rounded-2xl overflow-hidden border-0">
<CardContent className="p-4">
<div className="flex items-center justify-between">
<div>
<p className="text-gray-500 text-sm"></p>
<p className="text-2xl font-bold text-gray-900">{stats.totalUsers.toLocaleString()}</p>
<p className="text-xs text-gray-500">+{stats.todayNewUsers} </p>
</div>
<Users className="h-8 w-8 text-violet-500" />
</div>
</CardContent>
</Card>
<Card className="admin-card-stat rounded-2xl overflow-hidden border-0">
<CardContent className="p-4">
<div className="flex items-center justify-between">
<div>
<p className="text-gray-500 text-sm"></p>
<p className="text-2xl font-bold text-gray-900">{stats.totalStreamers}</p>
<p className="text-xs text-gray-500">{stats.activeStreamers} </p>
</div>
<Radio className="h-8 w-8 text-violet-500" />
</div>
</CardContent>
</Card>
<Card className="admin-card-stat rounded-2xl overflow-hidden border-0">
<CardContent className="p-4">
<div className="flex items-center justify-between">
<div>
<p className="text-gray-500 text-sm"></p>
<p className="text-2xl font-bold text-gray-900">{stats.totalOrders}</p>
<p className="text-xs text-gray-500">+{stats.todayOrders} </p>
</div>
<FileText className="h-8 w-8 text-violet-500" />
</div>
</CardContent>
</Card>
<Card className="admin-card-stat rounded-2xl overflow-hidden border-0">
<CardContent className="p-4">
<div className="flex items-center justify-between">
<div>
<p className="text-gray-500 text-sm"></p>
<p className="text-2xl font-bold text-gray-900">¥{stats.totalRevenue.toLocaleString()}</p>
<p className="text-xs text-gray-500">{stats.pendingPawns} </p>
</div>
<TrendingUp className="h-8 w-8 text-violet-500" />
</div>
</CardContent>
</Card>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<Card className="admin-card-stat rounded-2xl overflow-hidden border-0">
<CardHeader>
<CardTitle className="text-gray-900 flex items-center gap-2 text-base">
<BarChart3 className="w-5 h-5 text-violet-500" />
</CardTitle>
</CardHeader>
<CardContent>
<div className="h-48 flex items-end justify-around gap-2">
{[65, 45, 78, 52, 88, 70, 95].map((height, i) => (
<div key={i} className="flex-1 flex flex-col items-center gap-1">
<div
className="w-full bg-gradient-to-t from-violet-500 to-cyan-400 rounded-t border border-violet-200/60"
style={{ height: `${height}%` }}
/>
<span className="text-xs text-gray-500">{["一", "二", "三", "四", "五", "六", "日"][i]}</span>
</div>
))}
</div>
</CardContent>
</Card>
<Card className="admin-card-stat rounded-2xl overflow-hidden border-0">
<CardHeader>
<CardTitle className="text-gray-900 flex items-center gap-2 text-base">
<PieChart className="w-5 h-5 text-violet-500" />
</CardTitle>
</CardHeader>
<CardContent>
<div className="flex items-center justify-around">
<div className="relative w-32 h-32">
<svg className="w-full h-full" viewBox="0 0 100 100">
<circle cx="50" cy="50" r="40" fill="none" stroke="rgb(226 232 240)" strokeWidth="20" />
<circle cx="50" cy="50" r="40" fill="none" stroke="rgb(139 92 246)" strokeWidth="20" strokeDasharray="75 175" strokeDashoffset="0" />
<circle cx="50" cy="50" r="40" fill="none" stroke="rgb(6 182 212)" strokeWidth="20" strokeDasharray="50 200" strokeDashoffset="-75" />
<circle cx="50" cy="50" r="40" fill="none" stroke="rgb(34 197 94)" strokeWidth="20" strokeDasharray="45 205" strokeDashoffset="-125" />
<circle cx="50" cy="50" r="40" fill="none" stroke="rgb(234 179 8)" strokeWidth="20" strokeDasharray="30 220" strokeDashoffset="-170" />
</svg>
</div>
<div className="space-y-2">
<div className="flex items-center gap-2">
<div className="w-3 h-3 rounded-full bg-violet-500" />
<span className="text-gray-600 text-sm"> 30%</span>
</div>
<div className="flex items-center gap-2">
<div className="w-3 h-3 rounded-full bg-cyan-500" />
<span className="text-gray-600 text-sm"> 20%</span>
</div>
<div className="flex items-center gap-2">
<div className="w-3 h-3 rounded-full bg-emerald-500" />
<span className="text-gray-600 text-sm"> 18%</span>
</div>
<div className="flex items-center gap-2">
<div className="w-3 h-3 rounded-full bg-amber-500" />
<span className="text-gray-600 text-sm"> 12%</span>
</div>
</div>
</div>
</CardContent>
</Card>
</div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
<Card className="admin-card-stat rounded-2xl overflow-hidden border-0">
<CardHeader>
<CardTitle className="text-gray-900 text-base"></CardTitle>
</CardHeader>
<CardContent className="space-y-2">
<Button
onClick={() => router.push("/admin/users")}
variant="outline"
className="w-full justify-between border-gray-200 text-gray-700 hover:bg-gray-50 rounded-xl"
>
<span className="flex items-center gap-2">
<UserCheck className="w-4 h-4" />
</span>
<Badge className="bg-sky-100 text-sky-600">{stats.todayNewUsers}</Badge>
</Button>
<Button
onClick={() => router.push("/admin/pawn")}
variant="outline"
className="w-full justify-between border-gray-200 text-gray-700 hover:bg-gray-50 rounded-xl"
>
<span className="flex items-center gap-2">
<Wallet className="w-4 h-4" />
</span>
<Badge className="bg-amber-100 text-amber-600">{stats.pendingPawns}</Badge>
</Button>
<Button
onClick={() => router.push("/admin/streamers")}
variant="outline"
className="w-full justify-between border-gray-200 text-gray-700 hover:bg-gray-50 rounded-xl"
>
<span className="flex items-center gap-2">
<Radio className="w-4 h-4" />
</span>
<Badge className="bg-violet-100 text-violet-600">2</Badge>
</Button>
<Button
onClick={() => router.push("/admin/orders")}
variant="outline"
className="w-full justify-between border-gray-200 text-gray-700 hover:bg-gray-50 rounded-xl"
>
<span className="flex items-center gap-2">
<FileText className="w-4 h-4" />
</span>
</Button>
</CardContent>
</Card>
<Card className="admin-card-stat rounded-2xl overflow-hidden border-0">
<CardHeader>
<CardTitle className="text-gray-900 text-base"></CardTitle>
</CardHeader>
<CardContent className="space-y-3">
{recentUsers.length > 0 ? (
recentUsers.map((user, i) => (
<div key={user.id ?? String(i)} className="flex items-center gap-3">
<Avatar className="w-8 h-8">
<AvatarImage src={user.avatar || "/placeholder.svg"} />
<AvatarFallback className="bg-violet-100 text-violet-700 text-xs">{user.name?.[0]}</AvatarFallback>
</Avatar>
<div className="flex-1 min-w-0">
<p className="text-gray-900 text-sm truncate font-medium">{user.name}</p>
<p className="text-gray-500 text-xs">{user.phone || "未绑定手机"}</p>
</div>
<Badge className="bg-emerald-100 text-emerald-700 text-xs"></Badge>
</div>
))
) : (
<p className="text-gray-500 text-sm text-center py-4"></p>
)}
</CardContent>
</Card>
<Card className="admin-card-stat rounded-2xl overflow-hidden border-0">
<CardHeader>
<CardTitle className="text-gray-900 flex items-center gap-2 text-base">
<Activity className="w-5 h-5 text-emerald-500" />
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div>
<div className="flex justify-between text-sm mb-1">
<span className="text-gray-600"></span>
<span className="text-emerald-600"></span>
</div>
<div className="h-2 bg-gray-200 rounded-full overflow-hidden">
<div className="h-full bg-emerald-500 rounded-full w-1/4" />
</div>
</div>
<div>
<div className="flex justify-between text-sm mb-1">
<span className="text-gray-600">API服务</span>
<span className="text-emerald-600"></span>
</div>
<div className="h-2 bg-gray-200 rounded-full overflow-hidden">
<div className="h-full bg-emerald-500 rounded-full w-[15%]" />
</div>
</div>
<div>
<div className="flex justify-between text-sm mb-1">
<span className="text-gray-600"></span>
<span className="text-cyan-600">48%</span>
</div>
<div className="h-2 bg-gray-200 rounded-full overflow-hidden">
<div className="h-full bg-cyan-500 rounded-full w-[48%]" />
</div>
</div>
<p className="text-gray-500 text-xs pt-2 border-t border-gray-200">: {new Date().toLocaleString("zh-CN")}</p>
</CardContent>
</Card>
</div>
</div>
)
}