主要改动: 1. TCP缓冲区优化(16MB)- 解决公网上传速度慢问题 2. 并发数降低到1-3 - 避免4并发导致ERR_CONNECTION_RESET 3. 动态并发控制 - 根据网络环境自动调整 4. Nginx超时增加到120s - 支持大分片上传 5. 上传进度修复 - 修复剩余时间计算 6. 新增任务-页面上传器和详情组件 7. 统一部署文档为deploy-remote-build.ps1 问题修复: - 公网用户上传从200-900KB/s提升到预期3-5MB/s - 内网用户上传更稳定,无连接重置错误 - UploadProgress组件剩余时间计算修正 Co-Authored-By: Claude <noreply@anthropic.com>
182 lines
7.3 KiB
TypeScript
182 lines
7.3 KiB
TypeScript
'use client'
|
||
|
||
import type { BatchUploadStatus, BatchFileItem } from './upload-types'
|
||
import { formatSpeed, calculateRemainingTime } from './upload-utils'
|
||
|
||
interface UploadProgressProps {
|
||
uploading: boolean
|
||
isBatchMode: boolean
|
||
batchUploadStatus: BatchUploadStatus | null
|
||
batchFiles?: BatchFileItem[] // 用于计算实际文件大小
|
||
uploadProgress: number
|
||
uploadSpeed: number
|
||
isProcessing: boolean
|
||
onCancel: () => void
|
||
}
|
||
|
||
export default function UploadProgress({
|
||
uploading,
|
||
isBatchMode,
|
||
batchUploadStatus,
|
||
batchFiles,
|
||
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 processingFiles = fileStatuses.filter(s => s.status === 'uploading' || s.status === 'processing' || s.status === 'pending')
|
||
|
||
// 只显示正在处理/等待中的文件,已完成的文件不显示(避免队列过长)
|
||
const visibleFileStatuses = fileStatuses.filter(s => s.status !== 'done')
|
||
|
||
// 计算剩余字节数(使用实际文件大小)
|
||
const remainingBytes = fileStatuses
|
||
.slice(currentFileIndex)
|
||
.reduce((sum, status, idx) => {
|
||
const fileStatus = batchUploadStatus.fileStatuses[currentFileIndex + idx]
|
||
if (!fileStatus) return sum
|
||
|
||
// 从batchFiles中查找对应的文件大小
|
||
const batchFile = batchFiles?.find(bf => bf.file.name === fileStatus.fileName)
|
||
const fileSize = batchFile?.file.size || 0
|
||
|
||
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>
|
||
|
||
{/* 文件状态列表 - 只显示正在处理/等待中/失败的文件 */}
|
||
{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">
|
||
速度: {formatSpeed(uploadSpeed)}
|
||
</div>
|
||
)}
|
||
<button
|
||
type="button"
|
||
onClick={onCancel}
|
||
className="w-full btn-secondary"
|
||
>
|
||
取消上传
|
||
</button>
|
||
</div>
|
||
)
|
||
}
|
||
|