feat: enhance live room and planet matchmaking features

Add fan group entry on live room; update planet matching with 4 type buttons.

Co-authored-by: undefined <undefined+undefined@users.noreply.github.com>
This commit is contained in:
v0
2025-12-01 10:46:13 +00:00
parent 451663a0bd
commit e267fa09f6
81 changed files with 4125 additions and 597 deletions

119
app/coach/[id]/page.tsx Normal file
View File

@@ -0,0 +1,119 @@
"use client"
import { ArrowLeft, Star, Trophy, Users, Shield, Gamepad2, Award, MessageCircle } from "lucide-react"
import { useRouter } from "next/navigation"
import Image from "next/image"
import { Button } from "@/components/ui/button"
import { toast } from "@/hooks/use-toast"
export default function CoachDetailPage({ params }: { params: { id: string } }) {
const router = useRouter()
const handleBook = () => {
toast({
title: "预约成功",
description: "陪练师将在15分钟内联系您",
})
}
return (
<div className="min-h-screen bg-background pb-24 relative">
{/* Hero Section */}
<div className="h-48 relative">
<Image src="/esports-tournament-stadium.jpg" alt="Cover" fill className="object-cover" />
<div className="absolute inset-0 bg-gradient-to-b from-transparent to-background" />
<button
onClick={() => router.back()}
className="absolute top-4 left-4 p-2 rounded-full bg-black/20 backdrop-blur-md text-white z-10"
>
<ArrowLeft size={20} />
</button>
</div>
<div className="px-4 -mt-12 relative z-10">
<div className="flex items-end justify-between mb-4">
<div className="w-24 h-24 rounded-2xl border-4 border-background bg-muted relative overflow-hidden shadow-xl">
<Image src="/avatar-2.jpg" alt="Coach" fill className="object-cover" />
<div className="absolute bottom-0 right-0 w-5 h-5 rounded-full bg-green-500 border-2 border-background" />
</div>
<div className="flex gap-2 mb-2">
<Button size="sm" variant="outline" className="h-8 text-xs gap-1 bg-transparent">
<MessageCircle size={14} />
</Button>
<Button size="sm" className="h-8 text-xs bg-green-500 gap-1 hover:bg-green-600" onClick={handleBook}>
<Gamepad2 size={14} />
</Button>
</div>
</div>
<div className="mb-6">
<div className="flex items-center gap-2 mb-1">
<h1 className="text-2xl font-bold">-</h1>
<Award className="text-yellow-400" size={20} />
</div>
<p className="text-sm text-white/60 mb-3">LPL职业选手 · · 5</p>
<div className="flex items-center gap-4 text-xs text-white/40">
<span className="flex items-center gap-1">
<Star size={12} className="text-primary fill-primary" />
4.9
</span>
<span className="flex items-center gap-1">
<Trophy size={12} className="text-accent" />
2456
</span>
<span className="flex items-center gap-1">
<Users size={12} className="text-secondary" />
98%
</span>
</div>
</div>
{/* Services */}
<div className="space-y-4 mb-6">
<h3 className="font-bold text-sm flex items-center gap-2">
<Gamepad2 size={16} className="text-green-500" />
</h3>
<div className="space-y-3">
{[
{ name: "单局陪练", price: 80, desc: "边玩边教,即时指导", duration: "1局" },
{ name: "5局套餐", price: 360, desc: "系统提升优惠45元", duration: "5局", hot: true },
{ name: "10局特训", price: 650, desc: "深度培训,包上分", duration: "10局" },
].map((service, i) => (
<div key={i} className="glass-card p-4 rounded-xl relative">
{service.hot && (
<span className="absolute top-2 right-2 px-2 py-0.5 rounded-full bg-green-500/20 text-green-400 text-[10px] font-bold">
</span>
)}
<div className="flex items-center justify-between mb-2">
<div>
<h4 className="font-bold mb-1">{service.name}</h4>
<p className="text-xs text-white/60">{service.desc}</p>
</div>
<div className="text-right">
<div className="text-2xl font-bold text-green-500">{service.price}</div>
<div className="text-[10px] text-white/50"></div>
</div>
</div>
</div>
))}
</div>
</div>
{/* Trust Badge */}
<div className="glass-card p-4 rounded-xl bg-gradient-to-r from-green-500/10 to-transparent border-l-4 border-green-500">
<div className="flex items-center gap-3">
<Shield size={24} className="text-green-500" />
<div>
<h4 className="font-bold text-sm"></h4>
<p className="text-xs text-white/60">退</p>
</div>
</div>
</div>
</div>
</div>
)
}

208
app/coach/page.tsx Normal file
View File

@@ -0,0 +1,208 @@
"use client"
import { ArrowLeft, Trophy, Star, Gamepad2, MessageCircle, Award } from "lucide-react"
import { useRouter } from "next/navigation"
import Image from "next/image"
import { Button } from "@/components/ui/button"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import { toast } from "@/hooks/use-toast"
export default function CoachPage() {
const router = useRouter()
const handleBookCoach = (coachName: string) => {
toast({
title: "预约成功",
description: `已成功预约${coachName}的陪练服务`,
})
}
return (
<div className="min-h-screen pb-24 bg-background">
{/* Header */}
<header className="sticky top-0 z-40 glass px-4 py-3 flex items-center gap-3">
<button onClick={() => router.back()} className="p-2 -ml-2 hover:bg-white/5 rounded-lg transition-colors">
<ArrowLeft size={20} />
</button>
<h1 className="font-bold text-lg"></h1>
</header>
<div className="p-4 space-y-6">
{/* Banner */}
<div className="glass-card p-4 rounded-xl border-l-4 border-green-500">
<div className="flex items-center justify-between mb-3">
<h3 className="font-bold text-sm"></h3>
<span className="text-xs text-white/50">1000+ </span>
</div>
<p className="text-xs text-white/60 mb-3"></p>
<div className="grid grid-cols-3 gap-2 text-center">
<div className="bg-white/5 rounded-lg p-2">
<div className="text-xs font-bold text-green-500"></div>
<div className="text-[10px] text-white/50 mt-0.5"></div>
</div>
<div className="bg-white/5 rounded-lg p-2">
<div className="text-xs font-bold text-blue-500"></div>
<div className="text-[10px] text-white/50 mt-0.5"></div>
</div>
<div className="bg-white/5 rounded-lg p-2">
<div className="text-xs font-bold text-purple-500"></div>
<div className="text-[10px] text-white/50 mt-0.5"></div>
</div>
</div>
</div>
{/* Filters */}
<Tabs defaultValue="hot" className="w-full">
<TabsList className="w-full bg-white/5 p-1 rounded-xl">
<TabsTrigger value="hot" className="flex-1 text-xs">
</TabsTrigger>
<TabsTrigger value="pro" className="flex-1 text-xs">
</TabsTrigger>
<TabsTrigger value="king" className="flex-1 text-xs">
</TabsTrigger>
</TabsList>
<TabsContent value="hot" className="mt-4 space-y-3">
{[
{
name: "职业打野-小明",
title: "前LPL职业选手",
avatar: "/avatar-2.jpg",
games: ["英雄联盟"],
rank: "王者2000分",
orders: 2456,
rating: 4.9,
price: 80,
tags: ["职业", "打野", "带飞"],
online: true,
},
{
name: "上分小助手",
title: "国服路人王",
avatar: "/avatar-4.jpg",
games: ["王者荣耀", "和平精英"],
rank: "荣耀王者100星",
orders: 1823,
rating: 4.8,
price: 50,
tags: ["温柔", "耐心", "指导"],
online: true,
},
{
name: "FPS大神",
title: "前职业选手",
avatar: "/avatar-5.jpg",
games: ["无畏契约", "三角洲"],
rank: "超凡入圣",
orders: 987,
rating: 4.9,
price: 100,
tags: ["枪法", "意识", "专业"],
online: false,
},
{
name: "温柔陪练师",
title: "全能型选手",
avatar: "/avatar-6.jpg",
games: ["王者荣耀", "英雄联盟"],
rank: "多区王者",
orders: 3201,
rating: 5.0,
price: 60,
tags: ["声音好听", "技术强", "幽默"],
online: true,
},
].map((coach, i) => (
<div
key={i}
className="glass-card p-4 rounded-xl hover:bg-white/5 transition-colors cursor-pointer"
onClick={() => router.push(`/coach/${i + 1}`)}
>
<div className="flex items-start gap-3 mb-3">
<div className="relative">
<div className="w-16 h-16 rounded-lg bg-muted relative overflow-hidden">
<Image src={coach.avatar || "/placeholder.svg"} alt={coach.name} fill className="object-cover" />
</div>
{coach.online && (
<div className="absolute -bottom-1 -right-1 w-4 h-4 rounded-full bg-green-500 border-2 border-background" />
)}
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-1">
<h4 className="font-bold truncate">{coach.name}</h4>
{coach.rating === 5.0 && <Award className="w-4 h-4 text-yellow-400" />}
</div>
<p className="text-xs text-white/60 mb-2">{coach.title}</p>
<div className="flex items-center gap-3 text-xs text-white/50 mb-2">
<span className="flex items-center gap-1">
<Star size={12} className="text-primary fill-primary" />
{coach.rating}
</span>
<span className="flex items-center gap-1">
<Trophy size={12} className="text-accent" />
{coach.orders}
</span>
</div>
<div className="flex flex-wrap gap-1">
{coach.tags.map((tag, j) => (
<span key={j} className="px-2 py-0.5 rounded-full bg-white/5 text-[10px] text-white/60">
{tag}
</span>
))}
</div>
</div>
<div className="text-right">
<div className="text-lg font-bold text-green-500">{coach.price}</div>
<div className="text-[10px] text-white/50">/</div>
</div>
</div>
<div className="flex gap-2 mt-3">
<Button
size="sm"
variant="outline"
className="flex-1 h-8 text-xs border-white/10 hover:bg-white/5 bg-transparent"
onClick={(e) => {
e.stopPropagation()
router.push(`/coach/${i + 1}`)
}}
>
<MessageCircle size={14} className="mr-1" />
</Button>
<Button
size="sm"
className="flex-1 h-8 text-xs bg-green-500 hover:bg-green-600"
onClick={(e) => {
e.stopPropagation()
handleBookCoach(coach.name)
}}
>
<Gamepad2 size={14} className="mr-1" />
</Button>
</div>
</div>
))}
</TabsContent>
<TabsContent value="pro" className="mt-4">
<div className="text-center py-12 text-white/40 text-sm">...</div>
</TabsContent>
<TabsContent value="king" className="mt-4">
<div className="text-center py-12 text-white/40 text-sm">...</div>
</TabsContent>
</Tabs>
</div>
</div>
)
}

127
app/cp/[id]/page.tsx Normal file
View File

@@ -0,0 +1,127 @@
"use client"
import { ArrowLeft, Heart, Star, MessageCircle, Shield, Trophy, Users, Gamepad2 } from "lucide-react"
import { useRouter } from "next/navigation"
import Image from "next/image"
import { Button } from "@/components/ui/button"
import { toast } from "@/hooks/use-toast"
export default function CPDetailPage({ params }: { params: { id: string } }) {
const router = useRouter()
const handleSendRequest = () => {
toast({
title: "请求已发送",
description: "对方收到通知后会尽快回复你",
})
}
return (
<div className="min-h-screen bg-background pb-24 relative">
{/* Hero Section */}
<div className="h-48 relative">
<Image src="/esports-live-stream-gameplay.jpg" alt="Cover" fill className="object-cover" />
<div className="absolute inset-0 bg-gradient-to-b from-transparent to-background" />
<button
onClick={() => router.back()}
className="absolute top-4 left-4 p-2 rounded-full bg-black/20 backdrop-blur-md text-white z-10"
>
<ArrowLeft size={20} />
</button>
</div>
<div className="px-4 -mt-12 relative z-10">
<div className="flex items-end justify-between mb-4">
<div className="w-24 h-24 rounded-2xl border-4 border-background bg-muted relative overflow-hidden shadow-xl">
<Image src="/avatar-1.jpg" alt="Avatar" fill className="object-cover" />
<div className="absolute bottom-0 right-0 w-5 h-5 rounded-full bg-green-500 border-2 border-background" />
</div>
<div className="flex gap-2 mb-2">
<Button
size="sm"
variant="outline"
className="h-8 text-xs gap-1 bg-transparent border-pink-500/50 text-pink-400 hover:bg-pink-500/10"
>
<Heart size={14} />
</Button>
<Button size="sm" className="h-8 text-xs bg-pink-500 gap-1 hover:bg-pink-600" onClick={handleSendRequest}>
<MessageCircle size={14} />
</Button>
</div>
</div>
<div className="mb-6">
<div className="flex items-center gap-2 mb-1">
<h1 className="text-2xl font-bold"></h1>
<span className="px-2 py-0.5 rounded bg-pink-500/20 text-pink-400 text-[10px] border border-pink-500/30 font-bold">
· 22
</span>
</div>
<p className="text-sm text-white/60 mb-3">ADC一起开黑~</p>
<div className="flex items-center gap-4 text-xs text-white/40">
<span className="flex items-center gap-1">
<Star size={12} className="text-primary" />
耀
</span>
<span className="flex items-center gap-1">
<Trophy size={12} className="text-accent" />
385
</span>
<span className="flex items-center gap-1">
<Users size={12} className="text-secondary" />
68
</span>
</div>
</div>
{/* Games */}
<div className="space-y-4 mb-6">
<h3 className="font-bold text-sm flex items-center gap-2">
<Gamepad2 size={16} className="text-primary" />
</h3>
<div className="grid grid-cols-2 gap-3">
{[
{ game: "王者荣耀", rank: "星耀I", hero: "瑶、大乔" },
{ game: "和平精英", rank: "无敌战神", role: "四排辅助位" },
].map((item, i) => (
<div key={i} className="glass-card p-3 rounded-xl">
<div className="font-bold text-sm mb-1">{item.game}</div>
<div className="text-xs text-white/50">{item.rank}</div>
<div className="text-xs text-primary mt-1">{item.hero || item.role}</div>
</div>
))}
</div>
</div>
{/* Tags */}
<div className="space-y-4 mb-6">
<h3 className="font-bold text-sm"></h3>
<div className="flex flex-wrap gap-2">
{["声音好听", "技术不错", "温柔", "有耐心", "爱聊天"].map((tag, i) => (
<span
key={i}
className="px-3 py-1.5 rounded-full bg-pink-500/10 border border-pink-500/30 text-xs text-pink-400"
>
{tag}
</span>
))}
</div>
</div>
{/* Trust Badge */}
<div className="glass-card p-4 rounded-xl bg-gradient-to-r from-pink-500/10 to-transparent border-l-4 border-pink-500">
<div className="flex items-center gap-3">
<Shield size={24} className="text-pink-500" />
<div>
<h4 className="font-bold text-sm"></h4>
<p className="text-xs text-white/60"></p>
</div>
</div>
</div>
</div>
</div>
)
}

207
app/cp/page.tsx Normal file
View File

@@ -0,0 +1,207 @@
"use client"
import { ArrowLeft, Heart, Star, MessageCircle, Sparkles } from "lucide-react"
import { useRouter } from "next/navigation"
import Image from "next/image"
import { Button } from "@/components/ui/button"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import { toast } from "@/hooks/use-toast"
export default function CPPage() {
const router = useRouter()
const handleContact = (cpName: string) => {
toast({
title: "发送成功",
description: `已向${cpName}发送交友请求`,
})
}
return (
<div className="min-h-screen pb-24 bg-background">
{/* Header */}
<header className="sticky top-0 z-40 glass px-4 py-3 flex items-center gap-3">
<button onClick={() => router.back()} className="p-2 -ml-2 hover:bg-white/5 rounded-lg transition-colors">
<ArrowLeft size={20} />
</button>
<h1 className="font-bold text-lg">CP</h1>
</header>
<div className="p-4 space-y-6">
{/* Banner */}
<div className="glass-card p-4 rounded-xl border-l-4 border-pink-500 bg-gradient-to-r from-pink-500/10 to-transparent">
<div className="flex items-center justify-between mb-3">
<h3 className="font-bold text-sm flex items-center gap-2">
<Heart className="text-pink-500" size={18} />
CP
</h3>
<Sparkles className="text-pink-500 animate-pulse" size={18} />
</div>
<p className="text-xs text-white/60 mb-3"></p>
<div className="grid grid-cols-3 gap-2 text-center">
<div className="bg-white/5 rounded-lg p-2">
<div className="text-xs font-bold text-pink-500"></div>
<div className="text-[10px] text-white/50 mt-0.5"></div>
</div>
<div className="bg-white/5 rounded-lg p-2">
<div className="text-xs font-bold text-blue-500"></div>
<div className="text-[10px] text-white/50 mt-0.5"></div>
</div>
<div className="bg-white/5 rounded-lg p-2">
<div className="text-xs font-bold text-purple-500"></div>
<div className="text-[10px] text-white/50 mt-0.5"></div>
</div>
</div>
</div>
{/* Filters */}
<Tabs defaultValue="all" className="w-full">
<TabsList className="w-full bg-white/5 p-1 rounded-xl">
<TabsTrigger value="all" className="flex-1 text-xs">
</TabsTrigger>
<TabsTrigger value="girl" className="flex-1 text-xs">
</TabsTrigger>
<TabsTrigger value="boy" className="flex-1 text-xs">
</TabsTrigger>
</TabsList>
<TabsContent value="all" className="mt-4 space-y-3">
{[
{
name: "甜心辅助",
gender: "女",
age: 22,
avatar: "/avatar-1.jpg",
games: ["王者荣耀", "和平精英"],
voice: "温柔甜美",
rank: "星耀",
online: true,
tags: ["声音好听", "技术不错", "温柔"],
bio: "喜欢玩辅助找个靠谱的ADC一起开黑~",
},
{
name: "职业打野",
gender: "男",
age: 24,
avatar: "/avatar-2.jpg",
games: ["英雄联盟", "无畏契约"],
voice: "成熟稳重",
rank: "大师",
online: true,
tags: ["技术流", "有耐心", "幽默"],
bio: "前职业选手,带你上分不是梦",
},
{
name: "软萌小姐姐",
gender: "女",
age: 20,
avatar: "/avatar-3.jpg",
games: ["王者荣耀", "原神"],
voice: "软萌可爱",
rank: "钻石",
online: false,
tags: ["萌妹子", "爱聊天", "佛系"],
bio: "游戏菜但是爱玩,找个不嫌弃的~",
},
{
name: "中单刺客",
gender: "男",
age: 23,
avatar: "/avatar-4.jpg",
games: ["英雄联盟"],
voice: "阳光帅气",
rank: "王者",
online: true,
tags: ["Carry型", "稳定", "负责"],
bio: "擅长刺客中单,带妹上分专业户",
},
].map((cp, i) => (
<div
key={i}
className="glass-card p-4 rounded-xl hover:bg-white/5 transition-colors cursor-pointer"
onClick={() => router.push(`/cp/${i + 1}`)}
>
<div className="flex items-start gap-3 mb-3">
<div className="relative">
<div className="w-16 h-16 rounded-lg bg-muted relative overflow-hidden">
<Image src={cp.avatar || "/placeholder.svg"} alt={cp.name} fill className="object-cover" />
</div>
{cp.online && (
<div className="absolute -bottom-1 -right-1 w-4 h-4 rounded-full bg-green-500 border-2 border-background" />
)}
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-1">
<h4 className="font-bold truncate">{cp.name}</h4>
<span
className={`px-1.5 py-0.5 rounded text-[10px] ${cp.gender === "女" ? "bg-pink-500/20 text-pink-400" : "bg-blue-500/20 text-blue-400"}`}
>
{cp.gender} · {cp.age}
</span>
</div>
<div className="flex items-center gap-3 text-xs text-white/50 mb-2">
<span className="flex items-center gap-1">
<Star size={12} className="text-primary" />
{cp.rank}
</span>
<span>{cp.voice}</span>
</div>
<p className="text-xs text-white/60 mb-2 line-clamp-1">{cp.bio}</p>
<div className="flex flex-wrap gap-1">
{cp.tags.map((tag, j) => (
<span key={j} className="px-2 py-0.5 rounded-full bg-white/5 text-[10px] text-white/60">
{tag}
</span>
))}
</div>
</div>
</div>
<div className="flex gap-2 mt-3">
<Button
size="sm"
variant="outline"
className="flex-1 h-8 text-xs border-white/10 hover:bg-white/5 bg-transparent"
onClick={(e) => {
e.stopPropagation()
handleContact(cp.name)
}}
>
<MessageCircle size={14} className="mr-1" />
</Button>
<Button
size="sm"
className="flex-1 h-8 text-xs bg-pink-500 hover:bg-pink-600"
onClick={(e) => {
e.stopPropagation()
router.push(`/cp/${i + 1}`)
}}
>
<Heart size={14} className="mr-1" />
</Button>
</div>
</div>
))}
</TabsContent>
<TabsContent value="girl" className="mt-4">
<div className="text-center py-12 text-white/40 text-sm">...</div>
</TabsContent>
<TabsContent value="boy" className="mt-4">
<div className="text-center py-12 text-white/40 text-sm">...</div>
</TabsContent>
</Tabs>
</div>
</div>
)
}

View File

