gongxiangp2/components/upload/UploadProgress.tsx
2026-07-04 14:31:24 +08:00

182 lines
7.2 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client'
import type { BatchUploadStatus, BatchFileItem } from './upload-types'
import { formatTransferProgress } from './upload-utils'
interface UploadProgressProps {
uploading: boolean
isBatchMode: boolean
batchUploadStatus: BatchUploadStatus | null
batchFiles?: BatchFileItem[] // 用于计算实际文件大小
uploadProgress: number
uploadSpeed: number
uploadTotalBytes?: number
isProcessing: boolean
onCancel: () => void
}
export default function UploadProgress({
uploading,
isBatchMode,
batchUploadStatus,
batchFiles,
uploadProgress,
uploadSpeed,
uploadTotalBytes,
isProcessing,
onCancel,
}: UploadProgressProps) {
if (!uploading) return null
if (isBatchMode && batchUploadStatus) {
const { currentFileIndex, totalFiles, currentFileName, currentFileProgress, currentFileSpeed, fileStatuses } = batchUploadStatus
const completedFiles = fileStatuses.filter(s => s.status === 'done').length
const failedFiles = fileStatuses.filter(s => s.status === 'failed').length
const processingFiles = fileStatuses.filter(s => s.status === 'uploading' || s.status === 'processing' || s.status === 'pending')
const currentBatchFile = batchFiles?.find(bf => bf.file.name === currentFileName)
const currentFileTotalBytes = currentBatchFile?.file.size || 0
const currentFileLoadedBytes = currentFileTotalBytes * (currentFileProgress / 100)
// 只显示正在处理/等待中的文件,已完成的文件不显示(避免队列过长)
const visibleFileStatuses = fileStatuses.filter(s => s.status !== 'done')
return (
<div className="space-y-4">
{/* 整体进度 */}
<div className="space-y-2">
<div className="flex items-center justify-between text-sm">
<span className="text-gray-700 dark:text-gray-300">
整体进度: {completedFiles}/{totalFiles} 完成
{failedFiles > 0 && <span className="text-red-600 ml-2">({failedFiles} 失败)</span>}
</span>
<span className="text-gray-600 dark:text-gray-400">{uploadProgress}%</span>
</div>
<div className="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2">
<div
className="bg-blue-600 h-2 rounded-full transition-all duration-300"
style={{ width: `${uploadProgress}%` }}
/>
</div>
</div>
{/* 当前文件进度 */}
<div className="border border-gray-200 dark:border-gray-700 rounded-apple p-3 space-y-2">
<div className="flex items-center justify-between text-sm">
<span className="text-gray-700 dark:text-gray-300 font-medium">
当前文件 ({currentFileIndex + 1}/{totalFiles}): {currentFileName}
</span>
<span className="text-gray-600 dark:text-gray-400">{currentFileProgress}%</span>
</div>
<div className="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2">
<div
className="bg-green-600 h-2 rounded-full transition-all duration-300"
style={{ width: `${currentFileProgress}%` }}
/>
</div>
{currentFileSpeed > 0 && (
<div className="text-xs text-gray-500 dark:text-gray-400">
<span>
{formatTransferProgress(
currentFileSpeed,
currentFileLoadedBytes,
currentFileTotalBytes || undefined,
)}
</span>
</div>
)}
</div>
{/* 文件状态列表 - 只显示正在处理/等待中/失败的文件 */}
{visibleFileStatuses.length > 0 && (
<div className="space-y-3">
<div className="text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide">
队列 ({processingFiles.length} 个{failedFiles > 0 && `,${failedFiles} 个失败`})
</div>
<div className="max-h-60 overflow-y-auto space-y-2">
{visibleFileStatuses.map((status, idx) => (
<div
key={idx}
className={`flex items-center justify-between text-sm p-2 rounded ${
status.status === 'failed'
? 'bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800'
: 'bg-gray-50 dark:bg-gray-800'
}`}
>
<div className="flex items-center space-x-2 flex-1 min-w-0">
{status.status === 'done' && <span className="text-green-600">✓</span>}
{status.status === 'failed' && <span className="text-red-600">✗</span>}
{status.status === 'uploading' && <span className="text-blue-600 animate-spin">⟳</span>}
{status.status === 'processing' && <span className="text-yellow-600">⏳</span>}
{status.status === 'pending' && <span className="text-gray-400">○</span>}
<span className="text-gray-700 dark:text-gray-300 truncate" title={status.fileName}>
{status.fileName}
</span>
</div>
<div className="flex items-center space-x-2">
<span className="text-gray-600 dark:text-gray-400 text-xs">{status.progress}%</span>
{status.error && (
<span className="text-red-600 text-xs truncate max-w-[200px]" title={status.error}>
{status.error.length > 20 ? status.error.substring(0, 20) + '...' : status.error}
</span>
)}
</div>
</div>
))}
</div>
</div>
)}
{/* 所有文件都完成后的提示 */}
{visibleFileStatuses.length === 0 && completedFiles === totalFiles && (
<div className="text-center py-4 text-sm text-green-600 dark:text-green-400">
✓ 所有文件上传完成
</div>
)}
<button
type="button"
onClick={onCancel}
className="w-full btn-secondary"
>
取消上传
</button>
</div>
)
}
// 单文件上传进度
return (
<div className="space-y-2">
<div className="flex items-center justify-between text-sm">
<span className="text-gray-700 dark:text-gray-300">
{isProcessing ? '处理中...' : '上传中...'}
</span>
<span className="text-gray-600 dark:text-gray-400">{uploadProgress}%</span>
</div>
<div className="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2">
<div
className="bg-blue-600 h-2 rounded-full transition-all duration-300"
style={{ width: `${uploadProgress}%` }}
/>
</div>
{uploadSpeed > 0 && !isProcessing && (
<div className="text-xs text-gray-500 dark:text-gray-400">
{formatTransferProgress(
uploadSpeed,
uploadTotalBytes ? uploadTotalBytes * (uploadProgress / 100) : undefined,
uploadTotalBytes,
)}
</div>
)}
<button
type="button"
onClick={onCancel}
className="w-full btn-secondary"
>
取消上传
</button>
</div>
)
}