gongxiangp2/components/upload/UploadProgress.tsx
chenpei cd6e40d9d2 优化:网络传输性能和稳定性 - TCP缓冲区+并发控制优化
主要改动:
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>
2026-01-27 09:33:09 +08:00

182 lines
7.3 KiB
TypeScript
Raw 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 { 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>
)
}