diff --git a/app/account-help/page.tsx b/app/account-help/page.tsx new file mode 100644 index 0000000..c2aa2a6 --- /dev/null +++ b/app/account-help/page.tsx @@ -0,0 +1,70 @@ +"use client" + +import { useRouter } from "next/navigation" +import { ArrowLeft, Shield, Phone, HelpCircle, ChevronRight, Lock, Mail, FileText, MessageSquare } from "lucide-react" + +export default function AccountHelpPage() { + const router = useRouter() + + const sections = [ + { + title: "账号安全", + items: [ + { icon: Lock, label: "修改密码", desc: "定期修改密码保障账号安全" }, + { icon: Phone, label: "绑定手机", desc: "已绑定: 138****8888" }, + { icon: Mail, label: "绑定邮箱", desc: "未绑定" }, + { icon: Shield, label: "实名认证", desc: "已认证" }, + ], + }, + { + title: "帮助中心", + items: [ + { icon: HelpCircle, label: "常见问题", desc: "查看热门问题解答" }, + { icon: FileText, label: "用户协议", desc: "查看服务条款" }, + { icon: FileText, label: "隐私政策", desc: "了解隐私保护" }, + { icon: MessageSquare, label: "意见反馈", desc: "告诉我们您的建议" }, + ], + }, + ] + + return ( +
+
+ +

账号与帮助

+
+ +
+ {sections.map((section, sectionIndex) => ( +
+

{section.title}

+
+ {section.items.map((item, itemIndex) => ( + + ))} +
+
+ ))} + +
+

玩值电竞 v1.0.0

+

© 2025 玩值电竞 All Rights Reserved

+
+
+
+ ) +} diff --git a/app/admin/loading.tsx b/app/admin/loading.tsx new file mode 100644 index 0000000..f15322a --- /dev/null +++ b/app/admin/loading.tsx @@ -0,0 +1,3 @@ +export default function Loading() { + return null +} diff --git a/app/admin/mall/client.tsx b/app/admin/mall/client.tsx new file mode 100644 index 0000000..050257c --- /dev/null +++ b/app/admin/mall/client.tsx @@ -0,0 +1,280 @@ +"use client" + +import { useState, useEffect } from "react" +import { useRouter } from "next/navigation" +import { ArrowLeft, Search, Plus, Edit, Trash2, Eye, RefreshCw } from "lucide-react" +import { Button } from "@/components/ui/button" +import { Input } from "@/components/ui/input" +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" +import { Badge } from "@/components/ui/badge" +import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog" +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" +import { db, type Product } from "@/lib/db/schema" + +export default function AdminMallClient() { + const router = useRouter() + const [products, setProducts] = useState([]) + const [searchTerm, setSearchTerm] = useState("") + const [categoryFilter, setCategoryFilter] = useState("all") + const [loading, setLoading] = useState(true) + const [showAddDialog, setShowAddDialog] = useState(false) + const [newProduct, setNewProduct] = useState({ name: "", category: "avatar", price: 0, description: "" }) + + useEffect(() => { + loadProducts() + }, []) + + const loadProducts = async () => { + setLoading(true) + try { + const allProducts = await db.products.toArray() + setProducts(allProducts) + } catch (error) { + console.error("Failed to load products:", error) + } + setLoading(false) + } + + const filteredProducts = products.filter((product) => { + const matchesSearch = product.name.toLowerCase().includes(searchTerm.toLowerCase()) + const matchesCategory = categoryFilter === "all" || product.category === categoryFilter + return matchesSearch && matchesCategory + }) + + const deleteProduct = async (productId: number) => { + if (confirm("确定要删除该商品吗?")) { + await db.products.delete(productId) + loadProducts() + } + } + + const addProduct = async () => { + if (!newProduct.name || newProduct.price <= 0) { + alert("请填写商品名称和价格") + return + } + await db.products.add({ + name: newProduct.name, + category: newProduct.category as any, + price: newProduct.price, + originalPrice: Math.floor(newProduct.price * 1.2), + description: newProduct.description, + image: `/placeholder.svg?height=200&width=200&query=${newProduct.name}`, + stock: 999, + sales: 0, + rating: 5.0, + tags: [newProduct.category], + isHot: false, + isNew: true, + createdAt: new Date(), + }) + setShowAddDialog(false) + setNewProduct({ name: "", category: "avatar", price: 0, description: "" }) + loadProducts() + } + + const categories = [ + { value: "all", label: "全部分类" }, + { value: "avatar", label: "头像框" }, + { value: "gift", label: "礼物" }, + { value: "vip", label: "VIP特权" }, + { value: "skin", label: "皮肤" }, + { value: "points", label: "游戏点卡" }, + ] + + return ( +
+
+ +

商城管理

+
+ + + +
+
+ + setSearchTerm(e.target.value)} + className="bg-slate-700/50 border-slate-600 text-white pl-10" + /> +
+ + + +
+
+
+ +
+ + +

{products.length}

+

总商品数

+
+
+ + +

{products.filter((p) => (p.stock || 0) > 0).length}

+

在售商品

+
+
+ + +

{products.filter((p) => p.isHot).length}

+

热卖商品

+
+
+ + +

+ {products.reduce((sum, p) => sum + (p.sales || 0), 0).toLocaleString()} +

+

总销量

+
+
+
+ + + + 商品列表 ({filteredProducts.length}) + + + {loading ? ( +
加载中...
+ ) : ( +
+ {filteredProducts.map((product) => ( + +
+ {product.name} + {product.isHot && 热卖} + {product.isNew && 新品} +
+ + + +
+
+ +

{product.name}

+
+ {product.price} 币 + 销量 {product.sales} +
+
+
+ ))} +
+ )} +
+
+ + + + + 添加新商品 + +
+
+ + setNewProduct({ ...newProduct, name: e.target.value })} + placeholder="请输入商品名称" + className="bg-slate-700/50 border-slate-600 text-white" + /> +
+
+ + +
+
+ + setNewProduct({ ...newProduct, price: Number(e.target.value) })} + placeholder="请输入价格" + className="bg-slate-700/50 border-slate-600 text-white" + /> +
+
+ + setNewProduct({ ...newProduct, description: e.target.value })} + placeholder="商品描述" + className="bg-slate-700/50 border-slate-600 text-white" + /> +
+
+ + +
+
+
+
+
+ ) +} diff --git a/app/admin/mall/loading.tsx b/app/admin/mall/loading.tsx new file mode 100644 index 0000000..f15322a --- /dev/null +++ b/app/admin/mall/loading.tsx @@ -0,0 +1,3 @@ +export default function Loading() { + return null +} diff --git a/app/admin/mall/page.tsx b/app/admin/mall/page.tsx new file mode 100644 index 0000000..6c90ee6 --- /dev/null +++ b/app/admin/mall/page.tsx @@ -0,0 +1,7 @@ +import dynamic from "next/dynamic" + +const AdminMallClient = dynamic(() => import("./client"), { ssr: false }) + +export default function AdminMallPage() { + return +} diff --git a/app/admin/orders/client.tsx b/app/admin/orders/client.tsx new file mode 100644 index 0000000..edb1b0d --- /dev/null +++ b/app/admin/orders/client.tsx @@ -0,0 +1,233 @@ +"use client" + +import { useState, useEffect } from "react" +import { useRouter } from "next/navigation" +import { ArrowLeft, Search, MoreVertical, Eye, RefreshCw, CheckCircle, XCircle, Clock, Package } from "lucide-react" +import { Button } from "@/components/ui/button" +import { Input } from "@/components/ui/input" +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" +import { Badge } from "@/components/ui/badge" +import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu" +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" +import { db, type Order } from "@/lib/db/schema" + +export default function AdminOrdersClient() { + const router = useRouter() + const [orders, setOrders] = useState([]) + const [searchTerm, setSearchTerm] = useState("") + const [statusFilter, setStatusFilter] = useState("all") + const [loading, setLoading] = useState(true) + + useEffect(() => { + loadOrders() + }, []) + + const loadOrders = async () => { + setLoading(true) + try { + const allOrders = await db.orders.orderBy("createdAt").reverse().toArray() + setOrders(allOrders) + } catch (error) { + console.error("Failed to load orders:", error) + } + setLoading(false) + } + + const filteredOrders = orders.filter((order) => { + const matchesSearch = + order.orderNo?.includes(searchTerm) || order.itemName?.toLowerCase().includes(searchTerm.toLowerCase()) + const matchesStatus = statusFilter === "all" || order.status === statusFilter + return matchesSearch && matchesStatus + }) + + const updateOrderStatus = async (orderId: number, status: string) => { + await db.orders.update(orderId, { status: status as any }) + loadOrders() + } + + const getStatusBadge = (status: string) => { + switch (status) { + case "completed": + return 已完成 + case "pending": + return 待处理 + case "cancelled": + return 已取消 + case "refunded": + return 已退款 + default: + return {status} + } + } + + return ( +
+
+ +

订单管理

+
+ + + +
+
+ + setSearchTerm(e.target.value)} + className="bg-slate-700/50 border-slate-600 text-white pl-10" + /> +
+ + +
+
+
+ +
+ + +

{orders.length}

+

总订单数

+
+
+ + +

{orders.filter((o) => o.status === "pending").length}

+

待处理

+
+
+ + +

{orders.filter((o) => o.status === "completed").length}

+

已完成

+
+
+ + +

+ {orders.reduce((sum, o) => sum + (o.amount || 0), 0).toLocaleString()} 币 +

+

订单总额

+
+
+
+ + + + 订单列表 ({filteredOrders.length}) + + + {loading ? ( +
加载中...
+ ) : filteredOrders.length === 0 ? ( +
暂无订单数据
+ ) : ( +
+ + + + + + + + + + + + + + {filteredOrders.map((order) => ( + + + + + + + + + + ))} + +
订单号商品类型金额状态时间操作
{order.orderNo} +
+ + {order.itemName} +
+
+ + {order.type === "product" ? "商品" : order.type === "service" ? "服务" : "预订"} + + {order.amount} 币{getStatusBadge(order.status)} + {order.createdAt ? new Date(order.createdAt).toLocaleString("zh-CN") : "-"} + + + + + + + + + 查看详情 + + {order.status === "pending" && ( + <> + updateOrderStatus(order.id!, "completed")} + > + + 完成订单 + + updateOrderStatus(order.id!, "cancelled")} + > + + 取消订单 + + + )} + {order.status === "completed" && ( + updateOrderStatus(order.id!, "refunded")} + > + + 申请退款 + + )} + + +
+
+ )} +
+
+
+ ) +} diff --git a/app/admin/orders/loading.tsx b/app/admin/orders/loading.tsx new file mode 100644 index 0000000..f15322a --- /dev/null +++ b/app/admin/orders/loading.tsx @@ -0,0 +1,3 @@ +export default function Loading() { + return null +} diff --git a/app/admin/orders/page.tsx b/app/admin/orders/page.tsx new file mode 100644 index 0000000..b784c21 --- /dev/null +++ b/app/admin/orders/page.tsx @@ -0,0 +1,7 @@ +import dynamic from "next/dynamic" + +const AdminOrdersClient = dynamic(() => import("./client"), { ssr: false }) + +export default function AdminOrdersPage() { + return +} diff --git a/app/admin/page.tsx b/app/admin/page.tsx new file mode 100644 index 0000000..0929995 --- /dev/null +++ b/app/admin/page.tsx @@ -0,0 +1,584 @@ +"use client" + +import { useState, useEffect } from "react" +import { useRouter } from "next/navigation" +import { + LayoutDashboard, + Users, + Radio, + Building2, + Star, + ShoppingBag, + FileText, + Wallet, + MessageSquare, + Settings, + LogOut, + TrendingUp, + UserCheck, + CreditCard, + Gamepad2, + Hotel, + Trophy, + ChevronRight, + Bell, + Search, + Menu, + X, + BarChart3, + PieChart, + Activity, +} from "lucide-react" +import { Button } from "@/components/ui/button" +import { Input } from "@/components/ui/input" +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" +import { Badge } from "@/components/ui/badge" +import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" +import { db } from "@/lib/db/schema" + +// 后台统计数据类型 +interface DashboardStats { + totalUsers: number + totalStreamers: number + totalOrders: number + totalRevenue: number + todayNewUsers: number + todayOrders: number + activeStreamers: number + pendingPawns: number +} + +// 侧边栏菜单项 +const menuItems = [ + { id: "dashboard", label: "数据概览", icon: LayoutDashboard, path: "/admin" }, + { id: "users", label: "用户管理", icon: Users, path: "/admin/users" }, + { id: "streamers", label: "主播管理", icon: Radio, path: "/admin/streamers" }, + { id: "guilds", label: "公会管理", icon: Building2, path: "/admin/guilds" }, + { id: "stars", label: "明星管理", icon: Star, path: "/admin/stars" }, + { id: "live", label: "直播管理", icon: Gamepad2, path: "/admin/live" }, + { id: "party", label: "派对群管理", icon: MessageSquare, path: "/admin/party" }, + { id: "mall", label: "商城管理", icon: ShoppingBag, path: "/admin/mall" }, + { id: "orders", label: "订单管理", icon: FileText, path: "/admin/orders" }, + { id: "pawn", label: "典当管理", icon: Wallet, path: "/admin/pawn" }, + { id: "hotel", label: "酒店/网咖", icon: Hotel, path: "/admin/hotel" }, + { id: "content", label: "内容管理", icon: Trophy, path: "/admin/content" }, + { id: "finance", label: "财务管理", icon: CreditCard, path: "/admin/finance" }, + { id: "settings", label: "系统设置", icon: Settings, path: "/admin/settings" }, +] + +export default function AdminDashboard() { + const router = useRouter() + const [isLoggedIn, setIsLoggedIn] = useState(false) + const [username, setUsername] = useState("") + const [password, setPassword] = useState("") + const [sidebarOpen, setSidebarOpen] = useState(true) + const [currentMenu, setCurrentMenu] = useState("dashboard") + const [stats, setStats] = useState({ + totalUsers: 0, + totalStreamers: 0, + totalOrders: 0, + totalRevenue: 0, + todayNewUsers: 0, + todayOrders: 0, + activeStreamers: 0, + pendingPawns: 0, + }) + const [recentUsers, setRecentUsers] = useState([]) + const [recentOrders, setRecentOrders] = useState([]) + + // 加载统计数据 + useEffect(() => { + if (isLoggedIn) { + loadDashboardData() + } + }, [isLoggedIn]) + + const loadDashboardData = async () => { + try { + const users = await db.users.toArray() + const streamers = await db.streamers.toArray() + const orders = await db.orders.toArray() + const pawns = await db.accountPawns.toArray() + const transactions = await db.transactions.toArray() + + // 计算总收入 + const totalRevenue = transactions.filter((t) => t.type === "recharge").reduce((sum, t) => sum + t.amount, 0) + + // 今日数据 + const today = new Date() + today.setHours(0, 0, 0, 0) + const todayTimestamp = today.getTime() + + const todayNewUsers = users.filter((u) => new Date(u.createdAt).getTime() >= todayTimestamp).length + + const todayOrders = orders.filter((o) => new Date(o.createdAt).getTime() >= todayTimestamp).length + + setStats({ + totalUsers: users.length, + totalStreamers: streamers.length, + totalOrders: orders.length, + totalRevenue, + todayNewUsers, + todayOrders, + activeStreamers: streamers.filter((s) => s.isLive).length, + pendingPawns: pawns.filter((p) => p.status === "pending").length, + }) + + // 最近用户 + setRecentUsers(users.slice(-5).reverse()) + + // 最近订单 + setRecentOrders(orders.slice(-5).reverse()) + } catch (error) { + console.error("Failed to load dashboard data:", error) + } + } + + // 登录处理 + const handleLogin = () => { + if (username === "admin" && password === "admin123") { + setIsLoggedIn(true) + localStorage.setItem("admin_logged_in", "true") + } else { + alert("用户名或密码错误") + } + } + + // 检查登录状态 + useEffect(() => { + const loggedIn = localStorage.getItem("admin_logged_in") + if (loggedIn === "true") { + setIsLoggedIn(true) + } + }, []) + + // 退出登录 + const handleLogout = () => { + setIsLoggedIn(false) + localStorage.removeItem("admin_logged_in") + } + + // 登录页面 + if (!isLoggedIn) { + return ( +
+ + +
+ +
+ 玩值电竞后台管理 +

请登录管理员账号

+
+ +
+ + setUsername(e.target.value)} + placeholder="请输入用户名" + className="bg-slate-700/50 border-slate-600 text-white" + /> +
+
+ + setPassword(e.target.value)} + placeholder="请输入密码" + className="bg-slate-700/50 border-slate-600 text-white" + onKeyDown={(e) => e.key === "Enter" && handleLogin()} + /> +
+ +

默认账号: admin / admin123

+
+
+
+ ) + } + + // 后台主界面 + return ( +
+ {/* 侧边栏 */} + + + {/* 主内容区 */} +
+ {/* 顶部栏 */} +
+
+ +
+ + +
+
+
+ +
+ + + AD + + 管理员 +
+
+
+ + {/* 仪表盘内容 */} +
+

数据概览

+ + {/* 统计卡片 */} +
+ + +
+
+

总用户数

+

{stats.totalUsers.toLocaleString()}

+

+{stats.todayNewUsers} 今日新增

+
+
+ +
+
+
+
+ + + +
+
+

主播数量

+

{stats.totalStreamers}

+

{stats.activeStreamers} 正在直播

+
+
+ +
+
+
+
+ + + +
+
+

总订单数

+

{stats.totalOrders}

+

+{stats.todayOrders} 今日订单

+
+
+ +
+
+
+
+ + + +
+
+

总收入

+

