Files
wzdj/lib/db/hooks.ts
v0 ecae8fcef8 Add new files
#VERCEL_SKIP

Co-authored-by: undefined <undefined+undefined@users.noreply.github.com>
2026-01-04 05:02:13 +00:00

463 lines
13 KiB
TypeScript

// 数据库操作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<Error | null>(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<User | null>(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<User>) => {
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<Streamer[]>([])
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<Star[]>([])
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<Guild[]>([])
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<Companion[]>([])
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<EsportsVenue[]>([])
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<Product[]>([])
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<Transaction[]>([])
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<Moment[]>([])
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<Message[]>([])
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<Message, "id"> = {
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<Notification[]>([])
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<Transaction, "id"> = {
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 }
}