Files
live-hub-py/web/frontend/src/pages/DashboardPage.tsx
T
2026-07-05 12:25:18 +08:00

193 lines
6.5 KiB
TypeScript

import { Card, Col, Row, Space, Statistic, Tag, Typography, theme } from 'antd';
import { useEffect, useMemo, useState } from 'react';
import {
accountApi,
cookieApi,
huyaApi,
loginApi,
type HuyaTaskItem,
type LoginTaskItem,
} from '../api/modules';
import { usePermissions } from '../hooks/usePermissions';
const { Title, Text } = Typography;
interface PlatformStats {
accounts: number;
tasks: number;
success: number;
failed: number;
}
interface DashboardStats {
douyu: PlatformStats & {
cookies: number;
};
huya: PlatformStats & {
goods: number;
rechargeGoods: number;
};
}
const EMPTY_STATS: DashboardStats = {
douyu: {
accounts: 0,
tasks: 0,
success: 0,
failed: 0,
cookies: 0,
},
huya: {
accounts: 0,
tasks: 0,
success: 0,
failed: 0,
goods: 0,
rechargeGoods: 0,
},
};
function countFailed<T extends { status: string }>(items: T[]) {
return items.filter((item) => ['failed', 'error'].includes(item.status)).length;
}
function StatCard({
title,
value,
color,
loading,
}: {
title: string;
value: number;
color?: string;
loading?: boolean;
}) {
return (
<Col xs={24} sm={12} lg={6}>
<Card size="small" loading={loading}>
<Statistic title={title} value={value} styles={{ content: color ? { color } : undefined }} />
</Card>
</Col>
);
}
export default function DashboardPage() {
const { token } = theme.useToken();
const { can, canAny } = usePermissions();
const [loading, setLoading] = useState(true);
const [stats, setStats] = useState<DashboardStats>(EMPTY_STATS);
const canViewDouyuAccounts = canAny(['account:view_all', 'account:view_assigned']);
const canViewDouyuTasks = canAny(['login:batch', 'login:view_all', 'login:view_assigned']);
const canViewCookies = can('cookie:view');
const canViewHuyaAccounts = can('huya:account');
const canViewHuyaTasks = can('huya:task');
useEffect(() => {
let ignore = false;
async function loadDashboard() {
setLoading(true);
const [
douyuAccountsResult,
douyuTasksResult,
cookiesResult,
huyaAccountsResult,
huyaTasksResult,
huyaGoodsResult,
huyaRechargeGoodsResult,
] = await Promise.allSettled([
canViewDouyuAccounts ? accountApi.list() : Promise.resolve([]),
canViewDouyuTasks ? loginApi.listTasks() : Promise.resolve([]),
canViewCookies ? cookieApi.list() : Promise.resolve([]),
canViewHuyaAccounts ? huyaApi.listAccounts() : Promise.resolve([]),
canViewHuyaTasks ? huyaApi.listTasks() : Promise.resolve([]),
canViewHuyaTasks ? huyaApi.listGoods() : Promise.resolve([]),
canViewHuyaTasks ? huyaApi.listRechargeGoods() : Promise.resolve([]),
]);
if (ignore) return;
const douyuAccounts = douyuAccountsResult.status === 'fulfilled' ? douyuAccountsResult.value : [];
const douyuTasks = douyuTasksResult.status === 'fulfilled' ? douyuTasksResult.value : [];
const cookies = cookiesResult.status === 'fulfilled' ? cookiesResult.value : [];
const huyaAccounts = huyaAccountsResult.status === 'fulfilled' ? huyaAccountsResult.value : [];
const huyaTasks = huyaTasksResult.status === 'fulfilled' ? huyaTasksResult.value : [];
const huyaGoods = huyaGoodsResult.status === 'fulfilled' ? huyaGoodsResult.value : [];
const huyaRechargeGoods = huyaRechargeGoodsResult.status === 'fulfilled' ? huyaRechargeGoodsResult.value : [];
setStats({
douyu: {
accounts: douyuAccounts.length,
tasks: douyuTasks.length,
success: douyuTasks.filter((task: LoginTaskItem) => task.status === 'success').length,
failed: countFailed(douyuTasks),
cookies: cookies.length,
},
huya: {
accounts: huyaAccounts.length,
tasks: huyaTasks.length,
success: huyaTasks.filter((task: HuyaTaskItem) => task.status === 'success').length,
failed: countFailed(huyaTasks),
goods: huyaGoods.length,
rechargeGoods: huyaRechargeGoods.length,
},
});
setLoading(false);
}
void loadDashboard();
return () => {
ignore = true;
};
}, [canViewCookies, canViewDouyuAccounts, canViewDouyuTasks, canViewHuyaAccounts, canViewHuyaTasks]);
const totalStats = useMemo(() => ({
accounts: stats.douyu.accounts + stats.huya.accounts,
tasks: stats.douyu.tasks + stats.huya.tasks,
success: stats.douyu.success + stats.huya.success,
failed: stats.douyu.failed + stats.huya.failed,
}), [stats]);
return (
<div>
<div style={{ marginBottom: 16 }}>
<Space align="center" wrap>
<Title level={2} style={{ margin: 0 }}>概览</Title>
<Tag color="blue">斗鱼</Tag>
<Tag color="orange">虎牙</Tag>
</Space>
</div>
<Row gutter={[12, 12]} style={{ marginBottom: 20 }}>
<StatCard title="账号总数" value={totalStats.accounts} loading={loading} />
<StatCard title="任务总数" value={totalStats.tasks} loading={loading} />
<StatCard title="成功任务" value={totalStats.success} color={token.colorSuccess} loading={loading} />
<StatCard title="失败任务" value={totalStats.failed} color={token.colorError} loading={loading} />
</Row>
<div style={{ marginBottom: 10 }}>
<Title level={4} style={{ margin: 0 }}>斗鱼</Title>
<Text type="secondary">账号登录、Cookie 产出和批量登录任务</Text>
</div>
<Row gutter={[12, 12]} style={{ marginBottom: 20 }}>
<StatCard title="斗鱼账号" value={stats.douyu.accounts} loading={loading} />
<StatCard title="登录任务" value={stats.douyu.tasks} loading={loading} />
<StatCard title="成功 Cookie" value={stats.douyu.cookies} color={token.colorSuccess} loading={loading} />
<StatCard title="失败/异常" value={stats.douyu.failed} color={token.colorError} loading={loading} />
</Row>
<div style={{ marginBottom: 10 }}>
<Title level={4} style={{ margin: 0 }}>虎牙</Title>
<Text type="secondary">账号 Cookie、活动任务、兑换商品和充值商品</Text>
</div>
<Row gutter={[12, 12]}>
<StatCard title="虎牙账号" value={stats.huya.accounts} loading={loading} />
<StatCard title="虎牙任务" value={stats.huya.tasks} loading={loading} />
<StatCard title="兑换商品" value={stats.huya.goods} loading={loading} />
<StatCard title="充值商品" value={stats.huya.rechargeGoods} loading={loading} />
</Row>
</div>
);
}