一键检测重复-仓库管理

This commit is contained in:
yml2213
2026-08-08 20:51:50 +08:00
parent 65271124c0
commit b39780f26d
4 changed files with 211 additions and 4 deletions
+2 -1
View File
@@ -1,11 +1,12 @@
import api from './client';
import type { BasicSummary, CookieCheckResult, CookieItem, LoginTaskItem, PageParams, PaginatedResponse, MessageDeletedResponse, MessageResponse } from './types';
import type { BasicSummary, CookieCheckResult, CookieDuplicateResponse, CookieItem, LoginTaskItem, PageParams, PaginatedResponse, MessageDeletedResponse, MessageResponse } from './types';
export const cookieApi = {
list: () => api.get<CookieItem[], CookieItem[]>('/cookies'),
listPaged: (params: PageParams & { include_cookie?: boolean; account_names?: string }) =>
api.get<PaginatedResponse<CookieItem>, PaginatedResponse<CookieItem>>('/cookies', { params }),
summary: () => api.get<BasicSummary, BasicSummary>('/cookies/summary'),
duplicates: () => api.get<CookieDuplicateResponse, CookieDuplicateResponse>('/cookies/duplicates'),
get: (id: number) => api.get<CookieItem, CookieItem>(`/cookies/${id}`),
exportCsv: (format?: string, accountNames?: string) => api.get<Blob, Blob>('/cookies/export', {
responseType: 'blob',
+24
View File
@@ -225,6 +225,30 @@ export interface CookieCheckResult {
checked_at: string;
}
export interface CookieDuplicateRecord {
id: number;
account_id: number;
batch_id: string;
finished_at: string | null;
}
export interface CookieDuplicateGroup {
account_key: string;
account_names: string[];
cookie_count: number;
account_count: number;
cookie_ids: number[];
account_ids: number[];
records: CookieDuplicateRecord[];
}
export interface CookieDuplicateResponse {
success: boolean;
duplicate_groups: number;
duplicate_rows: number;
groups: CookieDuplicateGroup[];
}
// ==================== Douyu Activity ====================
export interface DouyuTaskAccountItem {
+113 -3
View File
@@ -1,8 +1,8 @@
import { useEffect, useState, useCallback } from 'react';
import { Table, Button, Card, Row, Col, Statistic, Tag, Popconfirm, Space, Typography, Input, theme, Dropdown, Tooltip } from 'antd';
import { Table, Button, Card, Row, Col, Statistic, Tag, Popconfirm, Space, Typography, Input, theme, Dropdown, Tooltip, Modal, Alert, Empty } from 'antd';
import { message } from '../utils/antdMessage';
import { DownloadOutlined, DeleteOutlined, CopyOutlined, SearchOutlined, LoadingOutlined, ReloadOutlined, FilterOutlined } from '@ant-design/icons';
import { cookieApi, type BasicSummary, type CookieCheckResult, type CookieItem } from '../api/modules';
import { DownloadOutlined, DeleteOutlined, CopyOutlined, SearchOutlined, LoadingOutlined, ReloadOutlined, FilterOutlined, ScanOutlined } from '@ant-design/icons';
import { cookieApi, type BasicSummary, type CookieCheckResult, type CookieDuplicateGroup, type CookieDuplicateResponse, type CookieItem } from '../api/modules';
import { usePermissions } from '../hooks/usePermissions';
import { formatTime } from '../utils/time';
import { getErrorMessage } from '../utils/error';
@@ -65,6 +65,9 @@ export default function CookiePage() {
const [checkingIds, setCheckingIds] = useState<Set<number>>(new Set());
const [checkResults, setCheckResults] = useState<Map<number, CookieCheckResult>>(new Map());
const [reloginIds, setReloginIds] = useState<Set<number>>(new Set());
const [duplicateOpen, setDuplicateOpen] = useState(false);
const [duplicateLoading, setDuplicateLoading] = useState(false);
const [duplicateResult, setDuplicateResult] = useState<CookieDuplicateResponse | null>(null);
const { can } = usePermissions();
const canView = can('cookie:view');
@@ -153,6 +156,24 @@ export default function CookiePage() {
void handleCheck(ids);
};
const handleDuplicateCheck = async () => {
setDuplicateOpen(true);
setDuplicateLoading(true);
try {
const result = await cookieApi.duplicates();
setDuplicateResult(result);
if (result.duplicate_groups > 0) {
message.warning(`发现 ${result.duplicate_groups} 个重复账号,共 ${result.duplicate_rows} 条 CK 记录`);
} else {
message.success('未发现重复账号');
}
} catch (e: unknown) {
message.error(getErrorMessage(e));
} finally {
setDuplicateLoading(false);
}
};
const loadCookies = useCallback(async () => {
setLoading(true);
try {
@@ -440,11 +461,63 @@ export default function CookiePage() {
},
];
const duplicateColumns = [
{
title: '账号',
dataIndex: 'account_names',
width: 180,
ellipsis: true,
render: (names: string[]) => names.join(' / '),
},
{
title: 'CK 条数',
dataIndex: 'cookie_count',
width: 90,
align: 'center' as const,
render: (count: number) => <Tag color="error">{count}</Tag>,
},
{
title: '账号记录数',
dataIndex: 'account_count',
width: 110,
align: 'center' as const,
},
{
title: 'CK 记录 ID',
dataIndex: 'cookie_ids',
width: 220,
ellipsis: true,
render: (ids: number[]) => ids.join(', '),
},
{
title: '账号 ID',
dataIndex: 'account_ids',
width: 130,
ellipsis: true,
render: (ids: number[]) => ids.join(', '),
},
{
title: '最近记录',
width: 170,
align: 'center' as const,
render: (_: unknown, group: CookieDuplicateGroup) => formatTime(group.records[0]?.finished_at),
},
];
return (
<div>
<div style={{ marginBottom: 16, display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<h2 style={{ margin: 0 }}>Cookie </h2>
<Space>
{canView && (
<Button
icon={<ScanOutlined />}
loading={duplicateLoading}
onClick={handleDuplicateCheck}
>
</Button>
)}
<Button
icon={<CopyOutlined />}
onClick={handleCopySelected}
@@ -595,6 +668,43 @@ export default function CookiePage() {
}}
scroll={{ x: 1210 }}
/>
<Modal
title="数据库重复 CK 检测"
open={duplicateOpen}
onCancel={() => setDuplicateOpen(false)}
footer={null}
width={960}
>
{duplicateLoading ? (
<div style={{ padding: '48px 0', textAlign: 'center' }}>
<LoadingOutlined spin />
<Text style={{ marginLeft: 8 }}>...</Text>
</div>
) : duplicateResult ? (
<>
<Alert
type={duplicateResult.duplicate_groups > 0 ? 'warning' : 'success'}
showIcon
message={duplicateResult.duplicate_groups > 0
? `发现 ${duplicateResult.duplicate_groups} 个重复账号,共 ${duplicateResult.duplicate_rows} 条 CK 记录`
: '未发现重复账号'}
style={{ marginBottom: 16 }}
/>
{duplicateResult.groups.length > 0 ? (
<Table<CookieDuplicateGroup>
rowKey="account_key"
size="small"
pagination={{ pageSize: 10, showTotal: (count) => `${count}` }}
columns={duplicateColumns}
dataSource={duplicateResult.groups}
scroll={{ x: 900 }}
/>
) : (
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description="未发现重复账号" />
)}
</>
) : null}
</Modal>
</div>
);
}