一键检测重复-仓库管理
This commit is contained in:
@@ -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',
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user