¥{stats.totalRevenue.toLocaleString()}

+

{stats.pendingPawns} 待处理典当

+
+
+ +
+
+
+
+
+ + {/* 图表区域 */} +
+ + + + + 收入趋势 + + + +
+ {[65, 45, 78, 52, 88, 70, 95].map((height, i) => ( +
+
+ {["一", "二", "三", "四", "五", "六", "日"][i]} +
+ ))} +
+ + + + + + + + 业务分布 + + + +
+
+ + + + + + + +
+
+
+
+ 直播打赏 30% +
+
+
+ 商城消费 20% +
+
+
+ 账号典当 18% +
+
+
+ 其他收入 12% +
+
+
+ + +
+ + {/* 快捷操作和最近数据 */} +
+ {/* 快捷操作 */} + + + 快捷操作 + + + + + + + + + + {/* 最近用户 */} + + + 最近注册用户 + + + {recentUsers.length > 0 ? ( + recentUsers.map((user, i) => ( +
+ + + {user.nickname?.[0]} + +
+

{user.nickname}

+

{user.phone || "未绑定手机"}

+
+ 新用户 +
+ )) + ) : ( +

暂无数据

+ )} +
+
+ + {/* 系统状态 */} + + + + + 系统状态 + + + +
+
+ 数据库 + 正常运行 +
+
+
+
+
+
+
+ API服务 + 正常 +
+
+
+
+
+
+
+ 存储空间 + 48% +
+
+
+
+
+
+

最后更新: {new Date().toLocaleString("zh-CN")}

+
+ + +
+
+
+
+ ) +} diff --git a/app/admin/pawn/client.tsx b/app/admin/pawn/client.tsx new file mode 100644 index 0000000..83cd097 --- /dev/null +++ b/app/admin/pawn/client.tsx @@ -0,0 +1,254 @@ +"use client" + +import { useState, useEffect } from "react" +import { useRouter } from "next/navigation" +import { ArrowLeft, Search, MoreVertical, Eye, RefreshCw, CheckCircle, XCircle, Wallet, FileText } from "lucide-react" +import { Button } from "@/components/ui/button" +import { Input } from "@/components/ui/input" +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" +import { Badge } from "@/components/ui/badge" +import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu" +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" +import { db, type AccountPawn } from "@/lib/db/schema" + +export default function AdminPawnClient() { + const router = useRouter() + const [pawns, setPawns] = useState([]) + const [searchTerm, setSearchTerm] = useState("") + const [statusFilter, setStatusFilter] = useState("all") + const [loading, setLoading] = useState(true) + + useEffect(() => { + loadPawns() + }, []) + + const loadPawns = async () => { + setLoading(true) + try { + const allPawns = await db.accountPawns.orderBy("createdAt").reverse().toArray() + setPawns(allPawns) + } catch (error) { + console.error("Failed to load pawns:", error) + } + setLoading(false) + } + + const filteredPawns = pawns.filter((pawn) => { + const matchesSearch = + pawn.game?.toLowerCase().includes(searchTerm.toLowerCase()) || pawn.contactPhone?.includes(searchTerm) + const matchesStatus = statusFilter === "all" || pawn.status === statusFilter + return matchesSearch && matchesStatus + }) + + const updatePawnStatus = async (pawnId: number, status: string) => { + await db.accountPawns.update(pawnId, { status: status as any }) + loadPawns() + } + + const getStatusBadge = (status: string) => { + switch (status) { + case "pending": + return 待审核 + case "approved": + return 已通过 + case "pawned": + return 质押中 + case "redeemed": + return 已赎回 + case "expired": + return 已过期 + case "rejected": + return 已拒绝 + default: + return {status} + } + } + + return ( +
+
+ +

典当管理

+
+ + + +
+
+ + setSearchTerm(e.target.value)} + className="bg-slate-700/50 border-slate-600 text-white pl-10" + /> +
+ + +
+
+
+ +
+ + +

{pawns.length}

+

总申请数

+
+
+ + +

{pawns.filter((p) => p.status === "pending").length}

+

待审核

+
+
+ + +

{pawns.filter((p) => p.status === "pawned").length}

+

质押中

+
+
+ + +

+ ¥ + {pawns + .filter((p) => p.status === "pawned") + .reduce((sum, p) => sum + (p.pawnAmount || 0), 0) + .toLocaleString()} +

+

在押金额

+
+
+
+ + + + 典当申请 ({filteredPawns.length}) + + + {loading ? ( +
加载中...
+ ) : filteredPawns.length === 0 ? ( +
暂无典当申请
+ ) : ( +
+ + + + + + + + + + + + + + + {filteredPawns.map((pawn) => ( + + + + + + + + + + + ))} + +
游戏估值典当金额期限联系电话状态申请时间操作
+
+ + {pawn.game} +
+
¥{pawn.estimatedValue?.toLocaleString()}¥{pawn.pawnAmount?.toLocaleString()}{pawn.pawnPeriod}天{pawn.contactPhone}{getStatusBadge(pawn.status)} + {pawn.createdAt ? new Date(pawn.createdAt).toLocaleString("zh-CN") : "-"} + + + + + + + + + 查看详情 + + + + 查看截图 + + {pawn.status === "pending" && ( + <> + updatePawnStatus(pawn.id!, "approved")} + > + + 审核通过 + + updatePawnStatus(pawn.id!, "rejected")} + > + + 拒绝申请 + + + )} + {pawn.status === "approved" && ( + updatePawnStatus(pawn.id!, "pawned")} + > + + 确认放款 + + )} + {pawn.status === "pawned" && ( + updatePawnStatus(pawn.id!, "redeemed")} + > + + 确认赎回 + + )} + + +
+
+ )} +
+
+
+ ) +} diff --git a/app/admin/pawn/loading.tsx b/app/admin/pawn/loading.tsx new file mode 100644 index 0000000..f15322a --- /dev/null +++ b/app/admin/pawn/loading.tsx @@ -0,0 +1,3 @@ +export default function Loading() { + return null +} diff --git a/app/admin/pawn/page.tsx b/app/admin/pawn/page.tsx new file mode 100644 index 0000000..e64f1ad --- /dev/null +++ b/app/admin/pawn/page.tsx @@ -0,0 +1,7 @@ +import dynamic from "next/dynamic" + +const AdminPawnClient = dynamic(() => import("./client"), { ssr: false }) + +export default function AdminPawnPage() { + return +} diff --git a/app/admin/settings/loading.tsx b/app/admin/settings/loading.tsx new file mode 100644 index 0000000..f15322a --- /dev/null +++ b/app/admin/settings/loading.tsx @@ -0,0 +1,3 @@ +export default function Loading() { + return null +} diff --git a/app/admin/settings/page.tsx b/app/admin/settings/page.tsx new file mode 100644 index 0000000..fc3071f --- /dev/null +++ b/app/admin/settings/page.tsx @@ -0,0 +1,300 @@ +"use client" + +import { useState } from "react" +import { useRouter } from "next/navigation" +import { ArrowLeft, Save, Settings, Shield, Globe, CreditCard } from "lucide-react" +import { Button } from "@/components/ui/button" +import { Input } from "@/components/ui/input" +import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card" +import { Switch } from "@/components/ui/switch" +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" + +export default function AdminSettingsPage() { + const router = useRouter() + const [settings, setSettings] = useState({ + siteName: "玩值电竞", + siteDescription: "专业游戏电竞社交平台", + contactEmail: "support@wanzhi.com", + contactPhone: "400-888-8888", + enableRegistration: true, + enablePawn: true, + enableLive: true, + enableMall: true, + maintenanceMode: false, + minPawnAmount: 5000, + maxPawnPeriod: 30, + pawnFeeRate: 0.05, + rechargeBonus: 0.1, + vipDiscount: 0.9, + }) + + const handleSave = () => { + localStorage.setItem("admin_settings", JSON.stringify(settings)) + alert("设置已保存") + } + + return ( +
+ {/* 顶部导航 */} +
+
+ +

系统设置

+
+ +
+ + + + + + 基础设置 + + + + 功能开关 + + + + 典当配置 + + + + 支付配置 + + + + + + + 基础信息 + 配置平台基本信息 + + +
+
+ + setSettings({ ...settings, siteName: e.target.value })} + className="bg-slate-700/50 border-slate-600 text-white" + /> +
+
+ + setSettings({ ...settings, siteDescription: e.target.value })} + className="bg-slate-700/50 border-slate-600 text-white" + /> +
+
+ + setSettings({ ...settings, contactEmail: e.target.value })} + className="bg-slate-700/50 border-slate-600 text-white" + /> +
+
+ + setSettings({ ...settings, contactPhone: e.target.value })} + className="bg-slate-700/50 border-slate-600 text-white" + /> +
+
+
+
+
+ + + + + 功能开关 + 控制平台功能模块 + + +
+
+

用户注册

+

允许新用户注册账号

+
+ setSettings({ ...settings, enableRegistration: v })} + /> +
+
+
+

账号典当

+

开启游戏账号典当功能

+
+ setSettings({ ...settings, enablePawn: v })} + /> +
+
+
+

直播功能

+

开启直播观看和互动

+
+ setSettings({ ...settings, enableLive: v })} + /> +
+
+
+

商城功能

+

开启商城购物功能

+
+ setSettings({ ...settings, enableMall: v })} + /> +
+
+
+

维护模式

+

开启后用户将无法访问平台

+
+ setSettings({ ...settings, maintenanceMode: v })} + /> +
+
+
+
+ + + + + 典当配置 + 配置账号典当业务参数 + + +
+
+ + setSettings({ ...settings, minPawnAmount: Number(e.target.value) })} + className="bg-slate-700/50 border-slate-600 text-white" + /> +
+
+ + setSettings({ ...settings, maxPawnPeriod: Number(e.target.value) })} + className="bg-slate-700/50 border-slate-600 text-white" + /> +
+
+ + setSettings({ ...settings, pawnFeeRate: Number(e.target.value) })} + className="bg-slate-700/50 border-slate-600 text-white" + /> +

当前费率: {(settings.pawnFeeRate * 100).toFixed(1)}%

+
+
+
+
+
+ + + + + 支付配置 + 配置充值和支付参数 + + +
+
+ + setSettings({ ...settings, rechargeBonus: Number(e.target.value) })} + className="bg-slate-700/50 border-slate-600 text-white" + /> +

充值送 {(settings.rechargeBonus * 100).toFixed(0)}%

+
+
+ + setSettings({ ...settings, vipDiscount: Number(e.target.value) })} + className="bg-slate-700/50 border-slate-600 text-white" + /> +

VIP享 {(settings.vipDiscount * 10).toFixed(1)} 折

+
+
+
+

支付渠道

+
+ +
+
+ +
+
+

微信支付

+

已开通

+
+
+
+ +
+
+ +
+
+

支付宝

+

已开通

+
+
+
+ +
+
+ +
+
+

币支付

+

已开通

