Files
weflow-new/src/pages/Export/components/TaskCenter/index.tsx
T
2026-06-30 00:18:31 +08:00

149 lines
6.4 KiB
TypeScript

import React, { memo } from 'react'
import { CheckCircle2, XCircle, Loader2, PlayCircle, PauseCircle, Trash2, StopCircle } from 'lucide-react'
import type { ExportTask } from '../../types'
import type { BackgroundTaskRecord } from '../../../../types/backgroundTask'
import {
backgroundTaskSourceLabels,
backgroundTaskStatusLabels
} from '../../constants'
import { formatDurationMs } from '../../utils/format'
import './TaskCenter.scss'
interface TaskCenterProps {
exportTasks: ExportTask[]
onCancelExportTask: (taskId: string) => void
onClearCompletedExportTasks: () => void
backgroundTasks: BackgroundTaskRecord[]
onPauseBackgroundTask: (taskId: string) => void
onResumeBackgroundTask: (taskId: string) => void
onCancelBackgroundTask: (taskId: string) => void
}
const TaskCenter: React.FC<TaskCenterProps> = ({
exportTasks,
onCancelExportTask,
onClearCompletedExportTasks,
backgroundTasks,
onPauseBackgroundTask,
onResumeBackgroundTask,
onCancelBackgroundTask
}) => {
const hasTasks = exportTasks.length > 0 || backgroundTasks.length > 0
if (!hasTasks) return null
const activeExportTasks = exportTasks.filter(t => t.status === 'running' || t.status === 'cancel_requested')
const completedExportTasks = exportTasks.filter(t => t.status === 'success' || t.status === 'error')
return (
<div className="task-center">
<div className="task-center-header">
<h2 className="title">任务中心</h2>
{completedExportTasks.length > 0 && (
<button className="clear-btn" onClick={onClearCompletedExportTasks}>
<Trash2 size={14} /> 清理已完成
</button>
)}
</div>
<div className="task-list">
{/* Export Tasks */}
{exportTasks.map(task => {
const hasMessageProgress = Number(task.progress.phaseTotal) > 0
const phaseLabel = task.progress.phaseLabel || task.progress.phase
const labelAlreadyHasCount = hasMessageProgress && /\d[\d,]*\s*\/\s*\d[\d,]*\s*条/.test(phaseLabel)
const countText = hasMessageProgress
? `${Math.max(0, Math.floor(Number(task.progress.phaseProgress) || 0)).toLocaleString()} / ${Math.max(0, Math.floor(Number(task.progress.phaseTotal) || 0)).toLocaleString()} 条`
: `${Math.max(0, Math.floor(Number(task.progress.current) || 0))} / ${Math.max(0, Math.floor(Number(task.progress.total) || 0))}`
return (
<div key={task.id} className={`task-card export-task status-${task.status}`}>
<div className="task-card-header">
<div className="task-info">
<span className="task-title">{task.title}</span>
<span className="task-status-badge">
{task.status === 'running' && <Loader2 className="spin" size={12} />}
{task.status === 'success' && <CheckCircle2 size={12} />}
{task.status === 'error' && <XCircle size={12} />}
{task.status === 'cancel_requested' && <Loader2 className="spin" size={12} />}
{task.status === 'running' ? '导出中' : task.status === 'success' ? '已完成' : task.status === 'cancel_requested' ? '取消中' : '失败'}
</span>
</div>
<div className="task-actions">
{task.status === 'running' && (
<button className="action-btn cancel" onClick={() => onCancelExportTask(task.id)} title="取消任务">
<StopCircle size={16} />
</button>
)}
</div>
</div>
<div className="task-progress-area">
<div className="progress-bar-bg">
<div
className="progress-bar-fill"
style={{ width: `${Math.max(0, Math.min(100, (task.progress.current / Math.max(1, task.progress.total)) * 100))}%` }}
/>
</div>
<div className="progress-stats">
<span className="phase">{phaseLabel}</span>
{!labelAlreadyHasCount && <span className="count">{countText}</span>}
</div>
{task.error && <div className="task-error-msg">{task.error}</div>}
</div>
</div>
)
})}
{/* Global Background Tasks */}
{backgroundTasks.length > 0 && (
<div className="bg-tasks-section">
<h3 className="section-title">全局后台任务</h3>
{backgroundTasks.map(bgTask => {
const isPausable = bgTask.status === 'running'
const isResumable = bgTask.status === 'paused'
const isCancelable = bgTask.status !== 'completed' && bgTask.status !== 'canceled' && bgTask.status !== 'failed'
return (
<div key={bgTask.id} className={`task-card bg-task status-${bgTask.status}`}>
<div className="task-card-header">
<div className="task-info">
<span className="task-title">{backgroundTaskSourceLabels[bgTask.sourcePage] || '后台任务'}</span>
<span className="task-status-badge">{backgroundTaskStatusLabels[bgTask.status]}</span>
</div>
<div className="task-actions">
{isPausable && (
<button className="action-btn" onClick={() => onPauseBackgroundTask(bgTask.id)} title="暂停">
<PauseCircle size={16} />
</button>
)}
{isResumable && (
<button className="action-btn" onClick={() => onResumeBackgroundTask(bgTask.id)} title="恢复">
<PlayCircle size={16} />
</button>
)}
{isCancelable && (
<button className="action-btn cancel" onClick={() => onCancelBackgroundTask(bgTask.id)} title="取消">
<StopCircle size={16} />
</button>
)}
</div>
</div>
{bgTask.progressText && (
<div className="task-progress-text">
{bgTask.progressText}
</div>
)}
</div>
)
})}
</div>
)}
</div>
</div>
)
}
export default memo(TaskCenter)