1
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user