145 lines
5.6 KiB
TypeScript
145 lines
5.6 KiB
TypeScript
'use client'
|
||
|
||
import type { BatchUploadStatus } from './upload-types'
|
||
import { formatSpeed, calculateRemainingTime } from './upload-utils'
|
||
|
||
interface UploadProgressProps {
|
||
uploading: boolean
|
||
isBatchMode: boolean
|
||
batchUploadStatus: BatchUploadStatus | null
|
||
uploadProgress: number
|
||
uploadSpeed: number
|
||
isProcessing: boolean
|
||
onCancel: () => void
|
||
}
|
||
|
||
export default function UploadProgress({
|
||
uploading,
|
||
isBatchMode,
|
||
batchUploadStatus,
|
||
uploadProgress,
|
||
uploadSpeed,
|
||
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 remainingBytes = fileStatuses
|
||
.slice(currentFileIndex)
|
||
.reduce((sum, status, idx) => {
|
||
const file = batchUploadStatus.fileStatuses[currentFileIndex + idx]
|
||
if (!file) return sum
|
||
const fileSize = 100 // 假设每个文件100单位,实际应该从batchFiles获取
|
||
return sum + (fileSize * (100 - status.progress) / 100)
|
||
}, 0)
|
||
|
||
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="flex items-center justify-between text-xs text-gray-500 dark:text-gray-400">
|
||
<span>速度: {formatSpeed(currentFileSpeed)}</span>
|
||
<span>剩余时间: {calculateRemainingTime(remainingBytes, currentFileSpeed)}</span>
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
{/* 文件状态列表 */}
|
||
<div className="max-h-60 overflow-y-auto space-y-2">
|
||
{fileStatuses.map((status, idx) => (
|
||
<div key={idx} className="flex items-center justify-between text-sm p-2 bg-gray-50 dark:bg-gray-800 rounded">
|
||
<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">{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" title={status.error}>错误</span>
|
||
)}
|
||
</div>
|
||
</div>
|
||
))}
|
||
</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">
|
||
速度: {formatSpeed(uploadSpeed)}
|
||
</div>
|
||
)}
|
||
<button
|
||
type="button"
|
||
onClick={onCancel}
|
||
className="w-full btn-secondary"
|
||
>
|
||
取消上传
|
||
</button>
|
||
</div>
|
||
)
|
||
}
|
||
|