diff --git a/app/profile/edit/page.tsx b/app/profile/edit/page.tsx
new file mode 100644
index 0000000..c711530
--- /dev/null
+++ b/app/profile/edit/page.tsx
@@ -0,0 +1,155 @@
+"use client"
+
+import { useState } from "react"
+import { ArrowLeft, Camera, ChevronRight, Check } from "lucide-react"
+import { Button } from "@/components/ui/button"
+import { Input } from "@/components/ui/input"
+import { useRouter } from "next/navigation"
+import { useAppContext } from "@/components/providers/app-provider"
+import Image from "next/image"
+
+export default function ProfileEditPage() {
+ const router = useRouter()
+ const { user, updateUser } = useAppContext()
+ const [name, setName] = useState(user.name || "")
+ const [bio, setBio] = useState("")
+ const [gender, setGender] = useState<"male" | "female" | "unknown">("male")
+ const [showAvatarPicker, setShowAvatarPicker] = useState(false)
+ const [selectedAvatar, setSelectedAvatar] = useState(user.avatar || "/gamer-profile-avatar.jpg")
+
+ const avatarOptions = [
+ "/gamer-profile-avatar.jpg",
+ "/gamer-boy-esports-jersey.jpg",
+ "/streamer-1.jpg",
+ "/streamer-2.jpg",
+ "/streamer-3.jpg",
+ "/cp-avatar-1.jpg",
+ "/cp-avatar-2.jpg",
+ "/coach-avatar-1.jpg",
+ ]
+
+ const handleSave = () => {
+ updateUser({
+ name,
+ avatar: selectedAvatar,
+ })
+ router.back()
+ }
+
+ return (
+
+ {/* Header */}
+
+
+
+
编辑资料
+
+
+
+
+
+ {/* Avatar Section */}
+
+
setShowAvatarPicker(true)}>
+
+
+
+
+
+
+
+
点击更换头像
+
+
+ {/* 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}
- 币
+ 币
-
-
-
- LV.8
+
+
+
+
+ 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} 币
+
setShowSuccess(false)} className="w-full bg-primary hover:bg-primary/90">
+ 确定
+
+
+
+
+ )}
)
}
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 */}
+
+ router.back()} className="p-2 hover:bg-foreground/10 rounded-full transition-colors">
+
+
+ 设置
+
+
+
+ {/* General Settings */}
+
+
+
+
router.push("/account-security")}
+ className="w-full flex items-center justify-between p-4"
+ >
+
+
+ 账号安全
+
+
+
+
+
+ {/* Other Settings */}
+
+
+
+
+ 语言设置
+
+
+ 简体中文
+
+
+
+
setShowClearConfirm(true)}
+ className="w-full flex items-center justify-between p-4 border-b border-border"
+ >
+
+
+ 清除缓存
+
+
+ 12.5MB
+
+
+
+
+
+
+ 关于我们
+
+
+ v1.0.0
+
+
+
+
+
+
+ {/* Clear Cache Confirm */}
+ {showClearConfirm && (
+
+
+
清除缓存
+
确定要清除所有缓存数据吗?
+
+ setShowClearConfirm(false)}
+ >
+ 取消
+
+
+ 确认清除
+
+
+
+
+ )}
+
+ )
+}
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 */}
+
+
+
router.back()} className="p-2 hover:bg-foreground/10 rounded-full transition-colors">
+
+
+
账单明细
+
+
+
+
+
+
+ {/* 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 @@
+