This commit is contained in:
2026-07-21 10:25:30 +08:00
parent fb3b95b687
commit 6283bb8ceb
+25 -21
View File
@@ -12,10 +12,14 @@ import 'dayjs/locale/zh-cn';
dayjs.locale('zh-cn'); dayjs.locale('zh-cn');
const MODULE_LABELS: Record<string, string> = { const MODULE_LABELS: Record<string, string> = {
'chat_ai': 'AI创作', 'ai_creation': 'AI创作',
'hot_opening_replicate': '爆款开头', 'generation_record': '项目生成',
'hot_opening_replicate': '爆款开头复刻',
'shot_replicate': '拆镜复刻', 'shot_replicate': '拆镜复刻',
'project': '项目生成', 'payment': '支付充值',
'admin': '后台管理',
'team': '团队管理',
'unknown': '历史未知',
'other': '其他', 'other': '其他',
}; };
@@ -91,10 +95,7 @@ const AdminDashboard: React.FC = () => {
</div> </div>
<Row gutter={[12, 12]}> <Row gutter={[12, 12]}>
{[ {[
{ title: '用户数量', value: stats?.totalUsers, icon: <UserOutlined />, color: '#6366f1' }, { title: '新增用户数量', value: stats?.totalUsers, icon: <UserOutlined />, color: '#6366f1' },
{ title: '总项目数', value: stats?.totalProjects, icon: <ProjectOutlined />, color: '#3b82f6' },
{ title: '项目记录', value: stats?.totalRecords, icon: <FileTextOutlined />, color: '#10b981' },
{ title: '创作记录', value: stats?.totalGenerations, icon: <PlayCircleOutlined />, color: '#f59e0b' },
{ title: '总收入', value: stats?.totalRevenue, icon: <WalletOutlined />, color: '#ec4899', prefix: '¥' }, { title: '总收入', value: stats?.totalRevenue, icon: <WalletOutlined />, color: '#ec4899', prefix: '¥' },
{ title: '消耗积分', value: stats?.creditsConsumedToday, icon: <DollarOutlined />, color: '#ef4444' }, { title: '消耗积分', value: stats?.creditsConsumedToday, icon: <DollarOutlined />, color: '#ef4444' },
].map(s => ( ].map(s => (
@@ -166,34 +167,37 @@ const CompactStatCard: React.FC<{ title: string; value?: number; icon: React.Rea
</Card> </Card>
); );
// ── 折线图(按日期+模块)── // ── 折线图(按日期+模块,固定展示选中日期往前7天)──
const LineChart: React.FC<{ data: { date: string; module: string; credits: number }[] }> = ({ data }) => { const LineChart: React.FC<{ data: { date: string; module: string; credits: number }[] }> = ({ data }) => {
// 以数据中最新日期为基准,往前推 7 天;不足 7 天按实际天数
if (!data.length) return <EmptyChart />; if (!data.length) return <EmptyChart />;
// 按日期聚合 const sortedDates = Array.from(new Set(data.map(d => d.date))).sort();
const maxDate = sortedDates[sortedDates.length - 1];
// 生成 [maxDate-6, maxDate] 共 7 天
const baseDayjs = dayjs(maxDate);
const sevenDays: string[] = [];
for (let i = 6; i >= 0; i--) sevenDays.push(baseDayjs.subtract(i, 'day').format('YYYY-MM-DD'));
const dateMap = new Map<string, number>(); const dateMap = new Map<string, number>();
const moduleSet = new Set<string>(); data.forEach(d => { dateMap.set(d.date, (dateMap.get(d.date) || 0) + d.credits); });
data.forEach(d => { dateMap.set(d.date, (dateMap.get(d.date) || 0) + d.credits); moduleSet.add(d.module); }); const maxVal = Math.max(...sevenDays.map(d => dateMap.get(d) || 0), 1);
const dates = Array.from(dateMap.keys()).sort();
const maxVal = Math.max(...dateMap.values(), 1);
const maxPoints = 14;
const step = Math.max(1, Math.floor(dates.length / maxPoints));
const sampled = dates.filter((_, i) => i % step === 0 || i === dates.length - 1);
return ( return (
<div style={{ height: 220, display: 'flex', flexDirection: 'column' }}> <div style={{ height: 220, display: 'flex', flexDirection: 'column' }}>
<div style={{ flex: 1, display: 'flex', alignItems: 'flex-end', gap: 2, borderBottom: '1px solid #f1f5f9', paddingBottom: 4 }}> <div style={{ flex: 1, display: 'flex', alignItems: 'flex-end', gap: 4, borderBottom: '1px solid #f1f5f9', paddingBottom: 4 }}>
{sampled.map(d => { {sevenDays.map(d => {
const val = dateMap.get(d) || 0; const val = dateMap.get(d) || 0;
const pct = (val / maxVal) * 100; const pct = (val / maxVal) * 100;
return ( return (
<div key={d} style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', height: '100%', justifyContent: 'flex-end' }}> <div key={d} style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', height: '100%', justifyContent: 'flex-end' }}>
<div style={{ width: '70%', maxWidth: 28, height: `${Math.max(pct, 2)}%`, background: 'linear-gradient(180deg, #6366f1 0%, #8b5cf6 100%)', borderRadius: '3px 3px 0 0', minHeight: 3, transition: 'height 0.3s' }} /> <span style={{ fontSize: 9, color: '#6366f1', fontWeight: 600, marginBottom: 2 }}>{val > 0 ? val.toFixed(0) : ''}</span>
<div style={{ width: '65%', maxWidth: 32, height: `${Math.max(pct, 2)}%`, background: 'linear-gradient(180deg, #6366f1 0%, #8b5cf6 100%)', borderRadius: '3px 3px 0 0', minHeight: 3, transition: 'height 0.3s' }} />
</div> </div>
); );
})} })}
</div> </div>
<div style={{ display: 'flex', gap: 2, marginTop: 4 }}> <div style={{ display: 'flex', gap: 4, marginTop: 4 }}>
{sampled.map(d => ( {sevenDays.map(d => (
<div key={d} style={{ flex: 1, textAlign: 'center' }}> <div key={d} style={{ flex: 1, textAlign: 'center' }}>
<span style={{ fontSize: 9, color: '#94a3b8' }}>{d.slice(5)}</span> <span style={{ fontSize: 9, color: '#94a3b8' }}>{d.slice(5)}</span>
</div> </div>