182 lines
7.2 KiB
TypeScript
182 lines
7.2 KiB
TypeScript
'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>
|
||
)
|
||
}
|
||
|