+
+
+
+
+
+
+
+
+
+
+ ) +} diff --git a/app/admin/streamers/client.tsx b/app/admin/streamers/client.tsx new file mode 100644 index 0000000..e89c9a1 --- /dev/null +++ b/app/admin/streamers/client.tsx @@ -0,0 +1,313 @@ +"use client" + +import { useState, useEffect } from "react" +import { useRouter } from "next/navigation" +import { + ArrowLeft, + Search, + Plus, + MoreVertical, + Radio, + Eye, + Edit, + Trash2, + CheckCircle, + XCircle, + RefreshCw, + Users, +} from "lucide-react" +import { Button } from "@/components/ui/button" +import { Input } from "@/components/ui/input" +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" +import { Badge } from "@/components/ui/badge" +import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" +import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu" +import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog" +import { db, type Streamer } from "@/lib/db/schema" + +export default function AdminStreamersClient() { + const router = useRouter() + const [streamers, setStreamers] = useState([]) + const [searchTerm, setSearchTerm] = useState("") + const [loading, setLoading] = useState(true) + const [showAddDialog, setShowAddDialog] = useState(false) + const [newStreamer, setNewStreamer] = useState({ name: "", game: "", description: "" }) + + useEffect(() => { + loadStreamers() + }, []) + + const loadStreamers = async () => { + setLoading(true) + try { + const allStreamers = await db.streamers.toArray() + setStreamers(allStreamers) + } catch (error) { + console.error("Failed to load streamers:", error) + } + setLoading(false) + } + + const filteredStreamers = streamers.filter( + (streamer) => + streamer.name.toLowerCase().includes(searchTerm.toLowerCase()) || + streamer.game?.toLowerCase().includes(searchTerm.toLowerCase()), + ) + + const toggleLiveStatus = async (streamerId: number, isLive: boolean) => { + await db.streamers.update(streamerId, { isLive: !isLive }) + loadStreamers() + } + + const deleteStreamer = async (streamerId: number) => { + if (confirm("确定要删除该主播吗?")) { + await db.streamers.delete(streamerId) + loadStreamers() + } + } + + const addStreamer = async () => { + if (!newStreamer.name || !newStreamer.game) { + alert("请填写主播名称和游戏类型") + return + } + await db.streamers.add({ + name: newStreamer.name, + game: newStreamer.game, + description: newStreamer.description, + avatar: `/placeholder.svg?height=100&width=100&query=${newStreamer.name} avatar`, + cover: `/placeholder.svg?height=200&width=300&query=${newStreamer.game} gaming`, + followers: 0, + isLive: false, + isVerified: false, + tags: [newStreamer.game], + guildId: undefined, + createdAt: new Date(), + }) + setShowAddDialog(false) + setNewStreamer({ name: "", game: "", description: "" }) + loadStreamers() + } + + return ( +
+
+ +

主播管理

+
+ + + +
+
+ + setSearchTerm(e.target.value)} + className="bg-slate-700/50 border-slate-600 text-white pl-10" + /> +
+ + +
+
+
+ +
+ + +

{streamers.length}

+

总主播数

+
+
+ + +

{streamers.filter((s) => s.isLive).length}

+

正在直播

+
+
+ + +

{streamers.filter((s) => s.isVerified).length}

+

已认证

+
+
+ + +

+ {streamers.reduce((sum, s) => sum + (s.followers || 0), 0).toLocaleString()} +

+

总粉丝数

+
+
+
+ + + + 主播列表 ({filteredStreamers.length}) + + + {loading ? ( +
加载中...
+ ) : ( +
+ {filteredStreamers.map((streamer) => ( + +
+ {streamer.name} + {streamer.isLive && ( + + + 直播中 + + )} + + + + + + + + 查看详情 + + + + 编辑资料 + + toggleLiveStatus(streamer.id!, streamer.isLive || false)} + > + {streamer.isLive ? ( + <> + + 关闭直播 + + ) : ( + <> + + 开启直播 + + )} + + deleteStreamer(streamer.id!)}> + + 删除主播 + + + +
+ +
+ + + {streamer.name[0]} + +
+
+ {streamer.name} + {streamer.isVerified && } +
+

{streamer.game}

+
+
+
+
+ + {(streamer.followers || 0).toLocaleString()} +
+
+ {streamer.tags?.slice(0, 2).map((tag, i) => ( + + {tag} + + ))} +
+
+
+
+ ))} +
+ )} +
+
+ + + + + 添加新主播 + +
+
+ + setNewStreamer({ ...newStreamer, name: e.target.value })} + placeholder="请输入主播名称" + className="bg-slate-700/50 border-slate-600 text-white" + /> +
+
+ + setNewStreamer({ ...newStreamer, game: e.target.value })} + placeholder="如:王者荣耀、英雄联盟" + className="bg-slate-700/50 border-slate-600 text-white" + /> +
+
+ + setNewStreamer({ ...newStreamer, description: e.target.value })} + placeholder="主播简介" + className="bg-slate-700/50 border-slate-600 text-white" + /> +
+
+ + +
+
+
+
+
+ ) +} diff --git a/app/admin/streamers/loading.tsx b/app/admin/streamers/loading.tsx new file mode 100644 index 0000000..f15322a --- /dev/null +++ b/app/admin/streamers/loading.tsx @@ -0,0 +1,3 @@ +export default function Loading() { + return null +} diff --git a/app/admin/streamers/page.tsx b/app/admin/streamers/page.tsx new file mode 100644 index 0000000..195ec21 --- /dev/null +++ b/app/admin/streamers/page.tsx @@ -0,0 +1,7 @@ +import dynamic from "next/dynamic" + +const AdminStreamersClient = dynamic(() => import("./client"), { ssr: false }) + +export default function AdminStreamersPage() { + return +} diff --git a/app/admin/users/client.tsx b/app/admin/users/client.tsx new file mode 100644 index 0000000..dcf14c1 --- /dev/null +++ b/app/admin/users/client.tsx @@ -0,0 +1,250 @@ +"use client" + +import { useState, useEffect } from "react" +import { useRouter } from "next/navigation" +import { ArrowLeft, Search, MoreVertical, UserCheck, UserX, Edit, Trash2, Eye, Plus, RefreshCw } from "lucide-react" +import { Button } from "@/components/ui/button" +import { Input } from "@/components/ui/input" +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" +import { Badge } from "@/components/ui/badge" +import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" +import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu" +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" +import { db, type User } from "@/lib/db/schema" + +export default function AdminUsersClient() { + const router = useRouter() + const [users, setUsers] = useState([]) + const [searchTerm, setSearchTerm] = useState("") + const [statusFilter, setStatusFilter] = useState("all") + const [loading, setLoading] = useState(true) + + useEffect(() => { + loadUsers() + }, []) + + const loadUsers = async () => { + setLoading(true) + try { + const allUsers = await db.users.toArray() + setUsers(allUsers) + } catch (error) { + console.error("Failed to load users:", error) + } + setLoading(false) + } + + const filteredUsers = users.filter((user) => { + const matchesSearch = + user.nickname?.toLowerCase().includes(searchTerm.toLowerCase()) || user.phone?.includes(searchTerm) + const matchesStatus = statusFilter === "all" || user.status === statusFilter + return matchesSearch && matchesStatus + }) + + const updateUserStatus = async (userId: number, status: string) => { + await db.users.update(userId, { status }) + loadUsers() + } + + const deleteUser = async (userId: number) => { + if (confirm("确定要删除该用户吗?")) { + await db.users.delete(userId) + loadUsers() + } + } + + return ( +
+
+ +

用户管理

+
+ + + +
+
+ + setSearchTerm(e.target.value)} + className="bg-slate-700/50 border-slate-600 text-white pl-10" + /> +
+ + + +
+
+
+ +
+ + +

{users.length}

+

总用户数

+
+
+ + +

{users.filter((u) => u.status === "active").length}

+

活跃用户

+
+
+ + +

+ {users.filter((u) => u.vipLevel && u.vipLevel > 0).length} +

+

VIP用户

+
+
+ + +

{users.filter((u) => u.status === "banned").length}

+

已封禁

+
+
+
+ + + + 用户列表 ({filteredUsers.length}) + + + {loading ? ( +
加载中...
+ ) : filteredUsers.length === 0 ? ( +
暂无用户数据
+ ) : ( +
+ + + + + + + + + + + + + + {filteredUsers.map((user) => ( + + + + + + + + + + ))} + +
用户手机号余额VIP状态注册时间操作
+
+ + + {user.nickname?.[0]} + +
+

{user.nickname}

+

ID: {user.id}

+
+
+
{user.phone || "未绑定"}{user.balance?.toLocaleString() || 0} 币 + {user.vipLevel && user.vipLevel > 0 ? ( + VIP{user.vipLevel} + ) : ( + 普通 + )} + + + {user.status === "active" ? "正常" : user.status === "banned" ? "封禁" : "待审"} + + + {user.createdAt ? new Date(user.createdAt).toLocaleDateString("zh-CN") : "-"} + + + + + + + + + 查看详情 + + + + 编辑资料 + + {user.status === "active" ? ( + updateUserStatus(user.id!, "banned")} + > + + 封禁用户 + + ) : ( + updateUserStatus(user.id!, "active")} + > + + 解除封禁 + + )} + deleteUser(user.id!)} + > + + 删除用户 + + + +
+
+ )} +
+
+
+ ) +} diff --git a/app/admin/users/loading.tsx b/app/admin/users/loading.tsx new file mode 100644 index 0000000..f15322a --- /dev/null +++ b/app/admin/users/loading.tsx @@ -0,0 +1,3 @@ +export default function Loading() { + return null +} diff --git a/app/admin/users/page.tsx b/app/admin/users/page.tsx new file mode 100644 index 0000000..2d91743 --- /dev/null +++ b/app/admin/users/page.tsx @@ -0,0 +1,7 @@ +import dynamic from "next/dynamic" + +const AdminUsersClient = dynamic(() => import("./client"), { ssr: false }) + +export default function AdminUsersPage() { + return +} diff --git a/app/customer-service/page.tsx b/app/customer-service/page.tsx new file mode 100644 index 0000000..ddd3711 --- /dev/null +++ b/app/customer-service/page.tsx @@ -0,0 +1,144 @@ +"use client" + +import { useState } from "react" +import { ArrowLeft, MessageCircle, Phone, Mail, Clock, ChevronRight, Send } from "lucide-react" +import { Button } from "@/components/ui/button" +import { Input } from "@/components/ui/input" +import { useRouter } from "next/navigation" + +export default function CustomerServicePage() { + const router = useRouter() + const [message, setMessage] = useState("") + const [messages, setMessages] = useState([ + { id: 1, type: "system", content: "您好!欢迎使用玩值电竞客服中心,请问有什么可以帮助您的?", time: "10:00" }, + ]) + + const faqList = [ + { q: "如何充值玩值币?", a: "点击首页右上角钱包图标,选择充值金额即可" }, + { q: "如何申请退款?", a: "进入订单详情页,点击申请退款按钮" }, + { q: "账号典当如何操作?", a: "进入商城-账号典当,填写账号信息提交评估" }, + { q: "如何联系主播?", a: "在主播主页点击私信按钮即可发送消息" }, + ] + + const sendMessage = () => { + if (!message.trim()) return + + const newMsg = { + id: messages.length + 1, + type: "user", + content: message, + time: new Date().toLocaleTimeString("zh-CN", { hour: "2-digit", minute: "2-digit" }), + } + setMessages([...messages, newMsg]) + setMessage("") + + // 模拟客服回复 + setTimeout(() => { + const reply = { + id: messages.length + 2, + type: "system", + content: "感谢您的咨询,客服正在为您处理中,请稍候...", + time: new Date().toLocaleTimeString("zh-CN", { hour: "2-digit", minute: "2-digit" }), + } + setMessages((prev) => [...prev, reply]) + }, 1000) + } + + return ( +
+ {/* Header */} +
+ +

客服中心

+
+ + {/* Quick Actions */} + + + {/* FAQ Section */} +
+

+ + 常见问题 +

+
+ {faqList.map((faq, i) => ( + + ))} +
+
+ + {/* Chat Area */} +
+ {messages.map((msg) => ( +
+
+

{msg.content}

+

+ {msg.time} +

+
+
+ ))} +
+ + {/* Input Area */} +
+
+ setMessage(e.target.value)} + placeholder="请输入您的问题..." + className="flex-1 bg-foreground/5 border-foreground/10" + onKeyPress={(e) => e.key === "Enter" && sendMessage()} + /> + +
+
+
+ ) +} diff --git a/app/layout.tsx b/app/layout.tsx index c89017e..e774b2d 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,34 +1,40 @@ -import type { Metadata } from "next"; -import { Inter } from 'next/font/google'; -import "./globals.css"; -import { MobileNav } from "@/components/layout/mobile-nav"; -import { AppProvider } from "@/components/providers/app-provider"; -import { Toaster } from "@/components/ui/toaster"; +import type React from "react" +import type { Metadata } from "next" +import { Inter } from "next/font/google" +import "./globals.css" +import { MobileNav } from "@/components/layout/mobile-nav" +import { AppProvider } from "@/components/providers/app-provider" +import { DatabaseProvider } from "@/components/providers/database-provider" +import { Toaster } from "@/components/ui/toaster" -const inter = Inter({ subsets: ["latin"] }); +const inter = Inter({ subsets: ["latin"] }) export const metadata: Metadata = { title: "玩值电竞 - WanZhi Esports", description: "专注打造比体育明星价值高10倍的电竞俱乐部", generator: 'v0.app' -}; +} export default function RootLayout({ children, }: Readonly<{ - children: React.ReactNode; + children: React.ReactNode }>) { return ( - - -
- {children} - - -
-
+ + + +
+ {children} + + +
+
+
- ); + ) } diff --git a/app/login/page.tsx b/app/login/page.tsx new file mode 100644 index 0000000..8200654 --- /dev/null +++ b/app/login/page.tsx @@ -0,0 +1,160 @@ +"use client" + +import { useState } from "react" +import { useRouter } from "next/navigation" +import { Button } from "@/components/ui/button" +import { Input } from "@/components/ui/input" +import { Phone, Lock, Eye, EyeOff, MessageSquare } from "lucide-react" +import Image from "next/image" +import { useAppContext } from "@/components/providers/app-provider" + +export default function LoginPage() { + const router = useRouter() + const { login } = useAppContext() + const [phone, setPhone] = useState("") + const [code, setCode] = useState("") + const [showPassword, setShowPassword] = useState(false) + const [loginType, setLoginType] = useState<"code" | "password">("code") + const [countdown, setCountdown] = useState(0) + const [isLoading, setIsLoading] = useState(false) + const [agreed, setAgreed] = useState(false) + + const sendCode = () => { + if (phone.length !== 11) return + setCountdown(60) + const timer = setInterval(() => { + setCountdown((prev) => { + if (prev <= 1) { + clearInterval(timer) + return 0 + } + return prev - 1 + }) + }, 1000) + } + + const handleLogin = async () => { + if (!agreed) return + if (phone.length !== 11) return + + setIsLoading(true) + const success = await login(phone, code) + setIsLoading(false) + + if (success) { + router.push("/") + } + } + + return ( +
+ {/* Logo Area */} +
+
+ 玩值电竞 +
+

玩值电竞

+

游戏社交 · 电竞服务 · 价值变现

+
+ + {/* Login Form */} +
+ {/* Phone Input */} +
+ + setPhone(e.target.value.replace(/\D/g, "").slice(0, 11))} + className="pl-12 h-12 bg-foreground/5 border-foreground/10 rounded-xl" + /> +
+ + {/* Code/Password Input */} +
+ {loginType === "code" ? ( + + ) : ( + + )} + setCode(e.target.value)} + className="pl-12 pr-24 h-12 bg-foreground/5 border-foreground/10 rounded-xl" + /> + {loginType === "code" ? ( + + ) : ( + + )} +
+ + {/* Switch Login Type */} +
+ + +
+ + {/* Agreement */} +
+ + + 我已阅读并同意 + 《用户协议》《隐私政策》 + +
+ + {/* Login Button */} + + + {/* Third Party Login */} +
+
+
+ 其他登录方式 +
+
+
+ {["微信", "QQ", "微博"].map((name) => ( + + ))} +
+
+
+
+ ) +} diff --git a/app/orders/page.tsx b/app/orders/page.tsx index a82e05f..715b5fe 100644 --- a/app/orders/page.tsx +++ b/app/orders/page.tsx @@ -1,12 +1,12 @@ "use client" -import { Button } from "@/components/ui/button" - -import { ArrowLeft, ShoppingBag, Clock, CheckCircle2, XCircle } from "lucide-react" +import { ArrowLeft, ShoppingBag, Clock, CheckCircle2, XCircle, RotateCcw } from "lucide-react" import { useRouter } from "next/navigation" import Image from "next/image" import { useAppContext } from "@/components/providers/app-provider" import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" +import { Button } from "@/components/ui/button" +import Link from "next/link" export default function OrdersPage() { const router = useRouter() @@ -20,8 +20,10 @@ export default function OrdersPage() { return "text-yellow-500" case "cancelled": return "text-red-500" + case "refunded": + return "text-blue-500" default: - return "text-white/50" + return "text-foreground/50" } } @@ -33,6 +35,8 @@ export default function OrdersPage() { return case "cancelled": return + case "refunded": + return default: return null } @@ -46,15 +50,40 @@ export default function OrdersPage() { return "进行中" case "cancelled": return "已取消" + case "refunded": + return "已退款" default: return status } } + const getTypeText = (type: string) => { + switch (type) { + case "service": + return "陪玩服务" + case "product": + return "商城商品" + case "course": + return "课程购买" + case "hotel": + return "酒店预订" + case "cafe": + return "网咖抢座" + case "recharge": + return "充值订单" + case "pawn": + return "账号典当" + default: + return "其他" + } + } + + const displayOrders = orders.filter((o) => o.type !== "recharge") + return (
-
-

订单中心

