import { ApiError } from '../types' import { API_CONFIG } from '../config' /** * 延迟函数 */ export const delay = (ms: number): Promise => { return new Promise(resolve => setTimeout(resolve, ms)) } /** * 生成唯一ID */ export const generateId = (): string => { return Date.now().toString(36) + Math.random().toString(36).substr(2) } /** * 格式化文件大小 */ export const formatFileSize = (bytes: number): string => { if (bytes === 0) return '0 Bytes' const k = 1024 const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'] const i = Math.floor(Math.log(bytes) / Math.log(k)) return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i] } /** * 获取文件扩展名 */ export const getFileExtension = (filename: string): string => { return filename.slice((filename.lastIndexOf('.') - 1 >>> 0) + 2) } /** * 验证文件类型 */ export const validateFileType = (file: File, allowedTypes: string[]): boolean => { const fileType = file.type const fileExtension = getFileExtension(file.name).toLowerCase() return allowedTypes.some(type => { if (type.includes('/')) { return fileType === type } return fileExtension === type.toLowerCase() }) } /** * 创建API错误对象 */ export const createApiError = ( code: number, message: string, details?: any ): ApiError => { return { code, message, details } } /** * 判断是否为API错误 */ export const isApiError = (error: any): error is ApiError => { return error && typeof error.code === 'number' && typeof error.message === 'string' } /** * 重试函数 */ export const retry = async ( fn: () => Promise, retries: number = API_CONFIG.RETRY_COUNT, delayMs: number = API_CONFIG.RETRY_DELAY ): Promise => { try { return await fn() } catch (error) { if (retries > 0) { await delay(delayMs) return retry(fn, retries - 1, delayMs * 2) // 指数退避 } throw error } } /** * 下载文件 */ export const downloadFile = (blob: Blob, filename: string): void => { const url = window.URL.createObjectURL(blob) const link = document.createElement('a') link.href = url link.download = filename document.body.appendChild(link) link.click() document.body.removeChild(link) window.URL.revokeObjectURL(url) } /** * 读取文件为Base64 */ export const readFileAsBase64 = (file: File): Promise => { return new Promise((resolve, reject) => { const reader = new FileReader() reader.onload = () => { const result = reader.result as string resolve(result.split(',')[1]) // 移除data:xxx;base64,前缀 } reader.onerror = reject reader.readAsDataURL(file) }) } /** * 读取文件为ArrayBuffer */ export const readFileAsArrayBuffer = (file: File): Promise => { return new Promise((resolve, reject) => { const reader = new FileReader() reader.onload = () => resolve(reader.result as ArrayBuffer) reader.onerror = reject reader.readAsArrayBuffer(file) }) } /** * 压缩图片 */ export const compressImage = ( file: File, quality: number = 0.8, maxWidth: number = 1920, maxHeight: number = 1080 ): Promise => { return new Promise((resolve, reject) => { const canvas = document.createElement('canvas') const ctx = canvas.getContext('2d') const img = new Image() img.onload = () => { // 计算压缩后的尺寸 let { width, height } = img if (width > maxWidth) { height = (height * maxWidth) / width width = maxWidth } if (height > maxHeight) { width = (width * maxHeight) / height height = maxHeight } canvas.width = width canvas.height = height // 绘制并压缩 ctx?.drawImage(img, 0, 0, width, height) canvas.toBlob( (blob) => { if (blob) { resolve(blob) } else { reject(new Error('图片压缩失败')) } }, file.type, quality ) } img.onerror = reject img.src = URL.createObjectURL(file) }) }