import { CloseCircleOutlined, ReloadOutlined, SearchOutlined, SwapOutlined, UserSwitchOutlined, } from '@ant-design/icons' import { Alert, App, Button, Card, DatePicker, Form, Input, Select, Space, Table, Typography } from 'antd' import type { TableColumnsType } from 'antd' import { useQuery } from '@tanstack/react-query' import dayjs from 'dayjs' import { useMemo, useState } from 'react' import { useNavigate, useSearchParams } from 'react-router' import PageHeader from '@/components/admin/PageHeader' import StatusTag from '@/components/admin/StatusTag' import { closeAdminTask, fetchAdminTasks, markAdminTaskManualReview, rebindAdminTaskKuaishouCloudRole, retryAdminTask, } from '@/services/admin' import type { AdminTaskActionResponse, AdminTaskListItem } from '@/types/admin' import { hasAdminRole } from '@/utils/admin-auth' import { adminTaskStatusOptions } from '@/utils/admin-options' import { formatAdminDateTime } from '@/utils/admin-time' type TaskFilterForm = { status?: string platformOrderId?: string taskNo?: string skuCode?: string roleId?: string dateRange?: unknown } type TaskListAction = 'retry' | 'rebind_role' | 'manual_review' | 'close' export default function AdminTasksPage() { const navigate = useNavigate() const { message, modal } = App.useApp() const [searchParams, setSearchParams] = useSearchParams() const [actionLoadingId, setActionLoadingId] = useState(null) const [lastClaimUrl, setLastClaimUrl] = useState('') const page = Number(searchParams.get('page') || 1) || 1 const pageSize = Number(searchParams.get('pageSize') || 20) || 20 const canManageTaskLifecycle = hasAdminRole('operator') const canCloseTasks = hasAdminRole('support') const filters = { status: searchParams.get('status') || '', platformOrderId: searchParams.get('platformOrderId') || '', taskNo: searchParams.get('taskNo') || '', skuCode: searchParams.get('skuCode') || '', roleId: searchParams.get('roleId') || '', dateFrom: searchParams.get('dateFrom') || '', dateTo: searchParams.get('dateTo') || '', } const queryParams = useMemo( () => ({ page, pageSize, ...filters }), [ filters.dateFrom, filters.dateTo, filters.platformOrderId, filters.roleId, filters.skuCode, filters.status, filters.taskNo, page, pageSize, ], ) const query = useQuery({ queryKey: ['admin-tasks', queryParams], queryFn: () => fetchAdminTasks(queryParams), }) const data = query.data?.data const columns: TableColumnsType = [ { title: '任务 / 订单', minWidth: 230, render: (_, row) => (
navigate(`/admin/tasks/${row.taskId}`)}> {row.taskNo} {row.platformOrderId}
), }, { title: '商品', minWidth: 210, render: (_, row) => (
{row.skuName || '-'} {row.skuCode || row.executorKey}
), }, { title: '状态', minWidth: 250, render: (_, row) => ( ), }, { title: '角色', minWidth: 180, render: (_, row) => (
{row.roleName || '-'} {row.roleId || row.loginType || '-'}
), }, { title: '时间', minWidth: 220, render: (_, row) => (
{formatAdminDateTime(row.updatedAt)} 创建 {formatAdminDateTime(row.createdAt)}
), }, { title: '异常 / 操作', minWidth: 260, fixed: 'right', render: (_, row) => { const rowLoading = actionLoadingId === row.taskId const actionRunning = actionLoadingId !== null const canRetry = canManageTaskLifecycle && row.executorKey !== 'manual_dispatch' && ['retry_pending', 'manual_review'].includes(row.status) const canRebind = canRebindListTask(row, canManageTaskLifecycle) const canClose = canCloseTasks && !['redeemed', 'closed'].includes(row.status) const hasActions = canManageTaskLifecycle || canRebind || canClose return (
{row.lastError || '当前无异常'} {hasActions ? ( {canManageTaskLifecycle ? ( ) : null} {canRebind ? ( ) : null} {canManageTaskLifecycle ? ( ) : null} {canClose ? ( ) : null} ) : null}
) }, }, ] function handleActionCommand(actionKey: TaskListAction, item: AdminTaskListItem) { const actionConfig = resolveTaskListAction(actionKey, item) modal.confirm({ title: '确认操作', content: actionConfig.confirmText, okText: '继续执行', cancelText: '取消', centered: true, onOk: async () => { setActionLoadingId(item.taskId) try { const response = await actionConfig.action() if (response.data.claimUrl) { setLastClaimUrl(response.data.claimUrl) } message.success(actionConfig.successMessage) await query.refetch() } catch (error) { message.error(error instanceof Error ? error.message : '操作失败') } finally { setActionLoadingId(null) } }, }) } function resolveTaskListAction(actionKey: TaskListAction, item: AdminTaskListItem): { action: () => Promise<{ data: AdminTaskActionResponse }> confirmText: string successMessage: string } { if (actionKey === 'retry') { return { action: () => retryAdminTask(item.taskId), confirmText: `确认重试任务 ${item.taskNo} 吗?`, successMessage: '任务已重试', } } if (actionKey === 'rebind_role') { return { action: () => rebindAdminTaskKuaishouCloudRole(item.taskId), confirmText: `确认为任务 ${item.taskNo} 换绑角色吗?当前虚拟号会退还,并生成新的绑定二维码。`, successMessage: '角色换绑资源已准备完成', } } if (actionKey === 'manual_review') { return { action: () => markAdminTaskManualReview(item.taskId), confirmText: `确认将任务 ${item.taskNo} 转为人工处理吗?`, successMessage: '任务已转人工处理', } } return { action: () => closeAdminTask(item.taskId), confirmText: `确认关闭任务 ${item.taskNo} 吗?关闭后不会自动继续推进。`, successMessage: '任务已关闭', } } function applyFilters(values: TaskFilterForm) { const { dateRange, ...restValues } = values const [dateFrom, dateTo] = Array.isArray(dateRange) ? dateRange.map((item: { format?: (pattern: string) => string }) => item?.format ? item.format('YYYY-MM-DD') : '', ) : ['', ''] setSearchParams( cleanParams({ ...restValues, dateFrom, dateTo, page: 1, pageSize, }), ) } function resetFilters() { setSearchParams(cleanParams({ page: 1, pageSize })) } return (
layout="inline" initialValues={{ ...filters, dateRange: filters.dateFrom && filters.dateTo ? [dayjs(filters.dateFrom), dayjs(filters.dateTo)] : undefined, }} onFinish={applyFilters} className="filter-form" > {lastClaimUrl ? ( {lastClaimUrl} } /> ) : null} rowKey="taskId" loading={query.isLoading || query.isFetching} columns={columns} dataSource={data?.items || []} scroll={{ x: 1240 }} pagination={{ current: data?.pagination.page || page, pageSize: data?.pagination.pageSize || pageSize, total: data?.pagination.total || 0, showSizeChanger: true, showTotal: (total) => `共 ${total} 条`, onChange: (nextPage, nextPageSize) => { setSearchParams(cleanParams({ ...filters, page: nextPage, pageSize: nextPageSize })) }, }} />
) } function canRebindListTask(item: AdminTaskListItem, canManageTaskLifecycle: boolean) { return ( canManageTaskLifecycle && item.executorKey === 'kuaishou_ct_assisted' && ['waiting_binding', 'role_confirmed', 'manual_review', 'retry_pending'].includes(item.status) ) } function cleanParams(params: Record) { const next = new URLSearchParams() Object.entries(params).forEach(([key, value]) => { const normalized = String(value ?? '').trim() if (normalized) { next.set(key, normalized) } }) return next }