gongxiangp2/components/upload/upload-utils.ts
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

127 lines
4.1 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.

// 上传相关的工具函数
/**
* 格式化文件大小
*/
export function formatBytes(bytes: number): string {
if (bytes === 0) return '0 B'
const k = 1024
const sizes = ['B', 'KB', 'MB', 'GB']
const i = Math.floor(Math.log(bytes) / Math.log(k))
return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i]
}
/**
* 格式化上传速度(字节/秒 -> 用户友好单位)
* 使用字节单位(B/s、KB/s、MB/s),而非比特单位
*/
export function formatSpeed(bytesPerSecond: number): string {
if (bytesPerSecond < 1024) {
return `${bytesPerSecond.toFixed(0)} B/s`
}
if (bytesPerSecond < 1024 * 1024) {
return `${(bytesPerSecond / 1024).toFixed(2)} KB/s`
}
return `${(bytesPerSecond / 1024 / 1024).toFixed(2)} MB/s`
}
/**
* 计算剩余时间
*/
export function calculateRemainingTime(remainingBytes: number, speed: number): string {
if (speed <= 0) return '计算中...'
const seconds = Math.ceil(remainingBytes / speed)
if (seconds < 60) return `${seconds}秒`
const minutes = Math.floor(seconds / 60)
const secs = seconds % 60
return `${minutes}分${secs}秒`
}
/**
* 获取文件扩展名
*/
export function getFileExtension(filename: string): string {
const idx = filename.lastIndexOf('.')
return idx >= 0 ? filename.slice(idx).toLowerCase() : ''
}
/**
* 验证主文件类型是否有效
*/
export function isValidMainFileType(file: File, materialType: string): boolean {
const ext = getFileExtension(file.name)
const archiveExts = ['.zip', '.rar', '.7z']
const videoExts = ['.mp4', '.mov', '.avi', '.mkv', '.mts', '.m4v']
const audioExts = ['.mp3', '.wav', '.flac', '.m4a', '.aac', '.ogg']
const psdExts = ['.psd', '.tif', '.tiff', '.psb'] // 详情模板支持 PSD、TIF、PSB
// 音乐:只能是音频文件
if (materialType === '音乐') return audioExts.includes(ext)
// 视频类:只能是视频文件
if (['主图视频', '安装视频', '种草视频'].includes(materialType)) return videoExts.includes(ext)
// 品牌VI灵感:只能是 PDF,展示图在单独的展示图字段里上传
if (materialType === '品牌VI灵感') return ext === '.pdf'
// 详情模板:支持直接上传 PSD/TIF/PSB 文件或压缩包
if (materialType === '详情模板') {
return psdExts.includes(ext) || archiveExts.includes(ext)
}
// 其他类型(3D场景/模特等):仍然按旧逻辑走压缩包
return archiveExts.includes(ext)
}
/**
* 获取主文件接受的类型
*/
export function getMainFileAccept(materialType: string): string {
if (materialType === '音乐') return '.mp3,.wav,.flac,.m4a,.aac,.ogg'
if (['主图视频', '安装视频', '种草视频'].includes(materialType))
return '.mp4,.mov,.avi,.mkv,.mts,.m4v'
if (materialType === '品牌VI灵感') return '.pdf'
// 详情模板:支持直接上传 PSD/TIF/PSB 文件或压缩包
if (materialType === '详情模板') {
return '.psd,.tif,.tiff,.psb,.zip,.rar,.7z'
}
return '.zip,.rar,.7z'
}
/**
* 从文件名提取SPU(简单逻辑:提取文件名中的大写字母和数字组合)
*/
export function extractSpuFromFileName(filename: string): string {
// 移除扩展名
const nameWithoutExt = filename.replace(/\.[^/.]+$/, '')
// 尝试提取类似 "XY-2024-001" 的格式
const spuMatch = nameWithoutExt.match(/[A-Z]{2,}-?\d{4}-?\d{3,}/i)
if (spuMatch) {
return spuMatch[0].toUpperCase()
}
// 如果没有匹配到,返回文件名(去除扩展名和特殊字符)
return nameWithoutExt.replace(/[^a-zA-Z0-9\-_]/g, '').toUpperCase()
}
/**
* 将File转换为base64格式(用于批量上传中的预览图)
*/
export async function convertFileToBase64(file: File): Promise<{ name: string; data: number[]; type: string }> {
const arrayBuffer = await file.arrayBuffer()
return {
name: file.name,
data: Array.from(new Uint8Array(arrayBuffer)),
type: file.type,
}
}
/**
* 检测素材类型(从文件名)
*/
export function detectMaterialType(filename: string): string | null {
// 不再根据文件扩展名自动检测素材类型
// 用户需要手动选择素材类型
return null
}