import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { Button, Card, Col, DatePicker, Dropdown, Form, Input, InputNumber, message, Modal, QRCode, Radio, Row, Select, Space, Table, Tag, Tooltip, Typography, theme, } from 'antd'; import type { MenuProps, TableProps } from 'antd'; import type { Dayjs } from 'dayjs'; import type { MouseEvent as ReactMouseEvent } from 'react'; import { AppstoreOutlined, CheckCircleOutlined, CreditCardOutlined, FieldTimeOutlined, LinkOutlined, MoreOutlined, PlayCircleOutlined, QrcodeOutlined, ReloadOutlined, SearchOutlined, SettingOutlined, ShoppingOutlined, } from '@ant-design/icons'; import { huyaApi, type HuyaAccountItem, type HuyaConfig, type HuyaGoodsItem, type HuyaRechargeGoodsItem, type HuyaTaskItem, } from '../api/modules'; import RealtimeLogPanel from '../components/RealtimeLogPanel'; import { usePermissions } from '../hooks/usePermissions'; import { useWebSocketLogs } from '../hooks/useWebSocketLogs'; import { formatTime } from '../utils/time'; import { getErrorMessage } from '../utils/error'; const { Text } = Typography; const FALLBACK_TASK_TYPES: Record = { get_bind_qr: '获取绑定二维码', confirm_bind: '确认绑定', query_points: '一键查询积分', query_game_name: '一键查询游戏名', query_exchange_records: '一键查询兑换记录', refresh_goods: '刷新商品列表', refresh_recharge_goods: '刷新充值商品列表', exchange_goods: '兑换商品', create_recharge_order: '生成支付二维码', }; const QUICK_ACTIONS = [ { key: 'get_bind_qr', icon: }, { key: 'confirm_bind', icon: }, { key: 'query_points', icon: }, { key: 'query_game_name', icon: }, { key: 'query_exchange_records', icon: }, { key: 'refresh_goods', icon: }, { key: 'refresh_recharge_goods', icon: }, { key: 'exchange_goods', icon: }, { key: 'create_recharge_order', icon: }, ]; const STATUS_COLORS: Record = { planned: 'default', pending: 'default', running: 'processing', success: 'success', failed: 'error', error: 'error', }; const STATUS_LABELS: Record = { planned: '已计划', pending: '等待中', running: '执行中', success: '成功', failed: '失败', error: '异常', }; const ACCOUNT_STATUS_LABELS: Record = { imported: '已导入', updated: '已更新', password_imported: '待登录', login_success: '登录成功', password_changed: '已改密', login_failed: '登录失败', active: '正常', invalid: '失效', points_queried: '已查积分', game_queried: '已查角色', game_not_bound: '未绑定', bind_qr_generated: '待扫码', bind_confirmed: '已绑定', goods_exchanged: '已兑换', recharge_order_created: '待支付', }; const ACCOUNT_STATUS_COLORS: Record = { imported: 'blue', updated: 'cyan', password_imported: 'warning', login_success: 'success', password_changed: 'success', login_failed: 'error', active: 'success', invalid: 'error', points_queried: 'success', game_queried: 'success', game_not_bound: 'default', bind_qr_generated: 'processing', bind_confirmed: 'success', goods_exchanged: 'success', recharge_order_created: 'processing', }; function accountLabel(account: HuyaAccountItem): string { const name = account.nickname || account.username || account.uid || `#${account.id}`; const tag = account.tag ? ` [${account.tag}]` : ''; const phone = account.game_phone ? ` / ${account.game_phone}` : ''; return `${name}${tag}${phone}`; } function accountDisplayName(account: HuyaAccountItem): string { return account.nickname || account.username || account.uid || `#${account.id}`; } function resultText(result: Record | null | undefined, key: string): string { const value = result?.[key]; return typeof value === 'string' ? value : ''; } function resultObject(result: Record | null | undefined, key: string): Record | null { const value = result?.[key]; if (!value || typeof value !== 'object' || Array.isArray(value)) return null; return value as Record; } function resultProfileNick(result: Record | null | undefined): string { const profile = result?.profile; if (!profile || typeof profile !== 'object' || Array.isArray(profile)) return ''; const nick = (profile as Record).nick; return typeof nick === 'string' ? nick : ''; } function taskPayloadValue(task: HuyaTaskItem | null | undefined, key: string): string { const payload = resultObject(task?.result, 'payload'); const value = payload?.[key]; if (typeof value === 'string') return value; if (typeof value === 'number') return String(value); return ''; } function taskProductText(task: HuyaTaskItem | null | undefined): string { const result = task?.result; return ( resultText(result, 'product_name') || taskPayloadValue(task, 'product_name') || resultText(result, 'product_id') || taskPayloadValue(task, 'product_id') || resultText(result, 'spu_id') || taskPayloadValue(task, 'spu_id') ); } function isInteractiveTarget(target: EventTarget | null): boolean { const element = target instanceof HTMLElement ? target : null; if (!element) return false; return Boolean(element.closest([ 'a', 'button', 'input', 'textarea', 'select', '[role="button"]', '[role="menuitem"]', '.ant-btn', '.ant-checkbox', '.ant-checkbox-wrapper', '.ant-dropdown', '.ant-dropdown-menu', '.ant-input', '.ant-picker', '.ant-select', '.ant-table-column-sorter', ].join(','))); } function contextMenuPosition(x: number, y: number) { if (typeof window === 'undefined') return { left: x, top: y }; return { left: Math.max(8, Math.min(x, window.innerWidth - 220)), top: Math.max(8, Math.min(y, window.innerHeight - 390)), }; } function goodsRawString(item: HuyaGoodsItem, key: string): string { const value = item.raw?.[key]; if (typeof value === 'string') return value; if (typeof value === 'number') return String(value); return ''; } function goodsRawNumber(item: HuyaGoodsItem, key: string): number { const value = item.raw?.[key]; if (typeof value === 'number') return value; if (typeof value === 'string' && value.trim()) return Number(value) || 0; return 0; } function goodsCategoryKey(item: HuyaGoodsItem): string { return goodsRawString(item, 'category_id') || goodsRawString(item, 'category_name') || 'uncategorized'; } function goodsCategoryLabel(item: HuyaGoodsItem): string { return goodsRawString(item, 'category_name') || goodsRawString(item, 'category_id') || '未分类'; } function formatRemainText(value: string): string { const text = String(value || '').trim(); if (!text || text.endsWith('%')) return text; return /^-?\d+(\.\d+)?$/.test(text) ? `${text}%` : text; } function formatPriceText(value: number | null | undefined): string { if (!value) return ''; return `¥${(value / 100).toFixed(2)}`; } function hasMiniQrcode(task: HuyaTaskItem): boolean { return task.task_type === 'get_bind_qr' && Boolean(resultText(task.result, 'mini_qrcode_image')); } function hasPaymentQrcode(task: HuyaTaskItem): boolean { return task.task_type === 'create_recharge_order' && Boolean(resultText(task.result, 'pay_url')); } function paymentStatus(task: HuyaTaskItem | null | undefined): string { const status = task?.result?.payment_status; return typeof status === 'string' ? status : ''; } function paymentStatusLabel(task: HuyaTaskItem | null | undefined): string { const label = task?.result?.payment_status_label; return typeof label === 'string' ? label : ''; } function isPaymentFinished(task: HuyaTaskItem | null | undefined): boolean { return task?.task_type === 'create_recharge_order' && paymentStatus(task) === 'paid'; } export default function HuyaTasksPage() { const { token } = theme.useToken(); const [form] = Form.useForm(); const [accounts, setAccounts] = useState([]); const [tasks, setTasks] = useState([]); const [goods, setGoods] = useState([]); const [rechargeGoods, setRechargeGoods] = useState([]); const [taskTypes, setTaskTypes] = useState>(FALLBACK_TASK_TYPES); const [tags, setTags] = useState([]); const [selectedIds, setSelectedIds] = useState([]); const [selectedTaskType, setSelectedTaskType] = useState('query_points'); const [searchText, setSearchText] = useState(''); const [tagFilter, setTagFilter] = useState(''); const [accountContextMenu, setAccountContextMenu] = useState<{ account: HuyaAccountItem; accountIds: number[]; x: number; y: number } | null>(null); const [selectedGoodsCategory, setSelectedGoodsCategory] = useState(''); const [selectedExchangeGoodsId, setSelectedExchangeGoodsId] = useState(''); const [exchangeAt, setExchangeAt] = useState(null); const [selectedRechargeGoodsId, setSelectedRechargeGoodsId] = useState(''); const [rechargeCount, setRechargeCount] = useState(1); const [rechargePayChannel, setRechargePayChannel] = useState('Weixin'); const [concurrency, setConcurrency] = useState(() => { const v = localStorage.getItem('huya_task_concurrency'); return v ? Math.max(1, Math.min(10, Number(v) || 3)) : 3; }); const [loading, setLoading] = useState(false); const [starting, setStarting] = useState(false); const [savingConfig, setSavingConfig] = useState(false); const [batchId, setBatchId] = useState(null); const [qrTask, setQrTask] = useState(null); const [payTask, setPayTask] = useState(null); const [exchangeRecordsTask, setExchangeRecordsTask] = useState(null); const autoOpenedQrTaskIds = useRef>(new Set()); const autoOpenQrReady = useRef(false); const autoOpenedPayTaskIds = useRef>(new Set()); const autoOpenPayReady = useRef(false); const notifiedPaidTaskIds = useRef>(new Set()); const { logs, connected: wsConnected, connect: connectLogs } = useWebSocketLogs(); const { can } = usePermissions(); const canTask = can('huya:task'); const canConfig = can('huya:config'); useEffect(() => { localStorage.setItem('huya_task_concurrency', String(concurrency)); }, [concurrency]); const rememberExistingQrcodes = useCallback((items: HuyaTaskItem[]) => { if (autoOpenQrReady.current) return; items.filter(hasMiniQrcode).forEach((task) => autoOpenedQrTaskIds.current.add(task.id)); autoOpenQrReady.current = true; }, []); const rememberExistingPaymentQrcodes = useCallback((items: HuyaTaskItem[]) => { if (autoOpenPayReady.current) return; items.filter(hasPaymentQrcode).forEach((task) => autoOpenedPayTaskIds.current.add(task.id)); autoOpenPayReady.current = true; }, []); const openQrTask = useCallback((task: HuyaTaskItem) => { autoOpenedQrTaskIds.current.add(task.id); setQrTask(task); }, []); const openPayTask = useCallback((task: HuyaTaskItem) => { autoOpenedPayTaskIds.current.add(task.id); setPayTask(task); }, []); const loadAll = useCallback(async () => { setLoading(true); try { const [accountResult, taskResult, goodsResult, rechargeGoodsResult, configResult, taskTypeResult, tagResult] = await Promise.allSettled([ huyaApi.listAccounts(), huyaApi.listTasks(), huyaApi.listGoods(), huyaApi.listRechargeGoods(), canConfig ? huyaApi.getConfig() : Promise.resolve(null), huyaApi.taskTypes(), huyaApi.listTags(), ]); if (accountResult.status === 'fulfilled') setAccounts(accountResult.value); if (taskResult.status === 'fulfilled') { rememberExistingQrcodes(taskResult.value); rememberExistingPaymentQrcodes(taskResult.value); setTasks(taskResult.value); } if (goodsResult.status === 'fulfilled') setGoods(goodsResult.value); if (rechargeGoodsResult.status === 'fulfilled') setRechargeGoods(rechargeGoodsResult.value); if (configResult.status === 'fulfilled' && configResult.value) form.setFieldsValue(configResult.value); if (taskTypeResult.status === 'fulfilled') setTaskTypes({ ...FALLBACK_TASK_TYPES, ...taskTypeResult.value }); if (tagResult.status === 'fulfilled') setTags(tagResult.value); const failedLabels = [ accountResult.status === 'rejected' ? `CK 列表: ${getErrorMessage(accountResult.reason)}` : '', taskResult.status === 'rejected' ? `任务记录: ${getErrorMessage(taskResult.reason)}` : '', goodsResult.status === 'rejected' ? `兑换商品: ${getErrorMessage(goodsResult.reason)}` : '', rechargeGoodsResult.status === 'rejected' ? `充值商品: ${getErrorMessage(rechargeGoodsResult.reason)}` : '', configResult.status === 'rejected' ? `虎牙配置: ${getErrorMessage(configResult.reason)}` : '', taskTypeResult.status === 'rejected' ? `任务类型: ${getErrorMessage(taskTypeResult.reason)}` : '', tagResult.status === 'rejected' ? `标签: ${getErrorMessage(tagResult.reason)}` : '', ].filter(Boolean); if (failedLabels.length > 0) { message.warning(`部分数据加载失败:${failedLabels.join(';')}`); } } finally { setLoading(false); } }, [canConfig, form, rememberExistingPaymentQrcodes, rememberExistingQrcodes]); const loadTasks = useCallback(async () => { try { const data = await huyaApi.listTasks(); rememberExistingQrcodes(data); rememberExistingPaymentQrcodes(data); setTasks(data); } catch { // 轮询失败不打扰操作,下一轮继续刷新。 } }, [rememberExistingPaymentQrcodes, rememberExistingQrcodes]); useEffect(() => { loadAll(); }, [loadAll]); useEffect(() => { const timer = setInterval(loadTasks, 3000); return () => clearInterval(timer); }, [loadTasks]); useEffect(() => { if (!autoOpenQrReady.current || qrTask) return; const nextQrTask = tasks .filter((task) => hasMiniQrcode(task) && !autoOpenedQrTaskIds.current.has(task.id)) .sort((a, b) => b.id - a.id)[0]; if (nextQrTask) openQrTask(nextQrTask); }, [openQrTask, qrTask, tasks]); useEffect(() => { if (!autoOpenPayReady.current || payTask) return; const nextPayTask = tasks .filter((task) => hasPaymentQrcode(task) && !isPaymentFinished(task) && !autoOpenedPayTaskIds.current.has(task.id)) .sort((a, b) => b.id - a.id)[0]; if (nextPayTask) openPayTask(nextPayTask); }, [openPayTask, payTask, tasks]); useEffect(() => { if (!payTask) return; const latest = tasks.find((task) => task.id === payTask.id); if (!latest) return; if (isPaymentFinished(latest)) { if (!notifiedPaidTaskIds.current.has(latest.id)) { notifiedPaidTaskIds.current.add(latest.id); message.success('虎牙支付成功'); } setPayTask(null); return; } if (latest !== payTask) setPayTask(latest); }, [payTask, tasks]); useEffect(() => { if (!accountContextMenu) return; const close = () => setAccountContextMenu(null); const closeOnEscape = (event: KeyboardEvent) => { if (event.key === 'Escape') close(); }; window.addEventListener('click', close); window.addEventListener('scroll', close, true); window.addEventListener('resize', close); window.addEventListener('keydown', closeOnEscape); return () => { window.removeEventListener('click', close); window.removeEventListener('scroll', close, true); window.removeEventListener('resize', close); window.removeEventListener('keydown', closeOnEscape); }; }, [accountContextMenu]); const toggleAccountSelection = useCallback((accountId: number) => { setSelectedIds((prev) => ( prev.includes(accountId) ? prev.filter((id) => id !== accountId) : [...prev, accountId] )); }, []); const tagColorMap = useMemo(() => { const map: Record = {}; tags.forEach((tag, index) => { map[tag] = ['blue', 'green', 'cyan', 'geekblue', 'purple', 'orange', 'magenta', 'volcano'][index % 8]; }); return map; }, [tags]); const filteredAccounts = useMemo(() => { const keyword = searchText.trim().toLowerCase(); return accounts.filter((account) => { if (tagFilter && account.tag !== tagFilter) return false; if (!keyword) return true; return ( account.uid.toLowerCase().includes(keyword) || account.yyuid.toLowerCase().includes(keyword) || account.username.toLowerCase().includes(keyword) || account.nickname.toLowerCase().includes(keyword) || account.tag.toLowerCase().includes(keyword) || account.game_name.toLowerCase().includes(keyword) || account.game_channel.toLowerCase().includes(keyword) || account.game_phone.toLowerCase().includes(keyword) || account.cookie_preview.toLowerCase().includes(keyword) ); }); }, [accounts, searchText, tagFilter]); const latestTaskByAccount = useMemo(() => { const map = new Map(); tasks.forEach((task) => { const current = map.get(task.account_id); if (!current || task.id > current.id) map.set(task.account_id, task); }); return map; }, [tasks]); const latestGoodsTaskByAccount = useMemo(() => { const map = new Map(); tasks.forEach((task) => { if (!['exchange_goods', 'create_recharge_order'].includes(task.task_type)) return; const current = map.get(task.account_id); if (!current || task.id > current.id) map.set(task.account_id, task); }); return map; }, [tasks]); const selectedAccounts = useMemo(() => { const selected = new Set(selectedIds); return accounts.filter((account) => selected.has(account.id)); }, [accounts, selectedIds]); const selectedAccountText = selectedAccounts.length === 1 ? accountLabel(selectedAccounts[0]) : `已选 ${selectedIds.length} 个账号`; const sortedGoods = useMemo(() => { return [...goods].sort((a, b) => ( goodsRawNumber(a, 'category_sort') - goodsRawNumber(b, 'category_sort') || goodsRawNumber(a, 'raw_order') - goodsRawNumber(b, 'raw_order') || a.id - b.id )); }, [goods]); const sortedRechargeGoods = useMemo(() => { return [...rechargeGoods].sort((a, b) => { const aOrder = goodsRawNumber({ raw: a.raw } as HuyaGoodsItem, 'raw_order'); const bOrder = goodsRawNumber({ raw: b.raw } as HuyaGoodsItem, 'raw_order'); return aOrder - bOrder || a.id - b.id; }); }, [rechargeGoods]); const rechargeGoodsOptions = useMemo(() => { return sortedRechargeGoods.map((item) => ({ value: item.spu_id, label: `${item.name || item.spu_id}${item.price ? ` / ${formatPriceText(item.price)}` : ''}`, })); }, [sortedRechargeGoods]); const selectedRechargeGoods = useMemo(() => { return sortedRechargeGoods.find((item) => item.spu_id === selectedRechargeGoodsId) || null; }, [sortedRechargeGoods, selectedRechargeGoodsId]); useEffect(() => { if (sortedRechargeGoods.length === 0) { if (selectedRechargeGoodsId) setSelectedRechargeGoodsId(''); return; } if (!sortedRechargeGoods.some((item) => item.spu_id === selectedRechargeGoodsId)) { setSelectedRechargeGoodsId(sortedRechargeGoods[0].spu_id); } }, [selectedRechargeGoodsId, sortedRechargeGoods]); const goodsCategories = useMemo(() => { const map = new Map(); sortedGoods.forEach((item) => { const key = goodsCategoryKey(item); const current = map.get(key); if (current) { current.count += 1; return; } map.set(key, { key, label: goodsCategoryLabel(item), sort: goodsRawNumber(item, 'category_sort'), count: 1, }); }); return Array.from(map.values()).sort((a, b) => a.sort - b.sort || a.label.localeCompare(b.label)); }, [sortedGoods]); useEffect(() => { if (goodsCategories.length === 0) { if (selectedGoodsCategory) setSelectedGoodsCategory(''); return; } if (!goodsCategories.some((item) => item.key === selectedGoodsCategory)) { setSelectedGoodsCategory(goodsCategories[0].key); } }, [goodsCategories, selectedGoodsCategory]); const filteredGoods = useMemo(() => { if (!selectedGoodsCategory) return sortedGoods; return sortedGoods.filter((item) => goodsCategoryKey(item) === selectedGoodsCategory); }, [sortedGoods, selectedGoodsCategory]); const goodsOptions = useMemo(() => { return sortedGoods.map((item) => ({ value: item.product_id, label: `${item.name || item.product_id}${item.price ? ` / ${item.price}积分` : ''}`, })); }, [sortedGoods]); const selectedExchangeGoods = useMemo(() => { return sortedGoods.find((item) => item.product_id === selectedExchangeGoodsId) || null; }, [selectedExchangeGoodsId, sortedGoods]); useEffect(() => { if (sortedGoods.length === 0) { if (selectedExchangeGoodsId) setSelectedExchangeGoodsId(''); return; } if (!sortedGoods.some((item) => item.product_id === selectedExchangeGoodsId)) { setSelectedExchangeGoodsId(sortedGoods[0].product_id); } }, [selectedExchangeGoodsId, sortedGoods]); const createPayload = (taskType: string) => { if (taskType === 'exchange_goods') { return { product_id: selectedExchangeGoods?.product_id || selectedExchangeGoodsId, product_name: selectedExchangeGoods?.name || '', scheduled_at: exchangeAt ? exchangeAt.toISOString() : '', }; } if (taskType !== 'create_recharge_order') return {}; return { spu_id: selectedRechargeGoods?.spu_id || selectedRechargeGoodsId, sku_id: selectedRechargeGoods?.sku_id || '', product_name: selectedRechargeGoods?.name || '', count: rechargeCount, pay_channel: rechargePayChannel, }; }; const startTask = async (taskType = selectedTaskType, accountIds = selectedIds) => { if (accountIds.length === 0) { message.warning('请先选择虎牙 CK'); return; } if (taskType === 'create_recharge_order' && !selectedRechargeGoodsId) { message.warning('请先选择充值商品'); return; } if (taskType === 'exchange_goods' && !selectedExchangeGoodsId) { message.warning('请先选择兑换商品'); return; } setStarting(true); try { const result = await huyaApi.createTasks({ account_ids: accountIds, task_type: taskType, concurrency, payload: createPayload(taskType), }); const finishTask = () => { setBatchId(null); setStarting(false); void loadAll(); }; setBatchId(result.batch_id); message.success(`已创建 ${taskTypes[taskType] || taskType},共 ${result.count} 个账号`); await loadTasks(); connectLogs(`/api/huya/ws/${result.batch_id}`, { onClose: finishTask, onResult: finishTask, onError: finishTask, }); } catch (e: unknown) { message.error(getErrorMessage(e)); setStarting(false); } }; const accountActionItems: MenuProps['items'] = QUICK_ACTIONS.map((item) => ({ key: item.key, icon: item.icon, label: taskTypes[item.key] || item.key, })); const runSingleAccountAction = (taskType: string, account: HuyaAccountItem) => { setSelectedIds([account.id]); setAccountContextMenu(null); void startTask(taskType, [account.id]); }; const runContextAccountAction = (taskType: string) => { if (!accountContextMenu) return; setSelectedIds(accountContextMenu.accountIds); setAccountContextMenu(null); void startTask(taskType, accountContextMenu.accountIds); }; const saveConfig = async () => { setSavingConfig(true); try { const values = await form.validateFields(); const result = await huyaApi.updateConfig(values); form.setFieldsValue(result); message.success('虎牙配置已保存'); } catch (e: unknown) { message.error(getErrorMessage(e)); } finally { setSavingConfig(false); } }; const successCount = tasks.filter((task) => task.status === 'success').length; const plannedCount = tasks.filter((task) => task.status === 'planned').length; const failedCount = tasks.filter((task) => ['failed', 'error'].includes(task.status)).length; const qrResult = qrTask?.result || null; const qrImage = resultText(qrResult, 'mini_qrcode_image'); const qrAccountName = qrTask ? resultProfileNick(qrResult) || qrTask.account_nickname || qrTask.account_uid || `#${qrTask.account_id}` : ''; const payResult = payTask?.result || null; const payUrl = resultText(payResult, 'pay_url'); const payProductName = resultText(payResult, 'product_name'); const payAmountText = resultText(payResult, 'amount_text'); const payChannelLabel = resultText(payResult, 'pay_channel_label'); const payStatus = paymentStatus(payTask); const payStatusText = paymentStatusLabel(payTask); const payOrderId = payResult?.order_id; const payAccountName = payTask ? payTask.account_nickname || payTask.account_uid || `#${payTask.account_id}` : ''; const exchangeRecordsResult = exchangeRecordsTask?.result || null; const exchangeRecordsRaw = exchangeRecordsResult?.records; const exchangeRecords = Array.isArray(exchangeRecordsRaw) ? exchangeRecordsRaw.filter((item) => item && typeof item === 'object') as Record[] : []; const exchangeRecordsAccountName = exchangeRecordsTask ? exchangeRecordsTask.account_nickname || exchangeRecordsTask.account_uid || `#${exchangeRecordsTask.account_id}` : ''; const renderTaskResult = (value: Record | null, record: HuyaTaskItem) => { const bindQrImage = resultText(value, 'mini_qrcode_image'); if (bindQrImage) { return ( ); } if (record.task_type === 'get_bind_qr') { if (value?.can_change_bind === false) { const changeAt = resultText(value, 'change_available_at'); return ( 不可更换 {changeAt ? {changeAt} : null} ); } return -; } if (record.task_type === 'confirm_bind') { if (value?.bind_confirmed === true) { const gameRole = resultObject(value, 'game_role'); const roleName = typeof gameRole?.role_name === 'string' ? gameRole.role_name : ''; return ( 已绑定 {roleName ? {roleName} : null} ); } return -; } if (record.task_type === 'query_game_name') { const roleName = resultText(value, 'role_name'); if (roleName) return {roleName}; if (value?.is_bound === false) return 未绑定; return -; } if (record.task_type === 'query_exchange_records') { const recordCount = value?.record_count; if (typeof recordCount === 'number' && recordCount > 0) { return ( ); } if (typeof recordCount === 'number') return 记录 {recordCount} 条; return -; } if (record.task_type === 'refresh_goods') { const goodsCount = value?.goods_count; if (typeof goodsCount === 'number') return 商品 {goodsCount} 个; return -; } if (record.task_type === 'exchange_goods') { const productName = resultText(value, 'product_name'); const orderId = resultText(value, 'order_id'); if (record.status !== 'success') { return productName ? {productName} : -; } return ( 已兑换 {productName ? {productName} : null} {orderId ? {orderId} : null} ); } if (record.task_type === 'refresh_recharge_goods') { const goodsCount = value?.goods_count; const failedCount = value?.failed_count; if (typeof goodsCount === 'number') { return ( 充值商品 {goodsCount} 个 {typeof failedCount === 'number' && failedCount > 0 ? 失败 {failedCount} : null} ); } return -; } if (record.task_type === 'create_recharge_order') { if (resultText(value, 'pay_url')) { if (isPaymentFinished(record)) return 已支付; const status = paymentStatus(record); return ( {status === 'timeout' ? 待支付 : 等待支付} ); } return -; } const availableScore = value?.available_score; if (typeof availableScore === 'number') { return 可用积分 {availableScore}; } return value ? {JSON.stringify(value)} : -; }; const handleAccountRowClick = (record: HuyaAccountItem, event: ReactMouseEvent) => { if (isInteractiveTarget(event.target)) return; setAccountContextMenu(null); toggleAccountSelection(record.id); }; const handleAccountRowContextMenu = (record: HuyaAccountItem, event: ReactMouseEvent) => { if (isInteractiveTarget(event.target)) return; event.preventDefault(); event.stopPropagation(); const accountIds = selectedIds.includes(record.id) && selectedIds.length > 0 ? selectedIds : [record.id]; setSelectedIds(accountIds); setAccountContextMenu({ account: record, accountIds, x: event.clientX, y: event.clientY, }); }; const accountColumns: TableProps['columns'] = [ { title: '#', width: 56, align: 'center', render: (_: unknown, record) => record.id, }, { title: '虎牙 CK', width: 250, render: (_: unknown, record) => ( {accountDisplayName(record)} {record.tag ? {record.tag} : null} UID {record.uid || record.yyuid || '-'} {record.cookie_preview || '-'} ), }, { title: '手机', dataIndex: 'game_phone', width: 128, ellipsis: true, render: (value: string) => value || -, }, { title: '游戏名', dataIndex: 'game_name', width: 170, ellipsis: true, render: (value: string, record) => value ? ( {value} {record.game_channel ? {record.game_channel} : null} ) : 未查, }, { title: '积分', dataIndex: 'points', width: 92, align: 'center', render: (points: number | null) => points ?? 未查, sorter: (a, b) => (a.points ?? -1) - (b.points ?? -1), }, { title: '兑换商品', width: 190, ellipsis: true, render: (_: unknown, record) => { const task = latestGoodsTaskByAccount.get(record.id); const product = taskProductText(task); if (!task || !product) return -; return ( {product} {taskTypes[task.task_type] || task.task_type} ); }, }, { title: '数据状态', width: 150, render: (_: unknown, record) => { const latest = latestTaskByAccount.get(record.id); return ( {ACCOUNT_STATUS_LABELS[record.status] || record.status || '-'} {latest ? ( {STATUS_LABELS[latest.status] || latest.status} ) : null} ); }, }, { title: '最近结果', width: 260, ellipsis: true, render: (_: unknown, record) => { const latest = latestTaskByAccount.get(record.id); if (!latest) return 暂无任务; return ( {taskTypes[latest.task_type] || latest.task_type}:{latest.message || '-'} {renderTaskResult(latest.result, latest)} ); }, }, { title: '更新时间', dataIndex: 'updated_at', width: 154, render: (value: string | null) => value ? formatTime(value) : -, }, { title: '操作', width: 76, fixed: 'right', align: 'center', render: (_: unknown, record) => { return ( { runSingleAccountAction(String(key), record); }, }} trigger={['click']} >