功能迭代:用户管理与存客宝同步、管理后台与小程序优化、开发文档更新
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -75,12 +75,30 @@ interface User {
|
||||
created_at: string
|
||||
}
|
||||
|
||||
// 订单类型(用于交易中心的订单管理标签)
|
||||
interface Order {
|
||||
id: string
|
||||
userId: string
|
||||
userNickname?: string
|
||||
userPhone?: string
|
||||
type: 'section' | 'fullbook' | 'match'
|
||||
sectionId?: string
|
||||
sectionTitle?: string
|
||||
amount: number
|
||||
status: 'pending' | 'completed' | 'failed'
|
||||
paymentMethod?: string
|
||||
referrerEarnings?: number
|
||||
createdAt: string
|
||||
}
|
||||
|
||||
export default function DistributionAdminPage() {
|
||||
const [activeTab, setActiveTab] = useState<'overview' | 'bindings' | 'withdrawals' | 'distributors'>('overview')
|
||||
// 标签页:数据概览、订单管理、绑定管理、提现审核
|
||||
const [activeTab, setActiveTab] = useState<'overview' | 'orders' | 'bindings' | 'withdrawals'>('overview')
|
||||
const [orders, setOrders] = useState<Order[]>([])
|
||||
const [overview, setOverview] = useState<DistributionOverview | null>(null)
|
||||
const [bindings, setBindings] = useState<Binding[]>([])
|
||||
const [withdrawals, setWithdrawals] = useState<Withdrawal[]>([])
|
||||
const [distributors, setDistributors] = useState<User[]>([])
|
||||
const [users, setUsers] = useState<User[]>([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [searchTerm, setSearchTerm] = useState('')
|
||||
const [statusFilter, setStatusFilter] = useState<string>('all')
|
||||
@@ -93,11 +111,27 @@ export default function DistributionAdminPage() {
|
||||
setLoading(true)
|
||||
|
||||
try {
|
||||
// 加载用户数据(分销商)
|
||||
// 加载用户数据
|
||||
const usersRes = await fetch('/api/db/users')
|
||||
const usersData = await usersRes.json()
|
||||
const users = usersData.users || []
|
||||
setDistributors(users)
|
||||
const usersArr = usersData.users || []
|
||||
setUsers(usersArr)
|
||||
|
||||
// 加载订单数据
|
||||
const ordersRes = await fetch('/api/orders')
|
||||
const ordersData = await ordersRes.json()
|
||||
if (ordersData.success && ordersData.orders) {
|
||||
// 补充用户信息
|
||||
const enrichedOrders = ordersData.orders.map((order: Order) => {
|
||||
const user = usersArr.find((u: User) => u.id === order.userId)
|
||||
return {
|
||||
...order,
|
||||
userNickname: user?.nickname || '未知用户',
|
||||
userPhone: user?.phone || '-'
|
||||
}
|
||||
})
|
||||
setOrders(enrichedOrders)
|
||||
}
|
||||
|
||||
// 加载绑定数据
|
||||
const bindingsRes = await fetch('/api/db/distribution')
|
||||
@@ -139,7 +173,7 @@ export default function DistributionAdminPage() {
|
||||
).length
|
||||
|
||||
// 计算佣金
|
||||
const totalEarnings = users.reduce((sum: number, u: User) => sum + (u.earnings || 0), 0)
|
||||
const totalEarnings = usersArr.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)
|
||||
@@ -171,8 +205,8 @@ export default function DistributionAdminPage() {
|
||||
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,
|
||||
totalDistributors: usersArr.filter((u: User) => u.referral_code).length,
|
||||
activeDistributors: usersArr.filter((u: User) => (u.earnings || 0) > 0).length,
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Load distribution data error:', error)
|
||||
@@ -292,26 +326,14 @@ export default function DistributionAdminPage() {
|
||||
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>
|
||||
<h1 className="text-2xl font-bold text-white">交易中心</h1>
|
||||
<p className="text-gray-400 mt-1">统一管理:订单、分销绑定、提现审核</p>
|
||||
</div>
|
||||
<Button
|
||||
onClick={loadData}
|
||||
@@ -324,13 +346,13 @@ export default function DistributionAdminPage() {
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Tab切换 */}
|
||||
{/* Tab切换 - 交易中心:合并分销+订单+提现 */}
|
||||
<div className="flex gap-2 mb-6 border-b border-gray-700 pb-4">
|
||||
{[
|
||||
{ key: 'overview', label: '数据概览', icon: TrendingUp },
|
||||
{ key: 'orders', label: '订单管理', icon: DollarSign },
|
||||
{ key: 'bindings', label: '绑定管理', icon: Link2 },
|
||||
{ key: 'withdrawals', label: '提现审核', icon: Wallet },
|
||||
{ key: 'distributors', label: '分销商', icon: Users },
|
||||
].map(tab => (
|
||||
<button
|
||||
key={tab.key}
|
||||
@@ -525,23 +547,23 @@ export default function DistributionAdminPage() {
|
||||
</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>
|
||||
<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>
|
||||
<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>
|
||||
@@ -557,6 +579,123 @@ export default function DistributionAdminPage() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 订单管理 - 新增标签页 */}
|
||||
{activeTab === 'orders' && (
|
||||
<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="completed">已完成</option>
|
||||
<option value="pending">待支付</option>
|
||||
<option value="failed">已失败</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<Card className="bg-[#0f2137] border-gray-700/50">
|
||||
<CardContent className="p-0">
|
||||
{orders.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>
|
||||
<th className="p-4 text-left font-medium">下单时间</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-gray-700/50">
|
||||
{orders
|
||||
.filter(order => {
|
||||
if (statusFilter !== 'all' && order.status !== statusFilter) return false
|
||||
if (searchTerm) {
|
||||
const term = searchTerm.toLowerCase()
|
||||
return (
|
||||
order.id?.toLowerCase().includes(term) ||
|
||||
order.userNickname?.toLowerCase().includes(term) ||
|
||||
order.userPhone?.includes(term) ||
|
||||
order.sectionTitle?.toLowerCase().includes(term)
|
||||
)
|
||||
}
|
||||
return true
|
||||
})
|
||||
.map(order => (
|
||||
<tr key={order.id} className="hover:bg-[#0a1628] transition-colors">
|
||||
<td className="p-4 font-mono text-xs text-gray-400">
|
||||
{order.id?.slice(0, 12)}...
|
||||
</td>
|
||||
<td className="p-4">
|
||||
<div>
|
||||
<p className="text-white text-sm">{order.userNickname}</p>
|
||||
<p className="text-gray-500 text-xs">{order.userPhone}</p>
|
||||
</div>
|
||||
</td>
|
||||
<td className="p-4">
|
||||
<div>
|
||||
<p className="text-white text-sm">
|
||||
{order.type === 'fullbook' ? '整本购买' :
|
||||
order.type === 'match' ? '匹配次数' :
|
||||
order.sectionTitle || `章节${order.sectionId}`}
|
||||
</p>
|
||||
<p className="text-gray-500 text-xs">
|
||||
{order.type === 'fullbook' ? '全书' :
|
||||
order.type === 'match' ? '功能' : '单章'}
|
||||
</p>
|
||||
</div>
|
||||
</td>
|
||||
<td className="p-4 text-[#38bdac] font-bold">
|
||||
¥{(order.amount || 0).toFixed(2)}
|
||||
</td>
|
||||
<td className="p-4 text-gray-300">
|
||||
{order.paymentMethod === 'wechat' ? '微信支付' :
|
||||
order.paymentMethod === 'alipay' ? '支付宝' :
|
||||
order.paymentMethod || '微信支付'}
|
||||
</td>
|
||||
<td className="p-4">
|
||||
{order.status === 'completed' ? (
|
||||
<Badge className="bg-green-500/20 text-green-400 border-0">已完成</Badge>
|
||||
) : order.status === 'pending' ? (
|
||||
<Badge className="bg-yellow-500/20 text-yellow-400 border-0">待支付</Badge>
|
||||
) : (
|
||||
<Badge className="bg-red-500/20 text-red-400 border-0">已失败</Badge>
|
||||
)}
|
||||
</td>
|
||||
<td className="p-4 text-[#FFD700]">
|
||||
{order.referrerEarnings ? `¥${order.referrerEarnings.toFixed(2)}` : '-'}
|
||||
</td>
|
||||
<td className="p-4 text-gray-400 text-sm">
|
||||
{order.createdAt ? new Date(order.createdAt).toLocaleString('zh-CN') : '-'}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 绑定管理 */}
|
||||
{activeTab === 'bindings' && (
|
||||
<div className="space-y-4">
|
||||
@@ -744,76 +883,6 @@ export default function DistributionAdminPage() {
|
||||
</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>
|
||||
|
||||
Reference in New Issue
Block a user