虎牙自动注册持久化成功流水与批次,支持失败续跑与随时导出
将注册批次/条目/成功记录落库,成功一个写入一条流水;服务中断可恢复并续跑失败项,换电脑也能导出 txt。同时修复 pending 被误判为运行中导致页面卡住的问题。
This commit is contained in:
@@ -3,6 +3,7 @@ import type {
|
||||
HuyaAccountItem,
|
||||
HuyaAutoRegisterBatch,
|
||||
HuyaAutoRegisterRequest,
|
||||
HuyaAutoRegisterRetryRequest,
|
||||
HuyaConfig,
|
||||
HuyaCookieItem,
|
||||
HuyaCookieImportResult,
|
||||
@@ -13,6 +14,7 @@ import type {
|
||||
HuyaPasswordLoginResult,
|
||||
HuyaPasswordLoginSelectedRequest,
|
||||
HuyaRechargeGoodsItem,
|
||||
HuyaRegisterSuccessLog,
|
||||
HuyaSmsCodeRequest,
|
||||
HuyaSmsCodeResult,
|
||||
HuyaSmsLoginRequest,
|
||||
@@ -43,10 +45,20 @@ export const huyaApi = {
|
||||
api.post<HuyaSmsLoginResult, HuyaSmsLoginResult>('/huya/accounts/sms-login', data, { timeout: 120000 }),
|
||||
startAutoRegister: (data: HuyaAutoRegisterRequest) =>
|
||||
api.post<HuyaAutoRegisterBatch, HuyaAutoRegisterBatch>('/huya/register/batches', data),
|
||||
listAutoRegisterBatches: (limit = 50) =>
|
||||
api.get<HuyaAutoRegisterBatch[], HuyaAutoRegisterBatch[]>('/huya/register/batches', { params: { limit } }),
|
||||
getAutoRegisterBatch: (batchId: string) =>
|
||||
api.get<HuyaAutoRegisterBatch, HuyaAutoRegisterBatch>(`/huya/register/batches/${batchId}`),
|
||||
stopAutoRegisterBatch: (batchId: string) =>
|
||||
api.post<MessageResponse, MessageResponse>(`/huya/register/batches/${batchId}/stop`),
|
||||
retryAutoRegisterBatch: (batchId: string, data?: HuyaAutoRegisterRetryRequest) =>
|
||||
api.post<HuyaAutoRegisterBatch, HuyaAutoRegisterBatch>(`/huya/register/batches/${batchId}/retry`, data || {}),
|
||||
exportAutoRegisterBatch: (batchId: string) =>
|
||||
api.get<Blob, Blob>(`/huya/register/batches/${batchId}/export`, { responseType: 'blob' }),
|
||||
listRegisterSuccessLogs: (params?: { batch_id?: string; tag?: string; limit?: number }) =>
|
||||
api.get<HuyaRegisterSuccessLog[], HuyaRegisterSuccessLog[]>('/huya/register/success-logs', { params }),
|
||||
exportRegisterSuccessLogs: (params?: { batch_id?: string; tag?: string; limit?: number }) =>
|
||||
api.get<Blob, Blob>('/huya/register/success-logs/export', { responseType: 'blob', params }),
|
||||
assign: (id: number, assigned_to: number | null) =>
|
||||
api.put<MessageResponse, MessageResponse>(`/huya/accounts/${id}/assign`, { assigned_to }),
|
||||
batchAssign: (account_ids: number[], assigned_to: number | null) =>
|
||||
|
||||
@@ -223,6 +223,15 @@ export interface HuyaAutoRegisterRequest {
|
||||
use_proxy?: boolean;
|
||||
}
|
||||
|
||||
export interface HuyaAutoRegisterRetryRequest {
|
||||
concurrency?: number;
|
||||
wait_seconds?: number;
|
||||
poll_interval?: number;
|
||||
password_prefix?: string;
|
||||
fixed_password?: string;
|
||||
use_proxy?: boolean;
|
||||
}
|
||||
|
||||
export interface HuyaAutoRegisterItem {
|
||||
line: number;
|
||||
phone: string;
|
||||
@@ -267,6 +276,23 @@ export interface HuyaAutoRegisterBatch {
|
||||
items: HuyaAutoRegisterItem[];
|
||||
}
|
||||
|
||||
export interface HuyaRegisterSuccessLog {
|
||||
id: number;
|
||||
batch_id: string;
|
||||
item_id: number | null;
|
||||
account_id: number | null;
|
||||
phone: string;
|
||||
username: string;
|
||||
uid: string;
|
||||
password: string;
|
||||
sms_url: string;
|
||||
tag: string;
|
||||
provider: string;
|
||||
created_by: number | null;
|
||||
created_at: string | null;
|
||||
export_line: string;
|
||||
}
|
||||
|
||||
export interface HuyaPasswordLoginBatchItem {
|
||||
line: number;
|
||||
username: string;
|
||||
|
||||
@@ -3,8 +3,15 @@ import {
|
||||
Button, Card, Col, Input, InputNumber, message, Row, Segmented, Space, Statistic, Switch, Table, Tag, Typography,
|
||||
} from 'antd';
|
||||
import type { TableProps } from 'antd';
|
||||
import { DownloadOutlined, PlayCircleOutlined, ReloadOutlined, StopOutlined } from '@ant-design/icons';
|
||||
import { huyaApi, type HuyaAutoRegisterBatch, type HuyaAutoRegisterItem } from '../api/modules';
|
||||
import {
|
||||
DownloadOutlined, PlayCircleOutlined, RedoOutlined, ReloadOutlined, StopOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import {
|
||||
huyaApi,
|
||||
type HuyaAutoRegisterBatch,
|
||||
type HuyaAutoRegisterItem,
|
||||
type HuyaRegisterSuccessLog,
|
||||
} from '../api/modules';
|
||||
import { formatTime } from '../utils/time';
|
||||
import { getErrorMessage } from '../utils/error';
|
||||
|
||||
@@ -48,6 +55,9 @@ const STATUS_LABELS: Record<string, string> = {
|
||||
success: '成功',
|
||||
error: '失败',
|
||||
stopped: '已停止',
|
||||
interrupted: '已中断',
|
||||
finished: '已完成',
|
||||
running: '运行中',
|
||||
};
|
||||
|
||||
const STATUS_COLORS: Record<string, string> = {
|
||||
@@ -59,9 +69,14 @@ const STATUS_COLORS: Record<string, string> = {
|
||||
success: 'success',
|
||||
error: 'error',
|
||||
stopped: 'warning',
|
||||
interrupted: 'warning',
|
||||
finished: 'success',
|
||||
running: 'processing',
|
||||
};
|
||||
|
||||
const RUNNING_STATUS = new Set(['pending', 'running']);
|
||||
// 只有真正 running 才锁 UI / 轮询;pending 可能是历史脏数据,不能当运行中
|
||||
const RUNNING_STATUS = new Set(['running']);
|
||||
const RETRYABLE_BATCH = new Set(['finished', 'stopped', 'error', 'interrupted', 'pending']);
|
||||
|
||||
function safeNumber(value: unknown, fallback: number) {
|
||||
const n = Number(value);
|
||||
@@ -96,6 +111,15 @@ function readStoredBatchId() {
|
||||
}
|
||||
}
|
||||
|
||||
async function downloadBlob(blob: Blob, filename: string) {
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = filename;
|
||||
link.click();
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
export default function HuyaRegisterPage() {
|
||||
const [initialForm] = useState(readStoredForm);
|
||||
const [text, setText] = useState(initialForm.text);
|
||||
@@ -108,13 +132,26 @@ export default function HuyaRegisterPage() {
|
||||
const [fixedPassword, setFixedPassword] = useState(initialForm.fixedPassword);
|
||||
const [useProxy, setUseProxy] = useState(initialForm.useProxy);
|
||||
const [batch, setBatch] = useState<HuyaAutoRegisterBatch | null>(null);
|
||||
const [history, setHistory] = useState<HuyaAutoRegisterBatch[]>([]);
|
||||
const [successLogs, setSuccessLogs] = useState<HuyaRegisterSuccessLog[]>([]);
|
||||
const [starting, setStarting] = useState(false);
|
||||
const [retrying, setRetrying] = useState(false);
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
const [stopping, setStopping] = useState(false);
|
||||
const [historyLoading, setHistoryLoading] = useState(false);
|
||||
const [logsLoading, setLogsLoading] = useState(false);
|
||||
const [exporting, setExporting] = useState(false);
|
||||
const restoredBatchRef = useRef(false);
|
||||
|
||||
const batchId = batch?.batch_id || '';
|
||||
const isRunning = !!batch && RUNNING_STATUS.has(batch.status);
|
||||
const isRunning = !!batch && batch.status === 'running';
|
||||
const hasUnfinished = !!batch && (
|
||||
(batch.failed_count || 0) + (batch.stopped_count || 0) > 0
|
||||
|| (batch.items || []).some((item) => item.status !== 'success')
|
||||
|| ((batch.success_count || 0) < (batch.total || 0))
|
||||
);
|
||||
const canRetry = !!batch && !isRunning && hasUnfinished
|
||||
&& (RETRYABLE_BATCH.has(batch.status) || batch.status === 'pending');
|
||||
|
||||
const loadBatchById = useCallback(async (id: string) => {
|
||||
if (!id) return;
|
||||
@@ -125,10 +162,10 @@ export default function HuyaRegisterPage() {
|
||||
localStorage.setItem(BATCH_STORAGE_KEY, data.batch_id);
|
||||
} catch (e: unknown) {
|
||||
const err = getErrorMessage(e);
|
||||
if (err.includes('批次不存在') || err.includes('服务已重启')) {
|
||||
if (err.includes('批次不存在')) {
|
||||
localStorage.removeItem(BATCH_STORAGE_KEY);
|
||||
setBatch(null);
|
||||
message.warning('上次自动注册批次已不存在,已清除恢复记录');
|
||||
message.warning('批次不存在,已清除本地恢复记录');
|
||||
} else {
|
||||
message.error(err);
|
||||
}
|
||||
@@ -142,6 +179,30 @@ export default function HuyaRegisterPage() {
|
||||
await loadBatchById(batchId);
|
||||
}, [batchId, loadBatchById]);
|
||||
|
||||
const loadHistory = useCallback(async () => {
|
||||
setHistoryLoading(true);
|
||||
try {
|
||||
const rows = await huyaApi.listAutoRegisterBatches(50);
|
||||
setHistory(rows);
|
||||
} catch (e: unknown) {
|
||||
message.error(getErrorMessage(e));
|
||||
} finally {
|
||||
setHistoryLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const loadSuccessLogs = useCallback(async () => {
|
||||
setLogsLoading(true);
|
||||
try {
|
||||
const rows = await huyaApi.listRegisterSuccessLogs({ limit: 100 });
|
||||
setSuccessLogs(rows);
|
||||
} catch (e: unknown) {
|
||||
message.error(getErrorMessage(e));
|
||||
} finally {
|
||||
setLogsLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem(FORM_STORAGE_KEY, JSON.stringify({
|
||||
text,
|
||||
@@ -163,7 +224,9 @@ export default function HuyaRegisterPage() {
|
||||
if (storedBatchId) {
|
||||
loadBatchById(storedBatchId);
|
||||
}
|
||||
}, [loadBatchById]);
|
||||
loadHistory();
|
||||
loadSuccessLogs();
|
||||
}, [loadBatchById, loadHistory, loadSuccessLogs]);
|
||||
|
||||
useEffect(() => {
|
||||
if (batch?.batch_id) {
|
||||
@@ -179,11 +242,28 @@ export default function HuyaRegisterPage() {
|
||||
return () => window.clearInterval(timer);
|
||||
}, [batchId, isRunning, refreshBatch]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isRunning) return undefined;
|
||||
// 批次结束时刷新历史与成功流水
|
||||
if (batch && (batch.status === 'finished' || batch.status === 'stopped' || batch.status === 'error')) {
|
||||
loadHistory();
|
||||
loadSuccessLogs();
|
||||
}
|
||||
return undefined;
|
||||
}, [batch?.status, isRunning, loadHistory, loadSuccessLogs, batch]);
|
||||
|
||||
const successRows = useMemo(
|
||||
() => (batch?.items || []).filter((item) => item.status === 'success' && item.password && (item.username || item.uid)),
|
||||
[batch],
|
||||
);
|
||||
|
||||
const retryableCount = useMemo(() => {
|
||||
if (!batch?.items?.length) {
|
||||
return (batch?.failed_count || 0) + (batch?.stopped_count || 0);
|
||||
}
|
||||
return batch.items.filter((item) => item.status !== 'success').length;
|
||||
}, [batch]);
|
||||
|
||||
const handleStart = async () => {
|
||||
if (!text.trim()) {
|
||||
message.warning('请先粘贴手机号池');
|
||||
@@ -207,6 +287,7 @@ export default function HuyaRegisterPage() {
|
||||
});
|
||||
setBatch(data);
|
||||
message.success('自动注册批次已启动');
|
||||
loadHistory();
|
||||
} catch (e: unknown) {
|
||||
message.error(getErrorMessage(e));
|
||||
} finally {
|
||||
@@ -228,21 +309,75 @@ export default function HuyaRegisterPage() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleExportSuccess = () => {
|
||||
if (successRows.length === 0) {
|
||||
message.warning('当前批次没有可导出的成功账号');
|
||||
return;
|
||||
const handleRetry = async () => {
|
||||
if (!batchId) return;
|
||||
setRetrying(true);
|
||||
try {
|
||||
const data = await huyaApi.retryAutoRegisterBatch(batchId, {
|
||||
concurrency,
|
||||
wait_seconds: waitSeconds,
|
||||
poll_interval: pollInterval,
|
||||
password_prefix: passwordMode === 'random' ? (passwordPrefix.trim() || 'hy') : undefined,
|
||||
fixed_password: passwordMode === 'fixed' ? fixedPassword.trim() : undefined,
|
||||
use_proxy: useProxy,
|
||||
});
|
||||
setBatch(data);
|
||||
message.success(`已开始续跑 ${retryableCount} 条失败/未完成项`);
|
||||
loadHistory();
|
||||
} catch (e: unknown) {
|
||||
message.error(getErrorMessage(e));
|
||||
} finally {
|
||||
setRetrying(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleExportCurrentBatch = async () => {
|
||||
if (!batchId) return;
|
||||
setExporting(true);
|
||||
try {
|
||||
// 优先服务端历史流水(换电脑可用);若无记录再回退当前页内存
|
||||
try {
|
||||
const blob = await huyaApi.exportAutoRegisterBatch(batchId);
|
||||
await downloadBlob(blob, `huya-register-${batchId}.txt`);
|
||||
message.success('已导出当前批次成功账号');
|
||||
return;
|
||||
} catch {
|
||||
// fallthrough
|
||||
}
|
||||
if (successRows.length === 0) {
|
||||
message.warning('当前批次没有可导出的成功账号');
|
||||
return;
|
||||
}
|
||||
const body = successRows
|
||||
.map((item) => `${item.username || item.uid}----${item.password}----${item.phone}----${item.sms_url}`)
|
||||
.join('\n');
|
||||
const blob = new Blob([body], { type: 'text/plain;charset=utf-8' });
|
||||
await downloadBlob(blob, `huya-register-accounts-${batchId}.txt`);
|
||||
message.success('已导出当前批次成功账号');
|
||||
} catch (e: unknown) {
|
||||
message.error(getErrorMessage(e));
|
||||
} finally {
|
||||
setExporting(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleExportAllSuccess = async () => {
|
||||
setExporting(true);
|
||||
try {
|
||||
const blob = await huyaApi.exportRegisterSuccessLogs({
|
||||
tag: tag.trim() || undefined,
|
||||
limit: 5000,
|
||||
});
|
||||
const name = tag.trim()
|
||||
? `huya-register-success-${tag.trim()}.txt`
|
||||
: 'huya-register-success-all.txt';
|
||||
await downloadBlob(blob, name);
|
||||
message.success(tag.trim() ? `已按标签「${tag.trim()}」导出成功历史` : '已导出全部成功历史');
|
||||
} catch (e: unknown) {
|
||||
message.error(getErrorMessage(e));
|
||||
} finally {
|
||||
setExporting(false);
|
||||
}
|
||||
const body = successRows
|
||||
.map((item) => `${item.username || item.uid}----${item.password}----${item.phone}----${item.sms_url}`)
|
||||
.join('\n');
|
||||
const blob = new Blob([body], { type: 'text/plain;charset=utf-8' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = `huya-register-accounts-${batchId || 'success'}.txt`;
|
||||
link.click();
|
||||
URL.revokeObjectURL(url);
|
||||
};
|
||||
|
||||
const columns: TableProps<HuyaAutoRegisterItem>['columns'] = [
|
||||
@@ -296,6 +431,125 @@ export default function HuyaRegisterPage() {
|
||||
},
|
||||
];
|
||||
|
||||
const historyColumns: TableProps<HuyaAutoRegisterBatch>['columns'] = [
|
||||
{
|
||||
title: '批次',
|
||||
dataIndex: 'batch_id',
|
||||
width: 130,
|
||||
render: (value: string) => <Text code>{value}</Text>,
|
||||
},
|
||||
{
|
||||
title: '标签',
|
||||
dataIndex: 'tag',
|
||||
width: 120,
|
||||
render: (value: string) => value || '-',
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
width: 100,
|
||||
render: (status: string) => (
|
||||
<Tag color={STATUS_COLORS[status] || 'default'}>
|
||||
{STATUS_LABELS[status] || status}
|
||||
</Tag>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '进度',
|
||||
width: 160,
|
||||
render: (_, row) => `${row.success_count}/${row.total} 成功 · 失败 ${row.failed_count}`,
|
||||
},
|
||||
{
|
||||
title: '时间',
|
||||
dataIndex: 'created_at',
|
||||
width: 170,
|
||||
render: (value: string | null) => formatTime(value),
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
width: 220,
|
||||
render: (_, row) => (
|
||||
<Space size={4}>
|
||||
<Button
|
||||
size="small"
|
||||
onClick={() => loadBatchById(row.batch_id)}
|
||||
>
|
||||
查看
|
||||
</Button>
|
||||
<Button
|
||||
size="small"
|
||||
icon={<RedoOutlined />}
|
||||
disabled={row.status === 'running' || (row.success_count >= row.total && row.total > 0)}
|
||||
onClick={async () => {
|
||||
try {
|
||||
setRetrying(true);
|
||||
const data = await huyaApi.retryAutoRegisterBatch(row.batch_id, {
|
||||
concurrency,
|
||||
wait_seconds: waitSeconds,
|
||||
poll_interval: pollInterval,
|
||||
use_proxy: useProxy,
|
||||
});
|
||||
setBatch(data);
|
||||
message.success('已开始续跑失败/未完成项');
|
||||
loadHistory();
|
||||
} catch (e: unknown) {
|
||||
message.error(getErrorMessage(e));
|
||||
} finally {
|
||||
setRetrying(false);
|
||||
}
|
||||
}}
|
||||
>
|
||||
续跑
|
||||
</Button>
|
||||
<Button
|
||||
size="small"
|
||||
icon={<DownloadOutlined />}
|
||||
disabled={(row.success_count || 0) <= 0}
|
||||
onClick={async () => {
|
||||
try {
|
||||
const blob = await huyaApi.exportAutoRegisterBatch(row.batch_id);
|
||||
await downloadBlob(blob, `huya-register-${row.batch_id}.txt`);
|
||||
} catch (e: unknown) {
|
||||
message.error(getErrorMessage(e));
|
||||
}
|
||||
}}
|
||||
>
|
||||
导出
|
||||
</Button>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const logColumns: TableProps<HuyaRegisterSuccessLog>['columns'] = [
|
||||
{ title: 'ID', dataIndex: 'id', width: 70 },
|
||||
{
|
||||
title: '批次',
|
||||
dataIndex: 'batch_id',
|
||||
width: 120,
|
||||
render: (value: string) => value || '-',
|
||||
},
|
||||
{ title: '手机号', dataIndex: 'phone', width: 140 },
|
||||
{
|
||||
title: '虎牙号',
|
||||
width: 140,
|
||||
render: (_, row) => row.username || row.uid || '-',
|
||||
},
|
||||
{ title: '密码', dataIndex: 'password', width: 120 },
|
||||
{
|
||||
title: '标签',
|
||||
dataIndex: 'tag',
|
||||
width: 100,
|
||||
render: (value: string) => value || '-',
|
||||
},
|
||||
{
|
||||
title: '时间',
|
||||
dataIndex: 'created_at',
|
||||
width: 170,
|
||||
render: (value: string | null) => formatTime(value),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Space direction="vertical" size={16} style={{ width: '100%' }}>
|
||||
<Title level={3} style={{ margin: 0 }}>虎牙自动注册</Title>
|
||||
@@ -315,7 +569,7 @@ export default function HuyaRegisterPage() {
|
||||
<Input
|
||||
value={tag}
|
||||
onChange={(event) => setTag(event.target.value)}
|
||||
placeholder="注册批次"
|
||||
placeholder="注册批次(导出历史可按标签过滤)"
|
||||
disabled={isRunning}
|
||||
/>
|
||||
</Col>
|
||||
@@ -393,8 +647,8 @@ export default function HuyaRegisterPage() {
|
||||
/>
|
||||
</div>
|
||||
</Col>
|
||||
<Col xs={24} md={4}>
|
||||
<Space style={{ width: '100%', paddingTop: 22 }}>
|
||||
<Col xs={24} md={12}>
|
||||
<Space wrap style={{ width: '100%', paddingTop: 22 }}>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<PlayCircleOutlined />}
|
||||
@@ -413,21 +667,44 @@ export default function HuyaRegisterPage() {
|
||||
>
|
||||
停止
|
||||
</Button>
|
||||
<Button
|
||||
icon={<RedoOutlined />}
|
||||
loading={retrying}
|
||||
disabled={!canRetry}
|
||||
onClick={handleRetry}
|
||||
>
|
||||
续跑失败项{retryableCount > 0 ? ` (${retryableCount})` : ''}
|
||||
</Button>
|
||||
<Button
|
||||
icon={<DownloadOutlined />}
|
||||
loading={exporting}
|
||||
onClick={handleExportAllSuccess}
|
||||
>
|
||||
导出全部成功历史
|
||||
</Button>
|
||||
</Space>
|
||||
</Col>
|
||||
</Row>
|
||||
<Text type="secondary">
|
||||
成功账号会立即写入数据库成功流水;服务重启后可从历史批次查看并续跑失败项。换电脑登录同一服务即可导出 txt。
|
||||
</Text>
|
||||
</Space>
|
||||
</Card>
|
||||
|
||||
<Card
|
||||
title="批次结果"
|
||||
title="当前批次结果"
|
||||
extra={(
|
||||
<Space>
|
||||
<Button icon={<ReloadOutlined />} loading={refreshing} disabled={!batchId} onClick={refreshBatch}>
|
||||
刷新
|
||||
</Button>
|
||||
<Button icon={<DownloadOutlined />} disabled={successRows.length === 0} onClick={handleExportSuccess}>
|
||||
导出账号
|
||||
<Button
|
||||
icon={<DownloadOutlined />}
|
||||
loading={exporting}
|
||||
disabled={!batchId}
|
||||
onClick={handleExportCurrentBatch}
|
||||
>
|
||||
导出本批成功
|
||||
</Button>
|
||||
</Space>
|
||||
)}
|
||||
@@ -440,6 +717,11 @@ export default function HuyaRegisterPage() {
|
||||
<Col xs={12} md={4}><Statistic title="停止" value={batch?.stopped_count || 0} /></Col>
|
||||
<Col xs={12} md={4}><Statistic title="批次" value={batch?.batch_id || '-'} /></Col>
|
||||
</Row>
|
||||
{batch?.message ? (
|
||||
<Text type="secondary" style={{ display: 'block', marginBottom: 12 }}>
|
||||
{STATUS_LABELS[batch.status] || batch.status} · {batch.message}
|
||||
</Text>
|
||||
) : null}
|
||||
<Table
|
||||
rowKey="line"
|
||||
columns={columns}
|
||||
@@ -449,6 +731,50 @@ export default function HuyaRegisterPage() {
|
||||
scroll={{ x: 1320 }}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
<Card
|
||||
title="历史批次"
|
||||
extra={(
|
||||
<Button icon={<ReloadOutlined />} loading={historyLoading} onClick={loadHistory}>
|
||||
刷新历史
|
||||
</Button>
|
||||
)}
|
||||
>
|
||||
<Table
|
||||
rowKey="batch_id"
|
||||
columns={historyColumns}
|
||||
dataSource={history}
|
||||
loading={historyLoading}
|
||||
pagination={{ pageSize: 10 }}
|
||||
scroll={{ x: 900 }}
|
||||
onRow={(row) => ({
|
||||
style: row.batch_id === batchId ? { background: 'rgba(22, 119, 255, 0.06)' } : undefined,
|
||||
})}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
<Card
|
||||
title="成功流水(最近 100 条)"
|
||||
extra={(
|
||||
<Space>
|
||||
<Button icon={<ReloadOutlined />} loading={logsLoading} onClick={loadSuccessLogs}>
|
||||
刷新
|
||||
</Button>
|
||||
<Button icon={<DownloadOutlined />} loading={exporting} onClick={handleExportAllSuccess}>
|
||||
导出
|
||||
</Button>
|
||||
</Space>
|
||||
)}
|
||||
>
|
||||
<Table
|
||||
rowKey="id"
|
||||
columns={logColumns}
|
||||
dataSource={successLogs}
|
||||
loading={logsLoading}
|
||||
pagination={{ pageSize: 10 }}
|
||||
scroll={{ x: 900 }}
|
||||
/>
|
||||
</Card>
|
||||
</Space>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user