Files
soul/app/admin/distribution/page.tsx
卡若 b60edb3d47 feat: 完整重构小程序匹配功能 + 修复UI对齐 + 文章数据API
主要更新:
1. 按H5网页端完全重构匹配功能(match页面)
   - 4种匹配类型: 创业合伙/资源对接/导师顾问/团队招募
   - 资源对接等类型弹出手机号/微信号输入框
   - 去掉重新匹配按钮,改为返回按钮

2. 修复所有卡片对齐和宽度问题
   - 目录页附录卡片居中
   - 首页阅读进度卡片满宽度
   - 我的页面菜单卡片对齐
   - 推广中心分享卡片统一宽度

3. 修复目录页图标和文字对齐
   - section-icon固定40rpx宽高
   - section-title与图标垂直居中

4. 更新真实完整文章标题(62篇)
   - 从book目录读取真实markdown文件名
   - 替换之前的简化标题

5. 新增文章数据API
   - /api/db/chapters - 获取完整书籍结构
   - 支持按ID获取单篇文章内容
2026-01-21 15:49:12 +08:00

822 lines
36 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client"
import { useState, useEffect } from "react"
import {
Users, TrendingUp, Clock, Wallet, Search, RefreshCw,
CheckCircle, XCircle, Zap, Calendar, DollarSign, Link2, Eye
} from "lucide-react"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Badge } from "@/components/ui/badge"
// 类型定义
interface DistributionOverview {
todayClicks: number
todayBindings: number
todayConversions: number
todayEarnings: number
monthClicks: number
monthBindings: number
monthConversions: number
monthEarnings: number
totalClicks: number
totalBindings: number
totalConversions: number
totalEarnings: number
expiringBindings: number
pendingWithdrawals: number
pendingWithdrawAmount: number
conversionRate: string
totalDistributors: number
activeDistributors: number
}
interface Binding {
id: string
referrer_id: string
referrer_name?: string
referrer_code: string
referee_id: string
referee_phone?: string
referee_nickname?: string
bound_at: string
expires_at: string
status: 'active' | 'converted' | 'expired' | 'cancelled'
days_remaining?: number
commission?: number
order_amount?: number
source?: string
}
interface Withdrawal {
id: string
user_id: string
user_name?: string
amount: number
method: 'wechat' | 'alipay'
account: string
name: string
status: 'pending' | 'completed' | 'rejected'
created_at: string
completed_at?: string
}
interface User {
id: string
nickname: string
phone: string
referral_code: string
has_full_book: boolean
earnings: number
pending_earnings: number
withdrawn_earnings: number
referral_count: number
created_at: string
}
export default function DistributionAdminPage() {
const [activeTab, setActiveTab] = useState<'overview' | 'bindings' | 'withdrawals' | 'distributors'>('overview')
const [overview, setOverview] = useState<DistributionOverview | null>(null)
const [bindings, setBindings] = useState<Binding[]>([])
const [withdrawals, setWithdrawals] = useState<Withdrawal[]>([])
const [distributors, setDistributors] = useState<User[]>([])
const [loading, setLoading] = useState(true)
const [searchTerm, setSearchTerm] = useState('')
const [statusFilter, setStatusFilter] = useState<string>('all')
useEffect(() => {
loadData()
}, [activeTab])
const loadData = async () => {
setLoading(true)
try {
// 加载用户数据(分销商)
const usersRes = await fetch('/api/db/users')
const usersData = await usersRes.json()
const users = usersData.users || []
setDistributors(users)
// 加载绑定数据
const bindingsRes = await fetch('/api/db/distribution')
const bindingsData = await bindingsRes.json()
setBindings(bindingsData.bindings || [])
// 加载提现数据
const withdrawalsRes = await fetch('/api/db/withdrawals')
const withdrawalsData = await withdrawalsRes.json()
setWithdrawals(withdrawalsData.withdrawals || [])
// 加载购买记录
const purchasesRes = await fetch('/api/db/purchases')
const purchasesData = await purchasesRes.json()
const purchases = purchasesData.purchases || []
// 计算概览数据
const today = new Date().toISOString().split('T')[0]
const monthStart = new Date(new Date().getFullYear(), new Date().getMonth(), 1).toISOString()
const todayBindings = (bindingsData.bindings || []).filter((b: Binding) =>
b.bound_at?.startsWith(today)
).length
const monthBindings = (bindingsData.bindings || []).filter((b: Binding) =>
b.bound_at >= monthStart
).length
const todayConversions = (bindingsData.bindings || []).filter((b: Binding) =>
b.status === 'converted' && b.bound_at?.startsWith(today)
).length
const monthConversions = (bindingsData.bindings || []).filter((b: Binding) =>
b.status === 'converted' && b.bound_at >= monthStart
).length
const totalConversions = (bindingsData.bindings || []).filter((b: Binding) =>
b.status === 'converted'
).length
// 计算佣金
const totalEarnings = users.reduce((sum: number, u: User) => sum + (u.earnings || 0), 0)
const pendingWithdrawAmount = (withdrawalsData.withdrawals || [])
.filter((w: Withdrawal) => w.status === 'pending')
.reduce((sum: number, w: Withdrawal) => sum + w.amount, 0)
// 即将过期绑定7天内
const sevenDaysLater = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString()
const expiringBindings = (bindingsData.bindings || []).filter((b: Binding) =>
b.status === 'active' && b.expires_at <= sevenDaysLater && b.expires_at > new Date().toISOString()
).length
setOverview({
todayClicks: Math.floor(Math.random() * 100) + 50, // 暂用模拟数据
todayBindings,
todayConversions,
todayEarnings: purchases.filter((p: any) => p.created_at?.startsWith(today))
.reduce((sum: number, p: any) => sum + (p.referrer_earnings || 0), 0),
monthClicks: Math.floor(Math.random() * 1000) + 500,
monthBindings,
monthConversions,
monthEarnings: purchases.filter((p: any) => p.created_at >= monthStart)
.reduce((sum: number, p: any) => sum + (p.referrer_earnings || 0), 0),
totalClicks: Math.floor(Math.random() * 5000) + 2000,
totalBindings: (bindingsData.bindings || []).length,
totalConversions,
totalEarnings,
expiringBindings,
pendingWithdrawals: (withdrawalsData.withdrawals || []).filter((w: Withdrawal) => w.status === 'pending').length,
pendingWithdrawAmount,
conversionRate: ((bindingsData.bindings || []).length > 0
? (totalConversions / (bindingsData.bindings || []).length * 100).toFixed(2)
: '0'),
totalDistributors: users.filter((u: User) => u.referral_code).length,
activeDistributors: users.filter((u: User) => (u.earnings || 0) > 0).length,
})
} catch (error) {
console.error('Load distribution data error:', error)
// 如果加载失败,设置空数据
setOverview({
todayClicks: 0,
todayBindings: 0,
todayConversions: 0,
todayEarnings: 0,
monthClicks: 0,
monthBindings: 0,
monthConversions: 0,
monthEarnings: 0,
totalClicks: 0,
totalBindings: 0,
totalConversions: 0,
totalEarnings: 0,
expiringBindings: 0,
pendingWithdrawals: 0,
pendingWithdrawAmount: 0,
conversionRate: '0',
totalDistributors: 0,
activeDistributors: 0,
})
} finally {
setLoading(false)
}
}
// 处理提现审核
const handleApproveWithdrawal = async (id: string) => {
if (!confirm('确认审核通过并打款?')) return
try {
await fetch('/api/db/withdrawals', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id, status: 'completed' })
})
loadData()
} catch (error) {
console.error('Approve withdrawal error:', error)
alert('操作失败')
}
}
const handleRejectWithdrawal = async (id: string) => {
const reason = prompt('请输入拒绝原因:')
if (!reason) return
try {
await fetch('/api/db/withdrawals', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id, status: 'rejected' })
})
loadData()
} catch (error) {
console.error('Reject withdrawal error:', error)
alert('操作失败')
}
}
// 获取状态徽章
const getStatusBadge = (status: string) => {
const styles: Record<string, string> = {
active: 'bg-green-500/20 text-green-400',
converted: 'bg-blue-500/20 text-blue-400',
expired: 'bg-gray-500/20 text-gray-400',
cancelled: 'bg-red-500/20 text-red-400',
pending: 'bg-orange-500/20 text-orange-400',
completed: 'bg-green-500/20 text-green-400',
rejected: 'bg-red-500/20 text-red-400',
}
const labels: Record<string, string> = {
active: '有效',
converted: '已转化',
expired: '已过期',
cancelled: '已取消',
pending: '待审核',
completed: '已完成',
rejected: '已拒绝',
}
return (
<Badge className={`${styles[status] || 'bg-gray-500/20 text-gray-400'} border-0`}>
{labels[status] || status}
</Badge>
)
}
// 过滤数据
const filteredBindings = bindings.filter(b => {
if (statusFilter !== 'all' && b.status !== statusFilter) return false
if (searchTerm) {
const term = searchTerm.toLowerCase()
return (
b.referee_nickname?.toLowerCase().includes(term) ||
b.referee_phone?.includes(term) ||
b.referrer_name?.toLowerCase().includes(term) ||
b.referrer_code?.toLowerCase().includes(term)
)
}
return true
})
const filteredWithdrawals = withdrawals.filter(w => {
if (statusFilter !== 'all' && w.status !== statusFilter) return false
if (searchTerm) {
const term = searchTerm.toLowerCase()
return (
w.user_name?.toLowerCase().includes(term) ||
w.account?.toLowerCase().includes(term)
)
}
return true
})
const filteredDistributors = distributors.filter(d => {
if (!d.referral_code) return false
if (searchTerm) {
const term = searchTerm.toLowerCase()
return (
d.nickname?.toLowerCase().includes(term) ||
d.phone?.includes(term) ||
d.referral_code?.toLowerCase().includes(term)
)
}
return true
})
return (
<div className="p-8 max-w-7xl mx-auto">
{/* 页面标题 */}
<div className="flex items-center justify-between mb-8">
<div>
<h1 className="text-2xl font-bold text-white"></h1>
<p className="text-gray-400 mt-1"></p>
</div>
<Button
onClick={loadData}
disabled={loading}
variant="outline"
className="border-gray-700 text-gray-300 hover:bg-gray-800"
>
<RefreshCw className={`w-4 h-4 mr-2 ${loading ? 'animate-spin' : ''}`} />
</Button>
</div>
{/* Tab切换 */}
<div className="flex gap-2 mb-6 border-b border-gray-700 pb-4">
{[
{ key: 'overview', label: '数据概览', icon: TrendingUp },
{ key: 'bindings', label: '绑定管理', icon: Link2 },
{ key: 'withdrawals', label: '提现审核', icon: Wallet },
{ key: 'distributors', label: '分销商', icon: Users },
].map(tab => (
<button
key={tab.key}
onClick={() => {
setActiveTab(tab.key as typeof activeTab)
setStatusFilter('all')
setSearchTerm('')
}}
className={`flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium transition-colors ${
activeTab === tab.key
? 'bg-[#38bdac] text-white'
: 'text-gray-400 hover:text-white hover:bg-gray-800'
}`}
>
<tab.icon className="w-4 h-4" />
{tab.label}
</button>
))}
</div>
{loading ? (
<div className="flex items-center justify-center py-20">
<RefreshCw className="w-8 h-8 text-[#38bdac] animate-spin" />
<span className="ml-2 text-gray-400">...</span>
</div>
) : (
<>
{/* 数据概览 */}
{activeTab === 'overview' && overview && (
<div className="space-y-6">
{/* 今日数据 */}
<div className="grid grid-cols-4 gap-4">
<Card className="bg-[#0f2137] border-gray-700/50">
<CardContent className="p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-gray-400 text-sm"></p>
<p className="text-2xl font-bold text-white mt-1">{overview.todayClicks}</p>
</div>
<div className="w-12 h-12 rounded-xl bg-blue-500/20 flex items-center justify-center">
<Eye className="w-6 h-6 text-blue-400" />
</div>
</div>
</CardContent>
</Card>
<Card className="bg-[#0f2137] border-gray-700/50">
<CardContent className="p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-gray-400 text-sm"></p>
<p className="text-2xl font-bold text-white mt-1">{overview.todayBindings}</p>
</div>
<div className="w-12 h-12 rounded-xl bg-green-500/20 flex items-center justify-center">
<Link2 className="w-6 h-6 text-green-400" />
</div>
</div>
</CardContent>
</Card>
<Card className="bg-[#0f2137] border-gray-700/50">
<CardContent className="p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-gray-400 text-sm"></p>
<p className="text-2xl font-bold text-white mt-1">{overview.todayConversions}</p>
</div>
<div className="w-12 h-12 rounded-xl bg-purple-500/20 flex items-center justify-center">
<CheckCircle className="w-6 h-6 text-purple-400" />
</div>
</div>
</CardContent>
</Card>
<Card className="bg-[#0f2137] border-gray-700/50">
<CardContent className="p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-gray-400 text-sm"></p>
<p className="text-2xl font-bold text-[#38bdac] mt-1">¥{overview.todayEarnings.toFixed(2)}</p>
</div>
<div className="w-12 h-12 rounded-xl bg-[#38bdac]/20 flex items-center justify-center">
<DollarSign className="w-6 h-6 text-[#38bdac]" />
</div>
</div>
</CardContent>
</Card>
</div>
{/* 重要提醒 */}
<div className="grid grid-cols-2 gap-4">
<Card className="bg-orange-500/10 border-orange-500/30">
<CardContent className="p-6">
<div className="flex items-center gap-4">
<div className="w-12 h-12 rounded-xl bg-orange-500/20 flex items-center justify-center">
<Clock className="w-6 h-6 text-orange-400" />
</div>
<div className="flex-1">
<p className="text-orange-300 font-medium"></p>
<p className="text-2xl font-bold text-white">{overview.expiringBindings} </p>
<p className="text-orange-300/60 text-sm">7</p>
</div>
</div>
</CardContent>
</Card>
<Card className="bg-blue-500/10 border-blue-500/30">
<CardContent className="p-6">
<div className="flex items-center gap-4">
<div className="w-12 h-12 rounded-xl bg-blue-500/20 flex items-center justify-center">
<Wallet className="w-6 h-6 text-blue-400" />
</div>
<div className="flex-1">
<p className="text-blue-300 font-medium"></p>
<p className="text-2xl font-bold text-white">{overview.pendingWithdrawals} </p>
<p className="text-blue-300/60 text-sm"> ¥{overview.pendingWithdrawAmount.toFixed(2)}</p>
</div>
<Button
onClick={() => setActiveTab('withdrawals')}
variant="outline"
className="border-blue-500/50 text-blue-400 hover:bg-blue-500/20"
>
</Button>
</div>
</CardContent>
</Card>
</div>
{/* 本月/累计统计 */}
<div className="grid grid-cols-2 gap-6">
<Card className="bg-[#0f2137] border-gray-700/50">
<CardHeader>
<CardTitle className="text-white flex items-center gap-2">
<Calendar className="w-5 h-5 text-[#38bdac]" />
</CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-2 gap-4">
<div className="p-4 bg-white/5 rounded-lg">
<p className="text-gray-400 text-sm"></p>
<p className="text-xl font-bold text-white">{overview.monthClicks}</p>
</div>
<div className="p-4 bg-white/5 rounded-lg">
<p className="text-gray-400 text-sm"></p>
<p className="text-xl font-bold text-white">{overview.monthBindings}</p>
</div>
<div className="p-4 bg-white/5 rounded-lg">
<p className="text-gray-400 text-sm"></p>
<p className="text-xl font-bold text-white">{overview.monthConversions}</p>
</div>
<div className="p-4 bg-white/5 rounded-lg">
<p className="text-gray-400 text-sm"></p>
<p className="text-xl font-bold text-[#38bdac]">¥{overview.monthEarnings.toFixed(2)}</p>
</div>
</div>
</CardContent>
</Card>
<Card className="bg-[#0f2137] border-gray-700/50">
<CardHeader>
<CardTitle className="text-white flex items-center gap-2">
<TrendingUp className="w-5 h-5 text-[#38bdac]" />
</CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-2 gap-4">
<div className="p-4 bg-white/5 rounded-lg">
<p className="text-gray-400 text-sm"></p>
<p className="text-xl font-bold text-white">{overview.totalClicks.toLocaleString()}</p>
</div>
<div className="p-4 bg-white/5 rounded-lg">
<p className="text-gray-400 text-sm"></p>
<p className="text-xl font-bold text-white">{overview.totalBindings.toLocaleString()}</p>
</div>
<div className="p-4 bg-white/5 rounded-lg">
<p className="text-gray-400 text-sm"></p>
<p className="text-xl font-bold text-white">{overview.totalConversions}</p>
</div>
<div className="p-4 bg-white/5 rounded-lg">
<p className="text-gray-400 text-sm"></p>
<p className="text-xl font-bold text-[#38bdac]">¥{overview.totalEarnings.toFixed(2)}</p>
</div>
</div>
<div className="mt-4 p-4 bg-[#38bdac]/10 rounded-lg flex items-center justify-between">
<span className="text-gray-300"></span>
<span className="text-[#38bdac] font-bold text-xl">{overview.conversionRate}%</span>
</div>
</CardContent>
</Card>
</div>
{/* 分销商统计 */}
<Card className="bg-[#0f2137] border-gray-700/50">
<CardHeader>
<CardTitle className="text-white flex items-center gap-2">
<Users className="w-5 h-5 text-[#38bdac]" />
</CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-4 gap-4">
<div className="p-4 bg-white/5 rounded-lg text-center">
<p className="text-3xl font-bold text-white">{overview.totalDistributors}</p>
<p className="text-gray-400 text-sm mt-1"></p>
</div>
<div className="p-4 bg-white/5 rounded-lg text-center">
<p className="text-3xl font-bold text-green-400">{overview.activeDistributors}</p>
<p className="text-gray-400 text-sm mt-1"></p>
</div>
<div className="p-4 bg-white/5 rounded-lg text-center">
<p className="text-3xl font-bold text-[#38bdac]">90%</p>
<p className="text-gray-400 text-sm mt-1"></p>
</div>
<div className="p-4 bg-white/5 rounded-lg text-center">
<p className="text-3xl font-bold text-orange-400">30</p>
<p className="text-gray-400 text-sm mt-1"></p>
</div>
</div>
</CardContent>
</Card>
</div>
)}
{/* 绑定管理 */}
{activeTab === 'bindings' && (
<div className="space-y-4">
<div className="flex gap-4">
<div className="relative flex-1">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400" />
<Input
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
placeholder="搜索用户昵称、手机号、推广码..."
className="pl-10 bg-[#0f2137] border-gray-700 text-white"
/>
</div>
<select
value={statusFilter}
onChange={(e) => setStatusFilter(e.target.value)}
className="px-4 py-2 bg-[#0f2137] border border-gray-700 rounded-lg text-white"
>
<option value="all"></option>
<option value="active"></option>
<option value="converted"></option>
<option value="expired"></option>
</select>
</div>
<Card className="bg-[#0f2137] border-gray-700/50">
<CardContent className="p-0">
{filteredBindings.length === 0 ? (
<div className="py-12 text-center text-gray-500"></div>
) : (
<div className="overflow-x-auto">
<table className="w-full text-sm">
<thead>
<tr className="bg-[#0a1628] text-gray-400">
<th className="p-4 text-left font-medium">访</th>
<th className="p-4 text-left font-medium"></th>
<th className="p-4 text-left font-medium"></th>
<th className="p-4 text-left font-medium"></th>
<th className="p-4 text-left font-medium"></th>
<th className="p-4 text-left font-medium"></th>
</tr>
</thead>
<tbody className="divide-y divide-gray-700/50">
{filteredBindings.map(binding => (
<tr key={binding.id} className="hover:bg-[#0a1628] transition-colors">
<td className="p-4">
<div>
<p className="text-white font-medium">{binding.referee_nickname || '匿名用户'}</p>
<p className="text-gray-500 text-xs">{binding.referee_phone}</p>
</div>
</td>
<td className="p-4">
<div>
<p className="text-white">{binding.referrer_name || '-'}</p>
<p className="text-gray-500 text-xs font-mono">{binding.referrer_code}</p>
</div>
</td>
<td className="p-4 text-gray-400">
{binding.bound_at ? new Date(binding.bound_at).toLocaleDateString('zh-CN') : '-'}
</td>
<td className="p-4 text-gray-400">
{binding.expires_at ? new Date(binding.expires_at).toLocaleDateString('zh-CN') : '-'}
</td>
<td className="p-4">{getStatusBadge(binding.status)}</td>
<td className="p-4">
{binding.commission ? (
<span className="text-[#38bdac] font-medium">¥{binding.commission.toFixed(2)}</span>
) : (
<span className="text-gray-500">-</span>
)}
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</CardContent>
</Card>
</div>
)}
{/* 提现审核 */}
{activeTab === 'withdrawals' && (
<div className="space-y-4">
<div className="flex gap-4">
<div className="relative flex-1">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400" />
<Input
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
placeholder="搜索用户名称、账号..."
className="pl-10 bg-[#0f2137] border-gray-700 text-white"
/>
</div>
<select
value={statusFilter}
onChange={(e) => setStatusFilter(e.target.value)}
className="px-4 py-2 bg-[#0f2137] border border-gray-700 rounded-lg text-white"
>
<option value="all"></option>
<option value="pending"></option>
<option value="completed"></option>
<option value="rejected"></option>
</select>
</div>
<Card className="bg-[#0f2137] border-gray-700/50">
<CardContent className="p-0">
{filteredWithdrawals.length === 0 ? (
<div className="py-12 text-center text-gray-500"></div>
) : (
<div className="overflow-x-auto">
<table className="w-full text-sm">
<thead>
<tr className="bg-[#0a1628] text-gray-400">
<th className="p-4 text-left font-medium"></th>
<th className="p-4 text-left font-medium"></th>
<th className="p-4 text-left font-medium"></th>
<th className="p-4 text-left font-medium"></th>
<th className="p-4 text-left font-medium"></th>
<th className="p-4 text-left font-medium"></th>
<th className="p-4 text-right font-medium"></th>
</tr>
</thead>
<tbody className="divide-y divide-gray-700/50">
{filteredWithdrawals.map(withdrawal => (
<tr key={withdrawal.id} className="hover:bg-[#0a1628] transition-colors">
<td className="p-4">
<p className="text-white font-medium">{withdrawal.user_name || withdrawal.name}</p>
</td>
<td className="p-4">
<span className="text-[#38bdac] font-bold">¥{withdrawal.amount.toFixed(2)}</span>
</td>
<td className="p-4">
<Badge className={
withdrawal.method === 'wechat'
? 'bg-green-500/20 text-green-400 border-0'
: 'bg-blue-500/20 text-blue-400 border-0'
}>
{withdrawal.method === 'wechat' ? '微信' : '支付宝'}
</Badge>
</td>
<td className="p-4">
<div>
<p className="text-white font-mono text-xs">{withdrawal.account}</p>
<p className="text-gray-500 text-xs">{withdrawal.name}</p>
</div>
</td>
<td className="p-4 text-gray-400">
{withdrawal.created_at ? new Date(withdrawal.created_at).toLocaleString('zh-CN') : '-'}
</td>
<td className="p-4">{getStatusBadge(withdrawal.status)}</td>
<td className="p-4 text-right">
{withdrawal.status === 'pending' && (
<div className="flex gap-2 justify-end">
<Button
size="sm"
onClick={() => handleApproveWithdrawal(withdrawal.id)}
className="bg-[#38bdac] hover:bg-[#2da396] text-white"
>
<CheckCircle className="w-4 h-4 mr-1" />
</Button>
<Button
size="sm"
variant="outline"
onClick={() => handleRejectWithdrawal(withdrawal.id)}
className="border-red-500/50 text-red-400 hover:bg-red-500/20"
>
<XCircle className="w-4 h-4 mr-1" />
</Button>
</div>
)}
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</CardContent>
</Card>
</div>
)}
{/* 分销商管理 */}
{activeTab === 'distributors' && (
<div className="space-y-4">
<div className="flex gap-4">
<div className="relative flex-1">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400" />
<Input
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
placeholder="搜索分销商名称、手机号、推广码..."
className="pl-10 bg-[#0f2137] border-gray-700 text-white"
/>
</div>
</div>
<Card className="bg-[#0f2137] border-gray-700/50">
<CardContent className="p-0">
{filteredDistributors.length === 0 ? (
<div className="py-12 text-center text-gray-500"></div>
) : (
<div className="overflow-x-auto">
<table className="w-full text-sm">
<thead>
<tr className="bg-[#0a1628] text-gray-400">
<th className="p-4 text-left font-medium"></th>
<th className="p-4 text-left font-medium">广</th>
<th className="p-4 text-left font-medium"></th>
<th className="p-4 text-left font-medium"></th>
<th className="p-4 text-left font-medium"></th>
<th className="p-4 text-left font-medium"></th>
<th className="p-4 text-left font-medium"></th>
</tr>
</thead>
<tbody className="divide-y divide-gray-700/50">
{filteredDistributors.map(distributor => (
<tr key={distributor.id} className="hover:bg-[#0a1628] transition-colors">
<td className="p-4">
<div>
<p className="text-white font-medium">{distributor.nickname}</p>
<p className="text-gray-500 text-xs">{distributor.phone}</p>
</div>
</td>
<td className="p-4">
<span className="text-[#38bdac] font-mono text-sm">{distributor.referral_code}</span>
</td>
<td className="p-4">
<span className="text-white">{distributor.referral_count || 0}</span>
</td>
<td className="p-4">
<span className="text-[#38bdac] font-bold">¥{(distributor.earnings || 0).toFixed(2)}</span>
</td>
<td className="p-4">
<span className="text-white">¥{(distributor.pending_earnings || 0).toFixed(2)}</span>
</td>
<td className="p-4">
<span className="text-gray-400">¥{(distributor.withdrawn_earnings || 0).toFixed(2)}</span>
</td>
<td className="p-4 text-gray-400">
{distributor.created_at ? new Date(distributor.created_at).toLocaleDateString('zh-CN') : '-'}
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</CardContent>
</Card>
</div>
)}
</>
)}
</div>
)
}