Files
soul/app/admin/orders/page.tsx

225 lines
9.0 KiB
TypeScript
Raw Normal View History

"use client"
import { useState, useEffect, Suspense } from "react"
import { Card, CardContent } from "@/components/ui/card"
import { Input } from "@/components/ui/input"
import { Button } from "@/components/ui/button"
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
import { Badge } from "@/components/ui/badge"
import { Search, RefreshCw, Download, Filter, TrendingUp } from "lucide-react"
import { useStore } from "@/lib/store"
interface Purchase {
id: string
userId: string
type: "section" | "fullbook" | "match"
sectionId?: string
sectionTitle?: string
amount: number
status: "pending" | "completed" | "failed"
paymentMethod?: string
referrerEarnings?: number
createdAt: string
}
function OrdersContent() {
const { getAllPurchases, getAllUsers } = useStore()
const [purchases, setPurchases] = useState<Purchase[]>([])
const [users, setUsers] = useState<any[]>([])
const [searchTerm, setSearchTerm] = useState("")
const [statusFilter, setStatusFilter] = useState<string>("all")
const [isLoading, setIsLoading] = useState(true)
useEffect(() => {
setIsLoading(true)
setPurchases(getAllPurchases())
setUsers(getAllUsers())
setIsLoading(false)
}, [getAllPurchases, getAllUsers])
// 获取用户昵称
const getUserNickname = (userId: string) => {
const user = users.find(u => u.id === userId)
return user?.nickname || "未知用户"
}
// 获取用户手机号
const getUserPhone = (userId: string) => {
const user = users.find(u => u.id === userId)
return user?.phone || "-"
}
// 过滤订单
const filteredPurchases = purchases.filter((p) => {
const matchSearch =
getUserNickname(p.userId).includes(searchTerm) ||
getUserPhone(p.userId).includes(searchTerm) ||
p.sectionTitle?.includes(searchTerm) ||
p.id.includes(searchTerm)
const matchStatus = statusFilter === "all" || p.status === statusFilter
return matchSearch && matchStatus
})
// 统计数据
const totalRevenue = purchases.filter(p => p.status === "completed").reduce((sum, p) => sum + p.amount, 0)
const todayRevenue = purchases
.filter(p => {
const today = new Date().toDateString()
return p.status === "completed" && new Date(p.createdAt).toDateString() === today
})
.reduce((sum, p) => sum + p.amount, 0)
return (
<div className="p-8 max-w-7xl mx-auto">
<div className="flex justify-between items-center mb-8">
<div>
<h2 className="text-2xl font-bold text-white"></h2>
<p className="text-gray-400 mt-1"> {purchases.length} </p>
</div>
<div className="flex items-center gap-4">
<div className="flex items-center gap-2 text-sm">
<span className="text-gray-400">:</span>
<span className="text-[#38bdac] font-bold">¥{totalRevenue.toFixed(2)}</span>
<span className="text-gray-600">|</span>
<span className="text-gray-400">:</span>
<span className="text-[#FFD700] font-bold">¥{todayRevenue.toFixed(2)}</span>
</div>
</div>
</div>
<div className="flex items-center gap-4 mb-6">
<div className="relative flex-1 max-w-md">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-500" />
<Input
type="text"
placeholder="搜索订单号/用户/章节..."
className="pl-10 bg-[#0f2137] border-gray-700 text-white placeholder:text-gray-500"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
</div>
<div className="flex items-center gap-2">
<Filter className="w-4 h-4 text-gray-400" />
<select
value={statusFilter}
onChange={(e) => setStatusFilter(e.target.value)}
className="bg-[#0f2137] border border-gray-700 text-white rounded-lg px-3 py-2 text-sm"
>
<option value="all"></option>
<option value="completed"></option>
<option value="pending"></option>
<option value="failed"></option>
</select>
</div>
<Button
variant="outline"
className="border-gray-600 text-gray-300 hover:bg-gray-700/50 bg-transparent"
>
<Download className="w-4 h-4 mr-2" />
</Button>
</div>
<Card className="bg-[#0f2137] border-gray-700/50 shadow-xl">
<CardContent className="p-0">
{isLoading ? (
<div className="flex items-center justify-center py-12">
<RefreshCw className="w-6 h-6 text-[#38bdac] animate-spin" />
<span className="ml-2 text-gray-400">...</span>
</div>
) : (
<Table>
<TableHeader>
<TableRow className="bg-[#0a1628] hover:bg-[#0a1628] border-gray-700">
<TableHead className="text-gray-400"></TableHead>
<TableHead className="text-gray-400"></TableHead>
<TableHead className="text-gray-400"></TableHead>
<TableHead className="text-gray-400"></TableHead>
<TableHead className="text-gray-400"></TableHead>
<TableHead className="text-gray-400"></TableHead>
<TableHead className="text-gray-400"></TableHead>
<TableHead className="text-gray-400"></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{filteredPurchases.map((purchase) => (
<TableRow key={purchase.id} className="hover:bg-[#0a1628] border-gray-700/50">
<TableCell className="font-mono text-xs text-gray-400">
{purchase.id.slice(0, 12)}...
</TableCell>
<TableCell>
<div>
<p className="text-white text-sm">{getUserNickname(purchase.userId)}</p>
<p className="text-gray-500 text-xs">{getUserPhone(purchase.userId)}</p>
</div>
</TableCell>
<TableCell>
<div>
<p className="text-white text-sm">
{purchase.type === "fullbook" ? "整本购买" :
purchase.type === "match" ? "匹配次数" :
purchase.sectionTitle || `章节${purchase.sectionId}`}
</p>
<p className="text-gray-500 text-xs">
{purchase.type === "fullbook" ? "全书" :
purchase.type === "match" ? "功能" : "单章"}
</p>
</div>
</TableCell>
<TableCell className="text-[#38bdac] font-bold">
¥{purchase.amount.toFixed(2)}
</TableCell>
<TableCell className="text-gray-300">
{purchase.paymentMethod === "wechat" ? "微信支付" :
purchase.paymentMethod === "alipay" ? "支付宝" :
purchase.paymentMethod || "微信支付"}
</TableCell>
<TableCell>
{purchase.status === "completed" ? (
<Badge className="bg-green-500/20 text-green-400 hover:bg-green-500/20 border-0">
</Badge>
) : purchase.status === "pending" ? (
<Badge className="bg-yellow-500/20 text-yellow-400 hover:bg-yellow-500/20 border-0">
</Badge>
) : (
<Badge className="bg-red-500/20 text-red-400 hover:bg-red-500/20 border-0">
</Badge>
)}
</TableCell>
<TableCell className="text-[#FFD700]">
{purchase.referrerEarnings ? `¥${purchase.referrerEarnings.toFixed(2)}` : "-"}
</TableCell>
<TableCell className="text-gray-400 text-sm">
{new Date(purchase.createdAt).toLocaleString()}
</TableCell>
</TableRow>
))}
{filteredPurchases.length === 0 && (
<TableRow>
<TableCell colSpan={8} className="text-center py-12 text-gray-500">
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
)}
</CardContent>
</Card>
</div>
)
}
export default function OrdersPage() {
return (
<Suspense fallback={null}>
<OrdersContent />
</Suspense>
)
}