fix: Billing+Tenant light theme, responsive grids, Market responsive stat cards

This commit is contained in:
qyyfw committed 2026-07-08 09:28:10 +08:00
1 parent 4d9691187a
commit 1f47951895
3 files changed
+1045 -1045

No files matched your search

+41 -41
View File
@@ -57,25 +57,25 @@ const Billing: React.FC = () => {
useEffect(() => { if (tab === 'packages') fetchPackages(); }, [tab, fetchPackages]); useEffect(() => { if (tab === 'packages') fetchPackages(); }, [tab, fetchPackages]);
useEffect(() => { if (tab === 'transactions') fetchTransactions(); }, [tab, fetchTransactions, page]); useEffect(() => { if (tab === 'transactions') fetchTransactions(); }, [tab, fetchTransactions, page]);
const typeColors: Record<string, string> = { per_token: '#6366f1', per_call: '#3b82f6', per_month: '#10b981', hybrid: '#f59e0b' }; const typeColors: Record<string, string> = { per_token: '#6C63FF', per_call: '#3b82f6', per_month: '#10b981', hybrid: '#f59e0b' };
const txColors: Record<string, string> = { recharge: '#10b981', consumption: '#ef4444', refund: '#f59e0b', gift: '#ec4899' }; const txColors: Record<string, string> = { recharge: '#10b981', consumption: '#ef4444', refund: '#f59e0b', gift: '#ec4899' };
return ( return (
<div style={{ padding: '24px', color: '#e2e8f0' }}> <div style={{ padding: '24px', color: 'var(--color-text-primary)' }}>
<h1 style={{ fontSize: '24px', fontWeight: 700, marginBottom: '24px' }}>计费与支付管理</h1> <h1 style={{ fontSize: '24px', fontWeight: 700, marginBottom: '24px' }}>计费与支付管理</h1>
{/* Stats */} {/* Stats */}
{stats && ( {stats && (
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: '16px', marginBottom: '24px' }}> <div className="responsive-grid-4" style={{ gap: '16px', marginBottom: '24px' }}>
{[ {[
{ label: '总收入(元)', value: stats.total_revenue?.toFixed(2) || '0.00', color: '#10b981' }, { 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_consumption?.toFixed(2) || '0.00', color: '#ef4444' },
{ label: '交易笔数', value: stats.total_transactions || 0, color: '#3b82f6' }, { label: '交易笔数', value: stats.total_transactions || 0, color: '#3b82f6' },
{ label: '待付账单', value: stats.pending_invoices || 0, color: '#f59e0b' }, { label: '待付账单', value: stats.pending_invoices || 0, color: '#f59e0b' },
].map((s, i) => ( ].map((s, i) => (
<div key={i} style={{ background: '#1e293b', borderRadius: '12px', padding: '20px', border: `1px solid ${s.color}33` }}> <div key={i} style={{ background: '#FFFFFF', borderRadius: '12px', padding: '20px', border: `1px solid ${s.color}33`, boxShadow: '0 1px 3px rgba(0,0,0,0.04)' }}>
<div style={{ fontSize: '28px', fontWeight: 800, color: s.color }}>{s.value}</div> <div style={{ fontSize: '28px', fontWeight: 800, color: s.color }}>{s.value}</div>
<div style={{ fontSize: '13px', color: '#94a3b8', marginTop: '4px' }}>{s.label}</div> <div style={{ fontSize: '13px', color: '#6B7280', marginTop: '4px' }}>{s.label}</div>
</div> </div>
))} ))}
</div> </div>
@@ -86,14 +86,14 @@ const Billing: React.FC = () => {
{([['overview', '概览'], ['rules', '计费规则'], ['packages', '充值套餐'], ['transactions', '交易记录']] as const).map(([k, label]) => ( {([['overview', '概览'], ['rules', '计费规则'], ['packages', '充值套餐'], ['transactions', '交易记录']] as const).map(([k, label]) => (
<button key={k} onClick={() => setTab(k)} <button key={k} onClick={() => setTab(k)}
style={{ padding: '8px 20px', borderRadius: '8px', border: 'none', cursor: 'pointer', style={{ padding: '8px 20px', borderRadius: '8px', border: 'none', cursor: 'pointer',
background: tab === k ? '#6366f1' : '#1e293b', color: tab === k ? '#fff' : '#94a3b8', fontWeight: 600, fontSize: '14px' }}>{label}</button> background: tab === k ? '#6C63FF' : '#F0F2F8', color: tab === k ? '#fff' : '#6B7280', fontWeight: 600, fontSize: '14px' }}>{label}</button>
))} ))}
</div> </div>
{/* Overview */} {/* Overview */}
{tab === 'overview' && stats && ( {tab === 'overview' && stats && (
<div style={{ background: '#1e293b', borderRadius: '12px', padding: '24px' }}> <div style={{ background: '#FFFFFF', borderRadius: '12px', padding: '24px', border: '1px solid rgba(0,0,0,0.08)', boxShadow: '0 1px 3px rgba(0,0,0,0.04)' }}>
<h3 style={{ fontSize: '16px', fontWeight: 600, marginBottom: '20px' }}>近7天收入趋势</h3> <h3 style={{ fontSize: '16px', fontWeight: 600, marginBottom: '20px', color: 'var(--color-text-primary)' }}>近7天收入趋势</h3>
{stats.daily_revenue_7d && stats.daily_revenue_7d.length > 0 ? ( {stats.daily_revenue_7d && stats.daily_revenue_7d.length > 0 ? (
<div style={{ display: 'flex', alignItems: 'flex-end', gap: '12px', height: '200px', padding: '0 20px' }}> <div style={{ display: 'flex', alignItems: 'flex-end', gap: '12px', height: '200px', padding: '0 20px' }}>
{stats.daily_revenue_7d.map((d, i) => { {stats.daily_revenue_7d.map((d, i) => {
@@ -101,37 +101,37 @@ const Billing: React.FC = () => {
const h = (d.revenue / max) * 160; const h = (d.revenue / max) * 160;
return ( return (
<div key={i} style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center' }}> <div key={i} style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
<div style={{ fontSize: '11px', color: '#94a3b8', marginBottom: '4px' }}>¥{d.revenue.toFixed(0)}</div> <div style={{ fontSize: '11px', color: '#6B7280', marginBottom: '4px' }}>¥{d.revenue.toFixed(0)}</div>
<div style={{ width: '100%', height: `${h}px`, background: 'linear-gradient(180deg, #6366f1, #818cf8)', borderRadius: '4px 4px 0 0' }} /> <div style={{ width: '100%', height: `${h}px`, background: 'linear-gradient(180deg, #6C63FF, #818cf8)', borderRadius: '4px 4px 0 0' }} />
<div style={{ fontSize: '11px', color: '#64748b', marginTop: '6px' }}>{d.date.substring(5)}</div> <div style={{ fontSize: '11px', color: '#9CA3AF', marginTop: '6px' }}>{d.date.substring(5)}</div>
</div> </div>
); );
})} })}
</div> </div>
) : <div style={{ textAlign: 'center', padding: '40px', color: '#64748b' }}>暂无收入数据</div>} ) : <div style={{ textAlign: 'center', padding: '40px', color: '#9CA3AF' }}>暂无收入数据</div>}
</div> </div>
)} )}
{/* Rules */} {/* Rules */}
{tab === 'rules' && ( {tab === 'rules' && (
<table style={{ width: '100%', borderCollapse: 'collapse', background: '#1e293b', borderRadius: '12px', overflow: 'hidden' }}> <table style={{ width: '100%', borderCollapse: 'collapse', background: '#FFFFFF', borderRadius: '12px', overflow: 'hidden', border: '1px solid rgba(0,0,0,0.08)' }}>
<thead><tr style={{ background: '#0f172a' }}> <thead><tr style={{ background: '#F8F9FC' }}>
{['规则名称', '计费类型', '关联模型', '单价', '免费额度', '状态'].map(h => ( {['规则名称', '计费类型', '关联模型', '单价', '免费额度', '状态'].map(h => (
<th key={h} style={{ padding: '12px 16px', textAlign: 'left', fontSize: '13px', color: '#94a3b8', fontWeight: 600 }}>{h}</th> <th key={h} style={{ padding: '12px 16px', textAlign: 'left', fontSize: '13px', color: '#6B7280', fontWeight: 600 }}>{h}</th>
))} ))}
</tr></thead> </tr></thead>
<tbody> <tbody>
{rules.map(r => ( {rules.map(r => (
<tr key={r.id} style={{ borderBottom: '1px solid #334155' }}> <tr key={r.id} style={{ borderBottom: '1px solid rgba(0,0,0,0.08)' }}>
<td style={{ padding: '12px 16px', fontSize: '14px', fontWeight: 600 }}>{r.name}</td> <td style={{ padding: '12px 16px', fontSize: '14px', fontWeight: 600 }}>{r.name}</td>
<td style={{ padding: '12px 16px' }}> <td style={{ padding: '12px 16px' }}>
<span style={{ padding: '2px 10px', borderRadius: '12px', fontSize: '12px', fontWeight: 600, background: `${typeColors[r.billing_type]}22`, color: typeColors[r.billing_type] }}>{r.billing_type}</span> <span style={{ padding: '2px 10px', borderRadius: '12px', fontSize: '12px', fontWeight: 600, background: `${typeColors[r.billing_type]}22`, color: typeColors[r.billing_type] }}>{r.billing_type}</span>
</td> </td>
<td style={{ padding: '12px 16px', fontSize: '13px', color: '#94a3b8' }}>{r.model_name || '通用'}</td> <td style={{ padding: '12px 16px', fontSize: '13px', color: '#6B7280' }}>{r.model_name || '通用'}</td>
<td style={{ padding: '12px 16px', fontSize: '14px' }}>¥{r.rate.toFixed(6)}</td> <td style={{ padding: '12px 16px', fontSize: '14px' }}>¥{r.rate.toFixed(6)}</td>
<td style={{ padding: '12px 16px', fontSize: '13px', color: '#94a3b8' }}>{r.free_quota?.toLocaleString() || 0}</td> <td style={{ padding: '12px 16px', fontSize: '13px', color: '#6B7280' }}>{r.free_quota?.toLocaleString() || 0}</td>
<td style={{ padding: '12px 16px' }}> <td style={{ padding: '12px 16px' }}>
<span style={{ color: r.status === 'active' ? '#10b981' : '#64748b', fontSize: '13px' }}>{r.status === 'active' ? '启用' : '停用'}</span> <span style={{ color: r.status === 'active' ? '#10b981' : '#9CA3AF', fontSize: '13px' }}>{r.status === 'active' ? '启用' : '停用'}</span>
</td> </td>
</tr> </tr>
))} ))}
@@ -141,22 +141,22 @@ const Billing: React.FC = () => {
{/* Packages */} {/* Packages */}
{tab === 'packages' && ( {tab === 'packages' && (
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: '16px' }}> <div className="responsive-grid-4" style={{ gap: '16px' }}>
{packages.map(p => ( {packages.map(p => (
<div key={p.id} style={{ background: '#1e293b', borderRadius: '16px', padding: '24px', position: 'relative', border: p.is_popular ? '2px solid #6366f1' : '1px solid #334155' }}> <div key={p.id} style={{ background: '#FFFFFF', borderRadius: '16px', padding: '24px', position: 'relative', border: p.is_popular ? '2px solid #6C63FF' : '1px solid rgba(0,0,0,0.08)', boxShadow: '0 1px 3px rgba(0,0,0,0.04)' }}>
{p.is_popular === 1 && ( {p.is_popular === 1 && (
<div style={{ position: 'absolute', top: '-10px', right: '16px', background: '#6366f1', color: '#fff', padding: '2px 12px', borderRadius: '12px', fontSize: '11px', fontWeight: 700 }}>推荐</div> <div style={{ position: 'absolute', top: '-10px', right: '16px', background: '#6C63FF', color: '#fff', padding: '2px 12px', borderRadius: '12px', fontSize: '11px', fontWeight: 700 }}>推荐</div>
)} )}
<h3 style={{ fontSize: '18px', fontWeight: 700, marginBottom: '8px' }}>{p.name}</h3> <h3 style={{ fontSize: '18px', fontWeight: 700, marginBottom: '8px' }}>{p.name}</h3>
<p style={{ fontSize: '13px', color: '#94a3b8', marginBottom: '16px' }}>{p.description || ''}</p> <p style={{ fontSize: '13px', color: '#6B7280', marginBottom: '16px' }}>{p.description || ''}</p>
<div style={{ fontSize: '32px', fontWeight: 800, color: '#6366f1' }}>¥{p.price}</div> <div style={{ fontSize: '32px', fontWeight: 800, color: '#6C63FF' }}>¥{p.price}</div>
{p.original_price && <div style={{ fontSize: '14px', color: '#64748b', textDecoration: 'line-through' }}>¥{p.original_price}</div>} {p.original_price && <div style={{ fontSize: '14px', color: '#9CA3AF', textDecoration: 'line-through' }}>¥{p.original_price}</div>}
<div style={{ marginTop: '16px', paddingTop: '16px', borderTop: '1px solid #334155', fontSize: '13px', color: '#94a3b8' }}> <div style={{ marginTop: '16px', paddingTop: '16px', borderTop: '1px solid rgba(0,0,0,0.08)', fontSize: '13px', color: '#6B7280' }}>
<div>主额度: <span style={{ color: '#e2e8f0', fontWeight: 600 }}>{p.tokens.toLocaleString()}</span> tokens</div> <div>主额度: <span style={{ color: '#1A1F36', fontWeight: 600 }}>{p.tokens.toLocaleString()}</span> tokens</div>
{p.bonus_tokens > 0 && <div>赠送: <span style={{ color: '#10b981', fontWeight: 600 }}>{p.bonus_tokens.toLocaleString()}</span> tokens</div>} {p.bonus_tokens > 0 && <div>赠送: <span style={{ color: '#10b981', fontWeight: 600 }}>{p.bonus_tokens.toLocaleString()}</span> tokens</div>}
</div> </div>
<div style={{ marginTop: '12px', fontSize: '12px' }}> <div style={{ marginTop: '12px', fontSize: '12px' }}>
<span style={{ color: p.status === 'active' ? '#10b981' : '#64748b' }}>{p.status === 'active' ? '● 在售' : '○ 已停用'}</span> <span style={{ color: p.status === 'active' ? '#10b981' : '#9CA3AF' }}>{p.status === 'active' ? '● 在售' : '○ 已停用'}</span>
</div> </div>
</div> </div>
))} ))}
@@ -166,38 +166,38 @@ const Billing: React.FC = () => {
{/* Transactions */} {/* Transactions */}
{tab === 'transactions' && ( {tab === 'transactions' && (
<> <>
{loading ? <div style={{ textAlign: 'center', padding: '40px', color: '#64748b' }}>加载中...</div> : ( {loading ? <div style={{ textAlign: 'center', padding: '40px', color: '#9CA3AF' }}>加载中...</div> : (
<table style={{ width: '100%', borderCollapse: 'collapse', background: '#1e293b', borderRadius: '12px', overflow: 'hidden' }}> <table style={{ width: '100%', borderCollapse: 'collapse', background: '#FFFFFF', borderRadius: '12px', overflow: 'hidden', border: '1px solid rgba(0,0,0,0.08)' }}>
<thead><tr style={{ background: '#0f172a' }}> <thead><tr style={{ background: '#F8F9FC' }}>
{['时间', '用户', '类型', '金额(元)', 'Tokens', '状态', '描述'].map(h => ( {['时间', '用户', '类型', '金额(元)', 'Tokens', '状态', '描述'].map(h => (
<th key={h} style={{ padding: '12px 16px', textAlign: 'left', fontSize: '13px', color: '#94a3b8', fontWeight: 600 }}>{h}</th> <th key={h} style={{ padding: '12px 16px', textAlign: 'left', fontSize: '13px', color: '#6B7280', fontWeight: 600 }}>{h}</th>
))} ))}
</tr></thead> </tr></thead>
<tbody> <tbody>
{transactions.map(t => ( {transactions.map(t => (
<tr key={t.id} style={{ borderBottom: '1px solid #334155' }}> <tr key={t.id} style={{ borderBottom: '1px solid rgba(0,0,0,0.08)' }}>
<td style={{ padding: '12px 16px', fontSize: '13px', color: '#64748b' }}>{t.created_at?.substring(0, 16)}</td> <td style={{ padding: '12px 16px', fontSize: '13px', color: '#9CA3AF' }}>{t.created_at?.substring(0, 16)}</td>
<td style={{ padding: '12px 16px', fontSize: '14px' }}>{t.username || t.user_id.substring(0, 8)}</td> <td style={{ padding: '12px 16px', fontSize: '14px' }}>{t.username || t.user_id.substring(0, 8)}</td>
<td style={{ padding: '12px 16px' }}> <td style={{ padding: '12px 16px' }}>
<span style={{ padding: '2px 10px', borderRadius: '12px', fontSize: '12px', fontWeight: 600, background: `${txColors[t.transaction_type]}22`, color: txColors[t.transaction_type] }}>{t.transaction_type}</span> <span style={{ padding: '2px 10px', borderRadius: '12px', fontSize: '12px', fontWeight: 600, background: `${txColors[t.transaction_type]}22`, color: txColors[t.transaction_type] }}>{t.transaction_type}</span>
</td> </td>
<td style={{ padding: '12px 16px', fontSize: '14px', fontWeight: 600, color: t.transaction_type === 'recharge' ? '#10b981' : '#ef4444' }}>{t.amount > 0 ? '+' : ''}{t.amount.toFixed(2)}</td> <td style={{ padding: '12px 16px', fontSize: '14px', fontWeight: 600, color: t.transaction_type === 'recharge' ? '#10b981' : '#ef4444' }}>{t.amount > 0 ? '+' : ''}{t.amount.toFixed(2)}</td>
<td style={{ padding: '12px 16px', fontSize: '13px', color: '#94a3b8' }}>{t.tokens?.toLocaleString() || '-'}</td> <td style={{ padding: '12px 16px', fontSize: '13px', color: '#6B7280' }}>{t.tokens?.toLocaleString() || '-'}</td>
<td style={{ padding: '12px 16px' }}> <td style={{ padding: '12px 16px' }}>
<span style={{ color: t.status === 'completed' ? '#10b981' : '#f59e0b', fontSize: '13px' }}>{t.status}</span> <span style={{ color: t.status === 'completed' ? '#10b981' : '#f59e0b', fontSize: '13px' }}>{t.status}</span>
</td> </td>
<td style={{ padding: '12px 16px', fontSize: '13px', color: '#64748b' }}>{t.description || '-'}</td> <td style={{ padding: '12px 16px', fontSize: '13px', color: '#9CA3AF' }}>{t.description || '-'}</td>
</tr> </tr>
))} ))}
{transactions.length === 0 && <tr><td colSpan={7} style={{ padding: '40px', textAlign: 'center', color: '#64748b' }}>暂无交易记录</td></tr>} {transactions.length === 0 && <tr><td colSpan={7} style={{ padding: '40px', textAlign: 'center', color: '#9CA3AF' }}>暂无交易记录</td></tr>}
</tbody> </tbody>
</table> </table>
)} )}
{total > 20 && ( {total > 20 && (
<div style={{ display: 'flex', justifyContent: 'center', gap: '8px', marginTop: '20px' }}> <div className="pagination-bar" style={{ marginTop: '20px' }}>
<button onClick={() => setPage(p => Math.max(1, p - 1))} disabled={page <= 1} style={{ padding: '6px 16px', borderRadius: '6px', border: '1px solid #475569', background: '#1e293b', color: '#94a3b8', cursor: page <= 1 ? 'not-allowed' : 'pointer' }}>上一页</button> <button onClick={() => setPage(p => Math.max(1, p - 1))} disabled={page <= 1} style={{ padding: '6px 16px', borderRadius: '6px', border: '1px solid rgba(0,0,0,0.12)', background: '#FFFFFF', color: '#6B7280', cursor: page <= 1 ? 'not-allowed' : 'pointer' }}>上一页</button>
<span style={{ padding: '6px 16px', color: '#94a3b8' }}>第 {page} 页</span> <span style={{ padding: '6px 16px', color: '#6B7280' }}>第 {page} 页</span>
<button onClick={() => setPage(p => p + 1)} disabled={page * 20 >= total} style={{ padding: '6px 16px', borderRadius: '6px', border: '1px solid #475569', background: '#1e293b', color: '#94a3b8', cursor: page * 20 >= total ? 'not-allowed' : 'pointer' }}>下一页</button> <button onClick={() => setPage(p => p + 1)} disabled={page * 20 >= total} style={{ padding: '6px 16px', borderRadius: '6px', border: '1px solid rgba(0,0,0,0.12)', background: '#FFFFFF', color: '#6B7280', cursor: page * 20 >= total ? 'not-allowed' : 'pointer' }}>下一页</button>
</div> </div>
)} )}
</> </>
File diff suppressed because it is too large. Load diff
+41 -41
View File
@@ -62,25 +62,25 @@ const Tenant: React.FC = () => {
}; };
const statusColors: Record<string, string> = { active: '#10b981', suspended: '#f59e0b', terminated: '#ef4444' }; const statusColors: Record<string, string> = { active: '#10b981', suspended: '#f59e0b', terminated: '#ef4444' };
const roleColors: Record<string, string> = { owner: '#a78bfa', admin: '#60a5fa', member: '#94a3b8', viewer: '#64748b' }; const roleColors: Record<string, string> = { owner: '#a78bfa', admin: '#60a5fa', member: '#6B7280', viewer: '#9CA3AF' };
return ( return (
<div style={{ padding: '24px', color: '#e2e8f0' }}> <div style={{ padding: '24px', color: 'var(--color-text-primary)' }}>
<h1 style={{ fontSize: '24px', fontWeight: 700, marginBottom: '24px' }}>多租户管理</h1> <h1 style={{ fontSize: '24px', fontWeight: 700, marginBottom: '24px' }}>多租户管理</h1>
{/* Stats cards */} {/* Stats cards */}
{stats && ( {stats && (
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(5, 1fr)', gap: '16px', marginBottom: '24px' }}> <div className="responsive-grid-auto" style={{ gap: '16px', marginBottom: '24px' }}>
{[ {[
{ label: '总租户数', value: stats.total_tenants, color: '#6366f1' }, { label: '总租户数', value: stats.total_tenants, color: '#6C63FF' },
{ label: '活跃租户', value: stats.active_tenants, color: '#10b981' }, { label: '活跃租户', value: stats.active_tenants, color: '#10b981' },
{ label: '已暂停', value: stats.suspended_tenants, color: '#f59e0b' }, { label: '已暂停', value: stats.suspended_tenants, color: '#f59e0b' },
{ label: '总成员数', value: stats.total_members, color: '#3b82f6' }, { label: '总成员数', value: stats.total_members, color: '#3b82f6' },
{ label: '待处理邀请', value: stats.pending_invitations, color: '#ec4899' }, { label: '待处理邀请', value: stats.pending_invitations, color: '#ec4899' },
].map((s, i) => ( ].map((s, i) => (
<div key={i} style={{ background: '#1e293b', borderRadius: '12px', padding: '20px', border: `1px solid ${s.color}33` }}> <div key={i} style={{ background: '#FFFFFF', borderRadius: '12px', padding: '20px', border: `1px solid ${s.color}33`, boxShadow: '0 1px 3px rgba(0,0,0,0.04)' }}>
<div style={{ fontSize: '32px', fontWeight: 800, color: s.color }}>{s.value}</div> <div style={{ fontSize: '32px', fontWeight: 800, color: s.color }}>{s.value}</div>
<div style={{ fontSize: '13px', color: '#94a3b8', marginTop: '4px' }}>{s.label}</div> <div style={{ fontSize: '13px', color: '#6B7280', marginTop: '4px' }}>{s.label}</div>
</div> </div>
))} ))}
</div> </div>
@@ -91,12 +91,12 @@ const Tenant: React.FC = () => {
{([['list', '租户列表'], ['members', '成员管理'], ['stats', '统计详情']] as const).map(([k, label]) => ( {([['list', '租户列表'], ['members', '成员管理'], ['stats', '统计详情']] as const).map(([k, label]) => (
<button key={k} onClick={() => setTab(k)} <button key={k} onClick={() => setTab(k)}
style={{ padding: '8px 20px', borderRadius: '8px', border: 'none', cursor: 'pointer', style={{ padding: '8px 20px', borderRadius: '8px', border: 'none', cursor: 'pointer',
background: tab === k ? '#6366f1' : '#1e293b', color: tab === k ? '#fff' : '#94a3b8', background: tab === k ? '#6C63FF' : '#F0F2F8', color: tab === k ? '#fff' : '#6B7280',
fontWeight: 600, fontSize: '14px' }}>{label}</button> fontWeight: 600, fontSize: '14px' }}>{label}</button>
))} ))}
{tab === 'list' && ( {tab === 'list' && (
<button onClick={() => setShowCreate(true)} style={{ marginLeft: 'auto', padding: '8px 20px', borderRadius: '8px', <button onClick={() => setShowCreate(true)} style={{ marginLeft: 'auto', padding: '8px 20px', borderRadius: '8px',
border: 'none', cursor: 'pointer', background: '#6366f1', color: '#fff', fontWeight: 600, fontSize: '14px' }}> border: 'none', cursor: 'pointer', background: '#6C63FF', color: '#fff', fontWeight: 600, fontSize: '14px' }}>
+ 创建租户 + 创建租户
</button> </button>
)} )}
@@ -105,32 +105,32 @@ const Tenant: React.FC = () => {
{/* Tab content */} {/* Tab content */}
{tab === 'list' && ( {tab === 'list' && (
<div> <div>
{loading ? <div style={{ textAlign: 'center', padding: '40px', color: '#64748b' }}>加载中...</div> : ( {loading ? <div style={{ textAlign: 'center', padding: '40px', color: '#9CA3AF' }}>加载中...</div> : (
<table style={{ width: '100%', borderCollapse: 'collapse', background: '#1e293b', borderRadius: '12px', overflow: 'hidden' }}> <table style={{ width: '100%', borderCollapse: 'collapse', background: '#FFFFFF', borderRadius: '12px', overflow: 'hidden', border: '1px solid rgba(0,0,0,0.08)' }}>
<thead> <thead>
<tr style={{ background: '#0f172a' }}> <tr style={{ background: '#F8F9FC' }}>
{['租户名称', '标识', '状态', '成员数', '存储上限', 'API/天', '创建时间', '操作'].map(h => ( {['租户名称', '标识', '状态', '成员数', '存储上限', 'API/天', '创建时间', '操作'].map(h => (
<th key={h} style={{ padding: '12px 16px', textAlign: 'left', fontSize: '13px', color: '#94a3b8', fontWeight: 600 }}>{h}</th> <th key={h} style={{ padding: '12px 16px', textAlign: 'left', fontSize: '13px', color: '#6B7280', fontWeight: 600 }}>{h}</th>
))} ))}
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{tenants.map(t => ( {tenants.map(t => (
<tr key={t.id} style={{ borderBottom: '1px solid #334155' }}> <tr key={t.id} style={{ borderBottom: '1px solid rgba(0,0,0,0.08)' }}>
<td style={{ padding: '12px 16px', fontSize: '14px', fontWeight: 600 }}>{t.name}</td> <td style={{ padding: '12px 16px', fontSize: '14px', fontWeight: 600 }}>{t.name}</td>
<td style={{ padding: '12px 16px', fontSize: '13px', color: '#94a3b8' }}>{t.slug}</td> <td style={{ padding: '12px 16px', fontSize: '13px', color: '#6B7280' }}>{t.slug}</td>
<td style={{ padding: '12px 16px' }}> <td style={{ padding: '12px 16px' }}>
<span style={{ padding: '2px 10px', borderRadius: '12px', fontSize: '12px', fontWeight: 600, <span style={{ padding: '2px 10px', borderRadius: '12px', fontSize: '12px', fontWeight: 600,
background: `${statusColors[t.status]}22`, color: statusColors[t.status] }}>{t.status}</span> background: `${statusColors[t.status]}22`, color: statusColors[t.status] }}>{t.status}</span>
</td> </td>
<td style={{ padding: '12px 16px', fontSize: '14px' }}>{t.member_count ?? '-'}</td> <td style={{ padding: '12px 16px', fontSize: '14px' }}>{t.member_count ?? '-'}</td>
<td style={{ padding: '12px 16px', fontSize: '13px', color: '#94a3b8' }}>{(t.max_storage_mb / 1024).toFixed(1)} GB</td> <td style={{ padding: '12px 16px', fontSize: '13px', color: '#6B7280' }}>{(t.max_storage_mb / 1024).toFixed(1)} GB</td>
<td style={{ padding: '12px 16px', fontSize: '13px', color: '#94a3b8' }}>{t.max_api_calls_per_day.toLocaleString()}</td> <td style={{ padding: '12px 16px', fontSize: '13px', color: '#6B7280' }}>{t.max_api_calls_per_day.toLocaleString()}</td>
<td style={{ padding: '12px 16px', fontSize: '13px', color: '#64748b' }}>{t.created_at?.substring(0, 10)}</td> <td style={{ padding: '12px 16px', fontSize: '13px', color: '#9CA3AF' }}>{t.created_at?.substring(0, 10)}</td>
<td style={{ padding: '12px 16px' }}> <td style={{ padding: '12px 16px' }}>
<button onClick={() => { setSelectedTenant(t.id); setTab('members'); }} <button onClick={() => { setSelectedTenant(t.id); setTab('members'); }}
style={{ padding: '4px 12px', borderRadius: '6px', border: '1px solid #475569', style={{ padding: '4px 12px', borderRadius: '6px', border: '1px solid rgba(0,0,0,0.12)',
background: 'transparent', color: '#94a3b8', cursor: 'pointer', fontSize: '12px' }}>管理</button> background: 'transparent', color: '#6B7280', cursor: 'pointer', fontSize: '12px' }}>管理</button>
</td> </td>
</tr> </tr>
))} ))}
@@ -138,12 +138,12 @@ const Tenant: React.FC = () => {
</table> </table>
)} )}
{total > 20 && ( {total > 20 && (
<div style={{ display: 'flex', justifyContent: 'center', gap: '8px', marginTop: '20px' }}> <div className="pagination-bar" style={{ marginTop: '20px' }}>
<button onClick={() => setPage(p => Math.max(1, p - 1))} disabled={page <= 1} <button onClick={() => setPage(p => Math.max(1, p - 1))} disabled={page <= 1}
style={{ padding: '6px 16px', borderRadius: '6px', border: '1px solid #475569', background: '#1e293b', color: '#94a3b8', cursor: page <= 1 ? 'not-allowed' : 'pointer' }}>上一页</button> style={{ padding: '6px 16px', borderRadius: '6px', border: '1px solid rgba(0,0,0,0.12)', background: '#FFFFFF', color: '#6B7280', cursor: page <= 1 ? 'not-allowed' : 'pointer' }}>上一页</button>
<span style={{ padding: '6px 16px', color: '#94a3b8' }}>第 {page} 页</span> <span style={{ padding: '6px 16px', color: '#6B7280' }}>第 {page} 页</span>
<button onClick={() => setPage(p => p + 1)} disabled={page * 20 >= total} <button onClick={() => setPage(p => p + 1)} disabled={page * 20 >= total}
style={{ padding: '6px 16px', borderRadius: '6px', border: '1px solid #475569', background: '#1e293b', color: '#94a3b8', cursor: page * 20 >= total ? 'not-allowed' : 'pointer' }}>下一页</button> style={{ padding: '6px 16px', borderRadius: '6px', border: '1px solid rgba(0,0,0,0.12)', background: '#FFFFFF', color: '#6B7280', cursor: page * 20 >= total ? 'not-allowed' : 'pointer' }}>下一页</button>
</div> </div>
)} )}
</div> </div>
@@ -153,25 +153,25 @@ const Tenant: React.FC = () => {
<div> <div>
<div style={{ marginBottom: '16px' }}> <div style={{ marginBottom: '16px' }}>
<select value={selectedTenant} onChange={e => setSelectedTenant(e.target.value)} <select value={selectedTenant} onChange={e => setSelectedTenant(e.target.value)}
style={{ padding: '8px 12px', borderRadius: '8px', background: '#1e293b', color: '#e2e8f0', border: '1px solid #475569', fontSize: '14px' }}> style={{ padding: '8px 12px', borderRadius: '8px', background: '#FFFFFF', color: '#1A1F36', border: '1px solid rgba(0,0,0,0.12)', fontSize: '14px' }}>
<option value="">选择租户...</option> <option value="">选择租户...</option>
{tenants.map(t => <option key={t.id} value={t.id}>{t.name}</option>)} {tenants.map(t => <option key={t.id} value={t.id}>{t.name}</option>)}
</select> </select>
</div> </div>
{selectedTenant && ( {selectedTenant && (
<table style={{ width: '100%', borderCollapse: 'collapse', background: '#1e293b', borderRadius: '12px', overflow: 'hidden' }}> <table style={{ width: '100%', borderCollapse: 'collapse', background: '#FFFFFF', borderRadius: '12px', overflow: 'hidden', border: '1px solid rgba(0,0,0,0.08)' }}>
<thead> <thead>
<tr style={{ background: '#0f172a' }}> <tr style={{ background: '#F8F9FC' }}>
{['用户名', '邮箱', '角色', '状态', '加入时间'].map(h => ( {['用户名', '邮箱', '角色', '状态', '加入时间'].map(h => (
<th key={h} style={{ padding: '12px 16px', textAlign: 'left', fontSize: '13px', color: '#94a3b8', fontWeight: 600 }}>{h}</th> <th key={h} style={{ padding: '12px 16px', textAlign: 'left', fontSize: '13px', color: '#6B7280', fontWeight: 600 }}>{h}</th>
))} ))}
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{members.map(m => ( {members.map(m => (
<tr key={m.id} style={{ borderBottom: '1px solid #334155' }}> <tr key={m.id} style={{ borderBottom: '1px solid rgba(0,0,0,0.08)' }}>
<td style={{ padding: '12px 16px', fontSize: '14px', fontWeight: 600 }}>{m.username || m.user_id.substring(0, 8)}</td> <td style={{ padding: '12px 16px', fontSize: '14px', fontWeight: 600 }}>{m.username || m.user_id.substring(0, 8)}</td>
<td style={{ padding: '12px 16px', fontSize: '13px', color: '#94a3b8' }}>{m.email || '-'}</td> <td style={{ padding: '12px 16px', fontSize: '13px', color: '#6B7280' }}>{m.email || '-'}</td>
<td style={{ padding: '12px 16px' }}> <td style={{ padding: '12px 16px' }}>
<span style={{ padding: '2px 10px', borderRadius: '12px', fontSize: '12px', fontWeight: 600, <span style={{ padding: '2px 10px', borderRadius: '12px', fontSize: '12px', fontWeight: 600,
background: `${roleColors[m.role]}22`, color: roleColors[m.role] }}>{m.role}</span> background: `${roleColors[m.role]}22`, color: roleColors[m.role] }}>{m.role}</span>
@@ -179,7 +179,7 @@ const Tenant: React.FC = () => {
<td style={{ padding: '12px 16px' }}> <td style={{ padding: '12px 16px' }}>
<span style={{ color: m.status === 'active' ? '#10b981' : '#ef4444', fontSize: '13px' }}>{m.status}</span> <span style={{ color: m.status === 'active' ? '#10b981' : '#ef4444', fontSize: '13px' }}>{m.status}</span>
</td> </td>
<td style={{ padding: '12px 16px', fontSize: '13px', color: '#64748b' }}>{m.joined_at?.substring(0, 16)}</td> <td style={{ padding: '12px 16px', fontSize: '13px', color: '#9CA3AF' }}>{m.joined_at?.substring(0, 16)}</td>
</tr> </tr>
))} ))}
</tbody> </tbody>
@@ -189,12 +189,12 @@ const Tenant: React.FC = () => {
)} )}
{tab === 'stats' && stats && ( {tab === 'stats' && stats && (
<div style={{ background: '#1e293b', borderRadius: '12px', padding: '32px' }}> <div style={{ background: '#FFFFFF', borderRadius: '12px', padding: '32px', border: '1px solid rgba(0,0,0,0.08)', boxShadow: '0 1px 3px rgba(0,0,0,0.04)' }}>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: '24px' }}> <div className="responsive-grid-2" style={{ gap: '24px' }}>
{Object.entries(stats).map(([k, v]) => ( {Object.entries(stats).map(([k, v]) => (
<div key={k} style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '16px', background: '#0f172a', borderRadius: '8px' }}> <div key={k} style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '16px', background: '#F8F9FC', borderRadius: '8px' }}>
<span style={{ fontSize: '14px', color: '#94a3b8' }}>{k.replace(/_/g, ' ').toUpperCase()}</span> <span style={{ fontSize: '14px', color: '#6B7280' }}>{k.replace(/_/g, ' ').toUpperCase()}</span>
<span style={{ fontSize: '24px', fontWeight: 800, color: '#6366f1' }}>{String(v)}</span> <span style={{ fontSize: '24px', fontWeight: 800, color: '#6C63FF' }}>{String(v)}</span>
</div> </div>
))} ))}
</div> </div>
@@ -205,8 +205,8 @@ const Tenant: React.FC = () => {
{showCreate && ( {showCreate && (
<div style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.6)', display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 1000 }} <div style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.6)', display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 1000 }}
onClick={() => setShowCreate(false)}> onClick={() => setShowCreate(false)}>
<div onClick={e => e.stopPropagation()} style={{ background: '#1e293b', borderRadius: '16px', padding: '32px', width: '480px', maxWidth: '90vw' }}> <div onClick={e => e.stopPropagation()} style={{ background: '#FFFFFF', borderRadius: '16px', padding: '32px', width: '480px', maxWidth: '90vw', boxShadow: '0 4px 24px rgba(0,0,0,0.12)' }}>
<h2 style={{ fontSize: '20px', fontWeight: 700, marginBottom: '24px' }}>创建租户</h2> <h2 style={{ fontSize: '20px', fontWeight: 700, marginBottom: '24px', color: '#1A1F36' }}>创建租户</h2>
{[ {[
{ k: 'name', label: '租户名称', type: 'text' }, { k: 'name', label: '租户名称', type: 'text' },
{ k: 'slug', label: '标识(英文)', type: 'text' }, { k: 'slug', label: '标识(英文)', type: 'text' },
@@ -216,14 +216,14 @@ const Tenant: React.FC = () => {
{ k: 'max_api_calls_per_day', label: 'API调用/天', type: 'number' }, { k: 'max_api_calls_per_day', label: 'API调用/天', type: 'number' },
].map(f => ( ].map(f => (
<div key={f.k} style={{ marginBottom: '16px' }}> <div key={f.k} style={{ marginBottom: '16px' }}>
<label style={{ display: 'block', fontSize: '13px', color: '#94a3b8', marginBottom: '6px' }}>{f.label}</label> <label style={{ display: 'block', fontSize: '13px', color: '#6B7280', marginBottom: '6px' }}>{f.label}</label>
<input type={f.type} value={(form as any)[f.k]} onChange={e => setForm({ ...form, [f.k]: f.type === 'number' ? Number(e.target.value) : e.target.value })} <input type={f.type} value={(form as any)[f.k]} onChange={e => setForm({ ...form, [f.k]: f.type === 'number' ? Number(e.target.value) : e.target.value })}
style={{ width: '100%', padding: '10px 12px', borderRadius: '8px', background: '#0f172a', color: '#e2e8f0', border: '1px solid #475569', fontSize: '14px', boxSizing: 'border-box' }} /> style={{ width: '100%', padding: '10px 12px', borderRadius: '8px', background: '#F8F9FC', color: '#1A1F36', border: '1px solid rgba(0,0,0,0.12)', fontSize: '14px', boxSizing: 'border-box' }} />
</div> </div>
))} ))}
<div style={{ display: 'flex', gap: '12px', marginTop: '24px' }}> <div style={{ display: 'flex', gap: '12px', marginTop: '24px' }}>
<button onClick={() => setShowCreate(false)} style={{ flex: 1, padding: '10px', borderRadius: '8px', border: '1px solid #475569', background: 'transparent', color: '#94a3b8', cursor: 'pointer', fontSize: '14px' }}>取消</button> <button onClick={() => setShowCreate(false)} style={{ flex: 1, padding: '10px', borderRadius: '8px', border: '1px solid rgba(0,0,0,0.12)', background: 'transparent', color: '#6B7280', cursor: 'pointer', fontSize: '14px' }}>取消</button>
<button onClick={handleCreate} style={{ flex: 1, padding: '10px', borderRadius: '8px', border: 'none', background: '#6366f1', color: '#fff', cursor: 'pointer', fontSize: '14px', fontWeight: 600 }}>创建</button> <button onClick={handleCreate} style={{ flex: 1, padding: '10px', borderRadius: '8px', border: 'none', background: '#6C63FF', color: '#fff', cursor: 'pointer', fontSize: '14px', fontWeight: 600 }}>创建</button>
</div> </div>
</div> </div>
</div> </div>