import { CloseCircleOutlined, ReloadOutlined, SearchOutlined, SwapOutlined, UserSwitchOutlined, } from '@ant-design/icons' import { Alert, App, Button, Card, Form, Input, Select, Space, Table, Tag, Tooltip, Typography, } from 'antd' import type { TableColumnsType } from 'antd' import { useQuery } from '@tanstack/react-query' import { useMemo, useState } from 'react' import { useNavigate, useSearchParams } from 'react-router' import { AdminRangePicker } from '@/components/admin/AdminDatePicker' import PageHeader from '@/components/admin/PageHeader' import StatusTag from '@/components/admin/StatusTag' import dayjs from '@/lib/dayjs' 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 { ADMIN_DEFAULT_PAGE_SIZE, buildAdminTablePagination, resolveAdminPageSize, } from '@/utils/admin-pagination' 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' type TaskListBlocker = { label: string color: string level: 'normal' | 'error' | 'done' detail: string /** 列表副文案:完成态默认隐藏,异常/进行中显示 */ showDetail: boolean } 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 = resolveAdminPageSize(searchParams.get('pageSize'), ADMIN_DEFAULT_PAGE_SIZE) 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: 210, render: (_, row) => (
navigate(`/admin/tasks/${row.taskId}`)}> {row.taskNo} {row.platformOrderId}
), }, { title: '商品', minWidth: 200, render: (_, row) => { const executor = resolveTaskExecutorDisplay(row.executorKey) return (
{row.skuName || '-'} {row.skuCode || '-'} {executor.label}
) }, }, { title: '当前卡点', minWidth: 200, render: (_, row) => { const blocker = resolveTaskListBlocker(row) return (
{blocker.label} {blocker.showDetail ? ( {blocker.detail} ) : null}
) }, }, { title: '状态', minWidth: 120, render: (_, row) => (
交付
资源
客户
} >
), }, { title: '角色', minWidth: 140, render: (_, row) => (
{row.roleName || '-'} {row.roleId || row.loginType ? ( {row.roleId || row.loginType} ) : null}
), }, { title: '更新时间', minWidth: 160, render: (_, row) => ( {formatAdminDateTime(row.updatedAt)} ), }, { title: '操作', minWidth: 200, 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 canManualReview = canManageTaskLifecycle && !['redeemed', 'closed', 'completed', 'manual_review'].includes(row.status) const canClose = canCloseTasks && !['redeemed', 'closed'].includes(row.status) const hasActions = canRetry || canRebind || canManualReview || canClose return (
{row.lastError ? ( {row.lastError} ) : null} {hasActions ? ( {canRetry ? ( ) : null} {canRebind ? ( ) : null} {canManualReview ? ( ) : null} {canClose ? ( ) : 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: 1280 }} size="middle" rowClassName={(row) => resolveTaskListBlocker(row).level === 'error' ? 'task-list-row-error' : '' } pagination={buildAdminTablePagination({ current: data?.pagination.page || page, pageSize: data?.pagination.pageSize || pageSize, total: data?.pagination.total || 0, 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 resolveTaskExecutorDisplay(value: unknown) { const executorKey = String(value || '').trim() if (executorKey === 'kuaishou_ct_assisted') { return { key: executorKey, label: '乐玩云', color: 'blue' } } if (executorKey === 'kuaishou-industry') { return { key: executorKey, label: '行业凭证', color: 'cyan' } } if (executorKey === 'kuaishou_feifei') { return { key: executorKey, label: '飞飞', color: 'purple' } } if (executorKey === 'manual_dispatch') { return { key: executorKey, label: '人工履约', color: 'orange' } } return { key: executorKey, label: executorKey || '未分配', color: 'default', } } function resolveTaskListBlocker(item: AdminTaskListItem): TaskListBlocker { const taskStatus = normalizeTaskListStatus(item.status) const deliveryStatus = normalizeTaskListStatus(item.deliveryStatus) const resourceStatus = normalizeTaskListStatus(item.resourceStatus) const customerStatus = normalizeTaskListStatus(item.customerStatus) const errorText = item.lastError || item.resultMessage if ( ['completed', 'redeemed'].includes(taskStatus) || ['delivered', 'completed', 'success', 'consumed'].includes(deliveryStatus) ) { return { label: '已完成', color: 'green', level: 'done', detail: item.resultMessage || '履约链路已完成。', showDetail: false, } } if (taskStatus === 'closed') { return { label: '已关闭', color: 'default', level: 'done', detail: item.resultMessage || item.lastError || '任务已关闭,不会继续自动推进。', showDetail: false, } } if ( item.lastError || ['manual_review', 'retry_pending', 'failed'].includes(taskStatus) || ['resource_exception', 'binding_exception', 'failed'].includes(resourceStatus) || ['customer_exception', 'failed'].includes(customerStatus) || ['failed'].includes(deliveryStatus) ) { return { label: '需处理', color: 'red', level: 'error', detail: errorText || '需要进入详情页按履约层级检查并重试。', showDetail: true, } } if (item.executorKey === 'manual_dispatch') { return { label: '人工履约', color: 'orange', level: 'normal', detail: item.resultMessage || '等待人工回写履约结果。', showDetail: true, } } if (['pending_config'].includes(resourceStatus) || !item.executorKey) { return { label: '待配置', color: 'gold', level: 'normal', detail: item.resultMessage || '商品还没有命中明确的履约配置。', showDetail: true, } } if ( ['not_started', 'pending', 'pending_prepare', 'pending_binding', 'pending_binding_prepare'].includes( resourceStatus, ) ) { return { label: '等资源', color: 'gold', level: 'normal', detail: item.resultMessage || '等待准备履约资源,可在详情页触发对应动作。', showDetail: true, } } if ( ['waiting_customer', 'waiting_binding', 'waiting_user_claim', 'user_binding'].includes( customerStatus, ) ) { return { label: '等客户', color: 'blue', level: 'normal', detail: item.roleConfirmedAt ? '客户已提交角色信息,等待平台履约收口。' : '等待客户打开链接、绑定或提交信息。', showDetail: true, } } if (deliveryStatus && !['pending', 'not_started'].includes(deliveryStatus)) { return { label: '平台处理中', color: 'cyan', level: 'normal', detail: item.resultMessage || '平台履约执行中,可在详情页查看各层进度。', showDetail: true, } } return { label: '进行中', color: 'blue', level: 'normal', detail: item.resultMessage || '当前没有明确异常,等待任务自动推进。', showDetail: Boolean(item.resultMessage), } } function normalizeTaskListStatus(value: unknown) { return String(value || '').trim().toLowerCase() } 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 }