import { useCallback, useEffect, useState } from 'react' import toast from 'react-hot-toast' import { Ban, Download, Loader2, RefreshCw, Trash2, Upload } from 'lucide-react' import { strmAPI } from '../api/strm' import type { StrmQueueSnapshot, StrmTask, StrmTaskStatus } from '../types/strm' import { STRM_PROVIDER_LABELS } from '../types/strm' import { apiErrorMessage, formatBytes, formatTime, taskStatusMeta } from './StrmManagePage' const FILTERS: { key: 'all' | StrmTaskStatus; label: string }[] = [ { key: 'all', label: '全部' }, { key: 'pending', label: '排队中' }, { key: 'running', label: '进行中' }, { key: 'done', label: '已完成' }, { key: 'failed', label: '失败' }, { key: 'canceled', label: '已取消' }, ] const PAGE_SIZE = 50 function StrmQueuePanel({ kind }: { kind: 'download' | 'upload' }) { const [snapshot, setSnapshot] = useState(null) const [filter, setFilter] = useState<'all' | StrmTaskStatus>('all') const [page, setPage] = useState(1) const [totalPages, setTotalPages] = useState(1) const [loading, setLoading] = useState(true) const [batchBusy, setBatchBusy] = useState(false) const refresh = useCallback(async () => { try { const status = filter === 'all' ? undefined : filter const data = kind === 'download' ? await strmAPI.downloads(status, page, PAGE_SIZE) : await strmAPI.uploads(status, page, PAGE_SIZE) const tp = Math.max(1, Math.ceil((data.total ?? data.tasks.length) / PAGE_SIZE)) if (page > tp) { // 当前页超出范围(数据被批量删除),回退到最后一页再刷新 setPage(tp) return } setTotalPages(tp) setSnapshot(data) } catch { /* keep last data */ } finally { setLoading(false) } }, [kind, filter, page]) useEffect(() => { refresh().catch(() => undefined) const timer = setInterval(() => refresh().catch(() => undefined), 3000) return () => clearInterval(timer) }, [refresh]) const cancelTask = async (task: StrmTask) => { try { if (kind === 'download') await strmAPI.cancelDownload(task.id) else await strmAPI.cancelUpload(task.id) toast.success('已取消任务') await refresh() } catch (err) { toast.error(apiErrorMessage(err)) } } const retryTask = async (task: StrmTask) => { try { if (kind === 'download') await strmAPI.retryDownload(task.id) else await strmAPI.retryUpload(task.id) toast.success('已重新入队') await refresh() } catch (err) { toast.error(apiErrorMessage(err)) } } // 批量操作:可选确认弹窗,操作中锁定按钮,结束后刷新。 const runBatch = async ( action: () => Promise<{ deleted?: number; retried?: number; canceled?: number }>, confirmMsg?: string, ) => { if (confirmMsg && !window.confirm(confirmMsg)) return setBatchBusy(true) try { const res = await action() if (res.deleted !== undefined) toast.success(`已清空 ${res.deleted} 条记录`) else if (res.retried !== undefined) toast.success(`已重新入队 ${res.retried} 个任务`) else if (res.canceled !== undefined) toast.success(`已取消 ${res.canceled} 个任务`) await refresh() } catch (err) { toast.error(apiErrorMessage(err)) } finally { setBatchBusy(false) } } const batchBtn = (title: string, cls: string, onClick: () => void) => ( ) const batchActionByFilter = () => { if (!isDownload) return null if (filter === 'done') return batchBtn( '清空成功记录', 'border-gray-200 text-rose-500 hover:bg-rose-50', () => runBatch(() => strmAPI.clearDoneDownloads(), '确定清空所有已完成下载记录?'), ) if (filter === 'failed') return batchBtn('批量重试', 'border-gray-200 text-brand-500 hover:bg-brand-50', () => runBatch(() => strmAPI.retryFailedDownloads(), '确定重新入队所有失败下载任务?'), ) if (filter === 'pending') return batchBtn( '批量取消', 'border-gray-200 text-amber-600 hover:bg-amber-50', () => runBatch(() => strmAPI.cancelPendingDownloads(), '确定取消所有排队中的下载任务?'), ) return null } const counts = snapshot?.counts const tasks = snapshot?.tasks.filter((t) => filter === 'all' || t.status === filter) ?? [] const isDownload = kind === 'download' const Icon = isDownload ? Download : Upload return (

{isDownload ? '下载队列' : '上传队列'}

{isDownload ? 'STRM 元数据下载情况(远端网盘 → 本地输出目录,3 秒自动刷新)' : 'STRM 元数据上传情况(本地 → 远端网盘,3 秒自动刷新)'}

{isDownload && ( )}
{FILTERS.map((item) => ( ))} {batchActionByFilter()}
{loading ? (
) : tasks.length === 0 ? (

{filter === 'all' ? (isDownload ? '暂无元数据下载任务' : '暂无元数据上传任务') : '该状态下暂无任务'}

) : (
{tasks.map((task) => { const status = taskStatusMeta(task.status) return ( ) })}
文件 提供方 {isDownload ? '本地目标' : '本地来源'} 远端路径 大小 状态 创建时间 操作
{task.file_name} {STRM_PROVIDER_LABELS[task.provider] ?? task.provider} {task.retry_count > 0 && ( 重试 {task.retry_count} )} {task.local_path} {task.remote_path} {formatBytes(task.size)} {task.status === 'running' && } {status.label} {task.error && (

{task.error}

)}
{formatTime(task.created_at)} {(task.status === 'pending' || task.status === 'running') && ( )} {(task.status === 'failed' || task.status === 'canceled') && ( )}
)} {(snapshot?.total ?? 0) > 0 && (
共 {snapshot?.total ?? 0} 条 · 第 {page} / {totalPages} 页
)}
) } function StatPill({ label, value, cls }: { label: string; value: number; cls: string }) { return (
{label} {value}
) } export function StrmDownloadQueuePage() { return } export function StrmUploadQueuePage() { return }