@@ -62,7 +91,7 @@ export default function OrdersPage() {
- + 全部 @@ -72,21 +101,29 @@ export default function OrdersPage() { 商品 + + 预订 +
- {["all", "service", "product"].map((tab) => ( + {["all", "service", "product", "hotel"].map((tab) => ( - {orders.filter((o) => tab === "all" || o.type === tab).length > 0 ? ( - orders - .filter((o) => tab === "all" || o.type === tab) + {displayOrders.filter( + (o) => tab === "all" || o.type === tab || (tab === "hotel" && (o.type === "hotel" || o.type === "cafe")), + ).length > 0 ? ( + displayOrders + .filter( + (o) => + tab === "all" || o.type === tab || (tab === "hotel" && (o.type === "hotel" || o.type === "cafe")), + ) .map((order, i) => ( -
+
- {order.date} - - {order.type === "service" ? "陪玩服务" : "商城商品"} + {order.date} + + {getTypeText(order.type)}
@@ -98,42 +135,48 @@ export default function OrdersPage() {
{order.title}
-
-

{order.title}

+
+

{order.title}

+

订单号: {order.orderNo || order.id}

- 数量: 1 + 数量: 1
- {order.price} - - {order.currency === "diamonds" ? "钻石" : "丸子币"} - + {order.price} +
-
- - +
+ + + + + +
)) ) : ( -
+

暂无订单

diff --git a/app/page.tsx b/app/page.tsx index 4c76aa3..df5687e 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -163,7 +163,13 @@ export default function HomePage() {

玩值电竞

- + + + {wallet.balance} + diff --git a/app/party/[id]/client.tsx b/app/party/[id]/client.tsx new file mode 100644 index 0000000..21e4256 --- /dev/null +++ b/app/party/[id]/client.tsx @@ -0,0 +1,276 @@ +"use client" + +import { useState } from "react" +import { useRouter } from "next/navigation" +import Image from "next/image" +import { + Mic, + MicOff, + MessageSquare, + Gift, + LogOut, + MoreHorizontal, + Send, + UserPlus, + Users, + Star, + Check, +} from "lucide-react" +import { Button } from "@/components/ui/button" +import { ScrollArea } from "@/components/ui/scroll-area" +import { Input } from "@/components/ui/input" +import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog" +import { useAppContext } from "@/components/providers/app-provider" + +const PARTY_DATA: Record = { + "1": { + name: "GM-远洋的派对群", + host: "GM-远洋", + avatar: "/streamer-1.jpg", + game: "英雄联盟", + background: "/party-lol.jpg", + }, + "2": { + name: "甜心女王粉丝派对", + host: "甜心女王", + avatar: "/streamer-5.jpg", + game: "王者荣耀", + background: "/party-hok.jpg", + }, + "3": { + name: "带粉上分车队", + host: "路人王", + avatar: "/streamer-3.jpg", + game: "和平精英", + background: "/party-team.jpg", + }, +} + +const GIFTS = [ + { id: 1, name: "小心心", price: 1, icon: "💕" }, + { id: 2, name: "棒棒糖", price: 10, icon: "🍭" }, + { id: 3, name: "玫瑰花", price: 52, icon: "🌹" }, + { id: 4, name: "告白气球", price: 520, icon: "🎈" }, + { id: 5, name: "跑车", price: 5000, icon: "🏎️" }, + { id: 6, name: "火箭", price: 10000, icon: "🚀" }, +] + +export function PartyRoomClient({ partyId }: { partyId: string }) { + const router = useRouter() + const { pay } = useAppContext() + const [isMicOn, setIsMicOn] = useState(false) + const [showGifts, setShowGifts] = useState(false) + const [showGiftSuccess, setShowGiftSuccess] = useState(false) + const [lastGift, setLastGift] = useState("") + const [messages, setMessages] = useState([ + { user: "系统", content: "欢迎来到派对房间", type: "system" }, + { user: "小柠檬", content: "大家好呀~", type: "user" }, + { user: "GM-远洋", content: "今天带大家上分!", type: "user" }, + ]) + const [inputValue, setInputValue] = useState("") + + const partyInfo = PARTY_DATA[partyId] || PARTY_DATA["1"] + + const seats = [ + { id: 0, user: { name: partyInfo.host, avatar: partyInfo.avatar, isMuted: false, isHost: true } }, + { id: 1, user: { name: "小柠檬", avatar: "/streamer-2.jpg", isMuted: false, isHost: false } }, + { id: 2, user: { name: "电竞萌妹", avatar: "/streamer-6.jpg", isMuted: true, isHost: false } }, + { id: 3, user: null }, + { id: 4, user: null }, + { id: 5, user: null }, + { id: 6, user: null }, + { id: 7, user: null }, + ] + + const handleSendMessage = () => { + if (!inputValue.trim()) return + setMessages([...messages, { user: "我", content: inputValue, type: "user" }]) + setInputValue("") + } + + const handleSendGift = (gift: (typeof GIFTS)[0]) => { + pay(gift.price, `送出${gift.name}`, "product") + setLastGift(`${gift.icon} ${gift.name}`) + setShowGifts(false) + setShowGiftSuccess(true) + setTimeout(() => setShowGiftSuccess(false), 2000) + } + + return ( +
+ {/* Background */} +
+ Background +
+
+ + {/* Gift Success Toast */} + {showGiftSuccess && ( +
+
+ + 已送出 {lastGift} +
+
+ )} + + {/* Header */} +
+
+ +
+
+ {partyInfo.host} +
+
+

+ {partyInfo.name} + +

+

+ + + 125人 + + {partyInfo.game} +

+
+
+
+ +
+ + {/* Seats Grid */} +
+
+ {seats.map((seat) => ( +
+
+
+ {seat.user ? ( + {seat.user.name} + ) : ( +
+ +
+ )} +
+ {seat.user?.isMuted && ( +
+ +
+ )} + {seat.user?.isHost && ( +
+ +
+ )} + {seat.id === 0 && ( +
+ )} +
+ + {seat.user ? seat.user.name : `${seat.id + 1}号麦`} + +
+ ))} +
+
+ + {/* Chat Area */} +
+ +
+ {messages.map((msg, i) => ( +
+ {msg.user}: + {msg.content} +
+ ))} +
+
+
+ + {/* Bottom Controls */} +
+
+ setInputValue(e.target.value)} + onKeyDown={(e) => e.key === "Enter" && handleSendMessage()} + /> + +
+
+
+
setIsMicOn(!isMicOn)}> +
+ {isMicOn ? : } +
+ {isMicOn ? "开麦" : "闭麦"} +
+
+
+ +
+ 私信 +
+
+ + +
+
+ + {/* Gift Dialog */} + + + + 送礼物给 {partyInfo.host} + +
+ {GIFTS.map((gift) => ( + + ))} +
+
+
+
+ ) +} diff --git a/app/party/[id]/loading.tsx b/app/party/[id]/loading.tsx new file mode 100644 index 0000000..9c61dd7 --- /dev/null +++ b/app/party/[id]/loading.tsx @@ -0,0 +1,10 @@ +export default function PartyLoading() { + return ( +
+
+
+

正在进入派对...

+
+
+ ) +} diff --git a/app/party/[id]/page.tsx b/app/party/[id]/page.tsx index af13f36..4f557c5 100644 --- a/app/party/[id]/page.tsx +++ b/app/party/[id]/page.tsx @@ -1,173 +1,6 @@ -"use client" +import { PartyRoomClient } from "./client" -import { useState } from "react" -import { useRouter } from "next/navigation" -import Image from "next/image" -import { Mic, MicOff, MessageSquare, Gift, LogOut, MoreHorizontal, Send, UserPlus } from "lucide-react" -import { Button } from "@/components/ui/button" -import { ScrollArea } from "@/components/ui/scroll-area" -import { toast } from "@/hooks/use-toast" -import { Input } from "@/components/ui/input" - -export default function PartyRoomPage({ params }: { params: { id: string } }) { - const router = useRouter() - const [isMicOn, setIsMicOn] = useState(false) - const [messages, setMessages] = useState([ - { user: "系统", content: "欢迎来到 玩值电竞 派对房间", type: "system" }, - { user: "小柠檬", content: "大家好呀~", type: "user" }, - { user: "GM-远洋", content: "来个唱歌好听的", type: "user" }, - ]) - const [inputValue, setInputValue] = useState("") - - const seats = Array(8) - .fill(null) - .map((_, i) => ({ - id: i, - user: - i < 3 - ? { - name: i === 0 ? "房主" : `用户${i}`, - avatar: i === 0 ? "gamer-girl-headphones.jpg" : "abstract-geometric-shapes.png", - isMuted: i === 2, - } - : null, - })) - - const handleSendMessage = () => { - if (!inputValue.trim()) return - setMessages([...messages, { user: "我", content: inputValue, type: "user" }]) - setInputValue("") - } - - return ( -
- {/* Background */} -
- Background -
-
- - {/* Header */} -
-
- -
-

王者荣耀五排车队

-

ID: {params.id} | 在线: 125

-
-
- -
- - {/* Seats Grid */} -
-
- {seats.map((seat) => ( -
-
-
- {seat.user ? ( - {seat.user.name} - ) : ( -
- -
- )} -
- {seat.user?.isMuted && ( -
- -
- )} - {/* Wave Animation for talking (mock) */} - {seat.id === 0 && ( -
- )} -
- - {seat.user ? seat.user.name : `${seat.id + 1}号麦`} - -
- ))} -
-
- - {/* Chat Area */} -
- -
- {messages.map((msg, i) => ( -
- {msg.user}: - {msg.content} -
- ))} -
-
-
- - {/* Bottom Controls */} -
-
- setInputValue(e.target.value)} - onKeyDown={(e) => e.key === "Enter" && handleSendMessage()} - /> - -
-
-
-
setIsMicOn(!isMicOn)}> -
- {isMicOn ? : } -
- {isMicOn ? "开麦" : "闭麦"} -
-
-
- -
- 私信 -
-
- - -
-
-
- ) +export default async function PartyRoomPage({ params }: { params: Promise<{ id: string }> }) { + const { id } = await params + return } diff --git a/app/planet/page.tsx b/app/planet/page.tsx index f085b80..6bbc4ae 100644 --- a/app/planet/page.tsx +++ b/app/planet/page.tsx @@ -8,7 +8,6 @@ import Image from "next/image" import { useState } from "react" import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog" import { useRouter } from "next/navigation" -import { useAppContext } from "@/components/providers/app-provider" type MatchCategory = "star" | "guild" | "cp" | "coach" @@ -83,16 +82,6 @@ const matchResultsData = { intro: "喜欢吃鸡,求带飞~", online: true, }, - { - id: 3, - name: "游戏小仙女", - title: "元气少女", - avatar: "/viewer-1.jpg", - age: 21, - game: "原神", - intro: "一起探索提瓦特吧", - online: false, - }, ], coach: [ { @@ -117,23 +106,11 @@ const matchResultsData = { rating: 4.8, orders: 1823, }, - { - id: 3, - name: "吃鸡教练", - title: "2000分大神", - avatar: "/streamer-3.jpg", - rank: "无敌战神", - price: 25, - game: "和平精英", - rating: 4.7, - orders: 3102, - }, ], } export default function PlanetPage() { const router = useRouter() - const { pay } = useAppContext() const [isMatching, setIsMatching] = useState(false) const [selectedCategory, setSelectedCategory] = useState("star") @@ -142,14 +119,13 @@ export default function PlanetPage() { setTimeout(() => { setIsMatching(false) - const results = matchResultsData[selectedCategory] const randomResult = results[Math.floor(Math.random() * results.length)] - // 直接跳转到对应页面 switch (selectedCategory) { case "star": - router.push(`/star/${randomResult.id}`) + // 直接跳转到明星的派对群聊天室 + router.push(`/party/${randomResult.id}`) break case "guild": router.push(`/guild/${randomResult.id}`) @@ -164,10 +140,6 @@ export default function PlanetPage() { }, 1500) } - const handleCategoryChange = (category: MatchCategory) => { - setSelectedCategory(category) - } - const getCategoryLabel = () => { switch (selectedCategory) { case "star": @@ -185,38 +157,36 @@ export default function PlanetPage() {

星球

-
- - - - - - - 筛选派对 - -
-
-

类型

-
- {["全部", "游戏", "聊天", "K歌", "交友"].map((tag) => ( - - ))} -
+ + + + + + + 筛选派对 + +
+
+

类型

+
+ {["全部", "游戏", "聊天", "K歌", "交友"].map((tag) => ( + + ))}
- - -
-
+
+ +
+
@@ -293,10 +263,8 @@ export default function PlanetPage() {
+

编辑资料

+ +
+
+ +
+ {/* Avatar Section */} +
+
setShowAvatarPicker(true)}> +
+ Avatar +
+
+ +
+
+

点击更换头像

+
+ + {/* Form Fields */} +
+
+
+ 昵称 + setName(e.target.value)} + className="bg-transparent border-none text-right w-48 h-auto p-0" + placeholder="请输入昵称" + maxLength={12} + /> +
+
+ 玩值ID + {user.odlId || "888888"} +
+
+ 性别 +
+ {["male", "female", "unknown"].map((g) => ( + + ))} +
+
+
+ 个性签名 +
+ {bio || "这个人很懒,什么都没写"} + +
+
+
+
+
+ + {/* Avatar Picker Modal */} + {showAvatarPicker && ( +
+
+
+

选择头像

+ +
+
+ {avatarOptions.map((avatar, i) => ( + + ))} +
+
+
+ )} +
+ ) +} diff --git a/app/profile/page.tsx b/app/profile/page.tsx index e22605c..abfd0b9 100644 --- a/app/profile/page.tsx +++ b/app/profile/page.tsx @@ -5,7 +5,6 @@ import { ChevronRight, FileText, MessageSquare, - Presentation, BookOpen, Bell, LogOut, @@ -15,54 +14,62 @@ import { User, Medal, Coins, + Edit3, + Camera, + HelpCircle, } from "lucide-react" import Image from "next/image" import Link from "next/link" import { Button } from "@/components/ui/button" import { useAppContext } from "@/components/providers/app-provider" -import { toast } from "@/hooks/use-toast" -import { Card, CardContent } from "@/components/ui/card" +import { useRouter } from "next/navigation" +import { useState } from "react" export default function ProfilePage() { - const { user, wallet } = useAppContext() + const { user, wallet, logout } = useAppContext() + const router = useRouter() + const [showLogoutConfirm, setShowLogoutConfirm] = useState(false) - const handleComingSoon = (feature: string) => { - toast({ - title: "功能开发中", - description: `${feature}即将上线,敬请期待`, - }) + const handleLogout = () => { + setShowLogoutConfirm(true) + } + + const confirmLogout = () => { + logout() + router.push("/login") } return ( -
+
{/* Header Area */} -
+
- + + +
{wallet.balance} - +
-
- Profile -
- LV.8 + +
+ Profile +
+ LV.{user.level || 8} +
-
+
+ +
+
-

{user.name}

-

- ID: {user.id || "888888"} +

+

{user.name}

+ + + +
+

+ ID: {user.odlId || "888888"}

-

电竞达人 | 王者荣耀主播

+

电竞达人 | 王者荣耀主播

-
+
{user.activity || 0}
-
获赞
+
获赞
-
+
{user.following || 0}
-
关注
+
关注
-
+
{user.followers || 0}
-
粉丝
+
粉丝
@@ -126,96 +148,97 @@ export default function ProfilePage() {

享网咖8折、酒店9折特权

- - - -
- -
- 钱包 - - -
- -
- 卡券 - - -
- -
- 足迹 - - -
- -
- 装扮 - -
-
+ {/* Quick Actions */} +
+ +
+ +
+ 钱包 + + +
+ +
+ 卡券 + + +
+ +
+ 账单 + + +
+ +
+ 装扮 + +
- {/* Menu Items */} -
+
{[ { icon: FileText, label: "我的订单", href: "/orders" }, { icon: BookOpen, label: "我的课程", href: "/my-courses" }, - { icon: Presentation, label: "商业计划", href: "/pitch-deck", hasBadge: true }, - { icon: MessageSquare, label: "联系客服", action: () => handleComingSoon("客服") }, - ].map((item, i) => - item.href ? ( - - - - {item.label} - -
- {item.hasBadge && ( - New - )} - -
- - ) : ( - - ), - )} + { icon: HelpCircle, label: "账号与帮助", href: "/account-help" }, + { icon: MessageSquare, label: "联系客服", href: "/customer-service" }, + ].map((item, i) => ( + + + + {item.label} + + + + ))}
+ + {/* Logout Confirmation Modal */} + {showLogoutConfirm && ( +
+
+

确认退出

+

退出后需要重新登录才能使用完整功能

+
+ + +
+
+
+ )}
) } diff --git a/app/recharge/page.tsx b/app/recharge/page.tsx index ca0e2c0..ef0fbe8 100644 --- a/app/recharge/page.tsx +++ b/app/recharge/page.tsx @@ -1,12 +1,18 @@ "use client" +import { useState } from "react" import { useRouter } from "next/navigation" -import { ArrowLeft, Coins, Gem } from "lucide-react" +import { ArrowLeft, Coins, Gem, Check, CreditCard } from "lucide-react" import { useAppContext } from "@/components/providers/app-provider" +import { Button } from "@/components/ui/button" export default function RechargePage() { const router = useRouter() const { recharge, wallet } = useAppContext() + const [selectedAmount, setSelectedAmount] = useState(null) + const [selectedPayment, setSelectedPayment] = useState<"wechat" | "alipay">("wechat") + const [showSuccess, setShowSuccess] = useState(false) + const [purchasedCoins, setPurchasedCoins] = useState(0) const amounts = [ { value: 6, coins: 60, bonus: 0 }, @@ -17,12 +23,23 @@ export default function RechargePage() { { value: 648, coins: 6480, bonus: 800 }, ] - const handleRecharge = (coins: number, bonus: number) => { - recharge(coins + bonus) + const handleSelectAmount = (index: number) => { + setSelectedAmount(index) } + const handleRecharge = () => { + if (selectedAmount === null) return + const item = amounts[selectedAmount] + const totalCoins = item.coins + item.bonus + recharge(totalCoins) + setPurchasedCoins(totalCoins) + setShowSuccess(true) + } + + const selectedItem = selectedAmount !== null ? amounts[selectedAmount] : null + return ( -
+
{/* Banner */} -
+
-

首充双倍

+

首充双倍

限时特惠 赠送绝版头像框

-
- +
+
-
- {amounts.map((item) => ( - - ))} + {/* 充值金额选择 */} +
+

选择充值金额

+
+ {amounts.map((item, index) => ( + + ))} +
{/* 支付方式 */}

支付方式

-
+ +
-

充值即代表同意《用户充值协议》

+

充值即代表同意《用户充值协议》

+ +
+
+ 应付金额 + + {selectedItem ? `¥${selectedItem.value}` : "请选择金额"} + +
+ +
+ + {/* 充值成功弹窗 */} + {showSuccess && ( +
+
+
+
+ +
+

充值成功

+

+ 已充值 {purchasedCoins} 币 +

+

当前余额: {wallet.balance} 币

+ +
+
+
+ )}
) } diff --git a/app/settings/page.tsx b/app/settings/page.tsx new file mode 100644 index 0000000..03f6c05 --- /dev/null +++ b/app/settings/page.tsx @@ -0,0 +1,120 @@ +"use client" + +import { ArrowLeft, ChevronRight, Moon, Bell, Shield, Globe, Info, Trash2 } from "lucide-react" +import { Button } from "@/components/ui/button" +import { Switch } from "@/components/ui/switch" +import { useRouter } from "next/navigation" +import { useState } from "react" + +export default function SettingsPage() { + const router = useRouter() + const [darkMode, setDarkMode] = useState(true) + const [notifications, setNotifications] = useState(true) + const [showClearConfirm, setShowClearConfirm] = useState(false) + + const handleClearCache = () => { + localStorage.clear() + setShowClearConfirm(false) + } + + return ( +
+ {/* Header */} +
+ +

设置

+
+ +
+ {/* General Settings */} +
+
+
+ + 深色模式 +
+ +
+
+
+ + 消息通知 +
+ +
+ +
+ + {/* Other Settings */} +
+ + + +
+
+ + {/* Clear Cache Confirm */} + {showClearConfirm && ( +
+
+

清除缓存

+

确定要清除所有缓存数据吗?

+
+ + +
+
+
+ )} +
+ ) +} diff --git a/app/transaction-history/page.tsx b/app/transaction-history/page.tsx new file mode 100644 index 0000000..47afd54 --- /dev/null +++ b/app/transaction-history/page.tsx @@ -0,0 +1,97 @@ +"use client" + +import { ArrowLeft, ArrowUpRight, ArrowDownLeft, Filter } from "lucide-react" +import { useRouter } from "next/navigation" +import { useAppContext } from "@/components/providers/app-provider" +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" + +export default function TransactionHistoryPage() { + const router = useRouter() + const { orders, wallet } = useAppContext() + + const incomeOrders = orders.filter((o) => o.type === "recharge") + const expenseOrders = orders.filter((o) => o.type !== "recharge") + + return ( +
+ {/* Header */} +
+
+ +

账单明细

+
+ +
+ + {/* Balance Card */} +
+

当前余额

+

+ {wallet.balance} +

+
+ + {/* Tabs */} + +
+ + + 全部 + + + 收入 + + + 支出 + + +
+ + {[ + { key: "all", data: orders }, + { key: "income", data: incomeOrders }, + { key: "expense", data: expenseOrders }, + ].map(({ key, data }) => ( + + {data.length > 0 ? ( + data.map((order, i) => ( +
+
+
+
+ {order.type === "recharge" ? : } +
+
+

{order.title}

+

{order.date}

+
+
+
+

+ {order.type === "recharge" ? "+" : "-"} + {order.price} +

+

+
+
+
+ )) + ) : ( +
+

暂无记录

+
+ )} +
+ ))} +
+
+ ) +} diff --git a/components/providers/app-provider.tsx b/components/providers/app-provider.tsx index e8c2e43..d6f382b 100644 --- a/components/providers/app-provider.tsx +++ b/components/providers/app-provider.tsx @@ -1,97 +1,254 @@ "use client" import type React from "react" -import { createContext, useContext, useState, useEffect } from "react" +import { createContext, useContext, useState, useEffect, useCallback } from "react" import { toast } from "@/hooks/use-toast" +import { db } from "@/lib/db/schema" type User = { + id?: number name: string avatar: string level: number isVip: boolean + vipType?: "month" | "quarter" | "year" vipExpiry?: string - id?: string + odlId?: string + phone?: string activity?: number following?: number followers?: number visitors?: number + isLoggedIn?: boolean } type Wallet = { - balance: number // 玩值币 - 唯一货币 + balance: number } type Order = { id: string - type: "service" | "product" + type: "service" | "product" | "recharge" | "hotel" | "cafe" | "course" | "pawn" title: string price: number - status: "pending" | "completed" | "cancelled" + status: "pending" | "completed" | "cancelled" | "refunded" date: string image?: string + orderNo?: string } type AppContextType = { user: User wallet: Wallet orders: Order[] + isLoggedIn: boolean recharge: (amount: number) => void - pay: (amount: number, title: string, type: "service" | "product", image?: string) => boolean + pay: (amount: number, title: string, type: Order["type"], image?: string) => boolean updateUser: (updates: Partial) => void + login: (phone: string, password?: string) => Promise + logout: () => void + syncFromDatabase: () => Promise + saveToDatabase: () => Promise } const AppContext = createContext(undefined) export function AppProvider({ children }: { children: React.ReactNode }) { + const [isLoggedIn, setIsLoggedIn] = useState(true) const [user, setUser] = useState({ - name: "卡若", + name: "电竞玩家", avatar: "/gamer-boy-esports-jersey.jpg", level: 12, isVip: false, - id: "888888", + odlId: "888888", activity: 128, following: 45, followers: 892, visitors: 1205, + isLoggedIn: true, }) const [wallet, setWallet] = useState({ - balance: 6880, // 统一的玩值币余额 + balance: 6880, }) const [orders, setOrders] = useState([ { id: "1", + orderNo: "WZ20240115001", type: "service", - title: "LOL陪玩 - 1小时", - price: 50, + title: "王者荣耀陪玩 - 2小时", + price: 80, status: "completed", - date: "2023-11-15", + date: "2024-01-15", + image: "/王者荣耀.jpg", + }, + { + id: "2", + orderNo: "WZ20240114002", + type: "course", + title: "打野进阶课:从入门到王者", + price: 299, + status: "completed", + date: "2024-01-14", image: "/lol.jpg", }, + { + id: "3", + orderNo: "WZ20240113003", + type: "hotel", + title: "玩值电竞酒店(旗舰店) - 1晚", + price: 299, + status: "completed", + date: "2024-01-13", + image: "/hotel-1.jpg", + }, ]) - useEffect(() => { - const savedWallet = localStorage.getItem("wanzhi_wallet_v2") - if (savedWallet) setWallet(JSON.parse(savedWallet)) + const syncFromDatabase = useCallback(async () => { + try { + const dbUser = await db.users.get(1) + if (dbUser) { + setUser({ + id: dbUser.id, + name: dbUser.name, + avatar: dbUser.avatar, + level: dbUser.level, + isVip: dbUser.isVip, + vipType: dbUser.vipType, + vipExpiry: dbUser.vipExpiry, + odlId: dbUser.odlId, + phone: dbUser.phone, + activity: dbUser.activity, + following: dbUser.following, + followers: dbUser.followers, + visitors: dbUser.visitors, + isLoggedIn: true, + }) + setWallet({ balance: dbUser.balance }) + setIsLoggedIn(true) + } - const savedOrders = localStorage.getItem("wanzhi_orders") - if (savedOrders) setOrders(JSON.parse(savedOrders)) + // 加载订单 + const dbOrders = await db.transactions.where("userId").equals(1).reverse().sortBy("createdAt") + if (dbOrders.length > 0) { + const mappedOrders: Order[] = dbOrders.slice(0, 20).map((t) => ({ + id: String(t.id), + orderNo: t.orderNo, + type: t.category === "充值" ? "recharge" : t.category === "服务消费" ? "service" : "product", + title: t.description, + price: Math.abs(t.amount), + status: t.status === "completed" ? "completed" : "pending", + date: t.createdAt.split("T")[0], + })) + setOrders((prev) => [...mappedOrders, ...prev.filter((o) => !mappedOrders.find((m) => m.id === o.id))]) + } + } catch (err) { + console.error("[v0] Sync from database error:", err) + } }, []) + const saveToDatabase = useCallback(async () => { + try { + const dbUser = await db.users.get(1) + if (dbUser) { + await db.users.update(1, { + name: user.name, + avatar: user.avatar, + level: user.level, + isVip: user.isVip, + vipType: user.vipType, + vipExpiry: user.vipExpiry, + balance: wallet.balance, + activity: user.activity, + following: user.following, + followers: user.followers, + visitors: user.visitors, + updatedAt: new Date().toISOString(), + }) + } + } catch (err) { + console.error("[v0] Save to database error:", err) + } + }, [user, wallet]) + + useEffect(() => { + const timer = setTimeout(() => { + syncFromDatabase() + }, 500) + return () => clearTimeout(timer) + }, [syncFromDatabase]) + useEffect(() => { localStorage.setItem("wanzhi_wallet_v2", JSON.stringify(wallet)) + db.users + .get(1) + .then((dbUser) => { + if (dbUser) { + db.users.update(1, { balance: wallet.balance, updatedAt: new Date().toISOString() }) + } + }) + .catch(() => {}) }, [wallet]) useEffect(() => { localStorage.setItem("wanzhi_orders", JSON.stringify(orders)) }, [orders]) - const recharge = (amount: number) => { - setWallet((prev) => ({ ...prev, balance: prev.balance + amount })) + const login = async (phone: string, password?: string): Promise => { + try { + // 模拟登录验证 + if (phone.length === 11) { + setIsLoggedIn(true) + setUser((prev) => ({ ...prev, phone, isLoggedIn: true })) + await db.users.update(1, { phone, lastLoginTime: new Date().toISOString() }) + return true + } + return false + } catch { + return false + } } - const pay = (amount: number, title: string, type: "service" | "product", image?: string): boolean => { + const logout = () => { + setIsLoggedIn(false) + setUser((prev) => ({ ...prev, isLoggedIn: false })) + localStorage.removeItem("wanzhi_wallet_v2") + localStorage.removeItem("wanzhi_orders") + } + + const recharge = (amount: number) => { + const orderNo = `RCH${Date.now()}` + setWallet((prev) => ({ ...prev, balance: prev.balance + amount })) + + const newOrder: Order = { + id: Math.random().toString(36).substr(2, 9), + orderNo, + type: "recharge", + title: `充值 ${amount} 玩值币`, + price: amount, + status: "completed", + date: new Date().toISOString().split("T")[0], + } + setOrders((prev) => [newOrder, ...prev]) + + db.transactions + .add({ + userId: 1, + orderNo, + type: "recharge", + category: "充值", + amount, + balanceBefore: wallet.balance, + balanceAfter: wallet.balance + amount, + description: `充值 ${amount} 玩值币`, + status: "completed", + createdAt: new Date().toISOString(), + }) + .catch(() => {}) + } + + const pay = (amount: number, title: string, type: Order["type"], image?: string): boolean => { if (wallet.balance < amount) { toast({ title: "余额不足", @@ -101,10 +258,12 @@ export function AppProvider({ children }: { children: React.ReactNode }) { return false } + const orderNo = `PAY${Date.now()}` setWallet((prev) => ({ ...prev, balance: prev.balance - amount })) const newOrder: Order = { id: Math.random().toString(36).substr(2, 9), + orderNo, type, title, price: amount, @@ -114,15 +273,64 @@ export function AppProvider({ children }: { children: React.ReactNode }) { } setOrders((prev) => [newOrder, ...prev]) + + db.transactions + .add({ + userId: 1, + orderNo, + type: "purchase", + category: + type === "service" + ? "服务消费" + : type === "hotel" + ? "酒店预订" + : type === "cafe" + ? "网咖抢座" + : type === "course" + ? "课程购买" + : "商品购买", + amount: -amount, + balanceBefore: wallet.balance, + balanceAfter: wallet.balance - amount, + description: title, + status: "completed", + createdAt: new Date().toISOString(), + }) + .catch(() => {}) + return true } const updateUser = (updates: Partial) => { setUser((prev) => ({ ...prev, ...updates })) + db.users + .get(1) + .then((dbUser) => { + if (dbUser) { + db.users.update(1, { ...updates, updatedAt: new Date().toISOString() }) + } + }) + .catch(() => {}) } return ( - {children} + + {children} + ) } diff --git a/components/providers/database-provider.tsx b/components/providers/database-provider.tsx new file mode 100644 index 0000000..84bf7c8 --- /dev/null +++ b/components/providers/database-provider.tsx @@ -0,0 +1,63 @@ +"use client" + +import { createContext, useContext, useState, useEffect, type ReactNode } from "react" +import Dexie from "dexie" + +interface DatabaseContextType { + isReady: boolean + error: Error | null +} + +const DatabaseContext = createContext({ + isReady: false, + error: null, +}) + +export function DatabaseProvider({ children }: { children: ReactNode }) { + const [isReady, setIsReady] = useState(false) + const [error, setError] = useState(null) + + useEffect(() => { + const initDatabase = async () => { + try { + // 动态导入数据库模块 + const { db } = await import("@/lib/db/schema") + const { seedDatabase } = await import("@/lib/db/seed") + + await db.open() + await seedDatabase() + setIsReady(true) + } catch (err) { + console.error("[v0] Database init error:", err) + // 如果IndexedDB不可用,仍然允许应用运行 + if (err instanceof Dexie.MissingAPIError) { + console.warn("[v0] IndexedDB not available, using fallback") + setIsReady(true) + } else { + setError(err as Error) + // 即使出错也允许应用运行 + setIsReady(true) + } + } + } + + initDatabase() + }, []) + + if (!isReady) { + return ( +
+
+
+

正在初始化...

+
+
+ ) + } + + return {children} +} + +export function useDatabaseContext() { + return useContext(DatabaseContext) +} diff --git a/components/ui/avatar.tsx b/components/ui/avatar.tsx new file mode 100644 index 0000000..aa98465 --- /dev/null +++ b/components/ui/avatar.tsx @@ -0,0 +1,53 @@ +'use client' + +import * as React from 'react' +import * as AvatarPrimitive from '@radix-ui/react-avatar' + +import { cn } from '@/lib/utils' + +function Avatar({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AvatarImage({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AvatarFallback({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { Avatar, AvatarImage, AvatarFallback } diff --git a/components/ui/badge.tsx b/components/ui/badge.tsx new file mode 100644 index 0000000..fc4126b --- /dev/null +++ b/components/ui/badge.tsx @@ -0,0 +1,46 @@ +import * as React from 'react' +import { Slot } from '@radix-ui/react-slot' +import { cva, type VariantProps } from 'class-variance-authority' + +import { cn } from '@/lib/utils' + +const badgeVariants = cva( + 'inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden', + { + variants: { + variant: { + default: + 'border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90', + secondary: + 'border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90', + destructive: + 'border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60', + outline: + 'text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground', + }, + }, + defaultVariants: { + variant: 'default', + }, + }, +) + +function Badge({ + className, + variant, + asChild = false, + ...props +}: React.ComponentProps<'span'> & + VariantProps & { asChild?: boolean }) { + const Comp = asChild ? Slot : 'span' + + return ( + + ) +} + +export { Badge, badgeVariants } diff --git a/components/ui/dropdown-menu.tsx b/components/ui/dropdown-menu.tsx new file mode 100644 index 0000000..a2096fa --- /dev/null +++ b/components/ui/dropdown-menu.tsx @@ -0,0 +1,257 @@ +'use client' + +import * as React from 'react' +import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu' +import { CheckIcon, ChevronRightIcon, CircleIcon } from 'lucide-react' + +import { cn } from '@/lib/utils' + +function DropdownMenu({ + ...props +}: React.ComponentProps) { + return +} + +function DropdownMenuPortal({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DropdownMenuTrigger({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DropdownMenuContent({ + className, + sideOffset = 4, + ...props +}: React.ComponentProps) { + return ( + + + + ) +} + +function DropdownMenuGroup({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DropdownMenuItem({ + className, + inset, + variant = 'default', + ...props +}: React.ComponentProps & { + inset?: boolean + variant?: 'default' | 'destructive' +}) { + return ( + + ) +} + +function DropdownMenuCheckboxItem({ + className, + children, + checked, + ...props +}: React.ComponentProps) { + return ( + + + + + + + {children} + + ) +} + +function DropdownMenuRadioGroup({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DropdownMenuRadioItem({ + className, + children, + ...props +}: React.ComponentProps) { + return ( + + + + + + + {children} + + ) +} + +function DropdownMenuLabel({ + className, + inset, + ...props +}: React.ComponentProps & { + inset?: boolean +}) { + return ( + + ) +} + +function DropdownMenuSeparator({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DropdownMenuShortcut({ + className, + ...props +}: React.ComponentProps<'span'>) { + return ( + + ) +} + +function DropdownMenuSub({ + ...props +}: React.ComponentProps) { + return +} + +function DropdownMenuSubTrigger({ + className, + inset, + children, + ...props +}: React.ComponentProps & { + inset?: boolean +}) { + return ( + + {children} + + + ) +} + +function DropdownMenuSubContent({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { + DropdownMenu, + DropdownMenuPortal, + DropdownMenuTrigger, + DropdownMenuContent, + DropdownMenuGroup, + DropdownMenuLabel, + DropdownMenuItem, + DropdownMenuCheckboxItem, + DropdownMenuRadioGroup, + DropdownMenuRadioItem, + DropdownMenuSeparator, + DropdownMenuShortcut, + DropdownMenuSub, + DropdownMenuSubTrigger, + DropdownMenuSubContent, +} diff --git a/components/ui/switch.tsx b/components/ui/switch.tsx new file mode 100644 index 0000000..3c4cfa3 --- /dev/null +++ b/components/ui/switch.tsx @@ -0,0 +1,31 @@ +'use client' + +import * as React from 'react' +import * as SwitchPrimitive from '@radix-ui/react-switch' + +import { cn } from '@/lib/utils' + +function Switch({ + className, + ...props +}: React.ComponentProps) { + return ( + + + + ) +} + +export { Switch } diff --git a/lib/db/hooks.ts b/lib/db/hooks.ts new file mode 100644 index 0000000..c5f6543 --- /dev/null +++ b/lib/db/hooks.ts @@ -0,0 +1,462 @@ +// 数据库操作Hooks +"use client" + +import { useState, useEffect, useCallback } from "react" +import { db } from "./schema" +import { seedDatabase } from "./seed" +import type { + User, + Streamer, + Star, + Guild, + Companion, + EsportsVenue, + Product, + Transaction, + Moment, + Message, + Notification, +} from "./schema" + +// 初始化数据库 +export function useInitDatabase() { + const [isReady, setIsReady] = useState(false) + const [error, setError] = useState(null) + + useEffect(() => { + const init = async () => { + try { + await db.open() + await seedDatabase() + setIsReady(true) + } catch (err) { + console.error("[v0] Database init error:", err) + setError(err as Error) + } + } + init() + }, []) + + return { isReady, error } +} + +// 用户相关 +export function useCurrentUser() { + const [user, setUser] = useState(null) + const [loading, setLoading] = useState(true) + + useEffect(() => { + const loadUser = async () => { + try { + const currentUser = await db.users.get(1) // 默认用户ID为1 + setUser(currentUser || null) + } catch (err) { + console.error("[v0] Load user error:", err) + } finally { + setLoading(false) + } + } + loadUser() + }, []) + + const updateUser = useCallback( + async (updates: Partial) => { + if (!user?.id) return + await db.users.update(user.id, { ...updates, updatedAt: new Date().toISOString() }) + setUser((prev) => (prev ? { ...prev, ...updates } : null)) + }, + [user?.id], + ) + + const updateBalance = useCallback( + async (amount: number, type: "add" | "subtract") => { + if (!user?.id) return false + const newBalance = type === "add" ? user.balance + amount : user.balance - amount + if (newBalance < 0) return false + await db.users.update(user.id, { balance: newBalance, updatedAt: new Date().toISOString() }) + setUser((prev) => (prev ? { ...prev, balance: newBalance } : null)) + return true + }, + [user], + ) + + return { user, loading, updateUser, updateBalance } +} + +// 主播列表 +export function useStreamers(options?: { game?: string; isLive?: boolean; limit?: number }) { + const [streamers, setStreamers] = useState([]) + const [loading, setLoading] = useState(true) + + useEffect(() => { + const load = async () => { + try { + let query = db.streamers.where("status").equals("approved") + if (options?.isLive !== undefined) { + query = db.streamers.where("isLive").equals(options.isLive ? 1 : 0) + } + let results = await query.toArray() + if (options?.game) { + results = results.filter((s) => s.game === options.game) + } + if (options?.limit) { + results = results.slice(0, options.limit) + } + setStreamers(results) + } catch (err) { + console.error("[v0] Load streamers error:", err) + } finally { + setLoading(false) + } + } + load() + }, [options?.game, options?.isLive, options?.limit]) + + return { streamers, loading } +} + +// 明星列表 +export function useStars(options?: { game?: string; limit?: number }) { + const [stars, setStars] = useState([]) + const [loading, setLoading] = useState(true) + + useEffect(() => { + const load = async () => { + try { + let results = await db.stars.where("status").equals("active").toArray() + if (options?.game) { + results = results.filter((s) => s.game === options.game) + } + if (options?.limit) { + results = results.slice(0, options.limit) + } + setStars(results) + } catch (err) { + console.error("[v0] Load stars error:", err) + } finally { + setLoading(false) + } + } + load() + }, [options?.game, options?.limit]) + + return { stars, loading } +} + +// 公会列表 +export function useGuilds(options?: { limit?: number }) { + const [guilds, setGuilds] = useState([]) + const [loading, setLoading] = useState(true) + + useEffect(() => { + const load = async () => { + try { + let results = await db.guilds.where("status").equals("active").toArray() + if (options?.limit) { + results = results.slice(0, options.limit) + } + setGuilds(results) + } catch (err) { + console.error("[v0] Load guilds error:", err) + } finally { + setLoading(false) + } + } + load() + }, [options?.limit]) + + return { guilds, loading } +} + +// CP/陪练列表 +export function useCompanions(options?: { category?: "cp" | "coach"; gender?: string; limit?: number }) { + const [companions, setCompanions] = useState([]) + const [loading, setLoading] = useState(true) + + useEffect(() => { + const load = async () => { + try { + let results = await db.companions.where("status").equals("active").toArray() + if (options?.category) { + results = results.filter((c) => c.category === options.category) + } + if (options?.gender) { + results = results.filter((c) => c.gender === options.gender) + } + if (options?.limit) { + results = results.slice(0, options.limit) + } + setCompanions(results) + } catch (err) { + console.error("[v0] Load companions error:", err) + } finally { + setLoading(false) + } + } + load() + }, [options?.category, options?.gender, options?.limit]) + + return { companions, loading } +} + +// 电竞酒店/网咖 +export function useVenues(options?: { type?: "hotel" | "cafe"; city?: string; limit?: number }) { + const [venues, setVenues] = useState([]) + const [loading, setLoading] = useState(true) + + useEffect(() => { + const load = async () => { + try { + let results = await db.esportsVenues.where("status").equals("active").toArray() + if (options?.type) { + results = results.filter((v) => v.type === options.type) + } + if (options?.city) { + results = results.filter((v) => v.city === options.city) + } + if (options?.limit) { + results = results.slice(0, options.limit) + } + setVenues(results) + } catch (err) { + console.error("[v0] Load venues error:", err) + } finally { + setLoading(false) + } + } + load() + }, [options?.type, options?.city, options?.limit]) + + return { venues, loading } +} + +// 商品列表 +export function useProducts(options?: { category?: string; game?: string; limit?: number }) { + const [products, setProducts] = useState([]) + const [loading, setLoading] = useState(true) + + useEffect(() => { + const load = async () => { + try { + let results = await db.products.where("status").equals("active").toArray() + if (options?.category) { + results = results.filter((p) => p.category === options.category) + } + if (options?.game) { + results = results.filter((p) => p.game === options.game) + } + if (options?.limit) { + results = results.slice(0, options.limit) + } + setProducts(results) + } catch (err) { + console.error("[v0] Load products error:", err) + } finally { + setLoading(false) + } + } + load() + }, [options?.category, options?.game, options?.limit]) + + return { products, loading } +} + +// 交易记录 +export function useTransactions(userId: number, options?: { type?: string; limit?: number }) { + const [transactions, setTransactions] = useState([]) + const [loading, setLoading] = useState(true) + + useEffect(() => { + const load = async () => { + try { + let results = await db.transactions.where("userId").equals(userId).toArray() + if (options?.type) { + results = results.filter((t) => t.type === options.type) + } + results = results.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()) + if (options?.limit) { + results = results.slice(0, options.limit) + } + setTransactions(results) + } catch (err) { + console.error("[v0] Load transactions error:", err) + } finally { + setLoading(false) + } + } + load() + }, [userId, options?.type, options?.limit]) + + return { transactions, loading } +} + +// 动态列表 +export function useMoments(options?: { userId?: number; limit?: number }) { + const [moments, setMoments] = useState([]) + const [loading, setLoading] = useState(true) + + const loadMoments = useCallback(async () => { + try { + let results = await db.moments.where("status").equals("active").toArray() + if (options?.userId) { + results = results.filter((m) => m.userId === options.userId) + } + results = results.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()) + if (options?.limit) { + results = results.slice(0, options.limit) + } + setMoments(results) + } catch (err) { + console.error("[v0] Load moments error:", err) + } finally { + setLoading(false) + } + }, [options?.userId, options?.limit]) + + useEffect(() => { + loadMoments() + }, [loadMoments]) + + const likeMoment = useCallback( + async (momentId: number) => { + const moment = await db.moments.get(momentId) + if (moment) { + await db.moments.update(momentId, { + likes: moment.likes + 1, + isLiked: true, + updatedAt: new Date().toISOString(), + }) + await loadMoments() + } + }, + [loadMoments], + ) + + return { moments, loading, likeMoment, refresh: loadMoments } +} + +// 消息列表 +export function useMessages(conversationId: number) { + const [messages, setMessages] = useState([]) + const [loading, setLoading] = useState(true) + + useEffect(() => { + const load = async () => { + try { + const results = await db.messages.where("conversationId").equals(conversationId).toArray() + setMessages(results.sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime())) + } catch (err) { + console.error("[v0] Load messages error:", err) + } finally { + setLoading(false) + } + } + load() + }, [conversationId]) + + const sendMessage = useCallback( + async (content: string, type: "text" | "image" | "voice" = "text") => { + const newMessage: Omit = { + conversationId, + senderId: 1, // 当前用户 + receiverId: 0, // 需要从conversation获取 + type, + content, + isRead: false, + createdAt: new Date().toISOString(), + } + const id = await db.messages.add(newMessage as Message) + setMessages((prev) => [...prev, { ...newMessage, id } as Message]) + }, + [conversationId], + ) + + return { messages, loading, sendMessage } +} + +// 通知列表 +export function useNotifications(userId: number) { + const [notifications, setNotifications] = useState([]) + const [unreadCount, setUnreadCount] = useState(0) + const [loading, setLoading] = useState(true) + + useEffect(() => { + const load = async () => { + try { + const results = await db.notifications.where("userId").equals(userId).toArray() + const sorted = results.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()) + setNotifications(sorted) + setUnreadCount(sorted.filter((n) => !n.isRead).length) + } catch (err) { + console.error("[v0] Load notifications error:", err) + } finally { + setLoading(false) + } + } + load() + }, [userId]) + + const markAsRead = useCallback(async (notificationId: number) => { + await db.notifications.update(notificationId, { isRead: true }) + setNotifications((prev) => prev.map((n) => (n.id === notificationId ? { ...n, isRead: true } : n))) + setUnreadCount((prev) => Math.max(0, prev - 1)) + }, []) + + const markAllAsRead = useCallback(async () => { + const unread = notifications.filter((n) => !n.isRead) + await Promise.all(unread.map((n) => db.notifications.update(n.id!, { isRead: true }))) + setNotifications((prev) => prev.map((n) => ({ ...n, isRead: true }))) + setUnreadCount(0) + }, [notifications]) + + return { notifications, unreadCount, loading, markAsRead, markAllAsRead } +} + +// 创建订单 +export function useCreateOrder() { + const createTransaction = useCallback( + async ( + userId: number, + type: Transaction["type"], + category: string, + amount: number, + description: string, + relatedId?: number, + relatedType?: string, + ) => { + const user = await db.users.get(userId) + if (!user) throw new Error("User not found") + + const balanceBefore = user.balance + const balanceAfter = + type === "recharge" || type === "income" || type === "refund" ? balanceBefore + amount : balanceBefore - amount + + if (balanceAfter < 0) { + throw new Error("Insufficient balance") + } + + const transaction: Omit = { + userId, + orderNo: `TXN${Date.now()}${Math.random().toString(36).substr(2, 6).toUpperCase()}`, + type, + category, + amount, + balanceBefore, + balanceAfter, + relatedId, + relatedType, + description, + status: "completed", + createdAt: new Date().toISOString(), + } + + await db.transactions.add(transaction as Transaction) + await db.users.update(userId, { balance: balanceAfter, updatedAt: new Date().toISOString() }) + + return transaction + }, + [], + ) + + return { createTransaction } +} diff --git a/lib/db/index.ts b/lib/db/index.ts new file mode 100644 index 0000000..cd56bfa --- /dev/null +++ b/lib/db/index.ts @@ -0,0 +1,5 @@ +// 数据库模块统一导出 +export { db, WanzhiDatabase } from "./schema" +export type * from "./schema" +export { seedDatabase, clearDatabase } from "./seed" +export * from "./hooks" diff --git a/lib/db/schema.ts b/lib/db/schema.ts new file mode 100644 index 0000000..03c76be --- /dev/null +++ b/lib/db/schema.ts @@ -0,0 +1,881 @@ +// 玩值电竞 - 完整数据库模式定义 +// 使用 Dexie.js (IndexedDB) 作为本地数据库 + +import Dexie, { type Table } from "dexie" + +// ==================== 用户相关 ==================== + +export interface User { + id?: number + odlId: string // 旧版用户ID兼容 + phone: string + password?: string // 加密存储 + name: string + avatar: string + level: number + experience: number + isVip: boolean + vipType?: "month" | "quarter" | "year" + vipExpiry?: string + gender: "male" | "female" | "unknown" + birthday?: string + bio?: string + tags: string[] + activity: number + following: number + followers: number + visitors: number + balance: number // 玩值币余额 + totalRecharge: number // 总充值金额 + totalSpent: number // 总消费金额 + inviteCode?: string + invitedBy?: string + registerSource: string + registerTime: string + lastLoginTime: string + status: "active" | "banned" | "deleted" + createdAt: string + updatedAt: string +} + +export interface UserAuth { + id?: number + userId: number + authType: "phone" | "wechat" | "qq" | "weibo" + authId: string // 第三方ID或手机号 + accessToken?: string + refreshToken?: string + expiresAt?: string + createdAt: string +} + +export interface UserFollow { + id?: number + userId: number + followUserId: number + followType: "user" | "streamer" | "star" + createdAt: string +} + +// ==================== 主播相关 ==================== + +export interface Streamer { + id?: number + userId: number // 关联用户 + name: string + avatar: string + coverImage: string + title: string + game: string + gameId: number + tags: string[] + level: number + fans: number + hotValue: number // 热度值 + isLive: boolean + liveRoomId?: string + totalGifts: number + totalIncome: number + guildId?: number // 所属公会 + commissionRate: number // 佣金比例 + status: "pending" | "approved" | "rejected" | "suspended" + verifiedAt?: string + createdAt: string + updatedAt: string +} + +export interface StreamerCourse { + id?: number + streamerId: number + title: string + description: string + coverImage: string + price: number + originalPrice: number + duration: number // 总时长(分钟) + lessonsCount: number + studentsCount: number + rating: number + game: string + category: string + tags: string[] + lessons: CourseLesson[] + status: "draft" | "published" | "offline" + createdAt: string + updatedAt: string +} + +export interface CourseLesson { + id: number + title: string + duration: number // 分钟 + isFree: boolean + videoUrl?: string + order: number +} + +export interface CoursePurchase { + id?: number + userId: number + courseId: number + streamerId: number + price: number + progress: number // 学习进度百分比 + lastWatchedLesson?: number + purchasedAt: string + completedAt?: string +} + +// ==================== 公会/俱乐部 ==================== + +export interface Guild { + id?: number + name: string + logo: string + coverImage: string + description: string + ownerId: number + memberCount: number + maxMembers: number + level: number + totalIncome: number + commissionRate: number // 平台抽成比例 + tags: string[] + games: string[] + requirements: string + benefits: string[] + status: "active" | "suspended" | "dissolved" + createdAt: string + updatedAt: string +} + +export interface GuildMember { + id?: number + guildId: number + userId: number + role: "owner" | "admin" | "member" + contribution: number // 贡献值 + monthlyIncome: number + joinedAt: string + status: "active" | "left" | "kicked" +} + +export interface GuildApplication { + id?: number + guildId: number + userId: number + message: string + status: "pending" | "approved" | "rejected" + reviewedBy?: number + reviewedAt?: string + createdAt: string +} + +// ==================== 明星/大神 ==================== + +export interface Star { + id?: number + userId: number + name: string + avatar: string + coverImage: string + title: string + game: string + gameId: number + tags: string[] + fans: number + hotValue: number // 热度值(万) + achievements: string[] + teamName?: string + socialLinks: { + weibo?: string + douyin?: string + bilibili?: string + } + partyRoomId?: string // 派对群ID + hourlyRate: number // 每小时价格 + isOnline: boolean + status: "active" | "suspended" + verifiedAt: string + createdAt: string + updatedAt: string +} + +export interface StarPartyRoom { + id?: number + starId: number + name: string + memberCount: number + maxMembers: number + entryFee: number + description: string + rules: string[] + isActive: boolean + createdAt: string +} + +export interface StarPartyMember { + id?: number + partyRoomId: number + userId: number + role: "host" | "vip" | "member" + joinedAt: string + expiredAt?: string +} + +// ==================== CP/陪练 ==================== + +export interface Companion { + id?: number + userId: number + name: string + avatar: string + coverImage: string + gender: "male" | "female" + age: number + tags: string[] + games: string[] + voiceSample?: string + bio: string + price: number // 每小时价格 + originalPrice: number + rating: number + ordersCount: number + responseRate: number + onlineStatus: "online" | "busy" | "offline" + category: "cp" | "coach" // CP或陪练 + coachType?: "pro" | "master" // 职业选手/王者大神 + rank?: string // 游戏段位 + guildId?: number + commissionRate: number + status: "active" | "suspended" | "reviewing" + createdAt: string + updatedAt: string +} + +export interface CompanionOrder { + id?: number + orderNo: string + userId: number + companionId: number + type: "cp" | "coach" + game: string + hours: number + price: number + totalAmount: number + message?: string + status: "pending" | "accepted" | "in_progress" | "completed" | "cancelled" | "refunded" + startTime?: string + endTime?: string + rating?: number + review?: string + createdAt: string + updatedAt: string +} + +// ==================== 星球匹配 ==================== + +export interface PlanetMatch { + id?: number + userId: number + matchType: "star" | "guild" | "cp" | "coach" + preferences: { + games?: string[] + gender?: string + priceRange?: [number, number] + tags?: string[] + } + matchedId?: number // 匹配结果ID + matchScore?: number // 匹配度 + status: "matching" | "matched" | "cancelled" + createdAt: string + matchedAt?: string +} + +// ==================== 直播相关 ==================== + +export interface LiveRoom { + id?: number + streamerId: number + title: string + coverImage: string + game: string + viewers: number + peakViewers: number + likes: number + gifts: LiveGift[] + status: "live" | "offline" | "replay" + startTime?: string + endTime?: string + duration?: number + createdAt: string +} + +export interface LiveGift { + id?: number + liveRoomId: number + userId: number + giftId: number + giftName: string + giftIcon: string + price: number + quantity: number + totalAmount: number + createdAt: string +} + +export interface LiveComment { + id?: number + liveRoomId: number + userId: number + userName: string + userAvatar: string + content: string + type: "normal" | "gift" | "enter" | "follow" + createdAt: string +} + +// ==================== 商城相关 ==================== + +export interface Product { + id?: number + name: string + description: string + image: string + images: string[] + category: "skin" | "equipment" | "points" | "peripheral" | "gift" + game?: string + price: number + originalPrice: number + stock: number + sales: number + rating: number + tags: string[] + specifications?: ProductSpec[] + status: "active" | "soldout" | "offline" + createdAt: string + updatedAt: string +} + +export interface ProductSpec { + name: string + options: string[] +} + +export interface CartItem { + id?: number + userId: number + productId: number + quantity: number + selectedSpecs?: Record + createdAt: string +} + +export interface ProductOrder { + id?: number + orderNo: string + userId: number + items: OrderItem[] + totalAmount: number + discountAmount: number + finalAmount: number + status: "pending" | "paid" | "shipped" | "completed" | "cancelled" | "refunded" + shippingAddress?: ShippingAddress + trackingNo?: string + paidAt?: string + shippedAt?: string + completedAt?: string + createdAt: string + updatedAt: string +} + +export interface OrderItem { + productId: number + productName: string + productImage: string + price: number + quantity: number + specs?: Record +} + +export interface ShippingAddress { + name: string + phone: string + province: string + city: string + district: string + address: string + isDefault: boolean +} + +// ==================== 游戏点卡 ==================== + +export interface GamePointCard { + id?: number + game: string + gameId: number + gameLogo: string + name: string + description: string + faceValue: number // 面值 + price: number + discount: number + stock: number + sales: number + deliveryType: "instant" | "manual" + instructions: string + status: "active" | "soldout" | "offline" + createdAt: string +} + +export interface PointCardOrder { + id?: number + orderNo: string + userId: number + cardId: number + game: string + faceValue: number + price: number + quantity: number + totalAmount: number + cardCodes?: string[] // 卡密 + status: "pending" | "paid" | "delivered" | "used" | "refunded" + createdAt: string + deliveredAt?: string +} + +// ==================== 账号交易/典当 ==================== + +export interface GameAccount { + id?: number + sellerId: number + game: string + gameId: number + server: string + title: string + description: string + images: string[] + level: number + rank?: string + skins: string[] + heroes: string[] + assets: Record // 游戏资产 + price: number + originalPrice?: number + evaluatedPrice?: number // AI评估价 + status: "reviewing" | "listed" | "sold" | "offline" | "pawned" + viewCount: number + favoriteCount: number + createdAt: string + updatedAt: string +} + +export interface AccountPawn { + id?: number + userId: number + accountId: number + game: string + evaluatedValue: number // 评估价值 + pawnAmount: number // 典当金额 + serviceFee: number // 服务费 + redeemAmount: number // 赎回金额 + period: 7 | 15 | 30 // 典当期限(天) + phone: string + screenshots: string[] + antiAddiction: "none" | "limited" | "full" + canRename: "yes" | "no" | "once" + selectedSkins: string[] + agreementSigned: boolean + status: "pending" | "evaluating" | "approved" | "active" | "redeemed" | "overdue" | "forfeited" + approvedAt?: string + redeemedAt?: string + expiresAt?: string + createdAt: string + updatedAt: string +} + +export interface AccountTransaction { + id?: number + orderNo: string + buyerId: number + sellerId: number + accountId: number + price: number + platformFee: number + sellerIncome: number + status: "pending" | "paid" | "transferring" | "completed" | "disputed" | "refunded" + createdAt: string + completedAt?: string +} + +// ==================== 电竞酒店/网咖 ==================== + +export interface EsportsVenue { + id?: number + name: string + type: "hotel" | "cafe" + logo: string + images: string[] + description: string + address: string + city: string + district: string + latitude: number + longitude: number + phone: string + rating: number + reviewCount: number + pricePerHour?: number // 网咖按小时 + pricePerNight?: number // 酒店按晚 + facilities: string[] + tags: string[] + openTime: string + closeTime: string + isOpen24Hours: boolean + status: "active" | "closed" | "renovating" + createdAt: string + updatedAt: string +} + +export interface VenueRoom { + id?: number + venueId: number + name: string + type: "single" | "double" | "vip" | "booth" + capacity: number + price: number + images: string[] + facilities: string[] + status: "available" | "occupied" | "maintenance" +} + +export interface VenueBooking { + id?: number + orderNo: string + userId: number + venueId: number + roomId?: number + type: "hotel" | "cafe" + checkInTime: string + checkOutTime: string + duration: number // 小时 + price: number + totalAmount: number + guests: number + contactName: string + contactPhone: string + status: "pending" | "confirmed" | "checked_in" | "completed" | "cancelled" + createdAt: string + confirmedAt?: string +} + +// ==================== 钱包/交易 ==================== + +export interface Transaction { + id?: number + userId: number + orderNo: string + type: "recharge" | "withdraw" | "purchase" | "gift" | "income" | "refund" | "transfer" + category: string // 具体分类 + amount: number + balanceBefore: number + balanceAfter: number + relatedId?: number // 关联订单ID + relatedType?: string // 关联类型 + description: string + status: "pending" | "completed" | "failed" + createdAt: string +} + +export interface RechargeOrder { + id?: number + orderNo: string + userId: number + amount: number + bonusAmount: number // 赠送金额 + totalAmount: number + paymentMethod: "wechat" | "alipay" | "card" + status: "pending" | "paid" | "failed" | "refunded" + paidAt?: string + createdAt: string +} + +export interface WithdrawOrder { + id?: number + orderNo: string + userId: number + amount: number + fee: number + actualAmount: number + accountType: "wechat" | "alipay" | "bank" + accountInfo: string + status: "pending" | "processing" | "completed" | "rejected" + reviewedBy?: number + reviewedAt?: string + completedAt?: string + rejectReason?: string + createdAt: string +} + +// ==================== 消息/通知 ==================== + +export interface Message { + id?: number + conversationId: number + senderId: number + receiverId: number + type: "text" | "image" | "voice" | "gift" | "system" + content: string + mediaUrl?: string + duration?: number // 语音时长 + isRead: boolean + createdAt: string +} + +export interface Conversation { + id?: number + type: "private" | "group" | "system" + participants: number[] + lastMessage?: string + lastMessageTime?: string + unreadCount: number + isPinned: boolean + isMuted: boolean + createdAt: string + updatedAt: string +} + +export interface Notification { + id?: number + userId: number + type: "system" | "order" | "follow" | "like" | "comment" | "gift" | "activity" + title: string + content: string + image?: string + link?: string + isRead: boolean + createdAt: string +} + +// ==================== 动态/社交 ==================== + +export interface Moment { + id?: number + userId: number + userName: string + userAvatar: string + content: string + images: string[] + video?: string + game?: string + tags: string[] + location?: string + likes: number + comments: number + shares: number + isLiked: boolean + visibility: "public" | "followers" | "private" + status: "active" | "deleted" | "hidden" + createdAt: string + updatedAt: string +} + +export interface MomentLike { + id?: number + momentId: number + userId: number + createdAt: string +} + +export interface MomentComment { + id?: number + momentId: number + userId: number + userName: string + userAvatar: string + content: string + replyTo?: number // 回复的评论ID + replyToUser?: string + likes: number + status: "active" | "deleted" + createdAt: string +} + +// ==================== 游戏数据 ==================== + +export interface Game { + id?: number + name: string + shortName: string + logo: string + coverImage: string + category: "moba" | "fps" | "mmorpg" | "card" | "sports" | "other" + platform: ("pc" | "mobile" | "console")[] + publisher: string + isHot: boolean + sortOrder: number + status: "active" | "inactive" + createdAt: string +} + +export interface GameServer { + id?: number + gameId: number + name: string + region: string + sortOrder: number + status: "active" | "inactive" +} + +// ==================== 数据库类定义 ==================== + +export class WanzhiDatabase extends Dexie { + // 用户相关 + users!: Table + userAuths!: Table + userFollows!: Table + + // 主播相关 + streamers!: Table + streamerCourses!: Table + coursePurchases!: Table + + // 公会相关 + guilds!: Table + guildMembers!: Table + guildApplications!: Table + + // 明星相关 + stars!: Table + starPartyRooms!: Table + starPartyMembers!: Table + + // CP/陪练 + companions!: Table + companionOrders!: Table + + // 星球匹配 + planetMatches!: Table + + // 直播相关 + liveRooms!: Table + liveGifts!: Table + liveComments!: Table + + // 商城相关 + products!: Table + cartItems!: Table + productOrders!: Table + + // 游戏点卡 + gamePointCards!: Table + pointCardOrders!: Table + + // 账号交易/典当 + gameAccounts!: Table + accountPawns!: Table + accountTransactions!: Table + + // 电竞酒店/网咖 + esportsVenues!: Table + venueRooms!: Table + venueBookings!: Table + + // 钱包/交易 + transactions!: Table + rechargeOrders!: Table + withdrawOrders!: Table + + // 消息/通知 + messages!: Table + conversations!: Table + notifications!: Table + + // 动态/社交 + moments!: Table + momentLikes!: Table + momentComments!: Table + + // 游戏数据 + games!: Table + gameServers!: Table + + constructor() { + super("WanzhiEsportsDB") + + this.version(1).stores({ + // 用户相关 + users: "++id, odlId, phone, name, status, createdAt", + userAuths: "++id, userId, authType, authId", + userFollows: "++id, userId, followUserId, followType", + + // 主播相关 + streamers: "++id, userId, name, game, guildId, status, isLive", + streamerCourses: "++id, streamerId, game, status, price", + coursePurchases: "++id, userId, courseId, streamerId", + + // 公会相关 + guilds: "++id, name, ownerId, status", + guildMembers: "++id, guildId, userId, role, status", + guildApplications: "++id, guildId, userId, status", + + // 明星相关 + stars: "++id, userId, name, game, status", + starPartyRooms: "++id, starId, isActive", + starPartyMembers: "++id, partyRoomId, userId", + + // CP/陪练 + companions: "++id, userId, category, gender, status, onlineStatus", + companionOrders: "++id, orderNo, userId, companionId, status", + + // 星球匹配 + planetMatches: "++id, userId, matchType, status", + + // 直播相关 + liveRooms: "++id, streamerId, game, status", + liveGifts: "++id, liveRoomId, userId", + liveComments: "++id, liveRoomId, userId", + + // 商城相关 + products: "++id, name, category, game, status", + cartItems: "++id, userId, productId", + productOrders: "++id, orderNo, userId, status", + + // 游戏点卡 + gamePointCards: "++id, game, gameId, status", + pointCardOrders: "++id, orderNo, userId, status", + + // 账号交易/典当 + gameAccounts: "++id, sellerId, game, status", + accountPawns: "++id, userId, accountId, status", + accountTransactions: "++id, orderNo, buyerId, sellerId, status", + + // 电竞酒店/网咖 + esportsVenues: "++id, name, type, city, status", + venueRooms: "++id, venueId, type, status", + venueBookings: "++id, orderNo, userId, venueId, status", + + // 钱包/交易 + transactions: "++id, userId, orderNo, type, status", + rechargeOrders: "++id, orderNo, userId, status", + withdrawOrders: "++id, orderNo, userId, status", + + // 消息/通知 + messages: "++id, conversationId, senderId, receiverId, isRead", + conversations: "++id, type, updatedAt", + notifications: "++id, userId, type, isRead", + + // 动态/社交 + moments: "++id, userId, game, status, createdAt", + momentLikes: "++id, momentId, userId", + momentComments: "++id, momentId, userId", + + // 游戏数据 + games: "++id, name, category, isHot, sortOrder", + gameServers: "++id, gameId, region", + }) + } +} + +// 创建数据库实例 +export const db = new WanzhiDatabase() diff --git a/lib/db/seed.ts b/lib/db/seed.ts new file mode 100644 index 0000000..acf9e92 --- /dev/null +++ b/lib/db/seed.ts @@ -0,0 +1,750 @@ +// 数据库初始化种子数据 +import { db } from "./schema" +import type { User, Game, Streamer, Star, Guild, Companion, EsportsVenue, Product, GamePointCard } from "./schema" + +export async function seedDatabase() { + // 检查是否已初始化 + const existingUsers = await db.users.count() + if (existingUsers > 0) { + console.log("[v0] Database already seeded") + return + } + + console.log("[v0] Seeding database...") + + // 1. 初始化游戏数据 + const games: Omit[] = [ + { + name: "王者荣耀", + shortName: "wzry", + logo: "/wzry.jpg", + coverImage: "/wzry-cover.jpg", + category: "moba", + platform: ["mobile"], + publisher: "腾讯", + isHot: true, + sortOrder: 1, + status: "active", + createdAt: new Date().toISOString(), + }, + { + name: "英雄联盟", + shortName: "lol", + logo: "/lol.jpg", + coverImage: "/lol-cover.jpg", + category: "moba", + platform: ["pc"], + publisher: "腾讯", + isHot: true, + sortOrder: 2, + status: "active", + createdAt: new Date().toISOString(), + }, + { + name: "和平精英", + shortName: "pubgm", + logo: "/pubgm.jpg", + coverImage: "/pubgm-cover.jpg", + category: "fps", + platform: ["mobile"], + publisher: "腾讯", + isHot: true, + sortOrder: 3, + status: "active", + createdAt: new Date().toISOString(), + }, + { + name: "原神", + shortName: "ys", + logo: "/ys.jpg", + coverImage: "/ys-cover.jpg", + category: "mmorpg", + platform: ["pc", "mobile"], + publisher: "米哈游", + isHot: true, + sortOrder: 4, + status: "active", + createdAt: new Date().toISOString(), + }, + { + name: "无畏契约", + shortName: "valorant", + logo: "/valorant.jpg", + coverImage: "/valorant-cover.jpg", + category: "fps", + platform: ["pc"], + publisher: "拳头", + isHot: true, + sortOrder: 5, + status: "active", + createdAt: new Date().toISOString(), + }, + { + name: "穿越火线", + shortName: "cf", + logo: "/cf.jpg", + coverImage: "/cf-cover.jpg", + category: "fps", + platform: ["pc", "mobile"], + publisher: "腾讯", + isHot: false, + sortOrder: 6, + status: "active", + createdAt: new Date().toISOString(), + }, + { + name: "梦幻西游", + shortName: "mhxy", + logo: "/mhxy.jpg", + coverImage: "/mhxy-cover.jpg", + category: "mmorpg", + platform: ["pc", "mobile"], + publisher: "网易", + isHot: false, + sortOrder: 7, + status: "active", + createdAt: new Date().toISOString(), + }, + { + name: "三角洲行动", + shortName: "delta", + logo: "/delta.jpg", + coverImage: "/delta-cover.jpg", + category: "fps", + platform: ["pc", "mobile"], + publisher: "腾讯", + isHot: true, + sortOrder: 8, + status: "active", + createdAt: new Date().toISOString(), + }, + ] + await db.games.bulkAdd(games) + + // 2. 初始化默认用户 + const defaultUser: Omit = { + odlId: "888888", + phone: "13800138000", + name: "卡若", + avatar: "/gamer-boy-esports-jersey.jpg", + level: 12, + experience: 2450, + isVip: false, + gender: "male", + tags: ["英雄联盟", "王者荣耀"], + activity: 128, + following: 45, + followers: 892, + visitors: 1205, + balance: 6880, + totalRecharge: 10000, + totalSpent: 3120, + registerSource: "app", + registerTime: "2023-01-15", + lastLoginTime: new Date().toISOString(), + status: "active", + createdAt: "2023-01-15", + updatedAt: new Date().toISOString(), + } + await db.users.add(defaultUser) + + // 3. 初始化主播数据 + const streamers: Omit[] = [ + { + userId: 1, + name: "GM-远洋", + avatar: "/streamer-1.jpg", + coverImage: "/lol-stream.jpg", + title: "国服第一盲僧", + game: "英雄联盟", + gameId: 2, + tags: ["打野", "教学"], + level: 28, + fans: 1256000, + hotValue: 125.6, + isLive: true, + totalGifts: 89000, + totalIncome: 156000, + commissionRate: 0.7, + status: "approved", + verifiedAt: "2023-02-01", + createdAt: "2023-01-20", + updatedAt: new Date().toISOString(), + }, + { + userId: 2, + name: "小狐狸", + avatar: "/streamer-2.jpg", + coverImage: "/valorant-stream.jpg", + title: "无畏契约电竞女神", + game: "无畏契约", + gameId: 5, + tags: ["竞技", "颜值"], + level: 25, + fans: 856000, + hotValue: 89.2, + isLive: true, + totalGifts: 67000, + totalIncome: 98000, + commissionRate: 0.7, + status: "approved", + verifiedAt: "2023-03-01", + createdAt: "2023-02-15", + updatedAt: new Date().toISOString(), + }, + { + userId: 3, + name: "王者阿杰", + avatar: "/streamer-3.jpg", + coverImage: "/wzry-stream.jpg", + title: "国服最强边路", + game: "王者荣耀", + gameId: 1, + tags: ["边路", "上分"], + level: 30, + fans: 2100000, + hotValue: 168.5, + isLive: false, + totalGifts: 125000, + totalIncome: 230000, + commissionRate: 0.75, + status: "approved", + verifiedAt: "2023-01-10", + createdAt: "2022-12-01", + updatedAt: new Date().toISOString(), + }, + { + userId: 4, + name: "原神小可爱", + avatar: "/streamer-4.jpg", + coverImage: "/ys-stream.jpg", + title: "深渊满星攻略", + game: "原神", + gameId: 4, + tags: ["攻略", "抽卡"], + level: 22, + fans: 568000, + hotValue: 56.8, + isLive: true, + totalGifts: 45000, + totalIncome: 78000, + commissionRate: 0.65, + status: "approved", + verifiedAt: "2023-04-01", + createdAt: "2023-03-10", + updatedAt: new Date().toISOString(), + }, + { + userId: 5, + name: "吃鸡大魔王", + avatar: "/streamer-5.jpg", + coverImage: "/pubgm-stream.jpg", + title: "单排上分王", + game: "和平精英", + gameId: 3, + tags: ["吃鸡", "技巧"], + level: 26, + fans: 980000, + hotValue: 98.0, + isLive: true, + totalGifts: 72000, + totalIncome: 135000, + commissionRate: 0.7, + status: "approved", + verifiedAt: "2023-02-15", + createdAt: "2023-01-25", + updatedAt: new Date().toISOString(), + }, + { + userId: 6, + name: "魔兽老玩家", + avatar: "/streamer-6.jpg", + coverImage: "/wow-stream.jpg", + title: "怀旧服金团领队", + game: "魔兽世界", + gameId: 9, + tags: ["怀旧", "金团"], + level: 20, + fans: 320000, + hotValue: 32.5, + isLive: false, + totalGifts: 28000, + totalIncome: 52000, + commissionRate: 0.65, + status: "approved", + verifiedAt: "2023-05-01", + createdAt: "2023-04-20", + updatedAt: new Date().toISOString(), + }, + ] + await db.streamers.bulkAdd(streamers) + + // 4. 初始化明星数据 + const stars: Omit[] = [ + { + userId: 10, + name: "Faker", + avatar: "/star-faker.jpg", + coverImage: "/star-faker-cover.jpg", + title: "LOL传奇中单", + game: "英雄联盟", + gameId: 2, + tags: ["世界冠军", "中单"], + fans: 46800000, + hotValue: 468.0, + achievements: ["三冠王", "S赛MVP"], + teamName: "T1", + socialLinks: { weibo: "faker_lol" }, + partyRoomId: "faker-party", + hourlyRate: 9999, + isOnline: true, + status: "active", + verifiedAt: "2022-01-01", + createdAt: "2022-01-01", + updatedAt: new Date().toISOString(), + }, + { + userId: 11, + name: "Uzi", + avatar: "/star-uzi.jpg", + coverImage: "/star-uzi-cover.jpg", + title: "永远的神", + game: "英雄联盟", + gameId: 2, + tags: ["ADC", "传奇"], + fans: 38500000, + hotValue: 385.0, + achievements: ["MSI冠军", "LPL冠军"], + teamName: "RNG", + socialLinks: { weibo: "uzi_lol", douyin: "uzi" }, + partyRoomId: "uzi-party", + hourlyRate: 8888, + isOnline: false, + status: "active", + verifiedAt: "2022-01-01", + createdAt: "2022-01-01", + updatedAt: new Date().toISOString(), + }, + { + userId: 12, + name: "梦泪", + avatar: "/star-menglei.jpg", + coverImage: "/star-menglei-cover.jpg", + title: "国服第一韩信", + game: "王者荣耀", + gameId: 1, + tags: ["韩信", "打野"], + fans: 52000000, + hotValue: 520.0, + achievements: ["KPL冠军", "最佳打野"], + teamName: "AG超玩会", + socialLinks: { douyin: "menglei" }, + partyRoomId: "menglei-party", + hourlyRate: 6666, + isOnline: true, + status: "active", + verifiedAt: "2022-06-01", + createdAt: "2022-06-01", + updatedAt: new Date().toISOString(), + }, + { + userId: 13, + name: "不求人", + avatar: "/star-buqiuren.jpg", + coverImage: "/star-buqiuren-cover.jpg", + title: "和平精英一哥", + game: "和平精英", + gameId: 3, + tags: ["吃鸡", "主播"], + fans: 41000000, + hotValue: 410.0, + achievements: ["PEL冠军", "全明星MVP"], + socialLinks: { douyin: "buqiuren" }, + partyRoomId: "buqiuren-party", + hourlyRate: 5888, + isOnline: true, + status: "active", + verifiedAt: "2022-03-01", + createdAt: "2022-03-01", + updatedAt: new Date().toISOString(), + }, + ] + await db.stars.bulkAdd(stars) + + // 5. 初始化公会数据 + const guilds: Omit[] = [ + { + name: "星耀电竞", + logo: "/guild-xingyao.jpg", + coverImage: "/guild-xingyao-cover.jpg", + description: "顶级电竞公会,培养众多职业选手", + ownerId: 1, + memberCount: 1280, + maxMembers: 2000, + level: 8, + totalIncome: 2580000, + commissionRate: 0.1, + tags: ["职业", "培训"], + games: ["英雄联盟", "王者荣耀"], + requirements: "段位王者以上", + benefits: ["签约底薪", "流量扶持", "赛事推荐"], + status: "active", + createdAt: "2022-01-01", + updatedAt: new Date().toISOString(), + }, + { + name: "凤凰涅槃", + logo: "/guild-fenghuang.jpg", + coverImage: "/guild-fenghuang-cover.jpg", + description: "女主播孵化基地", + ownerId: 2, + memberCount: 860, + maxMembers: 1500, + level: 6, + totalIncome: 1680000, + commissionRate: 0.12, + tags: ["颜值", "才艺"], + games: ["王者荣耀", "和平精英"], + requirements: "颜值在线,有才艺", + benefits: ["形象包装", "运营指导", "商务对接"], + status: "active", + createdAt: "2022-06-01", + updatedAt: new Date().toISOString(), + }, + { + name: "暗影战队", + logo: "/guild-anying.jpg", + coverImage: "/guild-anying-cover.jpg", + description: "FPS游戏专业公会", + ownerId: 3, + memberCount: 520, + maxMembers: 1000, + level: 5, + totalIncome: 980000, + commissionRate: 0.1, + tags: ["FPS", "硬核"], + games: ["和平精英", "无畏契约", "穿越火线"], + requirements: "FPS游戏高段位", + benefits: ["战队训练", "比赛机会", "装备赞助"], + status: "active", + createdAt: "2023-01-01", + updatedAt: new Date().toISOString(), + }, + ] + await db.guilds.bulkAdd(guilds) + + // 6. 初始化CP/陪练数据 + const companions: Omit[] = [ + { + userId: 20, + name: "甜心小姐姐", + avatar: "/cp-avatar-1.jpg", + coverImage: "/cp-cover-1.jpg", + gender: "female", + age: 22, + tags: ["甜美", "温柔", "会撒娇"], + games: ["王者荣耀", "和平精英"], + bio: "声音甜美,陪你上分更开心~", + price: 30, + originalPrice: 50, + rating: 4.9, + ordersCount: 1256, + responseRate: 98, + onlineStatus: "online", + category: "cp", + commissionRate: 0.3, + status: "active", + createdAt: "2023-01-01", + updatedAt: new Date().toISOString(), + }, + { + userId: 21, + name: "电竞小王子", + avatar: "/cp-avatar-2.jpg", + coverImage: "/cp-cover-2.jpg", + gender: "male", + age: 24, + tags: ["阳光", "幽默", "技术好"], + games: ["英雄联盟", "无畏契约"], + bio: "王者段位,带你轻松上分", + price: 35, + originalPrice: 60, + rating: 4.8, + ordersCount: 986, + responseRate: 95, + onlineStatus: "online", + category: "cp", + commissionRate: 0.3, + status: "active", + createdAt: "2023-02-01", + updatedAt: new Date().toISOString(), + }, + { + userId: 22, + name: "职业选手小明", + avatar: "/coach-avatar-1.jpg", + coverImage: "/coach-cover-1.jpg", + gender: "male", + age: 26, + tags: ["职业", "教学", "耐心"], + games: ["英雄联盟"], + voiceSample: "/voice-sample-1.mp3", + bio: "前职业选手,专业教学", + price: 100, + originalPrice: 150, + rating: 4.95, + ordersCount: 568, + responseRate: 99, + onlineStatus: "online", + category: "coach", + coachType: "pro", + rank: "王者", + commissionRate: 0.25, + status: "active", + createdAt: "2023-01-15", + updatedAt: new Date().toISOString(), + }, + { + userId: 23, + name: "王者大神", + avatar: "/coach-avatar-2.jpg", + coverImage: "/coach-cover-2.jpg", + gender: "male", + age: 23, + tags: ["上分", "技术流", "细节"], + games: ["王者荣耀"], + bio: "国服百星,带你冲击荣耀王者", + price: 80, + originalPrice: 120, + rating: 4.88, + ordersCount: 892, + responseRate: 97, + onlineStatus: "busy", + category: "coach", + coachType: "master", + rank: "荣耀王者100星", + commissionRate: 0.25, + status: "active", + createdAt: "2023-03-01", + updatedAt: new Date().toISOString(), + }, + ] + await db.companions.bulkAdd(companions) + + // 7. 初始化电竞酒店/网咖数据 + const venues: Omit[] = [ + { + name: "玩值电竞酒店(旗舰店)", + type: "hotel", + logo: "/hotel-logo-1.jpg", + images: ["/hotel-1.jpg"], + description: "五星级电竞酒店体验", + address: "厦门市思明区软件园二期", + city: "厦门", + district: "思明区", + latitude: 24.4798, + longitude: 118.0894, + phone: "0592-12345678", + rating: 4.9, + reviewCount: 1256, + pricePerNight: 399, + facilities: ["RTX4090", "电竞椅", "144Hz显示器", "独立卫浴"], + tags: ["热门", "高端"], + openTime: "00:00", + closeTime: "24:00", + isOpen24Hours: true, + status: "active", + createdAt: "2022-01-01", + updatedAt: new Date().toISOString(), + }, + { + name: "极客空间电竞公寓", + type: "hotel", + logo: "/hotel-logo-2.jpg", + images: ["/hotel-2.jpg"], + description: "年轻人的电竞乐园", + address: "厦门市湖里区五缘湾", + city: "厦门", + district: "湖里区", + latitude: 24.5102, + longitude: 118.1456, + phone: "0592-23456789", + rating: 4.7, + reviewCount: 896, + pricePerNight: 299, + facilities: ["RTX3080", "电竞椅", "双人房"], + tags: ["性价比"], + openTime: "00:00", + closeTime: "24:00", + isOpen24Hours: true, + status: "active", + createdAt: "2022-06-01", + updatedAt: new Date().toISOString(), + }, + { + name: "网鱼网咖(万达店)", + type: "cafe", + logo: "/cafe-logo-1.jpg", + images: ["/cafe-1.jpg"], + description: "专业电竞网咖", + address: "厦门市思明区万达广场", + city: "厦门", + district: "思明区", + latitude: 24.4656, + longitude: 118.1023, + phone: "0592-34567890", + rating: 4.8, + reviewCount: 2356, + pricePerHour: 12, + facilities: ["3080显卡", "特权公馆", "现磨咖啡"], + tags: ["连锁", "环境好"], + openTime: "00:00", + closeTime: "24:00", + isOpen24Hours: true, + status: "active", + createdAt: "2021-01-01", + updatedAt: new Date().toISOString(), + }, + { + name: "杰拉电竞馆", + type: "cafe", + logo: "/cafe-logo-2.jpg", + images: ["/cafe-2.jpg"], + description: "赛事级电竞馆", + address: "厦门市集美区银泰城", + city: "厦门", + district: "集美区", + latitude: 24.5789, + longitude: 118.0956, + phone: "0592-45678901", + rating: 4.6, + reviewCount: 1568, + pricePerHour: 10, + facilities: ["赛事级配置", "美女陪玩", "独立包间"], + tags: ["比赛场地"], + openTime: "09:00", + closeTime: "02:00", + isOpen24Hours: false, + status: "active", + createdAt: "2022-03-01", + updatedAt: new Date().toISOString(), + }, + ] + await db.esportsVenues.bulkAdd(venues) + + // 8. 初始化商品数据 + const products: Omit[] = [ + { + name: "王者荣耀皮肤礼包", + description: "稀有皮肤随机礼包", + image: "/product-skin-1.jpg", + images: ["/product-skin-1.jpg"], + category: "skin", + game: "王者荣耀", + price: 199, + originalPrice: 299, + stock: 1000, + sales: 5680, + rating: 4.8, + tags: ["热销", "限定"], + status: "active", + createdAt: "2023-01-01", + updatedAt: new Date().toISOString(), + }, + { + name: "电竞机械键盘", + description: "青轴机械键盘,RGB背光", + image: "/product-keyboard.jpg", + images: ["/product-keyboard.jpg"], + category: "peripheral", + price: 299, + originalPrice: 399, + stock: 500, + sales: 2356, + rating: 4.9, + tags: ["外设", "推荐"], + status: "active", + createdAt: "2023-02-01", + updatedAt: new Date().toISOString(), + }, + { + name: "游戏鼠标垫", + description: "超大电竞鼠标垫", + image: "/product-mousepad.jpg", + images: ["/product-mousepad.jpg"], + category: "peripheral", + price: 59, + originalPrice: 99, + stock: 2000, + sales: 8956, + rating: 4.7, + tags: ["热销"], + status: "active", + createdAt: "2023-03-01", + updatedAt: new Date().toISOString(), + }, + ] + await db.products.bulkAdd(products) + + // 9. 初始化游戏点卡数据 + const pointCards: Omit[] = [ + { + game: "王者荣耀", + gameId: 1, + gameLogo: "/wzry.jpg", + name: "点券充值", + description: "官方点券直充", + faceValue: 100, + price: 98, + discount: 0.98, + stock: 9999, + sales: 12568, + deliveryType: "instant", + instructions: "请提供游戏账号", + status: "active", + createdAt: "2023-01-01", + }, + { + game: "英雄联盟", + gameId: 2, + gameLogo: "/lol.jpg", + name: "点券充值", + description: "官方点券直充", + faceValue: 100, + price: 97, + discount: 0.97, + stock: 9999, + sales: 9856, + deliveryType: "instant", + instructions: "请提供游戏账号", + status: "active", + createdAt: "2023-01-01", + }, + { + game: "原神", + gameId: 4, + gameLogo: "/ys.jpg", + name: "创世结晶", + description: "官方充值", + faceValue: 648, + price: 628, + discount: 0.97, + stock: 9999, + sales: 15689, + deliveryType: "instant", + instructions: "请提供UID", + status: "active", + createdAt: "2023-01-01", + }, + ] + await db.gamePointCards.bulkAdd(pointCards) + + console.log("[v0] Database seeded successfully!") +} + +// 清空数据库 +export async function clearDatabase() { + await db.delete() + await db.open() + console.log("[v0] Database cleared") +} diff --git a/package.json b/package.json index ea8609c..080f2e4 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "cmdk": "1.0.4", "crypto": "1.0.1", "date-fns": "4.1.0", + "dexie": "4.2.1", "embla-carousel-react": "8.5.1", "framer-motion": "12.23.24", "input-otp": "1.4.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9ae6c03..0935615 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -116,6 +116,9 @@ importers: date-fns: specifier: 4.1.0 version: 4.1.0 + dexie: + specifier: 4.2.1 + version: 4.2.1 embla-carousel-react: specifier: 8.5.1 version: 8.5.1(react@19.2.0) @@ -1348,6 +1351,9 @@ packages: detect-node-es@1.1.0: resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} + dexie@4.2.1: + resolution: {integrity: sha512-Ckej0NS6jxQ4Po3OrSQBFddayRhTCic2DoCAG5zacOfOVB9P2Q5Xc5uL/nVa7ZVs+HdMnvUPzLFCB/JwpB6Csg==} + dom-helpers@5.2.1: resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} @@ -2880,6 +2886,8 @@ snapshots: detect-node-es@1.1.0: {} + dexie@4.2.1: {} + dom-helpers@5.2.1: dependencies: '@babel/runtime': 7.28.4 diff --git a/public/admin-avatar.png b/public/admin-avatar.png new file mode 100644 index 0000000..8412df8 Binary files /dev/null and b/public/admin-avatar.png differ diff --git a/public/dress-angel.jpg b/public/dress-angel.jpg index 54c7e61..06373b5 100644 Binary files a/public/dress-angel.jpg and b/public/dress-angel.jpg differ diff --git a/public/dress-demon.jpg b/public/dress-demon.jpg index 2fdc6b8..06373b5 100644 Binary files a/public/dress-demon.jpg and b/public/dress-demon.jpg differ diff --git a/public/dress-dunhuang.jpg b/public/dress-dunhuang.jpg index 47eef41..06373b5 100644 Binary files a/public/dress-dunhuang.jpg and b/public/dress-dunhuang.jpg differ diff --git a/public/dress-panda.jpg b/public/dress-panda.jpg index 603e2f5..06373b5 100644 Binary files a/public/dress-panda.jpg and b/public/dress-panda.jpg differ diff --git a/public/dress-tropical.jpg b/public/dress-tropical.jpg index 1c7665f..06373b5 100644 Binary files a/public/dress-tropical.jpg and b/public/dress-tropical.jpg differ diff --git a/public/dress-witch-cat.jpg b/public/dress-witch-cat.jpg index 088ef1e..8bbd02e 100644 Binary files a/public/dress-witch-cat.jpg and b/public/dress-witch-cat.jpg differ diff --git a/public/game-diablo.jpg b/public/game-diablo.jpg index 6a31024..b2f3c45 100644 Binary files a/public/game-diablo.jpg and b/public/game-diablo.jpg differ diff --git a/public/game-genshin.jpg b/public/game-genshin.jpg index 72ec0b6..cd27197 100644 Binary files a/public/game-genshin.jpg and b/public/game-genshin.jpg differ diff --git a/public/game-hearthstone.jpg b/public/game-hearthstone.jpg index 5bb5dd8..e620c32 100644 Binary files a/public/game-hearthstone.jpg and b/public/game-hearthstone.jpg differ diff --git a/public/game-hok.jpg b/public/game-hok.jpg index 746e9d6..120ab6e 100644 Binary files a/public/game-hok.jpg and b/public/game-hok.jpg differ diff --git a/public/game-lol.jpg b/public/game-lol.jpg index 3a41dfa..4a4f761 100644 Binary files a/public/game-lol.jpg and b/public/game-lol.jpg differ diff --git a/public/game-overwatch.jpg b/public/game-overwatch.jpg index 290964e..b88a1e7 100644 Binary files a/public/game-overwatch.jpg and b/public/game-overwatch.jpg differ diff --git a/public/game-pubg.jpg b/public/game-pubg.jpg index ed4dbb6..0c46988 100644 Binary files a/public/game-pubg.jpg and b/public/game-pubg.jpg differ diff --git a/public/hotel-icon.jpg b/public/hotel-icon.jpg index 2a9545d..6bd7ffd 100644 Binary files a/public/hotel-icon.jpg and b/public/hotel-icon.jpg differ diff --git a/public/live-genshin.jpg b/public/live-genshin.jpg index ba0088d..7bb2a34 100644 Binary files a/public/live-genshin.jpg and b/public/live-genshin.jpg differ diff --git a/public/live-hok.jpg b/public/live-hok.jpg index 29728e6..0fb8baf 100644 Binary files a/public/live-hok.jpg and b/public/live-hok.jpg differ diff --git a/public/live-lol.jpg b/public/live-lol.jpg index 14cab45..10c45e3 100644 Binary files a/public/live-lol.jpg and b/public/live-lol.jpg differ diff --git a/public/live-pubg.jpg b/public/live-pubg.jpg index 7140d46..77773b6 100644 Binary files a/public/live-pubg.jpg and b/public/live-pubg.jpg differ diff --git a/public/live-valorant.jpg b/public/live-valorant.jpg index 1845685..58e9f02 100644 Binary files a/public/live-valorant.jpg and b/public/live-valorant.jpg differ diff --git a/public/live-wow.jpg b/public/live-wow.jpg index 779492b..9a73f1a 100644 Binary files a/public/live-wow.jpg and b/public/live-wow.jpg differ diff --git a/public/party-hok.jpg b/public/party-hok.jpg new file mode 100644 index 0000000..a0b41b5 Binary files /dev/null and b/public/party-hok.jpg differ diff --git a/public/party-lol.jpg b/public/party-lol.jpg new file mode 100644 index 0000000..6af733e Binary files /dev/null and b/public/party-lol.jpg differ diff --git a/public/party-team.jpg b/public/party-team.jpg new file mode 100644 index 0000000..299dbb1 Binary files /dev/null and b/public/party-team.jpg differ diff --git a/public/point-card-valorant.jpg b/public/point-card-valorant.jpg index 345a441..bc1562b 100644 Binary files a/public/point-card-valorant.jpg and b/public/point-card-valorant.jpg differ diff --git a/public/streamer-1.jpg b/public/streamer-1.jpg index 26923a9..e942235 100644 Binary files a/public/streamer-1.jpg and b/public/streamer-1.jpg differ diff --git a/public/streamer-2.jpg b/public/streamer-2.jpg index f171485..b67a97f 100644 Binary files a/public/streamer-2.jpg and b/public/streamer-2.jpg differ diff --git a/public/streamer-3.jpg b/public/streamer-3.jpg index f2fbb22..d4f0b77 100644 Binary files a/public/streamer-3.jpg and b/public/streamer-3.jpg differ diff --git a/public/streamer-4.jpg b/public/streamer-4.jpg index cd9fa0b..1ce2044 100644 Binary files a/public/streamer-4.jpg and b/public/streamer-4.jpg differ diff --git a/public/streamer-5.jpg b/public/streamer-5.jpg index 149fc4c..8ef8b3f 100644 Binary files a/public/streamer-5.jpg and b/public/streamer-5.jpg differ diff --git a/public/streamer-6.jpg b/public/streamer-6.jpg index b581196..b209f82 100644 Binary files a/public/streamer-6.jpg and b/public/streamer-6.jpg differ diff --git a/public/tournament-event-today.jpg b/public/tournament-event-today.jpg index 98ffcf5..495aa4e 100644 Binary files a/public/tournament-event-today.jpg and b/public/tournament-event-today.jpg differ diff --git a/public/wanzhi-logo.jpg b/public/wanzhi-logo.jpg new file mode 100644 index 0000000..b7f1a4c Binary files /dev/null and b/public/wanzhi-logo.jpg differ diff --git a/public/wanzhi-logo.svg b/public/wanzhi-logo.svg new file mode 100644 index 0000000..e1c8a04 --- /dev/null +++ b/public/wanzhi-logo.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + W +