import { useState, useEffect, useCallback } from 'react'; import { adminAPI } from '@/api'; interface Rule { id: string; name: string; billing_type: string; model_name: string; rate: number; free_quota: number; status: string; } interface Package { id: string; name: string; description: string; tokens: number; bonus_tokens: number; price: number; original_price: number; is_popular: number; status: string; } interface Transaction { id: string; user_id: string; username: string; transaction_type: string; amount: number; tokens: number; status: string; created_at: string; description: string; } interface Stats { total_revenue: number; total_consumption: number; total_transactions: number; pending_invoices: number; active_rules: number; active_packages: number; daily_revenue_7d: { date: string; revenue: number }[]; } const Billing: React.FC = () => { const [tab, setTab] = useState<'overview' | 'rules' | 'packages' | 'transactions'>('overview'); const [rules, setRules] = useState([]); const [packages, setPackages] = useState([]); const [transactions, setTransactions] = useState([]); const [total, setTotal] = useState(0); const [page, setPage] = useState(1); const [stats, setStats] = useState(null); const [loading, setLoading] = useState(false); const fetchRules = useCallback(async () => { setLoading(true); try { const res = await adminAPI.getBillingRules(); const d = (res as any)?.data || res; setRules(d?.items || []); } catch (e) { console.error('fetchRules:', e); } setLoading(false); }, []); const fetchPackages = useCallback(async () => { try { const res = await adminAPI.getBillingPackages(); const d = (res as any)?.data || res; setPackages(Array.isArray(d) ? d : d?.items || []); } catch (e) { console.error('fetchPackages:', e); } }, []); const fetchTransactions = useCallback(async () => { setLoading(true); try { const res = await adminAPI.getBillingTransactions(page); const d = (res as any)?.data || res; setTransactions(d?.items || []); setTotal(d?.total || 0); } catch (e) { console.error('fetchTransactions:', e); } setLoading(false); }, [page]); const fetchStats = useCallback(async () => { try { const res = await adminAPI.getBillingStats(); setStats((res as any)?.data || res); } catch (e) { console.error('fetchStats:', e); } }, []); useEffect(() => { fetchStats(); }, [fetchStats]); useEffect(() => { if (tab === 'rules') fetchRules(); }, [tab, fetchRules]); useEffect(() => { if (tab === 'packages') fetchPackages(); }, [tab, fetchPackages]); useEffect(() => { if (tab === 'transactions') fetchTransactions(); }, [tab, fetchTransactions, page]); const typeColors: Record = { per_token: '#6366f1', per_call: '#3b82f6', per_month: '#10b981', hybrid: '#f59e0b' }; const txColors: Record = { recharge: '#10b981', consumption: '#ef4444', refund: '#f59e0b', gift: '#ec4899' }; return (

计费与支付管理

{/* Stats */} {stats && (
{[ { label: '总收入(元)', value: stats.total_revenue?.toFixed(2) || '0.00', color: '#10b981' }, { label: '总消费(元)', value: stats.total_consumption?.toFixed(2) || '0.00', color: '#ef4444' }, { label: '交易笔数', value: stats.total_transactions || 0, color: '#3b82f6' }, { label: '待付账单', value: stats.pending_invoices || 0, color: '#f59e0b' }, ].map((s, i) => (
{s.value}
{s.label}
))}
)} {/* Tabs */}
{([['overview', '概览'], ['rules', '计费规则'], ['packages', '充值套餐'], ['transactions', '交易记录']] as const).map(([k, label]) => ( ))}
{/* Overview */} {tab === 'overview' && stats && (

近7天收入趋势

{stats.daily_revenue_7d && stats.daily_revenue_7d.length > 0 ? (
{stats.daily_revenue_7d.map((d, i) => { const max = Math.max(...stats.daily_revenue_7d.map(r => r.revenue), 1); const h = (d.revenue / max) * 160; return (
¥{d.revenue.toFixed(0)}
{d.date.substring(5)}
); })}
) :
暂无收入数据
}
)} {/* Rules */} {tab === 'rules' && ( {['规则名称', '计费类型', '关联模型', '单价', '免费额度', '状态'].map(h => ( ))} {rules.map(r => ( ))}
{h}
{r.name} {r.billing_type} {r.model_name || '通用'} ¥{r.rate.toFixed(6)} {r.free_quota?.toLocaleString() || 0} {r.status === 'active' ? '启用' : '停用'}
)} {/* Packages */} {tab === 'packages' && (
{packages.map(p => (
{p.is_popular === 1 && (
推荐
)}

{p.name}

{p.description || ''}

¥{p.price}
{p.original_price &&
¥{p.original_price}
}
主额度: {p.tokens.toLocaleString()} tokens
{p.bonus_tokens > 0 &&
赠送: {p.bonus_tokens.toLocaleString()} tokens
}
{p.status === 'active' ? '● 在售' : '○ 已停用'}
))}
)} {/* Transactions */} {tab === 'transactions' && ( <> {loading ?
加载中...
: ( {['时间', '用户', '类型', '金额(元)', 'Tokens', '状态', '描述'].map(h => ( ))} {transactions.map(t => ( ))} {transactions.length === 0 && }
{h}
{t.created_at?.substring(0, 16)} {t.username || t.user_id.substring(0, 8)} {t.transaction_type} {t.amount > 0 ? '+' : ''}{t.amount.toFixed(2)} {t.tokens?.toLocaleString() || '-'} {t.status} {t.description || '-'}
暂无交易记录
)} {total > 20 && (
第 {page} 页
)} )}
); }; export default Billing;