193 lines
6.5 KiB
TypeScript
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>
|
|
);
|
|
}
|