'use client' import { useCallback, useEffect, useRef, useState } from 'react' import { AlertCircle, Check, Loader2, X } from 'lucide-react' export type PurgeJobView = { id: string; materialId: string; name: string; status: string totalFiles: number; processedFiles: number; sharedFiles: number; error: string | null } export function useMaterialPurgeJobs() { const [jobs, setJobs] = useState([]) const [error, setError] = useState(null) const [retryError, setRetryError] = useState(null) const [retrying, setRetrying] = useState(null) const tracked = useRef([]) const mounted = useRef(false) const dismissed = useRef(new Set()) const loading = useRef(false) const track = useCallback((next: PurgeJobView[]) => { tracked.current = Array.from(new Set([...next.map(job => job.id), ...tracked.current])).slice(0, 100) setJobs(previous => [...next, ...previous.filter(job => !next.some(item => item.id === job.id))]) }, []) const refresh = useCallback(async () => { if (loading.current) return loading.current = true try { const params = new URLSearchParams() tracked.current.forEach(id => params.append('id', id)) const response = await fetch(`/api/admin/materials/purge-jobs?${params}`, { cache: 'no-store' }) if (!response.ok) throw new Error('清理进度暂不可用') const result = await response.json() if (!mounted.current) return // 页面刷新后接管正在处理的任务,也要跟踪它们的最终完成状态。 tracked.current = Array.from(new Set([ ...result.jobs.filter((job: PurgeJobView) => job.status !== 'completed').map((job: PurgeJobView) => job.id), ...tracked.current, ])).slice(0, 100) setJobs(previous => { const next: PurgeJobView[] = result.jobs.filter((job: PurgeJobView) => !dismissed.current.has(job.id)) // 请求开始之后新提交的任务,不能被旧的列表响应抹掉。 return [...next, ...previous.filter(job => tracked.current.includes(job.id) && !next.some(item => item.id === job.id))] }) setError(null) } catch { if (mounted.current) setError('清理进度暂不可用') } finally { loading.current = false } }, []) useEffect(() => { mounted.current = true void refresh() return () => { mounted.current = false } }, [refresh]) const hasPending = jobs.some(job => job.status === 'queued' || job.status === 'processing') const pollMs = hasPending && !error ? 500 : 3000 useEffect(() => { const timer = setInterval(() => { if (!document.hidden) void refresh() }, pollMs) return () => clearInterval(timer) }, [refresh, pollMs]) const retry = async (id: string) => { setRetrying(id) setRetryError(null) try { const response = await fetch('/api/admin/materials/purge-jobs', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ jobId: id }), }) const result = await response.json() if (!response.ok) throw new Error(result.error || '重试失败,请稍后再试') track([result.job]) setError(null) } catch (failure: any) { setRetryError(failure.message || '重试失败,请稍后再试') } finally { setRetrying(null) } } const failed = jobs.filter(job => job.status === 'failed') const pending = jobs.filter(job => job.status === 'queued' || job.status === 'processing') const completed = jobs.filter(job => job.status === 'completed') const shared = completed.reduce((sum, job) => sum + job.sharedFiles, 0) const status = jobs.length || error || retryError ? (
{pending.length > 0 && } {completed.length > 0 && } {error && {error} }
{retryError &&

{retryError}

} {failed.length > 0 &&
    {failed.map(job =>
  • )}
}
) : null return { track, status } }