@@ -11,16 +11,16 @@ export default function DressUpPage() {
const router = useRouter()
const avatars = [
{ id: 1, name: "赛博朋克战士", price: 98, rarity: "传说", sales: 1205, image: "cyberpunk warrior avatar neon" },
{ id: 2, name: "甜心电竞少女", price: 68, rarity: "稀有", sales: 3420, image: "cute esports girl avatar pink" },
{ id: 3, name: "暗影刺客", price: 128, rarity: "传说", sales: 856, image: "shadow assassin avatar dark" },
{ id: 4, name: "机甲战神", price: 88, rarity: "史诗", sales: 2100, image: "mecha warrior avatar blue" },
{ id: 1, name: "赛博朋克战士", price: 98, rarity: "传说", sales: 1205, image: "/avatar-cyberpunk.jpg" },
{ id: 2, name: "甜心电竞少女", price: 68, rarity: "稀有", sales: 3420, image: "/avatar-cute.jpg" },
{ id: 3, name: "暗影刺客", price: 128, rarity: "传说", sales: 856, image: "/avatar-assassin.jpg" },
{ id: 4, name: "机甲战神", price: 88, rarity: "史诗", sales: 2100, image: "/avatar-mecha.jpg" },
]
const decorations = [
{ id: 101, name: "星空边框", price: 38, type: "边框", image: "starry frame decoration" },
{ id: 102, name: "炫光特效", price: 58, type: "特效", image: "neon glow effect" },
{ id: 103, name: "王者徽章", price: 48, type: "徽章", image: "champion badge gold" },
{ id: 101, name: "星空边框", price: 38, type: "边框", image: "/frame-starry.jpg" },
{ id: 102, name: "炫光特效", price: 58, type: "特效", image: "/effect-neon.jpg" },
{ id: 103, name: "王者徽章", price: 48, type: "徽章", image: "/badge-gold.jpg" },
]
return (
@@ -41,7 +41,7 @@ export default function DressUpPage() {
{/* Current Avatar Display */}
<div className="glass-card p-6 rounded-2xl mb-6 text-center border border-primary/20">
<div className="w-24 h-24 mx-auto mb-3 rounded-full border-4 border-primary p-1 relative">
<Image src="/current-user-avatar.png" alt="Current Avatar" fill className="rounded-full object-cover" />
<Image src="/avatar-cyberpunk.jpg" alt="Current Avatar" fill className="rounded-full object-cover" />
<div className="absolute -bottom-2 left-1/2 -translate-x-1/2 bg-primary text-[10px] px-2 py-0.5 rounded-full text-white font-bold">
LV.8
</div>
@@ -81,18 +81,13 @@ export default function DressUpPage() {
className="glass-card p-3 rounded-xl hover:bg-white/10 transition-colors cursor-pointer"
onClick={() =>
toast({
title: "即将购买",
description: `${avatar.name} - ¥${avatar.price}`,
title: "购买成功",
description: `已获得 ${avatar.name},消费 ¥${avatar.price}`,
})
}
>
<div className="relative w-full aspect-square rounded-lg overflow-hidden mb-2 bg-zinc-800">
<Image
src={`/.jpg?height=200&width=200&query=${avatar.image}`}
alt={avatar.name}
fill
className="object-cover"
/>
<Image src={avatar.image || "/placeholder.svg"} alt={avatar.name} fill className="object-cover" />
<div className="absolute top-2 right-2 bg-yellow-500/90 text-black text-[9px] px-1.5 py-0.5 rounded font-bold">
{avatar.rarity}
</div>
@@ -118,12 +113,7 @@ export default function DressUpPage() {
className="glass-card p-3 rounded-xl flex items-center gap-3 hover:bg-white/10 transition-colors cursor-pointer"
>
<div className="w-16 h-16 rounded-lg overflow-hidden bg-zinc-800 relative">
<Image
src={`/.jpg?height=100&width=100&query=${item.image}`}
alt={item.name}
fill
className="object-cover"
/>
<Image src={item.image || "/placeholder.svg"} alt={item.name} fill className="object-cover" />
</div>
<div className="flex-1">
<h4 className="font-bold text-sm mb-1">{item.name}</h4>

106
app/events/today/page.tsx Normal file
View File

@@ -0,0 +1,106 @@
"use client"
import { ArrowLeft, Clock, Users, Trophy, MapPin } from "lucide-react"
import { useRouter } from "next/navigation"
import Image from "next/image"
import Link from "next/link"
export default function TodayEventsPage() {
const router = useRouter()
const events = [
{
id: 1,
title: "2025 玩值杯全明星邀请赛",
game: "英雄联盟",
time: "20:00",
status: "即将开始",
teams: ["RNG", "EDG"],
venue: "上海梅赛德斯奔驰中心",
prize: "100万",
viewers: "25.6w",
image: "/tournament-event-today.jpg",
},
{
id: 2,
title: "王者荣耀职业联赛",
game: "王者荣耀",
time: "18:30",
status: "直播中",
teams: ["AG超玩会", "QGhappy"],
venue: "线上赛",
prize: "50万",
viewers: "18.2w",
image: "/tournament-event-today.jpg",
},
]
return (
<div className="min-h-screen pb-24 bg-background">
<header className="sticky top-0 z-40 bg-[#0D0D15]/90 backdrop-blur-md border-b border-white/5 px-4 py-3 flex items-center gap-3">
<button onClick={() => router.back()} className="p-1 hover:bg-white/10 rounded-full transition-colors">
<ArrowLeft size={20} className="text-white" />
</button>
<h1 className="font-bold text-lg text-white"></h1>
</header>
<div className="p-4 space-y-4">
{events.map((event) => (
<Link
key={event.id}
href={`/events/${event.id}`}
className="block bg-[#1A1A22] rounded-xl overflow-hidden border border-white/5 hover:border-primary/30 transition-all"
>
<div className="relative aspect-[16/9]">
<Image src={event.image || "/placeholder.svg"} alt={event.title} fill className="object-cover" />
<div className="absolute inset-0 bg-gradient-to-t from-black/90 via-transparent to-transparent" />
{event.status === "直播中" && (
<div className="absolute top-3 left-3 flex items-center gap-1 bg-secondary/90 backdrop-blur-md px-2 py-1 rounded-md text-xs font-bold text-white animate-pulse">
<span className="w-2 h-2 rounded-full bg-white" />
{event.status}
</div>
)}
<div className="absolute bottom-3 left-3 right-3">
<h3 className="font-bold text-white mb-1">{event.title}</h3>
<div className="flex items-center gap-2 text-xs text-white/70">
<Clock size={14} />
{event.time}
<Users size={14} className="ml-2" />
{event.viewers}
</div>
</div>
</div>
<div className="p-4 space-y-3">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="text-center">
<div className="font-bold text-white">{event.teams[0]}</div>
<div className="text-xs text-white/50"></div>
</div>
<div className="text-primary font-bold">VS</div>
<div className="text-center">
<div className="font-bold text-white">{event.teams[1]}</div>
<div className="text-xs text-white/50"></div>
</div>
</div>
<div className="text-right">
<div className="flex items-center gap-1 text-yellow-500 text-sm font-bold">
<Trophy size={14} />
{event.prize}
</div>
<div className="text-xs text-white/50"></div>
</div>
</div>
<div className="flex items-center gap-2 text-xs text-white/50">
<MapPin size={14} />
{event.venue}
</div>
</div>
</Link>
))}
</div>
</div>
)
}

View File

@@ -109,7 +109,7 @@ export default function GuildPage() {
<div className="relative">
<div className="w-16 h-16 rounded-lg bg-muted relative overflow-hidden">
<Image
src={`/.jpg?height=100&width=100&query=${guild.logo}`}
src={`/guild-logo-${i}.jpg?height=100&width=100&query=${guild.logo}`}
alt={guild.name}
fill
className="object-cover"

View File

@@ -5,6 +5,7 @@ import { Search, MapPin, Filter, Star, ArrowLeft, ChevronDown } from "lucide-rea
import { useRouter } from "next/navigation"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import { toast } from "@/hooks/use-toast"
import Image from "next/image"
const hotels = [
{
@@ -16,6 +17,7 @@ const hotels = [
distance: "0.8km",
tags: ["RTX4090", "电竞椅", "24h餐饮"],
district: "西湖区/黄龙体育中心",
image: "esports hotel room neon lights gaming setup luxury",
},
{
id: 2,
@@ -26,6 +28,7 @@ const hotels = [
distance: "1.2km",
tags: ["五黑房", "投影", "免费停车"],
district: "拱墅区/万达广场",
image: "gaming apartment interior cozy modern pc setup",
},
{
id: 3,
@@ -36,6 +39,7 @@ const hotels = [
distance: "2.5km",
tags: ["情侣房", "PS5", "VR体验"],
district: "滨江区/网易园区",
image: "vr gaming room couple setup futuristic lighting",
},
]
@@ -49,6 +53,7 @@ const cafes = [
distance: "0.5km",
tags: ["3080显卡", "特权公馆", "现磨咖啡"],
district: "武林广场商圈",
image: "internet cafe luxury rows of computers modern design",
},
{
id: 102,
@@ -59,6 +64,7 @@ const cafes = [
distance: "1.8km",
tags: ["赛事级配置", "美女陪玩", "独立包间"],
district: "城西银泰",
image: "esports arena internet cafe private room",
},
]
@@ -131,99 +137,136 @@ export default function HotelPage() {
</div>
</div>
<TabsContent value="hotel" className="mt-0 px-4 py-2 space-y-2 min-h-[50vh]">
<TabsContent value="hotel" className="mt-0 px-4 py-2 space-y-3 min-h-[50vh]">
{/* Added Header for Recommendations */}
<div className="flex items-center gap-2 mb-2">
<span className="text-sm font-bold text-white/90"></span>
</div>
{hotels.map((hotel) => (
<div
key={hotel.id}
className="flex flex-col py-3 border-b border-white/5 last:border-0 cursor-pointer active:bg-white/5 transition-colors"
className="flex gap-3 py-3 border-b border-white/5 last:border-0 cursor-pointer active:bg-white/5 transition-colors"
onClick={() => handleBook(hotel.name)}
>
<div className="flex items-start justify-between mb-2">
<div className="flex-1">
<div className="flex items-center gap-2 mb-1">
<h3 className="font-bold text-base">{hotel.name}</h3>
{hotel.id === 1 && (
<span className="text-[10px] bg-red-500/20 text-red-400 px-1.5 py-0.5 rounded border border-red-500/30">
</span>
)}
{/* Added Image on the left */}
<div className="relative w-24 h-24 rounded-lg overflow-hidden shrink-0 bg-muted">
<Image
src={`/.jpg?key=hotel-${hotel.id}&height=200&width=200&query=${hotel.image}`}
alt={hotel.name}
fill
className="object-cover"
/>
{hotel.id === 1 && (
<div className="absolute top-0 left-0 bg-red-500 text-white text-[10px] px-1.5 py-0.5 rounded-br-lg font-bold">
</div>
)}
</div>
<div className="flex-1 flex flex-col justify-between min-w-0">
<div>
<div className="flex items-start justify-between mb-1">
<h3 className="font-bold text-base truncate pr-2">{hotel.name}</h3>
</div>
<div className="flex items-center gap-2 text-xs text-white/60 mb-1">
<div className="flex items-center text-yellow-400 font-bold">
<Star size={11} fill="currentColor" />
<div className="flex items-center text-orange-400 font-bold bg-orange-500/10 px-1 rounded">
<Star size={10} fill="currentColor" />
<span className="ml-0.5">{hotel.rating}</span>
</div>
<span className="text-white/30">|</span>
<span>{hotel.reviews}</span>
<span className="text-white/30">|</span>
<span className="text-primary">{hotel.distance}</span>
<span className="text-primary font-bold">{hotel.reviews}</span>
</div>
<div className="text-xs text-white/40 truncate mb-1">
{hotel.district} | {hotel.distance}
</div>
<div className="flex flex-wrap gap-1">
{hotel.tags.map((tag, i) => (
<span
key={i}
className="px-1.5 py-0.5 rounded text-[10px] border border-white/10 text-white/50 bg-white/5"
>
{tag}
</span>
))}
</div>
<div className="text-xs text-white/40 mb-2">{hotel.district}</div>
</div>
<div className="text-right">
<div className="flex items-baseline gap-0.5 text-red-500 mb-1">
<div className="flex items-end justify-between mt-1">
<div className="flex items-baseline gap-0.5 text-red-500">
<span className="text-xs font-bold">¥</span>
<span className="text-2xl font-bold leading-none">{hotel.price}</span>
<span className="text-xl font-bold leading-none">{hotel.price}</span>
<span className="text-xs text-white/50 ml-1 font-normal"></span>
</div>
<button className="px-3 py-1 bg-gradient-to-r from-amber-500 to-yellow-600 text-white text-xs rounded-md font-bold active:scale-95 transition-transform">
<button className="px-3 py-1 bg-primary text-white text-xs rounded-full font-bold active:scale-95 transition-transform">
</button>
</div>
</div>
<div className="flex flex-wrap gap-1.5">
{hotel.tags.map((tag, i) => (
<span
key={i}
className="px-2 py-0.5 rounded text-[11px] border border-white/10 text-white/50 bg-white/5"
>
{tag}
</span>
))}
</div>
</div>
))}
</TabsContent>
<TabsContent value="cafe" className="mt-0 px-4 py-2 space-y-2 min-h-[50vh]">
<TabsContent value="cafe" className="mt-0 px-4 py-2 space-y-3 min-h-[50vh]">
<div className="flex items-center gap-2 mb-2">
<span className="text-sm font-bold text-white/90"></span>
</div>
{cafes.map((cafe) => (
<div
key={cafe.id}
className="flex flex-col py-3 border-b border-white/5 last:border-0 cursor-pointer active:bg-white/5 transition-colors"
className="flex gap-3 py-3 border-b border-white/5 last:border-0 cursor-pointer active:bg-white/5 transition-colors"
onClick={() => handleBook(cafe.name)}
>
<div className="flex items-start justify-between mb-2">
<div className="flex-1">
<h3 className="font-bold text-base mb-1">{cafe.name}</h3>
{/* Added Image on the left */}
<div className="relative w-24 h-24 rounded-lg overflow-hidden shrink-0 bg-muted">
<Image
src={`/.jpg?key=cafe-${cafe.id}&height=200&width=200&query=${cafe.image}`}
alt={cafe.name}
fill
className="object-cover"
/>
</div>
<div className="flex-1 flex flex-col justify-between min-w-0">
<div>
<h3 className="font-bold text-base mb-1 truncate">{cafe.name}</h3>
<div className="flex items-center gap-2 text-xs text-white/60 mb-1">
<div className="flex items-center text-yellow-400 font-bold">
<Star size={11} fill="currentColor" />
<div className="flex items-center text-orange-400 font-bold bg-orange-500/10 px-1 rounded">
<Star size={10} fill="currentColor" />
<span className="ml-0.5">{cafe.rating}</span>
</div>
<span className="text-white/30">|</span>
<span> ¥{(cafe.price * 3).toFixed(0)}</span>
<span className="text-white/30">|</span>
<span className="text-primary">{cafe.distance}</span>
<span className="text-white/40"> ¥{(cafe.price * 3).toFixed(0)}</span>
</div>
<div className="text-xs text-white/40 truncate mb-1">
{cafe.district} | {cafe.distance}
</div>
<div className="flex flex-wrap gap-1">
{cafe.tags.map((tag, i) => (
<span
key={i}
className="text-[10px] px-1.5 py-0.5 rounded bg-primary/5 text-primary border border-primary/20"
>
{tag}
</span>
))}
</div>
<div className="text-xs text-white/40 mb-2">{cafe.district}</div>
</div>
<div className="text-right">
<div className="flex items-baseline gap-0.5 mb-1">
<div className="flex items-end justify-between mt-1">
<div className="flex items-baseline gap-0.5">
<span className="text-xs text-white/60">¥</span>
<span className="text-xl font-bold text-primary leading-none">{cafe.price}</span>
<span className="text-lg font-bold text-primary leading-none">{cafe.price}</span>
<span className="text-xs text-white/40">/</span>
</div>
<button className="px-3 py-1 bg-white/10 text-white text-xs rounded-full font-bold active:scale-95 transition-transform hover:bg-white/20">
</button>
</div>
</div>
<div className="flex gap-1.5">
{cafe.tags.map((tag, i) => (
<span
key={i}
className="text-[11px] px-2 py-0.5 rounded bg-primary/10 text-primary border border-primary/20"
>
{tag}
</span>
))}
</div>
</div>
))}
</TabsContent>

View File

@@ -2,13 +2,39 @@
import { useState } from "react"
import { useRouter } from "next/navigation"
import { ArrowLeft, Heart, Share2, Gift, Send, MoreHorizontal } from "lucide-react"
import { ArrowLeft, Heart, Share2, Gift, Send, MoreHorizontal, Users, MessageCircle } from "lucide-react"
import Image from "next/image"
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"
import { Button } from "@/components/ui/button"
import { toast } from "@/hooks/use-toast"
export default function LiveRoomPage({ params }: { params: { id: string } }) {
const router = useRouter()
const [comment, setComment] = useState("")
const [isFollowed, setIsFollowed] = useState(false)
const [showCommunity, setShowCommunity] = useState(false)
const players = [
{ id: "1", name: "GM-远洋", avatar: "/avatar-1.jpg", title: "国服第一盲僧", fans: "46K", partyMembers: 1205 },
{ id: "2", name: "卡若", avatar: "/avatar-2.jpg", title: "职业战队退役", fans: "32K", partyMembers: 856 },
{ id: "3", name: "小柠檬", avatar: "/avatar-3.jpg", title: "人皮话多", fans: "18K", partyMembers: 432 },
{ id: "4", name: "阿伟", avatar: "/avatar-4.jpg", title: "绝地求生刚枪王", fans: "25K", partyMembers: 678 },
{ id: "5", name: "七七", avatar: "/avatar-5.jpg", title: "甜美声优", fans: "52K", partyMembers: 1502 },
{ id: "6", name: "大魔王", avatar: "/avatar-6.jpg", title: "中单法王", fans: "38K", partyMembers: 945 },
]
const currentPlayer = players.find((p) => p.id === params.id) || players[0]
const handleJoinParty = () => {
toast({
title: "加入成功",
description: `已加入 ${currentPlayer.name} 的粉丝派对群`,
})
setShowCommunity(false)
setTimeout(() => {
router.push(`/chat/${params.id}`)
}, 1000)
}
return (
<div className="h-screen w-full bg-black relative overflow-hidden flex flex-col">
@@ -26,10 +52,10 @@ export default function LiveRoomPage({ params }: { params: { id: string } }) {
</button>
<div className="flex items-center gap-2">
<div className="w-8 h-8 rounded-full border border-white/20 overflow-hidden">
<Image src="/gamer-avatar.jpg" alt="Avatar" width={32} height={32} />
<Image src={currentPlayer.avatar || "/placeholder.svg"} alt="Avatar" width={32} height={32} />
</div>
<div>
<h3 className="text-xs font-bold text-white">GM-</h3>
<h3 className="text-xs font-bold text-white">{currentPlayer.name}</h3>
<p className="text-[10px] text-white/70">12.5w观看</p>
</div>
</div>
@@ -45,12 +71,7 @@ export default function LiveRoomPage({ params }: { params: { id: string } }) {
<div className="flex -space-x-2">
{[1, 2, 3].map((i) => (
<div key={i} className="w-8 h-8 rounded-full border border-white/10 overflow-hidden">
<Image
src={`/avatar-.jpg?height=32&width=32&query=avatar_${i}`}
alt="Viewer"
width={32}
height={32}
/>
<Image src={`/avatar-${i}.jpg`} alt="Viewer" width={32} height={32} />
</div>
))}
</div>
@@ -60,6 +81,85 @@ export default function LiveRoomPage({ params }: { params: { id: string } }) {
</div>
</div>
<Dialog open={showCommunity} onOpenChange={setShowCommunity}>
<DialogTrigger asChild>
<button className="absolute right-4 top-1/3 z-30 flex flex-col items-center gap-1 bg-gradient-to-b from-primary/80 to-purple-600/80 backdrop-blur-md px-3 py-4 rounded-2xl border border-white/20 shadow-lg shadow-primary/30 hover:scale-105 transition-transform">
<Users size={24} className="text-white" />
<span className="text-[10px] text-white font-bold"></span>
<span className="text-[8px] text-white/70">{currentPlayer.partyMembers}</span>
</button>
</DialogTrigger>
<DialogContent className="bg-zinc-900 border-white/10 text-white max-w-sm">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<MessageCircle className="text-primary" size={20} />
{currentPlayer.name}
</DialogTitle>
</DialogHeader>
<div className="space-y-4 py-4">
{/* 主播信息卡片 */}
<div className="flex items-center gap-4 p-4 bg-white/5 rounded-xl border border-white/10">
<div className="w-16 h-16 rounded-full overflow-hidden border-2 border-primary">
<Image
src={currentPlayer.avatar || "/placeholder.svg"}
alt={currentPlayer.name}
width={64}
height={64}
className="object-cover"
/>
</div>
<div className="flex-1">
<h4 className="font-bold text-lg">{currentPlayer.name}</h4>
<p className="text-xs text-white/60">{currentPlayer.title}</p>
<div className="flex items-center gap-3 mt-2 text-xs text-white/50">
<span className="flex items-center gap-1">
<Heart size={12} className="text-pink-500" />
{currentPlayer.fans}
</span>
<span className="flex items-center gap-1">
<Users size={12} className="text-blue-500" />
{currentPlayer.partyMembers}
</span>
</div>
</div>
</div>
{/* 群介绍 */}
<div className="p-4 bg-gradient-to-r from-primary/10 to-purple-500/10 rounded-xl border border-primary/20">
<h5 className="font-bold text-sm mb-2 flex items-center gap-2">
<span className="w-2 h-2 rounded-full bg-green-500 animate-pulse" />
</h5>
<p className="text-xs text-white/70 leading-relaxed">
{currentPlayer.name}
</p>
</div>
{/* 群特权 */}
<div className="grid grid-cols-3 gap-2">
{[
{ icon: "🎁", text: "专属礼物" },
{ icon: "🎮", text: "组队开黑" },
{ icon: "💬", text: "语音聊天" },
].map((item, i) => (
<div key={i} className="flex flex-col items-center gap-1 p-3 bg-white/5 rounded-lg">
<span className="text-xl">{item.icon}</span>
<span className="text-[10px] text-white/70">{item.text}</span>
</div>
))}
</div>
<Button
onClick={handleJoinParty}
className="w-full bg-gradient-to-r from-primary to-purple-600 hover:from-primary/90 hover:to-purple-600/90 font-bold"
>
<Users size={16} className="mr-2" />
</Button>
</div>
</DialogContent>
</Dialog>
{/* Main Content Spacer */}
<div className="flex-1" />

281
app/mall/companion/page.tsx Normal file
View File

@@ -0,0 +1,281 @@
"use client"
import { useState } from "react"
import { useRouter } from "next/navigation"
import Image from "next/image"
import { Star, Play, Mic, Video, Gamepad2, GraduationCap } from "lucide-react"
import { Button } from "@/components/ui/button"
import { toast } from "@/hooks/use-toast"
export default function CompanionPage() {
const router = useRouter()
const [activeTab, setActiveTab] = useState("play") // play | learn
const games = [
{ name: "全部", id: "all" },
{ name: "英雄联盟", id: "lol" },
{ name: "王者荣耀", id: "hok" },
{ name: "无畏契约", id: "val" },
{ name: "和平精英", id: "pubg" },
{ name: "原神", id: "genshin" },
]
const [activeGame, setActiveGame] = useState("all")
const companions = [
{
id: 1,
name: "GM-远洋",
title: "国服第一盲僧",
price: 50,
unit: "局",
image: "/avatar-1.jpg",
tags: ["技术流", "风趣", "职业退役"],
game: "lol",
rating: 5.0,
orders: 1205,
voice: true,
video: true,
},
{
id: 2,
name: "七七",
title: "甜美声优",
price: 40,
unit: "小时",
image: "/avatar-5.jpg",
tags: ["声优", "聊天", "辅助"],
game: "hok",
rating: 4.9,
orders: 890,
voice: true,
video: false,
},
{
id: 3,
name: "卡若",
title: "前职业选手",
price: 80,
unit: "局",
image: "/avatar-2.jpg",
tags: ["包赢", "教学", "高分局"],
game: "lol",
rating: 5.0,
orders: 3400,
voice: true,
video: true,
},
{
id: 4,
name: "小柠檬",
title: "人皮话多",
price: 30,
unit: "局",
image: "/avatar-3.jpg",
tags: ["娱乐", "开心", "相声"],
game: "val",
rating: 4.8,
orders: 560,
voice: true,
video: false,
},
{
id: 5,
name: "大魔王",
title: "中单法王",
price: 100,
unit: "局",
image: "/avatar-6.jpg",
tags: ["技术教学", "上分", "严肃"],
game: "lol",
rating: 5.0,
orders: 8800,
voice: true,
video: true,
},
{
id: 6,
name: "阿伟",
title: "绝地刚枪王",
price: 60,
unit: "小时",
image: "/avatar-4.jpg",
tags: ["带妹", "吃鸡", "指挥"],
game: "pubg",
rating: 4.9,
orders: 1100,
voice: true,
video: false,
},
]
const courses = [
{
id: 1,
title: "中单瑞兹顶级教学",
author: "Faker",
price: 199,
image: "/course-1.jpg",
students: 12000,
game: "lol",
},
{
id: 2,
title: "下路对线细节全解",
author: "Uzi",
price: 199,
image: "/course-2.jpg",
students: 35000,
game: "lol",
},
{
id: 3,
title: "上单压制力养成",
author: "TheShy",
price: 159,
image: "/course-3.jpg",
students: 8900,
game: "lol",
},
{
id: 4,
title: "瓦罗兰特特工教学",
author: "康康",
price: 99,
image: "/course-4.jpg",
students: 4500,
game: "val",
},
]
return (
<div className="min-h-screen pb-24 bg-background">
<header className="sticky top-0 z-40 glass px-4 py-3 flex items-center gap-3">
<button onClick={() => router.back()} className="text-white/70 hover:text-white">
</button>
<h1 className="font-bold text-lg"> & </h1>
</header>
{/* Tabs */}
<div className="px-4 mt-2">
<div className="bg-white/5 p-1 rounded-xl flex">
<button
className={`flex-1 py-2 rounded-lg text-sm font-bold flex items-center justify-center gap-2 transition-all ${activeTab === "play" ? "bg-primary text-black shadow-lg shadow-primary/20" : "text-white/50 hover:text-white"}`}
onClick={() => setActiveTab("play")}
>
<Gamepad2 size={16} />
</button>
<button
className={`flex-1 py-2 rounded-lg text-sm font-bold flex items-center justify-center gap-2 transition-all ${activeTab === "learn" ? "bg-secondary text-white shadow-lg shadow-secondary/20" : "text-white/50 hover:text-white"}`}
onClick={() => setActiveTab("learn")}
>
<GraduationCap size={16} />
</button>
</div>
</div>
{/* Game Filter */}
<div className="px-4 mt-4 overflow-x-auto no-scrollbar pb-2">
<div className="flex gap-2">
{games.map((g) => (
<button
key={g.id}
onClick={() => setActiveGame(g.id)}
className={`px-4 py-1.5 rounded-full text-xs font-bold whitespace-nowrap transition-colors ${activeGame === g.id ? "bg-white text-black" : "bg-white/5 text-white/50 hover:bg-white/10"}`}
>
{g.name}
</button>
))}
</div>
</div>
{/* Content */}
<div className="px-4 mt-2 space-y-4">
{activeTab === "play" ? (
<div className="grid gap-3">
{companions
.filter((c) => activeGame === "all" || c.game === activeGame)
.map((c) => (
<div
key={c.id}
className="glass-card p-3 rounded-xl flex gap-3 cursor-pointer hover:bg-white/5 transition-colors group"
onClick={() => router.push(`/live/${c.id}`)}
>
<div className="relative w-24 h-24 rounded-lg overflow-hidden shrink-0">
<Image src={c.image || "/placeholder.svg"} alt={c.name} fill className="object-cover" />
<div className="absolute top-1 left-1 bg-black/60 backdrop-blur-sm px-1.5 py-0.5 rounded flex items-center gap-1 text-[10px] text-white font-bold">
<Star size={8} className="text-yellow-500 fill-yellow-500" />
{c.rating}
</div>
</div>
<div className="flex-1 flex flex-col justify-between py-0.5">
<div>
<div className="flex items-center justify-between">
<h3 className="font-bold text-sm flex items-center gap-1">
{c.name}
{c.voice && <Mic size={10} className="text-green-400" />}
{c.video && <Video size={10} className="text-blue-400" />}
</h3>
<span className="text-[10px] text-white/40">{c.orders}</span>
</div>
<p className="text-xs text-primary font-medium mt-0.5">{c.title}</p>
<div className="flex flex-wrap gap-1 mt-1.5">
{c.tags.map((tag, i) => (
<span key={i} className="text-[10px] bg-white/5 px-1.5 py-0.5 rounded text-white/50">
{tag}
</span>
))}
</div>
</div>
<div className="flex items-center justify-between mt-2">
<div className="flex items-end gap-1">
<span className="text-lg font-bold text-secondary">{c.price}</span>
<span className="text-xs text-white/50 mb-1">/{c.unit}</span>
</div>
<Button
size="sm"
className="h-7 text-xs px-3 font-bold bg-white/10 hover:bg-primary hover:text-black"
>
</Button>
</div>
</div>
</div>
))}
</div>
) : (
<div className="grid gap-4">
{courses
.filter((c) => activeGame === "all" || c.game === activeGame)
.map((c) => (
<div
key={c.id}
className="glass-card p-0 rounded-xl overflow-hidden cursor-pointer group"
onClick={() => toast({ title: "购买成功", description: "课程已添加至学习中心" })}
>
<div className="relative aspect-video">
<Image src={c.image || "/placeholder.svg"} alt={c.title} fill className="object-cover" />
<div className="absolute inset-0 bg-black/30 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity">
<Play size={40} className="text-white fill-white" />
</div>
<div className="absolute bottom-2 left-2 px-2 py-1 bg-black/60 backdrop-blur-sm rounded text-[10px] font-bold">
{c.author}
</div>
</div>
<div className="p-3">
<h3 className="font-bold text-sm mb-1">{c.title}</h3>
<div className="flex items-center justify-between">
<span className="text-xs text-white/50">{c.students} </span>
<span className="text-primary font-bold">¥{c.price}</span>
</div>
</div>
</div>
))}
</div>
)}
</div>
</div>
)
}

58
app/mall/courses/page.tsx Normal file
View File

@@ -0,0 +1,58 @@
"use client"
import { useRouter } from "next/navigation"
import Image from "next/image"
import { Play, Users } from "lucide-react"
import { Button } from "@/components/ui/button"
import { toast } from "@/hooks/use-toast"
export default function CoursesPage() {
const router = useRouter()
const courses = [
{ id: 1, title: "Faker 中单瑞兹教学", author: "Faker", price: 199, image: "/course-1.jpg", students: 12000 },
{ id: 2, title: "Uzi 下路对线细节", author: "Uzi", price: 199, image: "/course-2.jpg", students: 35000 },
{ id: 3, title: "TheShy 上单压制力", author: "TheShy", price: 159, image: "/course-3.jpg", students: 8900 },
{ id: 4, title: "10分钟学会云顶吃鸡", author: "云顶之弈", price: 49, image: "/course-4.jpg", students: 5000 },
]
return (
<div className="min-h-screen pb-24 bg-background">
<header className="sticky top-0 z-40 glass px-4 py-3 flex items-center gap-3">
<button onClick={() => router.back()} className="text-white/70">
</button>
<h1 className="font-bold text-lg"></h1>
</header>
<div className="p-4 space-y-4">
{courses.map((c) => (
<div
key={c.id}
className="glass-card p-3 rounded-xl flex gap-3 cursor-pointer hover:bg-white/10"
onClick={() => toast({ title: "购买成功", description: "课程已添加至学习中心" })}
>
<div className="relative w-32 aspect-video rounded-lg overflow-hidden shrink-0">
<Image src={c.image || "/placeholder.svg"} alt={c.title} fill className="object-cover" />
<div className="absolute inset-0 flex items-center justify-center bg-black/30">
<Play size={24} className="text-white/80" />
</div>
</div>
<div className="flex-1 flex flex-col justify-between">
<h3 className="font-bold text-sm">{c.title}</h3>
<div className="flex items-center gap-2 text-xs text-white/50">
<span className="bg-white/10 px-1.5 py-0.5 rounded text-white/70">{c.author}</span>
<span className="flex items-center gap-1">
<Users size={10} /> {c.students}
</span>
</div>
<div className="flex items-center justify-between mt-1">
<span className="text-primary font-bold">¥{c.price}</span>
<Button size="sm" className="h-6 text-[10px]">
</Button>
</div>
</div>
</div>
))}
</div>
</div>
)
}

56
app/mall/gear/page.tsx Normal file
View File

@@ -0,0 +1,56 @@
"use client"
import { useRouter } from "next/navigation"
import Image from "next/image"
import { Button } from "@/components/ui/button"
import { toast } from "@/hooks/use-toast"
export default function GearPage() {
const router = useRouter()
const products = [
{ id: 1, name: "罗技 G Pro X Superlight", price: 899, image: "/gear-mouse.jpg", tag: "职业首选" },
{ id: 2, name: "雷蛇 猎魂光蛛 V3", price: 1299, image: "/gear-keyboard.jpg", tag: "光轴科技" },
{ id: 3, name: "卓威 XL2546K", price: 3499, image: "/gear-monitor.jpg", tag: "360Hz" },
{ id: 4, name: "HyperX Cloud II", price: 599, image: "/gear-headset.jpg", tag: "7.1声道" },
]
return (
<div className="min-h-screen pb-24 bg-background">
<header className="sticky top-0 z-40 glass px-4 py-3 flex items-center gap-3">
<button onClick={() => router.back()} className="text-white/70">
</button>
<h1 className="font-bold text-lg"></h1>
</header>
<div className="p-4 grid grid-cols-2 gap-4">
{products.map((p) => (
<div
key={p.id}
className="glass-card rounded-xl overflow-hidden group cursor-pointer"
onClick={() => toast({ title: "加入购物车", description: p.name })}
>
<div className="relative aspect-square bg-white">
<Image
src={p.image || "/placeholder.svg"}
alt={p.name}
fill
className="object-contain p-4 group-hover:scale-105 transition-transform"
/>
<div className="absolute top-2 left-2 bg-red-500 text-white text-[10px] px-2 py-0.5 rounded font-bold">
{p.tag}
</div>
</div>
<div className="p-3">
<h3 className="font-bold text-sm text-white truncate mb-1">{p.name}</h3>
<div className="flex items-center justify-between">
<span className="text-primary font-bold">¥{p.price}</span>
<Button size="sm" className="h-6 text-[10px] px-2">
</Button>
</div>
</div>
</div>
))}
</div>
</div>
)
}

View File

@@ -9,20 +9,24 @@ import {
Gift,
Coins,
BookOpen,
GraduationCap,
Monitor,
Hotel,
Coffee,
Trophy,
User,
Keyboard,
Wallet,
Gamepad2,
Gem,
} from "lucide-react"
import { Button } from "@/components/ui/button"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import { useAppContext } from "@/components/providers/app-provider"
import Link from "next/link" // Import Link
import Link from "next/link"
import { useRouter } from "next/navigation"
export default function MallPage() {
const { wallet, pay, recharge } = useAppContext()
const { wallet, pay } = useAppContext()
const router = useRouter()
const handleBuy = (price: number, currency: "diamonds" | "coins", name: string, type: "product" | "service") => {
pay(price, currency, name, type)
@@ -32,9 +36,12 @@ export default function MallPage() {
<div className="min-h-screen pb-24 bg-background">
{/* Header */}
<header className="sticky top-0 z-40 glass px-4 py-3 flex items-center justify-between">
<h1 className="font-bold text-lg"></h1>
<h1 className="font-bold text-lg"></h1>
<div className="flex items-center gap-3">
<div className="flex items-center gap-2 px-3 py-1 rounded-full bg-white/5 border border-white/10">
<div
className="flex items-center gap-2 px-3 py-1 rounded-full bg-white/5 border border-white/10"
onClick={() => router.push("/recharge")}
>
<Diamond size={14} className="text-primary" />
<span className="text-xs font-bold">{wallet.diamonds}</span>
</div>
@@ -47,25 +54,24 @@ export default function MallPage() {
{/* Banner */}
<div className="px-4 mt-4">
<div className="w-full h-32 rounded-2xl bg-gradient-to-r from-purple-600 to-blue-600 relative overflow-hidden flex items-center px-6">
<div
className="w-full h-32 rounded-2xl bg-gradient-to-r from-purple-600 to-blue-600 relative overflow-hidden flex items-center px-6 shadow-lg shadow-purple-500/20 cursor-pointer"
onClick={() => router.push("/recharge")}
>
<div className="relative z-10">
<h2 className="text-xl font-bold text-white mb-1"></h2>
<p className="text-xs text-white/80 mb-3"> </p>
<Button
size="sm"
className="bg-white text-purple-600 hover:bg-white/90 h-7 text-xs font-bold"
onClick={() => recharge(100)}
>
<Button size="sm" className="bg-white text-purple-600 hover:bg-white/90 h-7 text-xs font-bold">
</Button>
</div>
<div className="absolute right-0 bottom-0 w-32 h-32 opacity-50">
<Diamond className="w-full h-full text-white/20 rotate-12 translate-x-8 translate-y-8" />
<Gem className="w-full h-full text-white/20 rotate-12 translate-x-8 translate-y-8" />
</div>
</div>
</div>
{/* Top Categories - Expanded as requested */}
{/* Top Categories - 8 Items */}
<div className="px-4 mt-6">
<h3 className="text-sm font-bold mb-3 flex items-center gap-2">
<Sparkles size={16} className="text-yellow-400" />
@@ -74,22 +80,22 @@ export default function MallPage() {
<div className="grid grid-cols-4 gap-3">
{[
{
name: "知识付费",
icon: BookOpen,
name: "明星陪玩",
icon: Gamepad2,
color: "text-blue-400",
bg: "bg-blue-500/10",
href: "/my-courses",
href: "/mall/companion?type=play",
},
{
name: "陪玩课程",
icon: GraduationCap,
name: "大师课程",
icon: BookOpen,
color: "text-pink-400",
bg: "bg-pink-500/10",
href: "/creator/apply",
href: "/mall/companion?type=course",
},
{
name: "电竞外设",
icon: Monitor,
icon: Keyboard,
color: "text-purple-400",
bg: "bg-purple-500/10",
href: "/mall/gear",
@@ -102,7 +108,7 @@ export default function MallPage() {
href: "/hotel",
},
{
name: "附近网",
name: "附近网",
icon: Coffee,
color: "text-cyan-400",
bg: "bg-cyan-500/10",
@@ -120,26 +126,26 @@ export default function MallPage() {
icon: User,
color: "text-green-400",
bg: "bg-green-500/10",
href: "/services/account",
href: "/services/account?tab=buy",
},
{
name: "更多服务",
icon: Sparkles,
color: "text-white/40",
bg: "bg-white/5",
href: "#",
name: "账号金融",
icon: Wallet,
color: "text-indigo-400",
bg: "bg-indigo-500/10",
href: "/services/account?tab=finance",
},
].map((item, i) => (
<Link
key={i}
href={item.href}
className="glass-card p-3 rounded-xl flex flex-col items-center gap-2 cursor-pointer hover:bg-white/10 transition-colors"
className="glass-card p-3 rounded-xl flex flex-col items-center gap-2 cursor-pointer hover:bg-white/10 transition-colors border border-white/5 hover:border-white/10"
>
<div className={`w-10 h-10 rounded-full ${item.bg} flex items-center justify-center`}>
<item.icon className={item.color} size={20} />
</div>
<div className="text-center">
<h3 className="text-[10px] font-bold whitespace-nowrap">{item.name}</h3>
<h3 className="text-[10px] font-bold whitespace-nowrap text-white/90">{item.name}</h3>
</div>
</Link>
))}
@@ -149,25 +155,25 @@ export default function MallPage() {
<Tabs defaultValue="dress" className="mt-6">
<div className="px-4">
<TabsList className="w-full bg-white/5 p-1 rounded-xl">
<TabsTrigger value="dress" className="flex-1 text-xs">
<TabsTrigger value="dress" className="flex-1 text-xs font-bold">
</TabsTrigger>
<TabsTrigger value="gift" className="flex-1 text-xs">
<TabsTrigger value="gift" className="flex-1 text-xs font-bold">
</TabsTrigger>
<TabsTrigger value="exchange" className="flex-1 text-xs">
<TabsTrigger value="exchange" className="flex-1 text-xs font-bold">
</TabsTrigger>
</TabsList>
</div>
<TabsContent value="dress" className="mt-4">
{/* Categories */}
<div className="flex items-center gap-4 px-4 overflow-x-auto no-scrollbar mb-4">
{["推荐", "头像", "聊天气泡", "入场特效", "座驾"].map((cat, i) => (
<div className="flex items-center gap-3 px-4 overflow-x-auto no-scrollbar mb-4">
{["热门推荐", "限定头像", "动态边框", "入场特效", "聊天气泡"].map((cat, i) => (
<button
key={i}
className={`whitespace-nowrap text-xs font-medium px-3 py-1.5 rounded-full transition-colors ${i === 0 ? "bg-primary text-white" : "bg-white/5 text-white/50"}`}
className={`whitespace-nowrap text-xs font-bold px-3 py-1.5 rounded-full transition-colors ${i === 0 ? "bg-primary text-black" : "bg-white/5 text-white/50"}`}
>
{cat}
</button>
@@ -177,30 +183,69 @@ export default function MallPage() {
{/* Grid */}
<div className="px-4 grid grid-cols-2 gap-4">
{[
{ name: "巫瞻猫影", price: 99, type: "frame", color: "from-purple-500/20 to-blue-500/20" },
{ name: "椰树风情", price: 128, type: "frame", color: "from-green-500/20 to-yellow-500/20" },
{ name: "黑白天使", price: 299, type: "wing", color: "from-gray-500/20 to-white/20" },
{ name: "敦煌飞天", price: 520, type: "ring", color: "from-orange-500/20 to-red-500/20" },
{ name: "恶魔之眼", price: 199, type: "frame", color: "from-red-900/20 to-black/20" },
{ name: "小熊猫", price: 68, type: "frame", color: "from-orange-300/20 to-yellow-300/20" },
{
name: "巫瞻猫影",
price: 99,
type: "frame",
color: "from-purple-500/20 to-blue-500/20",
query: "Esports avatar frame with cat and witch theme, purple and blue gradients, mystical symbols",
},
{
name: "椰树风情",
price: 128,
type: "frame",
color: "from-green-500/20 to-yellow-500/20",
query: "Tropical esports avatar frame with palm trees and summer vibes, green and yellow",
},
{
name: "黑白天使",
price: 299,
type: "wing",
color: "from-gray-500/20 to-white/20",
query: "Esports angel wings avatar decoration, black and white contrasting feathers, divine effect",
},
{
name: "敦煌飞天",
price: 520,
type: "ring",
color: "from-orange-500/20 to-red-500/20",
query: "Chinese Dunhuang flying apsara themed avatar ring, orange and red, traditional art style",
},
{
name: "恶魔之眼",
price: 199,
type: "frame",
color: "from-red-900/20 to-black/20",
query: "Dark esports avatar frame with demonic red eye motif, black and red, ominous vibe",
},
{
name: "小熊猫",
price: 68,
type: "frame",
color: "from-orange-300/20 to-yellow-300/20",
query: "Cute esports red panda avatar frame, orange and yellow, playful design",
},
].map((item, i) => (
<div
key={i}
className="glass-card rounded-xl p-4 flex flex-col items-center gap-3 group relative overflow-hidden"
className="glass-card rounded-xl p-4 flex flex-col items-center gap-3 group relative overflow-hidden border border-white/5 hover:border-primary/30"
>
<div
className={`absolute inset-0 bg-gradient-to-br ${item.color} opacity-0 group-hover:opacity-100 transition-opacity duration-500`}
/>
<div className="relative w-20 h-20 flex items-center justify-center">
<div className="w-16 h-16 rounded-full border-2 border-dashed border-white/20 flex items-center justify-center relative z-10">
<Sparkles className="text-white/20" />
</div>
<div className="absolute inset-0 rounded-full border-4 border-primary/30 blur-md animate-pulse-slow" />
{/* Placeholder for local image if we had one, otherwise Sparkles */}
<img
src={`/.jpg?key=w3tzs&height=80&width=80&query=${encodeURIComponent(item.query)}`}
alt={item.name}
className="w-16 h-16 object-contain group-hover:scale-110 transition-transform"
/>
<div className="absolute inset-0 rounded-full border-4 border-primary/30 blur-md animate-pulse-slow opacity-0 group-hover:opacity-100 transition-opacity" />
</div>
<div className="text-center relative z-10">
<h3 className="font-bold text-sm mb-1">{item.name}</h3>
<h3 className="font-bold text-sm mb-1 text-white/90">{item.name}</h3>
<div className="flex items-center justify-center gap-1 text-primary">
<Diamond size={12} />
<span className="text-xs font-bold">{item.price}</span>
@@ -219,6 +264,7 @@ export default function MallPage() {
</TabsContent>
<TabsContent value="gift" className="mt-4 px-4">
{/* Gift Content - Simplified */}
<div className="grid grid-cols-3 gap-3">
{[
{ name: "棒棒糖", price: 10, icon: "🍭" },
@@ -230,10 +276,10 @@ export default function MallPage() {
].map((item, i) => (
<div
key={i}
className="glass-card rounded-xl p-3 flex flex-col items-center gap-2 hover:bg-white/10 cursor-pointer transition-colors"
className="glass-card rounded-xl p-3 flex flex-col items-center gap-2 hover:bg-white/10 cursor-pointer transition-colors border border-white/5"
onClick={() => handleBuy(item.price, "diamonds", item.name, "product")}
>
<div className="text-3xl">{item.icon}</div>
<div className="text-3xl filter drop-shadow-lg">{item.icon}</div>
<div className="text-center">
<h3 className="text-xs font-bold">{item.name}</h3>
<div className="flex items-center justify-center gap-1 text-primary mt-1">
@@ -247,6 +293,7 @@ export default function MallPage() {
</TabsContent>
<TabsContent value="exchange" className="mt-4 px-4">
{/* Exchange Content */}
<div className="grid grid-cols-2 gap-4">
{[
{ name: "10元话费券", price: 1000, icon: Gift },
@@ -254,7 +301,7 @@ export default function MallPage() {
{ name: "改名卡", price: 2000, icon: Crown },
{ name: "双倍经验卡", price: 500, icon: Zap },
].map((item, i) => (
<div key={i} className="glass-card rounded-xl p-4 flex flex-col items-center gap-3">
<div key={i} className="glass-card rounded-xl p-4 flex flex-col items-center gap-3 border border-white/5">
<div className="w-12 h-12 rounded-full bg-yellow-500/10 flex items-center justify-center">
<item.icon className="text-yellow-500" size={24} />
</div>

View File

@@ -1,10 +1,10 @@
"use client" // Convert to client component
import { Trophy, Search, Heart, MessageCircle, Share2, RefreshCcw } from "lucide-react"
import { Trophy, Search, Heart, MessageCircle, Share2 } from "lucide-react"
import Image from "next/image"
import Link from "next/link"
import { toast } from "@/hooks/use-toast" // Import toast
import { useState } from "react" // Added state
import { useRouter } from "next/navigation" // Added useRouter
const liveStreams = [
{
@@ -49,9 +49,9 @@ const moments = [
{
id: 1,
user: "GM-远洋",
avatar: "/gamer-girl.jpg",
avatar: "gamer_girl_avatar_cute", // Fixed hardcoded path to keyword
desc: "可惜不是你喜欢的甜妹音 😷 #电竞少女 #英雄联盟 #日常",
video: "/esports-streaming-vertical.jpg",
video: "esports_streaming_vertical_setup", // Fixed path
likes: "1.2w",
comments: 458,
isLive: true,
@@ -61,9 +61,9 @@ const moments = [
{
id: 2,
user: "卡若",
avatar: "/pro-gamer-male.jpg",
avatar: "pro_gamer_male_avatar", // Fixed hardcoded path
desc: "中单刺客教学,这波操作你学会了吗? #英雄联盟 #教学",
video: "/lol-gameplay-highlight.jpg",
video: "lol_gameplay_highlight_screen", // Fixed path
likes: "8.5k",
comments: 230,
isLive: false,
@@ -73,9 +73,9 @@ const moments = [
{
id: 3,
user: "魔兽老张",
avatar: "/wow-player.jpg",
avatar: "wow_orc_player_avatar", // Fixed hardcoded path
desc: "ICC 25人H 教授打法细节讲解 #魔兽世界 #WLK",
video: "/placeholder.svg?height=600&width=400",
video: "wow_raid_boss_fight", // Fixed path
likes: "3.2k",
comments: 120,
isLive: false,
@@ -83,8 +83,26 @@ const moments = [
},
]
const mixedFeed = [
...moments.map((m) => ({ ...m, type: "video" })),
...liveStreams.map((l) => ({
id: l.id + 1000,
user: l.user,
avatar: l.avatar,
desc: l.title,
video: l.cover, // This will be used for the background image
likes: l.viewers,
comments: 99,
isLive: true,
type: "live",
roomId: l.id, // Added roomId for navigation
})),
].sort(() => Math.random() - 0.5)
export default function MomentsPage() {
const [activeTab, setActiveTab] = useState("live")
const router = useRouter() // Initialize router
const [activeTab, setActiveTab] = useState("recommend") // Changed default to recommend
const handleLike = () => toast({ title: "点赞成功", description: "已添加到我喜欢的视频" })
const handleShare = () => toast({ title: "分享成功", description: "链接已复制到剪贴板" })
const handleGift = () => toast({ title: "礼物发送成功", description: "主播已收到您的心意" })
@@ -95,8 +113,8 @@ export default function MomentsPage() {
return (
<div className="h-screen bg-black text-white pb-20 flex flex-col">
{/* Top Navigation */}
<div className="fixed top-0 left-0 right-0 z-30 pt-safe px-4 py-3 bg-gradient-to-b from-black/80 to-transparent">
<div className="flex items-center justify-center gap-6 text-lg font-bold shadow-sm">
<div className="fixed top-0 left-0 right-0 z-30 pt-safe px-4 py-3 bg-gradient-to-b from-black/80 to-transparent pointer-events-none">
<div className="flex items-center justify-center gap-6 text-lg font-bold shadow-sm pointer-events-auto">
<button
onClick={() => setActiveTab("follow")}
className={`${activeTab === "follow" ? "text-white scale-110" : "text-white/50"} transition-all`}
@@ -104,129 +122,57 @@ export default function MomentsPage() {
</button>
<button
onClick={() => setActiveTab("live")}
className={`${activeTab === "live" ? "text-white scale-110" : "text-white/50"} transition-all`}
onClick={() => setActiveTab("recommend")} // Merged Live & Video into Recommend
className={`${activeTab === "recommend" ? "text-white scale-110" : "text-white/50"} transition-all`}
>
</button>
<button
onClick={() => setActiveTab("video")}
className={`${activeTab === "video" ? "text-white scale-110" : "text-white/50"} transition-all`}
>
</button>
</div>
<Search className="absolute right-4 top-4 text-white/80 w-6 h-6" />
<Search className="absolute right-4 top-4 text-white/80 w-6 h-6 pointer-events-auto" />
</div>
{/* Main Content Area - Scroll Snap */}
<div className="flex-1 overflow-y-auto snap-y snap-mandatory no-scrollbar relative">
{activeTab === "live" && (
<div className="min-h-full pb-20">
<div className="flex items-center justify-center py-4 text-white/30 gap-2 text-xs">
<RefreshCcw size={12} />
<span></span>
</div>
<div className="grid grid-cols-2 gap-1 p-1">
{liveStreams.map((stream) => (
<Link
href={`/live/${stream.id}`}
key={stream.id}
className="relative aspect-[9/16] rounded-xl overflow-hidden group bg-zinc-900"
>
<Image
src={`/.jpg?key=hs14o&height=640&width=360&query=${stream.cover}`}
alt={stream.title}
fill
className="object-cover transition-transform duration-700 group-hover:scale-110"
/>
<div className="absolute inset-0 bg-gradient-to-b from-transparent via-transparent to-black/80" />
{/* Live Badge */}
<div className="absolute top-2 left-2 px-2 py-0.5 rounded-full bg-primary/90 backdrop-blur-sm flex items-center gap-1 border border-white/10">
<div className="w-1.5 h-1.5 rounded-full bg-white animate-pulse" />
<span className="text-[10px] font-bold">LIVE</span>
</div>
<div className="absolute top-2 right-2 text-[10px] bg-black/40 px-1.5 py-0.5 rounded text-white/80 backdrop-blur-sm">
{stream.location}
</div>
<div className="absolute bottom-0 left-0 w-full p-3">
<div className="flex items-center gap-2 mb-2">
<div className="w-6 h-6 rounded-full border border-white/50 overflow-hidden relative">
<Image
src={`/.jpg?key=4ej4r&height=50&width=50&query=${stream.avatar}`}
alt={stream.user}
fill
className="object-cover"
/>
</div>
<span className="text-xs font-bold truncate">{stream.user}</span>
</div>
<h3 className="text-sm leading-tight line-clamp-2 mb-1 text-white/90">{stream.title}</h3>
<span className="text-[10px] text-primary">{stream.viewers}</span>
</div>
</Link>
))}
{/* Repeat specifically to show scrolling content */}
{liveStreams.map((stream) => (
<Link
href={`/live/${stream.id}`}
key={`repeat-${stream.id}`}
className="relative aspect-[9/16] rounded-xl overflow-hidden group bg-zinc-900"
>
<Image
src={`/.jpg?key=u09oe&height=640&width=360&query=${stream.cover} 2`}
alt={stream.title}
fill
className="object-cover transition-transform duration-700 group-hover:scale-110"
/>
<div className="absolute inset-0 bg-gradient-to-b from-transparent via-transparent to-black/80" />
<div className="absolute top-2 left-2 px-2 py-0.5 rounded-full bg-primary/90 backdrop-blur-sm flex items-center gap-1 border border-white/10">
<div className="w-1.5 h-1.5 rounded-full bg-white animate-pulse" />
<span className="text-[10px] font-bold">LIVE</span>
</div>
<div className="absolute bottom-0 left-0 w-full p-3">
<div className="flex items-center gap-2 mb-2">
<div className="w-6 h-6 rounded-full border border-white/50 overflow-hidden relative">
<Image
src={`/.jpg?key=t53r6&height=50&width=50&query=${stream.avatar}`}
alt={stream.user}
fill
className="object-cover"
/>
</div>
<span className="text-xs font-bold truncate">{stream.user}</span>
</div>
<h3 className="text-sm leading-tight line-clamp-2 mb-1 text-white/90">{stream.title}</h3>
<span className="text-[10px] text-primary">{stream.viewers}</span>
</div>
</Link>
))}
</div>
</div>
)}
{activeTab === "video" && (
<div className="flex-1 overflow-y-auto snap-y snap-mandatory no-scrollbar relative bg-black">
{activeTab === "recommend" && (
<div className="h-full snap-y snap-mandatory overflow-y-scroll">
{moments.map((moment, i) => (
<div key={i} className="h-full w-full snap-start relative bg-zinc-900">
{/* Rendering Mixed Feed */}
{mixedFeed.map((item, i) => (
<div key={i} className="h-full w-full snap-start relative bg-zinc-900 group">
<Image
src={`/.jpg?key=dsvkk&height=800&width=400&query=${moment.video || "esports_highlight"}`}
alt="Video"
src={`/feed-${item.type}-${item.id}.jpg?height=800&width=400&query=${item.video} ${item.type === "live" ? "live stream" : ""}`}
alt="Content"
fill
className="object-cover"
/>
<div className="absolute inset-0 bg-gradient-to-b from-black/20 via-transparent to-black/60" />
{item.type === "live" && (
<>
<div className="absolute top-20 left-4 px-2 py-1 rounded bg-primary/90 backdrop-blur-sm flex items-center gap-1 animate-pulse z-20">
<div className="w-1.5 h-1.5 rounded-full bg-white" />
<span className="text-xs font-bold"></span>
</div>
<div
className="absolute inset-0 z-10 flex items-center justify-center bg-black/40 opacity-0 group-hover:opacity-100 transition-opacity cursor-pointer"
onClick={() => router.push(`/live/${item.roomId || item.id}`)}
>
<div className="px-6 py-3 bg-primary rounded-full font-bold text-white shadow-lg transform hover:scale-105 transition-transform flex items-center gap-2">
<span></span>
</div>
</div>
</>
)}
{/* Right Sidebar Actions */}
<div className="absolute right-2 bottom-20 flex flex-col items-center gap-6">
<div className="flex flex-col items-center gap-1">
<div className="absolute right-2 bottom-24 flex flex-col items-center gap-6 z-20 pointer-events-none">
{" "}
{/* Added pointer-events-none to prevent blocking click */}
<div className="flex flex-col items-center gap-1 pointer-events-auto">
{" "}
{/* Re-enable pointer events for buttons */}
<div className="w-10 h-10 rounded-full border-2 border-white overflow-hidden relative">
<Image
src={`/.jpg?key=450fz&height=50&width=50&query=${moment.avatar || "avatar"}`}
src={`/.jpg?key=avatar-${item.id}&height=50&width=50&query=${item.avatar}`}
alt="User"
fill
className="object-cover"
@@ -237,27 +183,29 @@ export default function MomentsPage() {
</div>
</div>
<div className="flex flex-col items-center gap-1">
<Heart size={28} className="text-white" />
<span className="text-xs font-bold">{moment.likes}</span>
<Heart size={28} className="text-white drop-shadow-md" />
<span className="text-xs font-bold shadow-black drop-shadow-md">{item.likes}</span>
</div>
<div className="flex flex-col items-center gap-1">
<MessageCircle size={28} className="text-white" />
<span className="text-xs font-bold">{moment.comments}</span>
<MessageCircle size={28} className="text-white drop-shadow-md" />
<span className="text-xs font-bold shadow-black drop-shadow-md">{item.comments}</span>
</div>
<div className="flex flex-col items-center gap-1">
<Share2 size={28} className="text-white" />
<span className="text-xs font-bold"></span>
<Share2 size={28} className="text-white drop-shadow-md" />
<span className="text-xs font-bold shadow-black drop-shadow-md"></span>
</div>
</div>
{/* Bottom Info */}
<div className="absolute left-4 bottom-20 w-3/4">
<h3 className="font-bold text-lg mb-2">@{moment.user}</h3>
<p className="text-sm opacity-90 leading-relaxed">{moment.desc}</p>
{moment.hasCourse && (
<div className="mt-3 inline-flex items-center gap-2 bg-white/10 backdrop-blur-md px-3 py-1.5 rounded-lg border border-white/10">
<div className="absolute left-4 bottom-24 w-3/4 z-20">
<h3 className="font-bold text-lg mb-2 drop-shadow-md">@{item.user}</h3>
<p className="text-sm opacity-90 leading-relaxed drop-shadow-md line-clamp-3">{item.desc}</p>
{/* @ts-ignore */}
{item.hasCourse && (
<div className="mt-3 inline-flex items-center gap-2 bg-black/40 backdrop-blur-md px-3 py-1.5 rounded-lg border border-white/10">
<Trophy size={14} className="text-yellow-400" />
<span className="text-xs font-bold text-yellow-100">: {moment.courseTitle}</span>
{/* @ts-ignore */}
<span className="text-xs font-bold text-yellow-100">: {item.courseTitle}</span>
</div>
)}
</div>
@@ -265,6 +213,12 @@ export default function MomentsPage() {
))}
</div>
)}
{activeTab === "follow" && (
<div className="flex items-center justify-center h-full text-white/50">
<p></p>
</div>
)}
</div>
</div>
)

View File

@@ -1,11 +1,14 @@
"use client" // Convert to client component
"use client"
import { Search, Bell, Flame, Trophy, Users, Star, Sword, Crosshair, Crown } from "lucide-react"
import { Search, Bell, Flame, Trophy, Users, Sword, Crosshair, Crown } from "lucide-react"
import Image from "next/image"
import Link from "next/link"
import { toast } from "@/hooks/use-toast" // Import toast
import { toast } from "@/hooks/use-toast"
import { useRouter } from "next/navigation"
export default function HomePage() {
const router = useRouter()
const handleJoinTeam = () => {
toast({
title: "申请已发送",
@@ -13,78 +16,218 @@ export default function HomePage() {
})
}
const starPlayers = [
{
id: "1",
name: "GM-远洋",
title: "国服第一盲僧",
price: "50币/局",
image: "/avatar-1.jpg",
isLive: true,
},
{
id: "2",
name: "卡若",
title: "职业战队退役",
price: "80币/局",
image: "/avatar-2.jpg",
isLive: true,
},
{
id: "3",
name: "小柠檬",
title: "人皮话多",
price: "30币/局",
image: "/avatar-3.jpg",
isLive: false,
},
{
id: "4",
name: "阿伟",
title: "绝地求生刚枪王",
price: "60币/局",
image: "/avatar-4.jpg",
isLive: false,
},
]
return (
<div className="flex flex-col min-h-screen pb-24">
{/* Header */}
<header className="sticky top-0 z-40 glass px-4 py-3 flex items-center justify-between">
<div className="flex items-center gap-2">
<div className="w-8 h-8 rounded-lg bg-gradient-to-br from-primary to-accent flex items-center justify-center">
<span className="font-bold text-black text-xs">WZ</span>
<div className="flex flex-col min-h-screen pb-24 bg-background">
{/* Refined Header */}
<header className="sticky top-0 z-40 bg-[#0D0D15]/80 backdrop-blur-md border-b border-white/5 px-4 py-3 flex items-center justify-between transition-all duration-300">
<div className="flex items-center gap-3">
<div className="relative w-8 h-8 rounded-lg overflow-hidden border border-white/10">
<div className="absolute inset-0 bg-gradient-to-br from-purple-600 to-blue-600 opacity-80" />
<div className="absolute inset-0 flex items-center justify-center font-black italic text-white text-xs">
WZ
</div>
</div>
<h1 className="font-bold text-lg tracking-tight"></h1>
<h1 className="font-bold text-lg tracking-tight text-white"></h1>
</div>
<div className="flex items-center gap-3">
<button className="p-2 rounded-full bg-white/5 hover:bg-white/10 transition-colors">
<Search size={20} className="text-white/80" />
<button className="w-9 h-9 rounded-full bg-white/5 flex items-center justify-center hover:bg-white/10 transition-colors border border-white/5">
<Search size={18} className="text-white/70" />
</button>
<Link href="/messages" className="p-2 rounded-full bg-white/5 hover:bg-white/10 transition-colors relative">
<Bell size={20} className="text-white/80" />
<span className="absolute top-1.5 right-1.5 w-2 h-2 bg-secondary rounded-full border border-background" />
<Link
href="/messages"
className="w-9 h-9 rounded-full bg-white/5 flex items-center justify-center hover:bg-white/10 transition-colors relative border border-white/5"
>
<Bell size={18} className="text-white/70" />
<span className="absolute top-2 right-2.5 w-1.5 h-1.5 bg-red-500 rounded-full ring-2 ring-black" />
</Link>
{/* </CHANGE> */}
</div>
</header>
<div className="p-4 space-y-6">
{/* Hero Banner */}
<div className="relative w-full aspect-[16/9] rounded-2xl overflow-hidden group">
<div className="p-4 space-y-8">
{/* Hero Banner - Kept unchanged as requested */}
<div
className="relative w-full aspect-[16/9] rounded-2xl overflow-hidden group cursor-pointer shadow-2xl shadow-purple-500/10"
onClick={() => router.push("/events/today")}
>
<Image
src="/esports-tournament-stadium-neon-lights-crowd.jpg"
alt="Tournament"
src="/tournament-event-today.jpg"
alt="Today's Tournament"
fill
className="object-cover transition-transform duration-700 group-hover:scale-105"
/>
<div className="absolute inset-0 bg-gradient-to-t from-background via-transparent to-transparent" />
<div className="absolute bottom-0 left-0 p-4 w-full">
<div className="inline-flex items-center gap-1 px-2 py-0.5 rounded-full bg-secondary/80 backdrop-blur-md text-[10px] font-bold mb-2">
<div className="absolute inset-0 bg-gradient-to-t from-[#0D0D15] via-transparent to-transparent" />
<div className="absolute bottom-0 left-0 p-5 w-full">
<div className="inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full bg-secondary/90 backdrop-blur-md text-[10px] font-bold mb-2 shadow-lg shadow-secondary/20">
<span className="w-1.5 h-1.5 rounded-full bg-white animate-pulse" />
LIVE
</div>
<h2 className="text-xl font-bold leading-tight mb-1">2025 </h2>
<p className="text-sm text-white/70"></p>
<h2 className="text-xl font-bold leading-tight mb-1 text-white">2025 </h2>
<p className="text-xs text-white/70"></p>
</div>
</div>
{/* WoW Section */}
<section className="relative overflow-hidden rounded-2xl border border-yellow-500/20">
<div className="absolute inset-0 bg-[url('/wow-bg.jpg')] bg-cover bg-center opacity-40" />
<div className="absolute inset-0 bg-gradient-to-r from-black/90 to-transparent" />
<div className="relative p-4">
<div className="flex items-center justify-between mb-3">
{/* Industry Recommendations - Meituan Style V4 (Refined) */}
<section className="space-y-4">
<div className="flex items-center justify-between px-1">
<h3 className="font-bold text-lg flex items-center gap-2 text-white">
<Flame size={18} className="text-red-500 fill-red-500" />
</h3>
</div>
<div className="grid grid-cols-2 gap-3">
<Link
href="/hotel"
className="bg-[#1A1A22] p-4 rounded-xl flex items-center justify-between group cursor-pointer hover:bg-[#20202A] transition-colors border border-white/5 shadow-lg"
>
<div>
<h4 className="font-bold text-base text-white mb-1.5 group-hover:text-yellow-400 transition-colors">
</h4>
<p className="text-[10px] text-white/40 group-hover:text-white/60"> · </p>
</div>
<div className="w-10 h-10 rounded-full bg-yellow-500/10 flex items-center justify-center group-hover:scale-110 transition-transform border border-yellow-500/20">
<Trophy size={20} className="text-yellow-500" />
</div>
</Link>
<Link
href="/hotel"
className="bg-[#1A1A22] p-4 rounded-xl flex items-center justify-between group cursor-pointer hover:bg-[#20202A] transition-colors border border-white/5 shadow-lg"
>
<div>
<h4 className="font-bold text-base text-white mb-1.5 group-hover:text-blue-400 transition-colors">
</h4>
<p className="text-[10px] text-white/40 group-hover:text-white/60"> · </p>
</div>
<div className="w-10 h-10 rounded-full bg-blue-500/10 flex items-center justify-center group-hover:scale-110 transition-transform border border-blue-500/20">
<Users size={20} className="text-blue-500" />
</div>
</Link>
</div>
</section>
{/* Game Icons Grid */}
<div className="grid grid-cols-4 gap-4 px-2">
{[
{
name: "英雄联盟",
icon: "Sword",
color: "text-blue-400",
bg: "bg-blue-500/10",
border: "border-blue-500/20",
link: "/zone/lol",
},
{
name: "无畏契约",
icon: "Crosshair",
color: "text-red-400",
bg: "bg-red-500/10",
border: "border-red-500/20",
link: "/zone/val",
},
{
name: "王者荣耀",
icon: "Crown",
color: "text-yellow-400",
bg: "bg-yellow-500/10",
border: "border-yellow-500/20",
link: "/zone/hok",
},
{
name: "CS:GO",
icon: "Target",
color: "text-orange-400",
bg: "bg-orange-500/10",
border: "border-orange-500/20",
link: "/zone/csgo",
},
].map((game, i) => (
<Link href={game.link} key={i} className="flex flex-col items-center gap-2 group cursor-pointer">
<div
className={`w-14 h-14 rounded-2xl ${game.bg} ${game.border} border flex items-center justify-center group-hover:scale-105 transition-transform relative overflow-hidden shadow-lg`}
>
<div className={`absolute inset-0 bg-current opacity-5 ${game.color}`} />
{game.icon === "Sword" && <Sword className={game.color} size={26} />}
{game.icon === "Crosshair" && <Crosshair className={game.color} size={26} />}
{game.icon === "Crown" && <Crown className={game.color} size={26} />}
{game.icon === "Target" && <Crosshair className={game.color} size={26} />}
</div>
<span className="text-xs font-medium text-white/60 group-hover:text-white transition-colors">
{game.name}
</span>
</Link>
))}
</div>
{/* WoW Section - Refined Card Style */}
<section className="relative overflow-hidden rounded-2xl border border-yellow-500/20 bg-[#12121A]">
<div className="absolute inset-0 bg-[url('/wow-bg.jpg')] bg-cover bg-center opacity-20 mix-blend-overlay" />
<div className="absolute inset-0 bg-gradient-to-r from-black/80 to-transparent" />
<div className="relative p-5">
<div className="flex items-center justify-between mb-4">
<h3 className="font-bold text-lg flex items-center gap-2 text-yellow-500">
<Sword size={18} />
</h3>
<Link href="/wow" className="text-xs text-yellow-500/70 hover:text-yellow-500">
&gt;
<Link
href="/zone/wow"
className="text-xs text-yellow-500/70 hover:text-yellow-500 flex items-center gap-1"
>
<span className="text-[10px]">&gt;</span>
</Link>
</div>
<div className="grid grid-cols-2 gap-3">
<div className="glass-card p-3 rounded-xl border-l-2 border-yellow-500/50">
<div className="bg-black/40 backdrop-blur-sm p-3 rounded-xl border-l-2 border-yellow-500/50 hover:bg-black/60 transition-colors">
<h4 className="font-bold text-sm text-yellow-100"></h4>
<p className="text-[10px] text-white/60 mt-1">ICC 25H全通团DPS</p>
<p className="text-[10px] text-white/50 mt-1 line-clamp-1">ICC 25H全通团DPS</p>
<button
className="mt-2 w-full py-1 rounded bg-yellow-600/20 text-yellow-500 text-xs font-bold hover:bg-yellow-600/30"
className="mt-3 w-full py-1.5 rounded-lg bg-yellow-600/10 text-yellow-500 text-xs font-bold hover:bg-yellow-600/20 border border-yellow-600/20 transition-colors"
onClick={() => toast({ title: "即将开放", description: "金团招募功能正在内测中" })}
>
</button>
</div>
<div className="glass-card p-3 rounded-xl border-l-2 border-blue-500/50">
<div className="bg-black/40 backdrop-blur-sm p-3 rounded-xl border-l-2 border-blue-500/50 hover:bg-black/60 transition-colors">
<h4 className="font-bold text-sm text-blue-100"></h4>
<p className="text-[10px] text-white/60 mt-1">20</p>
<p className="text-[10px] text-white/50 mt-1 line-clamp-1">20</p>
<button
className="mt-2 w-full py-1 rounded bg-blue-600/20 text-blue-500 text-xs font-bold hover:bg-blue-600/30"
className="mt-3 w-full py-1.5 rounded-lg bg-blue-600/10 text-blue-500 text-xs font-bold hover:bg-blue-600/20 border border-blue-600/20 transition-colors"
onClick={handleJoinTeam}
>
@@ -94,248 +237,44 @@ export default function HomePage() {
</div>
</section>
{/* Game Icons */}
<div className="grid grid-cols-4 gap-3 px-2">
{[
{ name: "英雄联盟", icon: "Sword", color: "text-blue-400", bg: "bg-blue-500/10", link: "/games/lol" },
{ name: "无畏契约", icon: "Crosshair", color: "text-red-400", bg: "bg-red-500/10", link: "/games/val" },
{ name: "王者荣耀", icon: "Crown", color: "text-yellow-400", bg: "bg-yellow-500/10", link: "/games/hok" },
{ name: "CS:GO", icon: "Target", color: "text-orange-400", bg: "bg-orange-500/10", link: "/games/csgo" },
].map((game, i) => (
<Link
href={game.link}
key={i}
className="flex flex-col items-center gap-2 group cursor-pointer p-2 rounded-xl hover:bg-white/5 transition-colors"
>
<div
className={`w-12 h-12 rounded-2xl ${game.bg} flex items-center justify-center border border-white/5 group-hover:scale-105 transition-transform relative overflow-hidden`}
>
<div className={`absolute inset-0 bg-current opacity-10 ${game.color}`} />
{/* Using Lucide icons mapped manually for now as dynamic import is complex in this snippet */}
{game.icon === "Sword" && <Sword className={game.color} size={24} />}
{game.icon === "Crosshair" && <Crosshair className={game.color} size={24} />}
{game.icon === "Crown" && <Crown className={game.color} size={24} />}
{game.icon === "Target" && <Crosshair className={game.color} size={24} />}
</div>
<span className="text-xs font-medium text-white/80 group-hover:text-white transition-colors">
{game.name}
</span>
</Link>
))}
</div>
{/* Industry Recommendations */}
<section className="space-y-3 mt-2">
{/* Star Players Section */}
<section className="space-y-4">
<div className="flex items-center justify-between">
<h3 className="font-bold text-lg flex items-center gap-2">
<Flame size={18} className="text-red-500" />
</h3>
</div>
<div className="grid grid-cols-2 gap-3">
<Link
href="/hotel"
className="glass-card p-0 rounded-xl border-0 relative overflow-hidden group cursor-pointer aspect-[4/3]"
>
<Image
src="/esports-hotel-cyberpunk-room.jpg"
alt="Hotel"
fill
className="object-cover transition-transform duration-700 group-hover:scale-110"
/>
<div className="absolute inset-0 bg-gradient-to-t from-black/90 via-black/20 to-transparent" />
<div className="absolute bottom-0 left-0 p-3 w-full">
<div className="flex items-center justify-between mb-1">
<h4 className="font-bold text-sm text-white"></h4>
<Trophy size={14} className="text-yellow-500" />
</div>
<p className="text-[10px] text-white/70"> </p>
</div>
</Link>
<Link
href="/hotel"
className="glass-card p-0 rounded-xl border-0 relative overflow-hidden group cursor-pointer aspect-[4/3]"
>
<Image
src="/internet-cafe-luxury-interior.jpg"
alt="Cafe"
fill
className="object-cover transition-transform duration-700 group-hover:scale-110"
/>
<div className="absolute inset-0 bg-gradient-to-t from-black/90 via-black/20 to-transparent" />
<div className="absolute bottom-0 left-0 p-3 w-full">
<div className="flex items-center justify-between mb-1">
<h4 className="font-bold text-sm text-white"></h4>
<Users size={14} className="text-blue-500" />
</div>
<p className="text-[10px] text-white/70"> </p>
</div>
</Link>
</div>
</section>
{/* Club Recommendations */}
<section>
<div className="flex items-center justify-between mb-3">
<h3 className="font-bold text-lg flex items-center gap-2">
<Trophy size={18} className="text-primary" />
</h3>
<Link href="#" className="text-xs text-white/50 hover:text-primary transition-colors">
</Link>
</div>
<div className="space-y-3">
{[
{
name: "洲越电竞",
status: "即将开业",
id: "50310",
members: 26,
tags: ["三角洲", "无畏契约"],
logo: "esports_team_blue_logo",
},
{
name: "机枪电竞",
status: "火热招募",
id: "50130",
members: 60,
tags: ["LOL", "云顶"],
logo: "esports_team_red_logo",
},
{
name: "东东电竞",
status: "官方自营",
id: "88888",
members: 1205,
tags: ["全能", "陪玩"],
logo: "esports_team_gold_logo",
},
].map((club, i) => (
<Link
href={`/club/${club.id}`}
key={i}
className="glass-card p-3 rounded-xl flex items-center gap-3 hover:bg-white/5 transition-colors cursor-pointer"
>
<div className="w-14 h-14 rounded-lg bg-muted relative overflow-hidden shrink-0 border border-white/10">
<Image
src={`/.jpg?key=6lzl1&height=100&width=100&query=${club.logo}`}
alt={club.name}
fill
className="object-cover"
/>
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center justify-between mb-1">
<h4 className="font-bold truncate">{club.name}</h4>
<div className="flex -space-x-1">
{club.tags.map((tag, j) => (
<div
key={j}
className="w-5 h-5 rounded-full bg-muted border border-card flex items-center justify-center text-[8px] overflow-hidden"
>
{tag[0]}
</div>
))}
</div>
</div>
<p className="text-xs text-white/50 mb-2">{club.status}</p>
<div className="flex items-center gap-3 text-[10px] text-white/40">
<span className="flex items-center gap-1">
<span className="font-mono">ID:</span> {club.id}
</span>
<span className="flex items-center gap-1">
<Users size={10} /> {club.members}
</span>
</div>
</div>
</Link>
))}
</div>
</section>
{/* Star Players / Streamers */}
<section>
<div className="flex items-center justify-between mb-3">
<h3 className="font-bold text-lg flex items-center gap-2">
<Star size={18} className="text-secondary" />
<h3 className="font-bold text-lg flex items-center gap-2 text-white">
<Flame size={18} className="text-secondary" />
</h3>
<Link href="#" className="text-xs text-white/50 hover:text-secondary transition-colors">
<Link href="/mall/companion" className="text-xs text-white/50 hover:text-secondary transition-colors">
</Link>
</div>
<div className="grid grid-cols-2 gap-3">
{[
{
id: "1",
name: "GM-远洋",
title: "国服第一盲僧",
price: "50币/局",
image: "professional esports player male headset focused",
},
{
id: "2",
name: "卡若",
title: "职业战队退役",
price: "80币/局",
image: "esports pro gamer male jersey confident",
},
{
id: "3",
name: "小柠檬",
title: "人皮话多",
price: "30币/局",
image: "cute female gamer streamer headphones smiling",
},
{
id: "4",
name: "阿伟",
title: "绝地求生刚枪王",
price: "60币/局",
image: "fps gamer male intense focus tactical",
},
{
id: "5",
name: "七七",
title: "甜美声优",
price: "40币/局",
image: "anime style female gamer kawaii pink",
},
{
id: "6",
name: "大魔王",
title: "中单法王",
price: "100币/局",
image: "legendary esports player male champion trophy",
},
].map((star, i) => (
{starPlayers.map((star) => (
<Link
key={i}
href={`/star/${star.id}`}
className="glass-card rounded-xl overflow-hidden group cursor-pointer"
key={star.id}
href={`/live/${star.id}`}
className="relative aspect-[3/4] rounded-xl overflow-hidden group cursor-pointer border border-white/5 shadow-lg"
>
<div className="relative aspect-[3/4]">
<Image
src={`/.jpg?key=ao2up&height=400&width=300&query=${star.image}`}
alt={star.name}
fill
className="object-cover transition-transform duration-500 group-hover:scale-110"
/>
<div className="absolute inset-0 bg-gradient-to-t from-black/90 via-transparent to-transparent" />
<div className="absolute bottom-0 left-0 w-full p-3">
<h4 className="font-bold text-sm">{star.name}</h4>
<p className="text-[10px] text-white/70 mb-1">{star.title}</p>
<div className="flex items-center justify-between">
<span className="text-xs font-bold text-primary">{star.price}</span>
<button className="w-6 h-6 rounded-full bg-secondary flex items-center justify-center">
<Flame size={12} className="text-white" />
</button>
</div>
<Image
src={star.image || "/placeholder.svg"}
alt={star.name}
fill
className="object-cover transition-transform duration-500 group-hover:scale-110"
/>
<div className="absolute inset-0 bg-gradient-to-t from-black/90 via-transparent to-transparent" />
{star.isLive && (
<div className="absolute top-2 left-2 flex items-center gap-1 bg-secondary/90 backdrop-blur-md px-2 py-0.5 rounded-md text-[10px] font-bold text-white border border-white/20 animate-pulse shadow-lg shadow-secondary/30">
<span className="w-1.5 h-1.5 rounded-full bg-white" />
</div>
)}
<div className="absolute bottom-0 left-0 w-full p-3 space-y-1">
<h4 className="font-bold text-sm text-white truncate">{star.name}</h4>
<p className="text-[10px] text-white/70 truncate">{star.title}</p>
<div className="text-xs font-bold text-primary">{star.price}</div>
</div>
</Link>
))}

View File

@@ -3,7 +3,7 @@
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import { Card } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { Mic, Users, Gamepad2, Headphones, Heart, Sparkles, Filter, Trophy, Crosshair } from "lucide-react"
import { Mic, Users, Gamepad2, Headphones, Heart, Sparkles, Filter, Trophy, Crosshair, Star } from "lucide-react"
import Image from "next/image"
import { useState } from "react"
import { toast } from "@/hooks/use-toast"
@@ -29,10 +29,11 @@ export default function PlanetPage() {
star: {
name: "GM-远洋",
title: "国服第一盲僧 · 电竞明星",
avatar: "/esports-pro-gamer-male-jersey-confident.jpg",
avatar: "/avatar-1.jpg",
viewers: "12.5w",
tags: ["LOL", "技术流"],
roomId: 1001,
route: "/star/1",
},
guild: {
name: "东东电竞公会",
@@ -41,22 +42,25 @@ export default function PlanetPage() {
viewers: "1205",
tags: ["全能", "培训"],
roomId: 1002,
route: "/guild/东东电竞",
},
cp: {
name: "甜心辅助",
title: "温柔妹子 · 找电竞CP",
avatar: "/cute-gamer-girl.jpg",
avatar: "/avatar-5.jpg",
viewers: "8.3w",
tags: ["王者", "温柔"],
roomId: 1003,
route: "/cp/1",
},
coach: {
name: "职业陪练-小明",
title: "前职业选手 · 游戏陪练",
avatar: "/game-coach-avatar.jpg",
avatar: "/avatar-4.jpg",
viewers: "3.2w",
tags: ["陪练", "上分"],
roomId: 1004,
route: "/coach/1",
},
}
@@ -69,11 +73,16 @@ export default function PlanetPage() {
})
setTimeout(() => {
router.push(`/chat/${result.roomId}`)
router.push(result.route)
}, 1500)
}, 3000)
}
const handleCategoryChange = (category: MatchCategory) => {
setSelectedCategory(category)
setMatchResult(null) // 切换时清除之前的匹配结果
}
const handleJoinParty = (title: string) => {
toast({
title: "正在加入派对",
@@ -94,6 +103,19 @@ export default function PlanetPage() {
}, 1000)
}
const getCategoryLabel = () => {
switch (selectedCategory) {
case "star":
return "电竞明星"
case "guild":
return "主播公会"
case "cp":
return "电竞CP"
case "coach":
return "游戏陪练"
}
}
return (
<div className="pb-24 pt-6 px-4">
<div className="flex items-center justify-between mb-6">
@@ -173,7 +195,52 @@ export default function PlanetPage() {
</TabsList>
<TabsContent value="voice" className="mt-0">
<div className="flex flex-col items-center justify-center py-12 space-y-8">
<div className="flex flex-col items-center justify-center py-8 space-y-6">
<div className="grid grid-cols-4 gap-2 w-full">
<Button
variant="outline"
onClick={() => handleCategoryChange("star")}
className={`flex flex-col h-auto py-3 border-white/10 bg-white/5 hover:bg-white/10 hover:border-primary/50 transition-all ${
selectedCategory === "star" ? "border-primary bg-primary/20 ring-2 ring-primary/50" : ""
}`}
>
<Star className={`w-5 h-5 mb-1 ${selectedCategory === "star" ? "text-primary" : "text-yellow-500"}`} />
<span className="text-[10px]"></span>
</Button>
<Button
variant="outline"
onClick={() => handleCategoryChange("guild")}
className={`flex flex-col h-auto py-3 border-white/10 bg-white/5 hover:bg-white/10 hover:border-blue-500/50 transition-all ${
selectedCategory === "guild" ? "border-blue-500 bg-blue-500/20 ring-2 ring-blue-500/50" : ""
}`}
>
<Users className={`w-5 h-5 mb-1 ${selectedCategory === "guild" ? "text-blue-400" : "text-blue-500"}`} />
<span className="text-[10px]"></span>
</Button>
<Button
variant="outline"
onClick={() => handleCategoryChange("cp")}
className={`flex flex-col h-auto py-3 border-white/10 bg-white/5 hover:bg-white/10 hover:border-pink-500/50 transition-all ${
selectedCategory === "cp" ? "border-pink-500 bg-pink-500/20 ring-2 ring-pink-500/50" : ""
}`}
>
<Heart className={`w-5 h-5 mb-1 ${selectedCategory === "cp" ? "text-pink-400" : "text-pink-500"}`} />
<span className="text-[10px]">CP</span>
</Button>
<Button
variant="outline"
onClick={() => handleCategoryChange("coach")}
className={`flex flex-col h-auto py-3 border-white/10 bg-white/5 hover:bg-white/10 hover:border-green-500/50 transition-all ${
selectedCategory === "coach" ? "border-green-500 bg-green-500/20 ring-2 ring-green-500/50" : ""
}`}
>
<Gamepad2
className={`w-5 h-5 mb-1 ${selectedCategory === "coach" ? "text-green-400" : "text-green-500"}`}
/>
<span className="text-[10px]"></span>
</Button>
</div>
<div className="relative w-64 h-64">
{(!matchResult || isMatching) && (
<>
@@ -198,12 +265,12 @@ export default function PlanetPage() {
<div className="w-64 h-auto bg-zinc-900 border border-primary/50 rounded-2xl p-5 flex flex-col items-center text-center shadow-2xl shadow-primary/30 animate-in zoom-in duration-300">
<div className="w-20 h-20 rounded-full border-2 border-primary p-1 mb-3 relative">
<Image
src={`/.jpg?key=cc76z&height=100&width=100&query=${matchResult.avatar}`}
src={matchResult.avatar || "/placeholder.svg"}
alt="Streamer"
fill
className="rounded-full object-cover"
/>
<div className="absolute -bottom-1 bg-primary text-[10px] px-2 py-0.5 rounded-full text-white font-bold border-2 border-zinc-900">
<div className="absolute -bottom-1 left-1/2 -translate-x-1/2 bg-primary text-[10px] px-2 py-0.5 rounded-full text-white font-bold border-2 border-zinc-900">
MATCHED
</div>
</div>
@@ -222,7 +289,7 @@ export default function PlanetPage() {
<div className="w-full bg-green-500/10 border border-green-500/30 rounded-lg p-3 flex items-center justify-center gap-2">
<div className="w-2 h-2 rounded-full bg-green-500 animate-ping" />
<span className="text-xs font-bold text-green-400">...</span>
<span className="text-xs font-bold text-green-400">...</span>
</div>
</div>
</div>
@@ -243,12 +310,7 @@ export default function PlanetPage() {
<>
<Mic className="w-12 h-12 text-white mx-auto mb-2" />
<span className="text-xl font-bold text-white"></span>
<p className="text-xs text-white/70 mt-1">
{selectedCategory === "star" && "寻找电竞明星"}
{selectedCategory === "guild" && "寻找主播公会"}
{selectedCategory === "cp" && "寻找电竞CP"}
{selectedCategory === "coach" && "寻找游戏陪练"}
</p>
<p className="text-xs text-white/70 mt-1">{getCategoryLabel()}</p>
</>
)}
</div>
@@ -260,38 +322,18 @@ export default function PlanetPage() {
<>
<div className="absolute top-0 left-1/2 -translate-x-1/2 -translate-y-4 animate-bounce duration-3000">
<div className="w-10 h-10 rounded-full border-2 border-primary bg-black overflow-hidden">
<Image src="/gamer-avatar.jpg" alt="User" width={40} height={40} />
<Image src="/user-avatar.jpg" alt="User" width={40} height={40} />
</div>
</div>
</>
)}
</div>
<div className="grid grid-cols-3 gap-4 w-full">
<Button
variant="outline"
onClick={() => setSelectedCategory("guild")}
className={`flex flex-col h-auto py-4 border-white/10 bg-white/5 hover:bg-white/10 hover:border-primary/50 ${selectedCategory === "guild" ? "border-blue-500 bg-blue-500/10" : ""}`}
>
<Users className="w-6 h-6 text-blue-500 mb-2" />
<span className="text-xs"></span>
</Button>
<Button
variant="outline"
onClick={() => setSelectedCategory("cp")}
className={`flex flex-col h-auto py-4 border-white/10 bg-white/5 hover:bg-white/10 hover:border-primary/50 ${selectedCategory === "cp" ? "border-pink-500 bg-pink-500/10" : ""}`}
>
<Heart className="w-6 h-6 text-pink-500 mb-2" />
<span className="text-xs">CP</span>
</Button>
<Button
variant="outline"
onClick={() => setSelectedCategory("coach")}
className={`flex flex-col h-auto py-4 border-white/10 bg-white/5 hover:bg-white/10 hover:border-primary/50 ${selectedCategory === "coach" ? "border-green-500 bg-green-500/10" : ""}`}
>
<Gamepad2 className="w-6 h-6 text-green-500 mb-2" />
<span className="text-xs"></span>
</Button>
<div className="text-center">
<p className="text-sm text-white/60">
: <span className="text-primary font-bold">{getCategoryLabel()}</span>
</p>
<p className="text-xs text-white/40 mt-1"></p>
</div>
</div>
</TabsContent>
@@ -343,12 +385,7 @@ export default function PlanetPage() {
key={avatar}
className="w-6 h-6 rounded-full border border-black bg-gray-800 overflow-hidden"
>
<Image
src={`/gamer-avatar.jpg?key=mup1i&height=24&width=24&query=gamer avatar ${avatar}`}
alt="User"
width={24}
height={24}
/>
<Image src={`/avatar-${avatar}.jpg`} alt="User" width={24} height={24} />
</div>
))}
</div>

View File

@@ -64,12 +64,7 @@ export default function ProfilePage() {
<div className="flex items-center gap-4 mb-6">
<div className="relative w-20 h-20 rounded-full border-2 border-primary p-1">
<Image
src={user.avatar || "/placeholder.svg?height=100&width=100&query=gamer avatar"}
alt="Profile"
fill
className="rounded-full object-cover"
/>
<Image src="/gamer-profile-avatar.jpg" alt="Profile" fill className="rounded-full object-cover" />
<div className="absolute bottom-0 right-0 bg-primary text-[10px] px-1.5 py-0.5 rounded-full text-white font-bold border border-black">
LV.8
</div>

93
app/recharge/page.tsx Normal file
View File

@@ -0,0 +1,93 @@
"use client"
import { useRouter } from "next/navigation"
import { ArrowLeft, Wallet, CreditCard, Gem } from "lucide-react"
import { useAppContext } from "@/components/providers/app-provider"
export default function RechargePage() {
const router = useRouter()
const { recharge } = useAppContext()
const amounts = [
{ value: 6, bonus: 0 },
{ value: 30, bonus: 2 },
{ value: 68, bonus: 5 },
{ value: 128, bonus: 12 },
{ value: 328, bonus: 35 },
{ value: 648, bonus: 80 },
]
return (
<div className="min-h-screen bg-background pb-12">
<header className="sticky top-0 z-40 glass px-4 py-3 flex items-center gap-3">
<button onClick={() => router.back()} className="text-white/70 hover:text-white">
<ArrowLeft size={20} />
</button>
<h1 className="font-bold text-lg"></h1>
</header>
<div className="p-4 space-y-6">
{/* Banner */}
<div className="w-full h-32 rounded-2xl bg-gradient-to-r from-purple-600 to-blue-600 relative overflow-hidden flex items-center px-6">
<div className="relative z-10">
<h2 className="text-xl font-bold text-white mb-1"></h2>
<p className="text-xs text-white/80"> </p>
</div>
<div className="absolute right-0 bottom-0 w-32 h-32 opacity-50">
<Gem className="w-full h-full text-white/20 rotate-12 translate-x-8 translate-y-8" />
</div>
</div>
{/* Grid */}
<div className="grid grid-cols-3 gap-3">
{amounts.map((item) => (
<button
key={item.value}
className="glass-card p-4 rounded-xl flex flex-col items-center justify-center gap-2 hover:bg-white/10 transition-colors border border-white/5 hover:border-primary/50"
onClick={() => recharge(item.value)}
>
<div className="flex items-center gap-1">
<Gem size={16} className="text-primary" />
<span className="font-bold text-lg">{item.value * 10}</span>
</div>
<span className="text-sm font-medium text-white/70">¥{item.value}</span>
{item.bonus > 0 && (
<span className="text-[10px] text-yellow-400 bg-yellow-500/10 px-1.5 rounded-full">
{item.bonus * 10}
</span>
)}
</button>
))}
</div>
{/* Payment Methods */}
<div className="space-y-3">
<h3 className="font-bold text-sm text-white/70"></h3>
<div className="glass-card rounded-xl p-0 overflow-hidden">
<div className="flex items-center gap-3 p-4 border-b border-white/5">
<div className="w-8 h-8 rounded-full bg-green-500/20 flex items-center justify-center">
<Wallet size={18} className="text-green-500" />
</div>
<div className="flex-1">
<div className="font-bold text-sm"></div>
<div className="text-[10px] text-white/40">使</div>
</div>
<div className="w-4 h-4 rounded-full border border-primary bg-primary" />
</div>
<div className="flex items-center gap-3 p-4">
<div className="w-8 h-8 rounded-full bg-blue-500/20 flex items-center justify-center">
<CreditCard size={18} className="text-blue-500" />
</div>
<div className="flex-1">
<div className="font-bold text-sm"></div>
</div>
<div className="w-4 h-4 rounded-full border border-white/20" />
</div>
</div>
</div>
<p className="text-center text-xs text-white/30 pt-4"></p>
</div>
</div>
)
}

View File

@@ -0,0 +1,3 @@
export default function Loading() {
return null
}

View File

@@ -0,0 +1,248 @@
"use client"
import { Search, Filter, Shield, Wallet, Gamepad2, ArrowUpRight } from "lucide-react"
import { useRouter, useSearchParams } from "next/navigation"
import Image from "next/image"
import { Button } from "@/components/ui/button"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import { toast } from "@/hooks/use-toast"
import { AccountEvaluation } from "@/components/account/account-evaluation"
export default function AccountTradePage() {
const router = useRouter()
const searchParams = useSearchParams()
const defaultTab = searchParams.get("tab") || "buy"
const games = [
{ name: "全部", icon: null },
{ name: "英雄联盟", icon: "/account-icon-lol.jpg" },
{ name: "无畏契约", icon: "/account-icon-val.jpg" },
{ name: "王者荣耀", icon: "/account-icon-hok.jpg" },
{ name: "原神", icon: "/account-icon-gen.jpg" },
]
const accounts = [
{
id: 1,
title: "英雄联盟-全英雄 全皮肤 龙瞎",
price: 2888,
server: "艾欧尼亚",
security: "找回包赔",
image: "/account-lol.jpg",
tags: ["顶号", "满级"],
},
{
id: 2,
title: "无畏契约-满特工 冠军套",
price: 1500,
server: "国服",
security: "保险护航",
image: "/account-val.jpg",
tags: ["成品号"],
},
{
id: 3,
title: "王者荣耀-V10 300皮肤",
price: 5600,
server: "安卓QQ",
security: "官方验号",
image: "/account-hok.jpg",
tags: ["贵族10", "国标"],
},
{
id: 4,
title: "原神-满命雷神 草神",
price: 3200,
server: "天空岛",
security: "签署合同",
image: "/account-genshin.jpg",
tags: ["三神", "高练度"],
},
]
return (
<div className="min-h-screen pb-24 bg-background">
<header className="sticky top-0 z-40 glass px-4 py-3 flex items-center gap-3">
<button onClick={() => router.back()} className="text-white/70 hover:text-white">
</button>
<h1 className="font-bold text-lg"></h1>
<div className="ml-auto">{/* Placeholder for right action if needed */}</div>
</header>
<Tabs defaultValue={defaultTab} className="flex flex-col min-h-[calc(100vh-60px)]">
<div className="px-4 mt-2">
<TabsList className="w-full bg-white/5 p-1 rounded-xl grid grid-cols-2">
<TabsTrigger
value="buy"
className="text-sm font-bold data-[state=active]:bg-primary data-[state=active]:text-black"
>
</TabsTrigger>
<TabsTrigger
value="finance"
className="text-sm font-bold data-[state=active]:bg-purple-500 data-[state=active]:text-white"
>
</TabsTrigger>
</TabsList>
</div>
<TabsContent value="buy" className="flex-1 px-4 space-y-4 mt-4 data-[state=inactive]:hidden">
{/* Search & Filter */}
<div className="flex gap-2">
<div className="relative flex-1">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 text-white/40" size={16} />
<input
type="text"
placeholder="搜索游戏、区服、英雄..."
className="w-full bg-white/5 rounded-lg py-2.5 pl-9 pr-4 text-sm text-white focus:outline-none focus:ring-1 focus:ring-primary placeholder:text-white/30"
/>
</div>
<Button variant="outline" className="border-white/10 bg-white/5 text-white/70 w-10 px-0">
<Filter size={16} />
</Button>
</div>
{/* Game Categories */}
<div className="overflow-x-auto no-scrollbar pb-2">
<div className="flex gap-3">
{games.map((g, i) => (
<div key={i} className="flex flex-col items-center gap-1 min-w-[60px] cursor-pointer group">
<div
className={`w-12 h-12 rounded-xl flex items-center justify-center border border-white/10 overflow-hidden transition-all group-hover:border-primary/50 ${!g.icon ? "bg-white/5" : "bg-black"}`}
>
{g.icon ? (
<Image
src={g.icon || "/placeholder.svg"}
alt={g.name}
width={48}
height={48}
className="object-cover"
/>
) : (
<Gamepad2 className="text-white/50" />
)}
</div>
<span className="text-[10px] text-white/60 group-hover:text-white">{g.name}</span>
</div>
))}
</div>
</div>
{/* Account List */}
<div className="grid gap-3">
{accounts.map((acc) => (
<div
key={acc.id}
className="glass-card p-3 rounded-xl flex gap-3 hover:bg-white/5 transition-colors cursor-pointer group"
onClick={() => toast({ title: "即将跳转详情", description: "该功能为模拟演示" })}
>
<div className="w-28 h-24 bg-muted rounded-lg relative overflow-hidden shrink-0 border border-white/5">
<Image
src={acc.image || "/placeholder.svg"}
alt={acc.title}
fill
className="object-cover transition-transform group-hover:scale-105"
/>
<div className="absolute top-1 left-1 bg-black/60 text-[9px] px-1.5 py-0.5 rounded backdrop-blur-sm text-white/90 font-medium">
{acc.server}
</div>
</div>
<div className="flex-1 flex flex-col justify-between py-0.5">
<div>
<h3 className="font-bold text-sm line-clamp-2 leading-snug">{acc.title}</h3>
<div className="flex flex-wrap gap-1 mt-1.5">
{acc.tags.map((tag) => (
<span key={tag} className="text-[9px] bg-white/5 text-white/50 px-1 rounded">
{tag}
</span>
))}
</div>
</div>
<div className="flex items-end justify-between mt-auto">
<div className="flex flex-col">
<span className="text-[10px] text-green-400 flex items-center gap-1 mb-0.5">
<Shield size={10} /> {acc.security}
</span>
<span className="text-lg font-bold text-primary leading-none">¥{acc.price}</span>
</div>
<Button
size="sm"
className="h-7 text-xs px-3 font-bold bg-white/10 hover:bg-primary hover:text-black"
>
</Button>
</div>
</div>
</div>
))}
</div>
</TabsContent>
<TabsContent value="finance" className="flex-1 px-4 space-y-4 mt-4 data-[state=inactive]:hidden">
{/* Finance Banner / Header Area */}
<div className="bg-[#1A1A2E] p-6 rounded-3xl border border-purple-500/20 relative overflow-hidden shadow-lg">
<div className="absolute top-0 right-0 w-32 h-32 bg-purple-500/10 rounded-full blur-3xl -mr-10 -mt-10 pointer-events-none" />
<div className="flex items-center justify-between mb-6 relative z-10">
<div className="flex items-center gap-4">
<div className="w-12 h-12 rounded-2xl bg-gradient-to-br from-purple-500/20 to-blue-500/20 flex items-center justify-center border border-white/10 shadow-inner">
<Wallet size={24} className="text-purple-300" />
</div>
<div>
<h2 className="text-lg font-bold text-white"></h2>
<p className="text-xs text-purple-200/60 mt-0.5">AI · · </p>
</div>
</div>
<div className="text-right">
<div className="text-xs text-white/40 mb-1"></div>
<div className="text-xl font-mono font-bold text-white tracking-tight">12,508</div>
</div>
</div>
<div className="grid grid-cols-3 gap-4 relative z-10">
<div className="bg-[#12121A] rounded-xl p-3 text-center border border-white/5">
<div className="text-[10px] text-white/40 mb-1.5 uppercase tracking-wider"></div>
<div className="text-sm font-bold text-purple-300">¥ 1.2亿</div>
</div>
<div className="bg-[#12121A] rounded-xl p-3 text-center border border-white/5">
<div className="text-[10px] text-white/40 mb-1.5 uppercase tracking-wider"></div>
<div className="text-sm font-bold text-purple-300">¥ 3,500</div>
</div>
<div className="bg-[#12121A] rounded-xl p-3 text-center border border-white/5">
<div className="text-[10px] text-white/40 mb-1.5 uppercase tracking-wider"></div>
<div className="text-sm font-bold text-purple-300">5 </div>
</div>
</div>
</div>
{/* Evaluation Form Component */}
<AccountEvaluation />
{/* Service Features */}
<div className="grid grid-cols-2 gap-3 pb-8">
<div className="bg-white/5 border border-white/5 p-3 rounded-xl flex items-center gap-3">
<div className="w-8 h-8 rounded-full bg-green-500/10 flex items-center justify-center">
<Shield className="text-green-400" size={16} />
</div>
<div>
<div className="text-xs font-bold"></div>
<div className="text-[10px] text-white/40"> </div>
</div>
</div>
<div className="bg-white/5 border border-white/5 p-3 rounded-xl flex items-center gap-3">
<div className="w-8 h-8 rounded-full bg-yellow-500/10 flex items-center justify-center">
<ArrowUpRight className="text-yellow-400" size={16} />
</div>
<div>
<div className="text-xs font-bold"></div>
<div className="text-[10px] text-white/40"> </div>
</div>
</div>
</div>
</TabsContent>
</Tabs>
</div>
)
}

View File

@@ -0,0 +1,93 @@
"use client"
import { useRouter } from "next/navigation"
import { Star, Zap, Trophy } from "lucide-react"
import { Button } from "@/components/ui/button"
import { toast } from "@/hooks/use-toast"
export default function LevelingPage() {
const router = useRouter()
const boosters = [
{ id: 1, title: "王者荣耀-荣耀王者直通车", price: 25, unit: "星", tags: ["极速", "金牌打手"], sales: 5000 },
{ id: 2, title: "LOL-钻石大师晋级赛", price: 88, unit: "局", tags: ["包赢", "职业退役"], sales: 1200 },
{ id: 3, title: "无畏契约-赋能战神代打", price: 50, unit: "胜场", tags: ["MVP", "直播"], sales: 800 },
{ id: 4, title: "原神-深渊满星/神瞳全收集", price: 10, unit: "层", tags: ["纯手工", "安全"], sales: 3000 },
]
return (
<div className="min-h-screen pb-24 bg-background">
<header className="sticky top-0 z-40 glass px-4 py-3 flex items-center gap-3">
<button onClick={() => router.back()} className="text-white/70">
</button>
<h1 className="font-bold text-lg"></h1>
</header>
<div className="p-4 space-y-4">
{/* Banner */}
<div className="relative w-full h-32 rounded-xl overflow-hidden bg-gradient-to-r from-orange-500 to-red-600 flex items-center px-6">
<div className="relative z-10">
<h2 className="text-xl font-bold text-white mb-1"></h2>
<p className="text-xs text-white/80"> · · </p>
</div>
<Trophy className="absolute right-4 bottom-[-10px] text-white/20 w-24 h-24 rotate-12" />
</div>
{/* Filters */}
<div className="flex gap-2 overflow-x-auto no-scrollbar pb-2">
{["全部", "王者荣耀", "英雄联盟", "无畏契约", "和平精英", "永劫无间"].map((tag, i) => (
<button
key={i}
className={`whitespace-nowrap px-4 py-1.5 rounded-full text-xs font-medium ${i === 0 ? "bg-primary text-black" : "bg-white/5 text-white/60"}`}
>
{tag}
</button>
))}
</div>
{/* List */}
<div className="grid gap-3">
{boosters.map((item) => (
<div
key={item.id}
className="glass-card p-4 rounded-xl flex justify-between items-center hover:bg-white/10 transition-colors"
onClick={() => toast({ title: "下单成功", description: "打手将在5分钟内上号" })}
>
<div>
<h3 className="font-bold text-sm mb-1">{item.title}</h3>
<div className="flex gap-2 mb-2">
{item.tags.map((t, i) => (
<span
key={i}
className="text-[10px] px-1.5 py-0.5 rounded bg-white/5 text-white/50 border border-white/5"
>
{t}
</span>
))}
</div>
<div className="flex items-center gap-3 text-xs text-white/40">
<span className="flex items-center gap-1">
<Zap size={12} className="text-yellow-500" /> 15
</span>
<span className="flex items-center gap-1">
<Star size={12} className="text-primary" /> {item.sales}+
</span>
</div>
</div>
<div className="text-right">
<div className="text-lg font-bold text-primary">
¥{item.price}
<span className="text-xs text-white/50 font-normal">/{item.unit}</span>
</div>
<Button size="sm" className="h-7 text-xs mt-1">
</Button>
</div>
</div>
))}
</div>
</div>
</div>
)
}

91
app/zone/[id]/page.tsx Normal file
View File

@@ -0,0 +1,91 @@
import { ArrowLeft, Users, Trophy, Sword, Flame } from "lucide-react"
import Link from "next/link"
import Image from "next/image"
export default function ZonePage({ params }: { params: { id: string } }) {
const games: Record<string, any> = {
lol: { name: "英雄联盟", color: "blue", bg: "bg-blue-900", image: "league of legends map summoners rift" },
val: { name: "无畏契约", color: "red", bg: "bg-red-900", image: "valorant agent neon futuristic" },
hok: { name: "王者荣耀", color: "yellow", bg: "bg-yellow-900", image: "honor of kings hero battle" },
csgo: { name: "CS:GO", color: "orange", bg: "bg-orange-900", image: "csgo dust2 map tactical" },
wow: { name: "魔兽世界", color: "yellow", bg: "bg-stone-900", image: "world of warcraft azeroth landscape" },
}
const game = games[params.id] || games.lol
return (
<div className={`min-h-screen ${game.bg} text-white pb-24`}>
{/* Header */}
<div className="sticky top-0 z-40 p-4 flex items-center gap-4 bg-black/20 backdrop-blur-md">
<Link href="/" className="p-2 bg-white/10 rounded-full hover:bg-white/20">
<ArrowLeft size={20} />
</Link>
<h1 className="font-bold text-lg">{game.name}</h1>
</div>
{/* Hero */}
<div className="relative aspect-video w-full">
<Image
src={`/.jpg?key=zone-${params.id}&height=400&width=600&query=${game.image}`}
alt={game.name}
fill
className="object-cover"
/>
<div className="absolute inset-0 bg-gradient-to-t from-black via-transparent to-transparent" />
<div className="absolute bottom-4 left-4">
<h2 className="text-2xl font-bold mb-2">{game.name}</h2>
<div className="flex gap-2">
<span className="px-2 py-1 bg-white/10 rounded text-xs backdrop-blur-md"> 1205</span>
<span className="px-2 py-1 bg-white/10 rounded text-xs backdrop-blur-md"> 3.2</span>
</div>
</div>
</div>
{/* Quick Actions */}
<div className="grid grid-cols-4 gap-2 p-4">
<div className="bg-white/5 rounded-xl p-3 flex flex-col items-center gap-2">
<Sword className="text-white/80" />
<span className="text-xs"></span>
</div>
<div className="bg-white/5 rounded-xl p-3 flex flex-col items-center gap-2">
<Trophy className="text-white/80" />
<span className="text-xs"></span>
</div>
<div className="bg-white/5 rounded-xl p-3 flex flex-col items-center gap-2">
<Users className="text-white/80" />
<span className="text-xs"></span>
</div>
<div className="bg-white/5 rounded-xl p-3 flex flex-col items-center gap-2">
<Flame className="text-white/80" />
<span className="text-xs"></span>
</div>
</div>
{/* Content */}
<div className="px-4 space-y-4">
<h3 className="font-bold flex items-center gap-2">
<div className={`w-1 h-4 bg-${game.color}-500 rounded-full`} />
</h3>
{[1, 2, 3].map((i) => (
<div key={i} className="bg-white/5 rounded-xl p-4 flex items-center gap-4">
<div className="w-12 h-12 rounded-full bg-white/10 relative overflow-hidden">
<Image
src={`/.jpg?key=team-${i}&height=100&width=100&query=esports avatar ${game.name}`}
alt="Avatar"
fill
className="object-cover"
/>
</div>
<div className="flex-1">
<h4 className="font-bold text-sm"> 1</h4>
<p className="text-xs text-white/50"> | | </p>
</div>
<button className={`px-4 py-1.5 rounded-full bg-${game.color}-600 text-xs font-bold`}></button>
</div>
))}
</div>
</div>
)
}

View File

@@ -0,0 +1,329 @@
"use client"
import type React from "react"
import { useState } from "react"
import { motion, AnimatePresence } from "framer-motion"
import { Check, Info, Calculator, RefreshCw, Shield, Wallet, Upload, ImageIcon, X } from "lucide-react"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { Label } from "@/components/ui/label"
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"
import { GAME_CASCADE_DATA } from "@/lib/data/game-cascade-data"
import { DynamicCascadeForm } from "./dynamic-cascade-form"
import { evaluateAccount, type EvaluationResult } from "@/lib/utils/account-evaluation"
export function AccountEvaluation() {
const [step, setStep] = useState(1)
const [loading, setLoading] = useState(false)
const [result, setResult] = useState<EvaluationResult | null>(null)
const [showQRCode, setShowQRCode] = useState(false)
const [game, setGame] = useState("王者荣耀")
const [cascadeData, setCascadeData] = useState<Record<string, any>>({})
const [phoneNumber, setPhoneNumber] = useState("")
const [screenshots, setScreenshots] = useState<File[]>([])
const handleScreenshotUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
const files = e.target.files
if (files) {
const newFiles = Array.from(files).slice(0, 5 - screenshots.length)
setScreenshots([...screenshots, ...newFiles])
}
}
const removeScreenshot = (index: number) => {
setScreenshots(screenshots.filter((_, i) => i !== index))
}
const handleEvaluate = () => {
setLoading(true)
setTimeout(() => {
const res = evaluateAccount({
gameName: game,
serverType: "热门服",
currentLevel: 30,
maxLevel: 30,
rankName: "荣耀王者",
assetCounts: {
legendary: 0,
epic: 0,
rare: 0,
common: 0,
},
registrationMonths: 12,
rechargeAmount6Months: 0,
mallConsumption: 0,
isRealNameVerified: true,
isRealNameOwner: true,
hasBanRecord: false,
})
setResult(res)
setLoading(false)
setStep(2)
}, 1500)
}
const reset = () => {
setResult(null)
setStep(1)
}
const availableGames = Object.keys(GAME_CASCADE_DATA)
return (
<div className="w-full">
<AnimatePresence mode="wait">
{step === 1 ? (
<motion.div
key="form"
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
className="space-y-6 bg-[#1A1A2E] border border-white/5 rounded-3xl p-6 shadow-xl relative overflow-hidden"
>
<div className="absolute top-0 right-0 w-32 h-32 bg-blue-600/10 rounded-full blur-3xl -mr-10 -mt-10 pointer-events-none" />
<div className="absolute bottom-0 left-0 w-32 h-32 bg-purple-600/10 rounded-full blur-3xl -ml-10 -mb-10 pointer-events-none" />
<div className="space-y-5 relative z-10">
<div className="space-y-2">
<Label className="text-gray-400"></Label>
<Select value={game} onValueChange={setGame}>
<SelectTrigger className="bg-[#12121A] border-white/10 h-11 text-white focus:ring-1 focus:ring-blue-500">
<SelectValue />
</SelectTrigger>
<SelectContent className="bg-[#1A1A2E] border-white/10 text-white max-h-[300px]">
{availableGames.map((g) => (
<SelectItem key={g} value={g}>
{g}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<DynamicCascadeForm gameName={game} onDataChange={setCascadeData} />
<div className="space-y-3">
<Label className="text-gray-400 flex items-center gap-2">
<ImageIcon size={16} />
(5)
</Label>
<div className="border-2 border-dashed border-white/10 rounded-xl p-4 bg-[#12121A] hover:border-blue-500/30 transition-colors">
<input
type="file"
id="screenshot-upload"
multiple
accept="image/*"
onChange={handleScreenshotUpload}
className="hidden"
disabled={screenshots.length >= 5}
/>
<label
htmlFor="screenshot-upload"
className={`flex flex-col items-center justify-center gap-2 cursor-pointer ${
screenshots.length >= 5 ? "opacity-50 cursor-not-allowed" : ""
}`}
>
<div className="w-12 h-12 rounded-full bg-blue-500/10 flex items-center justify-center">
<Upload className="text-blue-400" size={24} />
</div>
<p className="text-sm text-gray-400">
{screenshots.length >= 5 ? "已达上传上限" : "点击上传账号截图"}
</p>
<p className="text-xs text-gray-600"> JPGPNG </p>
</label>
</div>
{screenshots.length > 0 && (
<div className="grid grid-cols-3 gap-3 mt-3">
{screenshots.map((file, index) => (
<div key={index} className="relative group">
<div className="aspect-square rounded-lg overflow-hidden bg-[#12121A] border border-white/10">
<img
src={URL.createObjectURL(file) || "/placeholder.svg"}
alt={`Screenshot ${index + 1}`}
className="w-full h-full object-cover"
/>
</div>
<button
onClick={() => removeScreenshot(index)}
className="absolute -top-2 -right-2 w-6 h-6 bg-red-500 rounded-full flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity"
>
<X size={14} className="text-white" />
</button>
</div>
))}
</div>
)}
</div>
<div className="space-y-2">
<Label className="text-gray-400"></Label>
<Input
type="tel"
value={phoneNumber}
onChange={(e) => setPhoneNumber(e.target.value)}
placeholder="请输入您的手机号码"
className="bg-[#12121A] border-white/10 h-11 text-white focus:border-blue-500"
/>
</div>
</div>
<Button
className="w-full h-14 text-lg font-bold bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-500 hover:to-purple-500 shadow-lg shadow-blue-500/20 rounded-xl mt-4"
onClick={handleEvaluate}
disabled={loading || !phoneNumber}
>
{loading ? (
<div className="flex items-center gap-2">
<RefreshCw className="animate-spin" size={20} />
...
</div>
) : (
<div className="flex items-center gap-2">
<Calculator size={20} />
</div>
)}
</Button>
<p className="text-center text-xs text-white/30 flex items-center justify-center gap-1.5 mt-4">
<Shield size={12} />
</p>
</motion.div>
) : (
<motion.div
key="result"
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
className="bg-[#1A1A2E] border border-white/10 rounded-3xl overflow-hidden shadow-2xl"
>
<div className="bg-gradient-to-br from-[#1A1A2E] to-black p-6 relative overflow-hidden">
<div className="absolute top-0 right-0 w-64 h-64 bg-blue-600/10 rounded-full blur-3xl -mr-16 -mt-16 pointer-events-none" />
<div className="absolute bottom-0 left-0 w-64 h-64 bg-purple-600/10 rounded-full blur-3xl -ml-16 -mb-16 pointer-events-none" />
<div className="relative z-10">
<div className="flex justify-between items-start mb-6">
<div>
<h2 className="text-gray-400 text-sm mb-1.5"></h2>
<div className="text-5xl font-bold text-white flex items-end gap-3 tracking-tight">
¥ {result?.valuation.toLocaleString()}
<span className="text-sm text-gray-500 font-medium mb-2">CNY</span>
</div>
</div>
<div className="text-right">
<div className="inline-flex items-center gap-1.5 bg-[#0A2F1F] text-green-400 px-4 py-1.5 rounded-full text-xs font-bold border border-green-500/20">
<Check size={12} />
</div>
</div>
</div>
<div className="bg-gradient-to-r from-yellow-900/20 to-orange-900/20 border border-yellow-500/20 rounded-2xl p-5 mb-8 relative overflow-hidden">
<div className="absolute inset-0 bg-yellow-500/5" />
<div className="relative z-10">
<div className="flex justify-between items-center mb-3">
<div className="text-yellow-100 font-bold flex items-center gap-2 text-lg">
<Wallet size={20} className="text-yellow-500" />
{result?.details.consumptionMultiplier && result.details.consumptionMultiplier > 1 && (
<span className="text-[10px] px-2 py-0.5 bg-red-500/20 text-red-300 rounded-full border border-red-500/30">
+{((result.details.consumptionMultiplier - 1) * 100).toFixed(0)}%
</span>
)}
</div>
<div className="text-3xl font-bold text-yellow-400">¥ {result?.loanAmount.toLocaleString()}</div>
</div>
<div className="w-full bg-black/40 h-2 rounded-full overflow-hidden">
<div
className="bg-yellow-500 h-full rounded-full shadow-[0_0_10px_rgba(234,179,8,0.5)]"
style={{ width: "60%" }}
/>
</div>
<p className="text-xs text-yellow-200/50 mt-3 flex items-center gap-1.5">
<Info size={12} />
60%, 5
</p>
</div>
</div>
<div className="grid grid-cols-2 gap-4 mb-8">
<div className="bg-[#12121A] p-4 rounded-xl border border-white/5">
<div className="text-xs text-gray-500 mb-1.5"></div>
<div className="text-xl font-bold text-white">
{result?.details.baseScore} <span className="text-xs text-gray-600 font-normal">/ 50</span>
</div>
</div>
<div className="bg-[#12121A] p-4 rounded-xl border border-white/5">
<div className="text-xs text-gray-500 mb-1.5"></div>
<div className="text-xl font-bold text-white">
{result?.details.assetScore} <span className="text-xs text-gray-600 font-normal">/ 30</span>
</div>
</div>
<div className="bg-[#12121A] p-4 rounded-xl border border-white/5">
<div className="text-xs text-gray-500 mb-1.5"></div>
<div className="text-xl font-bold text-white">
{result?.details.bonusScore} <span className="text-xs text-gray-600 font-normal">/ 20</span>
</div>
</div>
<div className="bg-[#12121A] p-4 rounded-xl border border-white/5">
<div className="text-xs text-gray-500 mb-1.5"></div>
<div
className={`text-xl font-bold ${result?.details.riskCoefficient === 1.2 ? "text-green-400" : "text-yellow-400"}`}
>
×{result?.details.riskCoefficient}
</div>
</div>
</div>
<div className="flex gap-4">
<Button
variant="outline"
className="flex-1 bg-transparent border-white/10 text-white hover:bg-white/5 h-12 rounded-xl"
onClick={reset}
>
</Button>
<Button
className="flex-1 bg-yellow-500 hover:bg-yellow-400 text-black font-bold h-12 rounded-xl text-base shadow-[0_0_20px_rgba(234,179,8,0.2)]"
onClick={() => setShowQRCode(true)}
>
</Button>
</div>
</div>
</div>
</motion.div>
)}
</AnimatePresence>
<Dialog open={showQRCode} onOpenChange={setShowQRCode}>
<DialogContent className="bg-[#1A1A2E] border-white/10 text-white max-w-sm">
<DialogHeader>
<DialogTitle className="text-center text-xl font-bold"></DialogTitle>
</DialogHeader>
<div className="flex flex-col items-center gap-4 py-6">
<div className="w-48 h-48 bg-white rounded-xl p-4 flex items-center justify-center">
<div className="text-center text-black text-xs">
<div className="w-40 h-40 bg-gradient-to-br from-green-400 to-green-600 rounded-lg flex items-center justify-center mb-2">
<span className="text-white text-4xl font-bold"></span>
</div>
</div>
</div>
<div className="text-center space-y-2">
<p className="text-gray-400 text-sm"></p>
<p className="text-white font-bold">预计放款时间: 5-10</p>
<p className="text-xs text-gray-500">客服工作时间: 9:00 - 22:00</p>
</div>
</div>
</DialogContent>
</Dialog>
</div>
)
}

View File

@@ -0,0 +1,204 @@
"use client"
import { useState, useEffect } from "react"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { Label } from "@/components/ui/label"
import { Input } from "@/components/ui/input"
import { Checkbox } from "@/components/ui/checkbox"
import { GAME_CASCADE_DATA } from "@/lib/data/game-cascade-data"
type DynamicCascadeFormProps = {
gameName: string
onDataChange: (data: Record<string, any>) => void
}
export function DynamicCascadeForm({ gameName, onDataChange }: DynamicCascadeFormProps) {
const config = GAME_CASCADE_DATA[gameName]
const [selectedRegion, setSelectedRegion] = useState<string>("")
const [selectedServer, setSelectedServer] = useState<string>("")
const [selectedSubOption, setSelectedSubOption] = useState<string>("")
const [attributeValues, setAttributeValues] = useState<Record<string, any>>({})
useEffect(() => {
// Reset when game changes
setSelectedRegion("")
setSelectedServer("")
setSelectedSubOption("")
setAttributeValues({})
}, [gameName])
useEffect(() => {
// Notify parent of data changes
onDataChange({
region: selectedRegion,
server: selectedServer,
subOption: selectedSubOption,
...attributeValues,
})
}, [selectedRegion, selectedServer, selectedSubOption, attributeValues, onDataChange])
if (!config) {
return <div className="text-gray-400 text-sm"></div>
}
const currentRegion = config.regions.find((r) => r.value === selectedRegion)
const serverOptions = currentRegion?.children || []
const currentServer = serverOptions.find((s) => s.value === selectedServer)
const subOptions = currentServer?.children || []
const handleAttributeChange = (label: string, value: any) => {
setAttributeValues((prev) => ({ ...prev, [label]: value }))
}
const handleMultiSelectChange = (label: string, option: string, checked: boolean) => {
setAttributeValues((prev) => {
const current = prev[label] || []
if (checked) {
return { ...prev, [label]: [...current, option] }
} else {
return { ...prev, [label]: current.filter((v: string) => v !== option) }
}
})
}
return (
<div className="space-y-5">
{/* Region Selection */}
{config.regions.length > 0 && (
<div className="space-y-2">
<Label className="text-gray-400"></Label>
<Select
value={selectedRegion}
onValueChange={(v) => {
setSelectedRegion(v)
setSelectedServer("")
setSelectedSubOption("")
}}
>
<SelectTrigger className="bg-[#12121A] border-white/10 h-11 text-white focus:ring-1 focus:ring-blue-500">
<SelectValue placeholder="选择游戏区" />
</SelectTrigger>
<SelectContent className="bg-[#1A1A2E] border-white/10 text-white">
{config.regions.map((region) => (
<SelectItem key={region.value} value={region.value}>
{region.label}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
)}
{/* Server Selection */}
{serverOptions.length > 0 && (
<div className="space-y-2">
<Label className="text-gray-400"></Label>
<Select
value={selectedServer}
onValueChange={(v) => {
setSelectedServer(v)
setSelectedSubOption("")
}}
disabled={!selectedRegion}
>
<SelectTrigger className="bg-[#12121A] border-white/10 h-11 text-white focus:ring-1 focus:ring-blue-500">
<SelectValue placeholder="选择服务器" />
</SelectTrigger>
<SelectContent className="bg-[#1A1A2E] border-white/10 text-white max-h-[300px]">
{serverOptions.map((server) => (
<SelectItem key={server.value} value={server.value}>
{server.label}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
)}
{/* Sub Options */}
{subOptions.length > 0 && (
<div className="space-y-2">
<Label className="text-gray-400"></Label>
<Select value={selectedSubOption} onValueChange={setSelectedSubOption} disabled={!selectedServer}>
<SelectTrigger className="bg-[#12121A] border-white/10 h-11 text-white focus:ring-1 focus:ring-blue-500">
<SelectValue placeholder="选择属性" />
</SelectTrigger>
<SelectContent className="bg-[#1A1A2E] border-white/10 text-white">
{subOptions.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
)}
{/* Dynamic Attributes */}
{config.attributes.map((attr, index) => (
<div key={index} className="space-y-2">
<Label className="text-gray-400">
{attr.label}
{attr.required && <span className="text-red-400 ml-1">*</span>}
</Label>
{attr.type === "select" && attr.options && (
<Select
value={attributeValues[attr.label] || ""}
onValueChange={(v) => handleAttributeChange(attr.label, v)}
>
<SelectTrigger className="bg-[#12121A] border-white/10 h-11 text-white focus:ring-1 focus:ring-purple-500">
<SelectValue placeholder={`选择${attr.label}`} />
</SelectTrigger>
<SelectContent className="bg-[#1A1A2E] border-white/10 text-white max-h-[300px]">
{attr.options.map((option) => (
<SelectItem key={option} value={option}>
{option}
</SelectItem>
))}
</SelectContent>
</Select>
)}
{attr.type === "multiselect" && attr.options && (
<div className="bg-[#12121A] border border-white/10 rounded-lg p-4 max-h-[200px] overflow-y-auto space-y-2">
{attr.options.slice(0, 20).map((option) => (
<div key={option} className="flex items-center space-x-2">
<Checkbox
id={`${attr.label}-${option}`}
checked={(attributeValues[attr.label] || []).includes(option)}
onCheckedChange={(checked) => handleMultiSelectChange(attr.label, option, checked as boolean)}
className="border-white/20"
/>
<label htmlFor={`${attr.label}-${option}`} className="text-sm text-gray-300 cursor-pointer">
{option}
</label>
</div>
))}
{attr.options.length > 20 && <div className="text-xs text-gray-500 text-center pt-2">20...</div>}
</div>
)}
{attr.type === "input" && (
<Input
value={attributeValues[attr.label] || ""}
onChange={(e) => handleAttributeChange(attr.label, e.target.value)}
placeholder={`输入${attr.label}`}
className="bg-[#12121A] border-white/10 h-11 text-white focus:border-purple-500"
/>
)}
</div>
))}
{/* Screenshot Upload Hint */}
<div className="bg-[#12121A] border border-blue-500/20 rounded-lg p-3 mt-4">
<p className="text-xs text-blue-300 flex items-center gap-2">
<span className="text-blue-500">📸</span>
便
</p>
</div>
</div>
)
}

View File

@@ -0,0 +1,86 @@
"use client"
const PLAYERS = [
{
id: "1",
name: "GM-远洋",
title: "国服第一盲僧",
price: "50币/局",
image: "/avatar-1.jpg",
isLive: true,
viewers: "12.5w",
orders: 1205,
fans: "4.6w",
rank: "最强王者",
intro: "前职业选手S9国服第一盲僧擅长打野节奏带动包教包会。人皮话多心态极好带你体验飞一般的感觉",
},
{
id: "2",
name: "卡若",
title: "职业战队退役",
price: "80币/局",
image: "/avatar-2.jpg",
isLive: true,
viewers: "8.2w",
orders: 890,
fans: "3.2w",
rank: "峡谷之巅 800点",
intro: "退役职业选手,意识超群,指挥型打野。想学运营和意识的来,带你躺赢。",
},
{
id: "3",
name: "小柠檬",
title: "人皮话多",
price: "30币/局",
image: "/avatar-3.jpg",
isLive: false,
viewers: "0",
orders: 3400,
fans: "15.8w",
rank: "超凡大师",
intro: "声音甜美,心态好,不压力队友。主玩软辅,保护型辅助,给你最贴心的守护。",
},
{
id: "4",
name: "阿伟",
title: "绝地求生刚枪王",
price: "60币/局",
image: "/avatar-4.jpg",
isLive: false,
viewers: "0",
orders: 560,
fans: "1.2w",
rank: "亚服前500",
intro: "亚服路人王,刚枪猛男,带你吃鸡带你飞。车技一流,意识无敌。",
},
{
id: "5",
name: "七七",
title: "甜美声优",
price: "40币/局",
image: "/avatar-5.jpg",
isLive: true,
viewers: "5.6w",
orders: 2100,
fans: "8.9w",
rank: "钻石I",
intro: "全能补位,声优出道。不仅游戏打得好,唱歌还好听,快来点我吧!",
},
{
id: "6",
name: "大魔王",
title: "中单法王",
price: "100币/局",
image: "/avatar-6.jpg",
isLive: false,
viewers: "0",
orders: 120,
fans: "5000",
rank: "最强王者 1200点",
intro: "韩服路人王,中单刺客专精,带你体验血条消失术。",
},
]
export function StarPlayerSection() {
return null
}

View File

@@ -0,0 +1,32 @@
'use client'
import * as React from 'react'
import * as CheckboxPrimitive from '@radix-ui/react-checkbox'
import { CheckIcon } from 'lucide-react'
import { cn } from '@/lib/utils'
function Checkbox({
className,
...props
}: React.ComponentProps<typeof CheckboxPrimitive.Root>) {
return (
<CheckboxPrimitive.Root
data-slot="checkbox"
className={cn(
'peer border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50',
className,
)}
{...props}
>
<CheckboxPrimitive.Indicator
data-slot="checkbox-indicator"
className="flex items-center justify-center text-current transition-none"
>
<CheckIcon className="size-3.5" />
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
)
}
export { Checkbox }

24
components/ui/label.tsx Normal file
View File

@@ -0,0 +1,24 @@
'use client'
import * as React from 'react'
import * as LabelPrimitive from '@radix-ui/react-label'
import { cn } from '@/lib/utils'
function Label({
className,
...props
}: React.ComponentProps<typeof LabelPrimitive.Root>) {
return (
<LabelPrimitive.Root
data-slot="label"
className={cn(
'flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50',
className,
)}
{...props}
/>
)
}
export { Label }

185
components/ui/select.tsx Normal file
View File

@@ -0,0 +1,185 @@
'use client'
import * as React from 'react'
import * as SelectPrimitive from '@radix-ui/react-select'
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from 'lucide-react'
import { cn } from '@/lib/utils'
function Select({
...props
}: React.ComponentProps<typeof SelectPrimitive.Root>) {
return <SelectPrimitive.Root data-slot="select" {...props} />
}
function SelectGroup({
...props
}: React.ComponentProps<typeof SelectPrimitive.Group>) {
return <SelectPrimitive.Group data-slot="select-group" {...props} />
}
function SelectValue({
...props
}: React.ComponentProps<typeof SelectPrimitive.Value>) {
return <SelectPrimitive.Value data-slot="select-value" {...props} />
}
function SelectTrigger({
className,
size = 'default',
children,
...props
}: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
size?: 'sm' | 'default'
}) {
return (
<SelectPrimitive.Trigger
data-slot="select-trigger"
data-size={size}
className={cn(
"border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className,
)}
{...props}
>
{children}
<SelectPrimitive.Icon asChild>
<ChevronDownIcon className="size-4 opacity-50" />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
)
}
function SelectContent({
className,
children,
position = 'popper',
...props
}: React.ComponentProps<typeof SelectPrimitive.Content>) {
return (
<SelectPrimitive.Portal>
<SelectPrimitive.Content
data-slot="select-content"
className={cn(
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md',
position === 'popper' &&
'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',
className,
)}
position={position}
{...props}
>
<SelectScrollUpButton />
<SelectPrimitive.Viewport
className={cn(
'p-1',
position === 'popper' &&
'h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1',
)}
>
{children}
</SelectPrimitive.Viewport>
<SelectScrollDownButton />
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
)
}
function SelectLabel({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.Label>) {
return (
<SelectPrimitive.Label
data-slot="select-label"
className={cn('text-muted-foreground px-2 py-1.5 text-xs', className)}
{...props}
/>
)
}
function SelectItem({
className,
children,
...props
}: React.ComponentProps<typeof SelectPrimitive.Item>) {
return (
<SelectPrimitive.Item
data-slot="select-item"
className={cn(
"focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
className,
)}
{...props}
>
<span className="absolute right-2 flex size-3.5 items-center justify-center">
<SelectPrimitive.ItemIndicator>
<CheckIcon className="size-4" />
</SelectPrimitive.ItemIndicator>
</span>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item>
)
}
function SelectSeparator({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.Separator>) {
return (
<SelectPrimitive.Separator
data-slot="select-separator"
className={cn('bg-border pointer-events-none -mx-1 my-1 h-px', className)}
{...props}
/>
)
}
function SelectScrollUpButton({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {
return (
<SelectPrimitive.ScrollUpButton
data-slot="select-scroll-up-button"
className={cn(
'flex cursor-default items-center justify-center py-1',
className,
)}
{...props}
>
<ChevronUpIcon className="size-4" />
</SelectPrimitive.ScrollUpButton>
)
}
function SelectScrollDownButton({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {
return (
<SelectPrimitive.ScrollDownButton
data-slot="select-scroll-down-button"
className={cn(
'flex cursor-default items-center justify-center py-1',
className,
)}
{...props}
>
<ChevronDownIcon className="size-4" />
</SelectPrimitive.ScrollDownButton>
)
}
export {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectScrollDownButton,
SelectScrollUpButton,
SelectSeparator,
SelectTrigger,
SelectValue,
}

View File

@@ -0,0 +1,540 @@
export type CascadeLevel = {
value: string
label: string
children?: CascadeLevel[]
}
export type GameCascadeConfig = {
productType: string // 商品类型
regions: CascadeLevel[] // 游戏区
attributes: {
label: string
type: "select" | "multiselect" | "input" | "slider"
options?: string[]
required?: boolean
}[]
}
export const GAME_CASCADE_DATA: Record<string, GameCascadeConfig> = {
: {
productType: "账号",
regions: [
{
value: "苹果",
label: "苹果",
children: [
{
value: "全区全服",
label: "全区全服",
children: [
{ value: "可二次", label: "可二次实名" },
{ value: "不可二次", label: "不可二次实名" },
],
},
],
},
{
value: "安卓",
label: "安卓",
children: [
{
value: "全区全服",
label: "全区全服",
children: [
{ value: "可二次", label: "可二次实名" },
{ value: "不可二次", label: "不可二次实名" },
],
},
],
},
{
value: "steam",
label: "steam",
children: [{ value: "全区全服", label: "全区全服" }],
},
],
attributes: [],
},
西: {
productType: "账号",
regions: [
{
value: "畅玩服",
label: "畅玩服",
children: [
{ value: "建邺城", label: "建邺城" },
{ value: "龙拳", label: "龙拳" },
{ value: "横扫千军", label: "横扫千军" },
{ value: "龙卷雨击", label: "龙卷雨击" },
{ value: "纵横天下", label: "纵横天下" },
{ value: "紫气东来", label: "紫气东来" },
{ value: "无与伦比", label: "无与伦比" },
{ value: "紫禁之巅", label: "紫禁之巅" },
],
},
],
attributes: [
{
label: "门派",
type: "select",
options: [
"大唐官府",
"龙宫",
"化生寺",
"盘丝洞",
"普陀山",
"天宫",
"女儿村",
"五庄观",
"方寸山",
"魔王寨",
"阴曹地府",
"狮驼岭",
"无",
],
required: true,
},
{
label: "角色性别",
type: "select",
options: ["男", "女"],
},
{
label: "尊享权益",
type: "select",
options: ["超级绿通2000元", "高级绿通540元", "中级绿通90元", "无"],
},
{
label: "整组号",
type: "select",
options: ["是", "否"],
},
],
},
: {
productType: "账号",
regions: [
{
value: "360国服",
label: "360国服",
},
{
value: "其他服",
label: "其他服",
},
],
attributes: [
{
label: "有无违规插件",
type: "select",
options: ["无", "有", "未查询未知"],
required: true,
},
{
label: "十级高级车辆",
type: "multiselect",
options: [
"无",
"VK7201",
"07P(E)主战方案",
"WT12",
"777工程机动型",
"260工程",
"780工程",
"T-22",
"907工程",
"268工程V型",
"T95E6",
"M60",
"113BO",
"WZ111QL",
"齐天大圣",
"121B",
"T95酋长",
"酋长MK.VI",
],
},
{
label: "九级高级车辆",
type: "multiselect",
options: [
"无",
"50吨主战方案",
"昆泽试验车",
"T-55A",
"07HK主战方案3型",
"777工程II型",
"283工程",
"K-91II型",
"AE一期",
"1B概念车",
"WZ114",
],
},
{
label: "八级高级车辆",
type: "multiselect",
options: [
"无",
"59式",
"黄金59式",
"BZ-176",
"703工程II型",
"E-75TS",
"IS-3A",
"SU-130PM",
"T-44-100",
"超级潘兴",
"天蝎",
"克莱斯勒K",
],
},
],
},
穿线: {
productType: "账号",
regions: [
{
value: "电信",
label: "电信",
children: [
{ value: "安徽一区", label: "安徽一区" },
{ value: "福建一区", label: "福建一区" },
{ value: "广东一区", label: "广东一区" },
{ value: "广东二区", label: "广东二区" },
{ value: "湖北一区", label: "湖北一区" },
{ value: "江苏一区", label: "江苏一区" },
{ value: "上海一区", label: "上海一区" },
{ value: "四川一区", label: "四川一区" },
{ value: "浙江一区", label: "浙江一区" },
],
},
{
value: "网通",
label: "网通",
children: [
{ value: "北京一区", label: "北京一区" },
{ value: "北京二区", label: "北京二区" },
{ value: "河南一区", label: "河南一区" },
{ value: "辽宁一区", label: "辽宁一区" },
{ value: "山东一区", label: "山东一区" },
],
},
],
attributes: [
{
label: "英雄武器",
type: "select",
options: ["无英雄武器", "1-5V", "6-10V", "11-20V", "20-30V", "30V以上"],
required: true,
},
{
label: "所属战区",
type: "select",
options: [
"华北战区",
"河南战区",
"山东战区",
"东北战区",
"华中战区",
"华南战区",
"西部战区",
"东部战区",
"江苏战区",
],
},
{
label: "排位级别",
type: "select",
options: ["无排位", "新锐", "精英", "专家", "大师", "宗师", "枪王", "枪王之王"],
},
{
label: "实名修改",
type: "select",
options: ["可修改实名", "不可修改实名"],
},
],
},
耀: {
productType: "账号",
regions: [
{
value: "苹果",
label: "苹果",
children: [
{ value: "苹果QQ", label: "苹果QQ" },
{ value: "苹果微信", label: "苹果微信" },
],
},
{
value: "安卓",
label: "安卓",
children: [
{ value: "安卓QQ", label: "安卓QQ" },
{ value: "安卓微信", label: "安卓微信" },
],
},
],
attributes: [
{
label: "防沉迷",
type: "select",
options: ["无防沉迷", "有防沉迷"],
required: true,
},
{
label: "实名修改",
type: "select",
options: ["可修改", "不可修改"],
required: true,
},
{
label: "荣耀水晶皮肤",
type: "multiselect",
options: [
"倪克斯神谕-武则天",
"九霄神辉-花木兰",
"大秦宣太后-芈月",
"鸣剑曳影-李白",
"幻阙歌-貂蝉",
"星空梦想-鲁班七号",
"赤影疾锋-关羽",
"天鹅之梦-小乔",
"全息碎影-孙悟空",
"银白咏叹调-铠",
],
},
{
label: "传说皮肤",
type: "multiselect",
options: [
"音你心动-小乔",
"淬星耀世-赵云",
"末日机甲-孙尚香",
"天魔缭乱-吕布",
"凤求凰-李白",
"武圣-关羽",
"仲夏夜之梦-貂蝉",
"地狱火-孙悟空",
"齐天大圣-孙悟空",
],
},
],
},
: {
productType: "账号",
regions: [
{
value: "安卓",
label: "安卓",
children: [{ value: "QQ", label: "QQ" }],
},
{
value: "苹果",
label: "苹果",
children: [{ value: "微信", label: "微信" }],
},
],
attributes: [
{
label: "热门载具",
type: "multiselect",
options: [
"阿斯顿·马丁DBS(红)",
"阿斯顿·马丁DBX(橙)",
"鬼马风驰",
"海龙奔腾",
"金龙耀世",
"兰博基尼URUS(炫光金)",
"玛莎拉蒂MC20(幻星粉)",
"迈凯伦570S(炫冰蓝)",
"特斯拉Cybertruck(镭光铂)",
"焰龙咆哮",
],
},
{
label: "热门枪械",
type: "multiselect",
options: [
"AKM百合蜜语",
"AKM-盖世英雄",
"AWM花间之火",
"AWM焰龙焚天",
"GROZA焦糖拿铁",
"Kar98K圣光天使",
"M416风花雪月",
"M416五爪金龙",
"SCAR-L-纯梦嫁纱",
"UMP45相思残雪",
],
},
],
},
: {
productType: "账号",
regions: [
{
value: "mihoyo服",
label: "mihoyo服",
children: [{ value: "天空服", label: "天空服" }],
},
{
value: "B服",
label: "B服",
children: [{ value: "B服", label: "B服" }],
},
{
value: "国际服",
label: "国际服",
children: [{ value: "国际服", label: "国际服" }],
},
],
attributes: [
{
label: "邮箱",
type: "select",
options: ["未绑定邮箱", "网易邮箱出售", "QQ邮箱", "字母Q邮箱", "其他邮箱", "邮箱不出售"],
required: true,
},
{
label: "邮箱实名",
type: "select",
options: ["已实名", "未实名"],
required: true,
},
{
label: "五星角色",
type: "multiselect",
options: [
"林尼",
"艾尔海森",
"赛诺",
"妮露",
"纳西妲",
"流浪者",
"琴",
"迪卢克",
"七七",
"莫娜",
"刻晴",
"钟离",
"甘雨",
"胡桃",
"雷电将军",
"枫原万叶",
"神里绫华",
"那维莱特",
"芙宁娜",
],
},
{
label: "五星武器",
type: "multiselect",
options: [
"磐岩结绿",
"猎人之径",
"圣显之匙",
"千夜浮梦",
"风鹰剑",
"天空之刃",
"狼的末路",
"护摩之杖",
"雾切之回光",
"薙草之稻光",
"苍古自由之誓",
"阿莫斯之弓",
],
},
],
},
穿线: {
productType: "账号",
regions: [
{
value: "安卓",
label: "安卓",
children: [
{ value: "安卓QQ", label: "安卓QQ" },
{ value: "安卓微信", label: "安卓微信" },
],
},
{
value: "苹果",
label: "苹果",
children: [
{ value: "苹果QQ", label: "苹果QQ" },
{ value: "苹果微信", label: "苹果微信" },
],
},
],
attributes: [
{
label: "客户端",
type: "select",
options: ["全部", "苹果微信", "苹果QQ", "安卓微信", "安卓QQ", "全服通用"],
},
{
label: "实名修改",
type: "select",
options: ["可修改", "不可修改"],
required: true,
},
],
},
: {
productType: "账号",
regions: [
{
value: "亚服",
label: "亚服",
children: [
{
value: "大地的裂变",
label: "大地的裂变",
children: [
{ value: "伊弗斯", label: "伊弗斯" },
{ value: "瑪拉頓", label: "瑪拉頓" },
{ value: "阿拉希盆地", label: "阿拉希盆地" },
{ value: "鱼人", label: "鱼人" },
],
},
{
value: "正式服",
label: "正式服",
children: [
{ value: "暗影之月", label: "暗影之月" },
{ value: "众星之子", label: "众星之子" },
{ value: "地狱吼", label: "地狱吼" },
{ value: "世界之树", label: "世界之树" },
],
},
],
},
{
value: "国服",
label: "国服",
children: [
{
value: "正式服",
label: "正式服",
children: [
{ value: "回音山", label: "回音山" },
{ value: "巨龙之吼", label: "巨龙之吼" },
{ value: "索瑞森", label: "索瑞森" },
{ value: "罗宁", label: "罗宁" },
],
},
],
},
],
attributes: [],
},
}

200
lib/data/game-data.ts Normal file
View File

@@ -0,0 +1,200 @@
export type GameConfig = {
score: number
versions?: Record<string, string[]> // Version -> Servers
ranks?: Record<string, number>
}
export const GAME_DATA = {
// Game Value Configuration
games: {
耀: {
score: 30,
versions: {
: ["微信区", "QQ区"],
},
ranks: {
荣耀王者: 10,
最强王者: 8,
至尊星耀: 5,
永恒钻石: 3,
尊贵铂金: 1,
黄金及以下: 0,
},
},
: {
score: 30,
versions: {
: ["天空岛", "世界树"],
},
ranks: {
"60级": 10,
"55-59级": 8,
"50-54级": 5,
"45-49级": 3,
"45级以下": 0,
},
},
: {
score: 25,
versions: {
: ["微信区", "QQ区"],
},
ranks: {
无敌战神: 10,
超级王牌: 8,
荣耀皇冠: 5,
不朽星钻: 3,
星钻以下: 0,
},
},
: {
score: 30,
versions: {
: ["伊弗斯", "瑪拉頓", "阿拉希盆地", "鱼人", "逐风者", "古雷曼格", "乌苏雷", "札里克"],
: [
"暗影之月",
"众星之子",
"地狱吼",
"狂热之刃",
"阿萨斯",
"冰霜之刺",
"米奈希尔",
"血之谷",
"夜空之歌",
"巨龙之喉",
"世界之树",
"屠魔山谷",
"水晶之刺",
],
: [
"死亡之翼",
"白银之手",
"罗宁",
"主宰之剑",
"格瑞姆巴托",
"安苏",
"熊猫酒仙",
"国王之谷",
"燃烧之刃",
"影之哀伤",
],
怀: [
"哈霍兰",
"奥罗",
"匕首岭",
"布鲁",
"范克瑞斯",
"席瓦莱恩",
"碧玉矿洞",
"寒脊山小径",
"祈福",
"灰烬使者",
],
},
ranks: {
"角斗士/名人堂": 10,
"高阶督军/大元帅": 8,
"督军/元帅": 5,
"统帅/将军": 3,
普通: 0,
},
},
: {
score: 30,
versions: {
: [
"艾欧尼亚",
"祖安",
"诺克萨斯",
"班德尔城",
"皮城警备",
"战争学院",
"巨神峰",
"雷瑟守备",
"裁决之地",
"黑色玫瑰",
"暗影岛",
"钢铁烈阳",
"水晶之痕",
"均衡教派",
"影流",
"守望之海",
"征服之海",
"卡拉曼达",
"皮城警备",
],
: ["比尔吉沃特", "德玛西亚", "弗雷尔卓德", "无畏先锋", "恕瑞玛", "扭曲丛林", "巨龙之巢"],
},
ranks: {
最强王者: 10,
傲世宗师: 8,
超凡大师: 6,
璀璨钻石: 4,
华贵铂金: 2,
铂金以下: 0,
},
},
: {
score: 25,
versions: {
: ["重庆", "南京", "天津", "广州"],
},
ranks: {
赋能战魂: 10,
神话: 8,
超凡入圣: 6,
钻石: 4,
铂金: 2,
铂金以下: 0,
},
},
"CS:GO": {
score: 25,
versions: {
: ["完美世界"],
},
ranks: {
大地球: 10,
小地球: 8,
老鹰: 6,
菊花: 4,
AK: 2,
AK以下: 0,
},
},
} as Record<string, GameConfig>,
// Server Value Configuration
server_scores: {
热门服: 10, // Hot/New servers
普通服: 8, // Normal servers
鬼服: 5, // Low population
} as Record<string, number>,
// Asset Weight Configuration
asset_weights: {
传说级: 1.5, // Legendary
史诗级: 1.0, // Epic
优质级: 0.5, // Rare
普通级: 0.2, // Common
} as Record<string, number>,
// Risk Control Coefficient
correction_coeffs: {
本人_无封号: 1.2,
本人_有封号: 1.0,
非本人_无封号: 0.8,
非本人_有封号: 0.6,
未实名: 0.7,
} as Record<string, number>,
consumption_weights: {
level1: 0, // 0-100
level2: 1.05, // 100-500
level3: 1.1, // 500-2000
level4: 1.2, // 2000+
} as Record<string, number>,
// Base multiplier for final valuation (converts score to currency)
base_multiple: 100,
}

View File

@@ -0,0 +1,133 @@
import { GAME_DATA } from "@/lib/data/game-data"
export type EvaluationInput = {
gameName: string
serverType: string // "热门服" | "普通服" | "鬼服"
currentLevel: number
maxLevel: number
rankName: string
// Assets
assetCounts: {
legendary: number // 传说级
epic: number // 史诗级
rare: number // 优质级
common: number // 普通级
}
// Bonus
registrationMonths: number
rechargeAmount6Months: number
mallConsumption?: number // Added mall consumption parameter
// Risk
isRealNameVerified: boolean
isRealNameOwner: boolean
hasBanRecord: boolean
}
export type EvaluationResult = {
totalScore: number
valuation: number // Preliminary valuation
loanAmount: number // Loan amount
details: {
baseScore: number
assetScore: number
bonusScore: number
riskCoefficient: number
consumptionMultiplier: number // Added consumption multiplier to result
}
}
export function evaluateAccount(input: EvaluationInput): EvaluationResult {
const gameConfig = GAME_DATA.games[input.gameName] || { score: 20, ranks: {} }
// 1. Base Value Score (Max 50)
// Game Score (Max 30)
const gameScore = gameConfig.score
// Server Score (Max 10)
const serverScore = GAME_DATA.server_scores[input.serverType] || 8
// Level Score (Max 10)
// Score = (Current / Max) * 10
const levelRatio = Math.min(input.currentLevel / (input.maxLevel || 100), 1)
const levelScore = Number.parseFloat((levelRatio * 10).toFixed(1))
const baseScore = gameScore + serverScore + levelScore
// 2. Core Asset Score (Max 30)
// Asset Score = Sum(Count * Weight)
// Cap count at 20 to avoid outliers as per requirements
const getAssetScore = (count: number, weight: number) => Math.min(count, 20) * weight
const assetBaseScore =
getAssetScore(input.assetCounts.legendary, GAME_DATA.asset_weights["传说级"]) +
getAssetScore(input.assetCounts.epic, GAME_DATA.asset_weights["史诗级"]) +
getAssetScore(input.assetCounts.rare, GAME_DATA.asset_weights["优质级"]) +
getAssetScore(input.assetCounts.common, GAME_DATA.asset_weights["普通级"])
// Rank Bonus
const rankBonus = gameConfig.ranks ? gameConfig.ranks[input.rankName] || 0 : 0
const coreAssetScore = Math.min(assetBaseScore + rankBonus, 30)
// 3. Bonus Score (Max 20)
let timeScore = 0
if (input.registrationMonths >= 25) timeScore = 10
else if (input.registrationMonths >= 13) timeScore = 8
else if (input.registrationMonths >= 7) timeScore = 5
else timeScore = 3
// Recharge Amount (Max 10)
// Score = min(Amount * 0.001, 10)
const rechargeScore = Math.min(input.rechargeAmount6Months * 0.001, 10)
const bonusScore = timeScore + rechargeScore
// 4. Risk Correction Coefficient
let riskKey = "未实名"
if (input.isRealNameVerified) {
if (input.isRealNameOwner) {
riskKey = input.hasBanRecord ? "本人_有封号" : "本人_无封号"
} else {
riskKey = input.hasBanRecord ? "非本人_有封号" : "非本人_无封号"
}
}
const riskCoefficient = GAME_DATA.correction_coeffs[riskKey] || 0.7
let consumptionMultiplier = 1.0
const consumption = input.mallConsumption || 0
if (consumption >= 2000) {
consumptionMultiplier = GAME_DATA.consumption_weights.level4
} else if (consumption >= 500) {
consumptionMultiplier = GAME_DATA.consumption_weights.level3
} else if (consumption >= 100) {
consumptionMultiplier = GAME_DATA.consumption_weights.level2
} else {
consumptionMultiplier = 1.0
}
// Final Calculation
const totalScore = Number.parseFloat(((baseScore + coreAssetScore + bonusScore) * riskCoefficient).toFixed(2))
const valuation = Math.floor(totalScore * GAME_DATA.base_multiple)
let loanAmount = Math.floor(valuation * 0.6 * consumptionMultiplier)
if (loanAmount < 500) {
loanAmount = 0
}
return {
totalScore,
valuation,
loanAmount,
details: {
baseScore: Number.parseFloat(baseScore.toFixed(1)),
assetScore: Number.parseFloat(coreAssetScore.toFixed(1)),
bonusScore: Number.parseFloat(bonusScore.toFixed(1)),
riskCoefficient,
consumptionMultiplier, // Return multiplier for UI display
},
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 212 KiB

After

Width:  |  Height:  |  Size: 196 KiB

BIN
public/account-genshin.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 KiB

BIN
public/account-hok.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 KiB

BIN
public/account-icon-gen.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
public/account-icon-hok.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

BIN
public/account-icon-lol.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

BIN
public/account-icon-val.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

BIN
public/account-lol.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

BIN
public/account-val.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 79 KiB

BIN
public/avatar-3.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

BIN
public/avatar-4.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

BIN
public/avatar-5.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

BIN
public/avatar-6.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

BIN
public/avatar-assassin.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

BIN
public/avatar-cute.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

BIN
public/avatar-cyberpunk.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

BIN
public/avatar-mecha.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

BIN
public/badge-gold.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

BIN
public/club-logo-0.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
public/club-logo-1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
public/club-logo-2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
public/course-1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
public/course-2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
public/course-3.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
public/course-4.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
public/effect-neon.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 156 KiB

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 207 KiB

After

Width:  |  Height:  |  Size: 232 KiB

BIN
public/feed-live-1101.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
public/feed-live-1102.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

BIN
public/feed-live-1103.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

BIN
public/feed-live-1104.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 KiB

BIN
public/frame-starry.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

BIN
public/gear-headset.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
public/gear-keyboard.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
public/gear-monitor.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
public/gear-mouse.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
public/guild-logo-0.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

BIN
public/guild-logo-1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

BIN
public/guild-logo-2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

BIN
public/guild-logo-3.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

BIN
public/recharge-banner.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

BIN
public/user-avatar.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 111 KiB

After

Width:  |  Height:  |  Size: 134 KiB