'use client' import type { FC } from 'react' import React, { useCallback, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import { RiCheckLine, RiErrorWarningLine, RiLoader2Line, RiPauseLine, RiPlayLargeLine, RiRefreshLine, RiStopLine, } from '@remixicon/react' import { fetchProgressApi, resumeBatchApi, retryFailedTasksApi, stopBatchApi } from '@/service/web-extend' // extend: 批量运行工单 import type { BatchStatus } from '@/utils/batch-progress-manager' // extend: 批量运行工单 import ActionButton from '@/app/components/base/action-button' import { cn } from '@/utils/classnames' export type BatchProgressProps = { batchId: string fileName: string workflowId?: string jobData: { id: string fileName: string createdAt: string status: string totalRows: number processedRows: number error?: string } onDownload: () => void onRetrySuccess?: () => void onJobUpdate?: (jobData: { status: string, processedRows: number, error?: string }) => void // 新增:任务更新回调 } const BatchProgress: FC = ({ batchId, fileName, workflowId, jobData, onDownload, onRetrySuccess, onJobUpdate, }) => { const { t } = useTranslation() const [isLoading, setIsLoading] = useState(false) // 本地进度状态,用于独立刷新 const [localProgress, setLocalProgress] = useState({ status: jobData.status, processedRows: jobData.processedRows, totalRows: jobData.totalRows, error: jobData.error, }) // 自动刷新单个任务的进度(每 3 秒) useEffect(() => { // 只在任务进行中时刷新 if (localProgress.status !== 'pending' && localProgress.status !== 'processing') return const refreshInterval = setInterval(async () => { try { const progress = await fetchProgressApi(batchId) if (progress) { const newStatus = progress.status as string const newProcessedRows = progress.processed_rows as number const newError = progress.error as string | undefined // 只有当数据有变化时才更新 if ( newStatus !== localProgress.status || newProcessedRows !== localProgress.processedRows || newError !== localProgress.error ) { const updatedProgress = { status: newStatus, processedRows: newProcessedRows, totalRows: progress.total_rows as number, error: newError, } setLocalProgress(updatedProgress) // 通知父组件数据已更新(用于列表级别的状态同步) onJobUpdate?.({ status: newStatus, processedRows: newProcessedRows, error: newError, }) } } } catch (error) { console.error('Failed to fetch progress:', error) } }, 3000) return () => clearInterval(refreshInterval) }, [batchId, localProgress.status, localProgress.processedRows, localProgress.error, onJobUpdate]) // 停止批量处理 const handleStop = async () => { setIsLoading(true) try { const success = await stopBatchApi(batchId) if (success) { // 通知父组件刷新列表 onRetrySuccess?.() } } catch (error) { console.error('Failed to stop batch:', error) } finally { setIsLoading(false) } } // 恢复批量处理 const handleResume = async () => { setIsLoading(true) try { const success = await resumeBatchApi(batchId) if (success) { // 通知父组件刷新列表 onRetrySuccess?.() } } catch (error) { console.error('Failed to resume batch:', error) } finally { setIsLoading(false) } } // 重试失败任务(仅重试失败的任务,保留已完成的任务) const handleRetry = async () => { setIsLoading(true) try { const success = await retryFailedTasksApi(batchId) if (success) { // 通知父组件刷新列表 onRetrySuccess?.() } } catch (error) { console.error('Failed to retry failed tasks:', error) } finally { setIsLoading(false) } } const getStatusText = (status: BatchStatus) => { switch (status) { case 'pending': return t('batchWorkflow.pending', { ns: 'extend'}) case 'processing': return t('batchWorkflow.processing', { ns: 'extend'}) case 'completed': return t('batchWorkflow.completed', { ns: 'extend'}) case 'failed': return t('batchWorkflow.failed', { ns: 'extend'}) case 'stopped': return t('batchWorkflow.stopped', { ns: 'extend'}) default: return t('batchWorkflow.pending', { ns: 'extend'}) } } const getStatusColor = (status: BatchStatus) => { switch (status) { case 'pending': return 'text-gray-500' // Extend: 批量运行工单 case 'processing': return 'text-blue-700' // Extend: 批量运行工单 case 'completed': return 'text-green-500' case 'failed': return 'text-red-500' case 'stopped': return 'text-yellow-500' default: return 'text-gray-500' // Extend: 批量运行工单 } } const formatDate = (dateString: string) => { if (!dateString) return '-' const date = new Date(dateString) // 检查日期是否有效 if (isNaN(date.getTime())) return '-' return date.toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', }) } const currentTime = new Date().toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', }) // 计算进度 const progress = localProgress.totalRows > 0 ? (localProgress.processedRows / localProgress.totalRows) * 100 : 0 const status = localProgress.status as BatchStatus const failed_count = 0 // 从列表API没有这个字段,如果需要可以后续添加 const getBorderColor = (status: BatchStatus) => { switch (status) { case 'pending': return 'border-gray-300' case 'processing': return 'border-blue-500' case 'completed': return 'border-green-500' case 'failed': return 'border-red-500' case 'stopped': return 'border-yellow-500' default: return 'border-gray-300' } } return (
{/* 统一的批量任务信息框 */}
{/* 文件信息 */}
{t('batchWorkflow.uploadedFileName', { ns: 'extend' })}
{t('batchWorkflow.uploadTime', { ns: 'extend' })}
{fileName}
{formatDate(jobData.createdAt)}
{/* 进度条 */}
{status === 'processing' && } {status === 'completed' && } {status === 'failed' && } {status === 'pending' && } {status === 'stopped' && } {getStatusText(status)}
{isNaN(progress) ? '0' : Math.round(progress)}%
{/* 进度条可视化 */}
{/* 详细进度信息 */} {localProgress.totalRows > 0 && (
{t('batchWorkflow.processed', { processed: localProgress.processedRows || 0, total: localProgress.totalRows || 0, ns: 'extend', })}
)} {/* 错误信息显示 */} {localProgress.error && status === 'failed' && (
{t('batchWorkflow.errorOccurred', { ns: 'extend'} )}
{localProgress.error}
)}
{/* 操作按钮区域 */}
{/* 控制按钮 */} {(status === 'processing' || status === 'pending') && ( {isLoading ? ( ) : ( )} {t('batchWorkflow.stop', { ns: 'extend'})} )} {status === 'stopped' && ( {isLoading ? ( ) : ( )} {t('batchWorkflow.resume', { ns: 'extend'})} )} {(status === 'failed') && ( {isLoading ? ( ) : ( )} {t('batchWorkflow.retry', { ns: 'extend'})} )}
{/* 下载按钮 */} {(status === 'failed' || status === 'completed' || (status === 'processing' && progress >= 100)) && ( {t('batchWorkflow.download', { ns: 'extend'})} )}
) } export default React.memo(BatchProgress)