import React, { memo, useEffect, useMemo, useRef, useState } from 'react'; import { Table, Tag, Button, Input, Select, Dialog, MessagePlugin, DialogPlugin, Loading, Progress } from 'tdesign-react'; import { SearchIcon, AddIcon, RefreshIcon } from 'tdesign-icons-react'; import { useAppDispatch, useAppSelector } from 'modules/store'; import { loadAccounts, selectAccounts, selectAccountLoading } from 'modules/account'; import { deleteAccount, updateAccount, downloadCookie, uploadCookie, getLoginSSEUrl, checkAccounts, getCheckStatus, deleteAccounts, IAccount, ICheckTask, } from 'services/account'; import { PLATFORMS, WEB_PLATFORMS, IPlatform } from 'configs/platforms'; import CommonStyle from 'styles/common.module.less'; import style from './index.module.less'; const platformOptions = [ { label: '全部平台', value: 'all' }, ...PLATFORMS.map((p) => ({ label: p.name, value: String(p.type) })), ]; const statusTag = (status: number) => { if (status === 1) return 正常; if (status === -1) return 验证中; return 异常; }; const Account = () => { const dispatch = useAppDispatch(); const accounts = useAppSelector(selectAccounts); const loading = useAppSelector(selectAccountLoading); const [filterPlatform, setFilterPlatform] = useState('all'); const [searchKeyword, setSearchKeyword] = useState(''); const [addVisible, setAddVisible] = useState(false); const [editVisible, setEditVisible] = useState(false); const [editTarget, setEditTarget] = useState(null); const [editPlatform, setEditPlatform] = useState(0); const [editName, setEditName] = useState(''); // 登录态 const [loginPlatform, setLoginPlatform] = useState(WEB_PLATFORMS[0]?.type || 1); const [loginName, setLoginName] = useState(''); const [qrSrc, setQrSrc] = useState(''); const [loginStatus, setLoginStatus] = useState<'idle' | 'pending' | 'success' | 'fail'>('idle'); const [loginHint, setLoginHint] = useState(''); const esRef = useRef(null); // 多选 + 校验任务 const [selectedRowKeys, setSelectedRowKeys] = useState<(string | number)[]>([]); const [checkDialogVisible, setCheckDialogVisible] = useState(false); const [checkTask, setCheckTask] = useState(null); const checkPollRef = useRef | null>(null); useEffect(() => { dispatch(loadAccounts()); }, [dispatch]); useEffect(() => { return () => { if (esRef.current) { esRef.current.close(); esRef.current = null; } }; }, []); const filtered = useMemo(() => { return accounts.filter((a) => { const matchPlatform = filterPlatform === 'all' || String(a.type) === filterPlatform; const kw = searchKeyword.trim().toLowerCase(); const matchKw = !kw || a.name.toLowerCase().includes(kw) || a.platform.includes(kw); return matchPlatform && matchKw; }); }, [accounts, filterPlatform, searchKeyword]); const refresh = () => dispatch(loadAccounts()); const startCheck = async (ids: number[]) => { if (!ids.length) { MessagePlugin.warning('请先选择要校验的账号'); return; } try { const res = await checkAccounts(ids); if (!res.taskId) { MessagePlugin.error('未返回校验任务 ID'); return; } setCheckDialogVisible(true); pollCheck(res.taskId); } catch { MessagePlugin.error('提交校验任务失败'); } }; const pollCheck = (taskId: string) => { if (checkPollRef.current) clearInterval(checkPollRef.current); const poll = async () => { try { const task = await getCheckStatus(taskId); setCheckTask(task); if (task && (task.status === 'done' || task.status === 'error')) { if (checkPollRef.current) { clearInterval(checkPollRef.current); checkPollRef.current = null; } MessagePlugin.success('校验完成'); refresh(); } } catch { // 轮询失败继续 } }; poll(); checkPollRef.current = setInterval(poll, 2000); }; const batchDelete = () => { if (!selectedRowKeys.length) { MessagePlugin.warning('请先选择账号'); return; } DialogPlugin.confirm({ header: '批量删除', body: '确定删除选中的 ' + selectedRowKeys.length + ' 个账号?对应 Cookie 文件也会被删除。', confirmBtn: { content: '删除', theme: 'danger' }, onConfirm: async () => { try { await deleteAccounts(selectedRowKeys as number[]); MessagePlugin.success('删除成功'); setSelectedRowKeys([]); refresh(); } catch { MessagePlugin.error('删除失败'); } }, }); }; const onDelete = (row: IAccount) => { DialogPlugin.confirm({ header: '删除账号', body: '确定删除账号「' + row.name + '」?对应 Cookie 文件也会被删除。', confirmBtn: { content: '删除', theme: 'danger' }, onConfirm: async () => { try { await deleteAccount(row.id); MessagePlugin.success('删除成功'); refresh(); } catch { MessagePlugin.error('删除失败'); } }, }); }; const openEdit = (row: IAccount) => { setEditTarget(row); setEditPlatform(row.type); setEditName(row.name); setEditVisible(true); }; const submitEdit = async () => { if (!editTarget) return; try { await updateAccount({ id: editTarget.id, type: editPlatform, userName: editName }); MessagePlugin.success('更新成功'); setEditVisible(false); refresh(); } catch { MessagePlugin.error('更新失败'); } }; const onUploadCookie = (row: IAccount) => { const input = document.createElement('input'); input.type = 'file'; input.accept = '.json'; input.onchange = async () => { const file = input.files && input.files[0]; if (!file) return; const formData = new FormData(); formData.append('file', file); formData.append('id', String(row.id)); formData.append('platform', String(row.type)); try { await uploadCookie(formData); MessagePlugin.success('Cookie 上传成功'); refresh(); } catch { MessagePlugin.error('Cookie 上传失败'); } }; input.click(); }; const startLogin = () => { if (!loginName.trim()) { MessagePlugin.warning('请输入账号名称'); return; } if (esRef.current) esRef.current.close(); setQrSrc(''); setLoginHint(''); setLoginStatus('pending'); const url = getLoginSSEUrl(loginPlatform, loginName.trim()); const es = new EventSource(url); esRef.current = es; es.onmessage = (e) => { const msg = e.data; if (msg === '200') { setLoginStatus('success'); es.close(); esRef.current = null; MessagePlugin.success('登录成功'); setAddVisible(false); setLoginStatus('idle'); refresh(); } else if (msg === '500') { setLoginStatus('fail'); es.close(); esRef.current = null; MessagePlugin.error('登录失败'); } else if (msg && msg.startsWith('data:image')) { setQrSrc(msg); setLoginHint(''); } else if (msg && msg !== '') { setLoginHint(msg); } }; es.onerror = () => { if (esRef.current && loginStatus !== 'pending') { es.close(); esRef.current = null; } }; }; const columns = [ { colKey: 'row-select', type: 'multiple' as const, width: 50 }, { colKey: 'name', title: '账号名称', width: 170, ellipsis: true }, { colKey: 'platform', title: '平台', width: 140, cell: ({ row }: { row: IAccount }) => { const p = PLATFORMS.find((x) => x.type === row.type); return {row.platform}; }, }, { colKey: 'status', title: '状态', width: 120, cell: ({ row }: { row: IAccount }) => ( startCheck([row.id])} title='点击校验该账号 Cookie'> {statusTag(row.status)} ), }, { colKey: 'op', title: '操作', width: 320, cell: ({ row }: { row: IAccount }) => (
), }, ]; const selectedPlatform = PLATFORMS.find((p) => p.type === loginPlatform) as IPlatform | undefined; const loginSupported = selectedPlatform?.web === true; return (
} clearable style={{ width: 240 }} />
setSelectedRowKeys(v)} /> {/* 添加账号(扫码登录) */} { if (esRef.current) { esRef.current.close(); esRef.current = null; } setLoginStatus('idle'); setQrSrc(''); setLoginHint(''); setAddVisible(false); }} footer={false} width={460} >
{!loginSupported ? (

该平台登录暂未接入 Web 后端(已接入 CLI 主线)。可通过「上传 Cookie」方式导入账号。

) : ( <> {loginStatus === 'idle' && ( )} {loginStatus === 'pending' && (
{qrSrc ? ( 登录二维码 ) : loginHint ? (

{loginHint}

) : (
)} {qrSrc &&

请使用对应平台 App 扫码登录

}
)} {loginStatus === 'fail' && (

登录失败,请重试。

)} )}
{/* 编辑账号 */} setEditVisible(false)} onConfirm={submitEdit} confirmBtn={{ content: '保存' }} cancelBtn={{ content: '取消' }} width={420} >
{/* Cookie 校验进度 */} setCheckDialogVisible(false)} footer={false} width={520} > {checkTask ? (
进度:{checkTask.done} / {checkTask.total} {checkTask.status === 'done' && 完成} {checkTask.status === 'error' && 出错}
{checkTask.error &&

{checkTask.error}

}
{checkTask.logs.length === 0 ? (

校验中...

) : ( checkTask.logs.map((log, i) =>
{log}
) )}
) : (

正在提交校验任务...

)}
); }; export default memo(Account);