184 lines
4.1 KiB
TypeScript
184 lines
4.1 KiB
TypeScript
import { ApiError } from '../types'
|
|
import { API_CONFIG } from '../config'
|
|
|
|
/**
|
|
* 延迟函数
|
|
*/
|
|
export const delay = (ms: number): Promise<void> => {
|
|
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 <T>(
|
|
fn: () => Promise<T>,
|
|
retries: number = API_CONFIG.RETRY_COUNT,
|
|
delayMs: number = API_CONFIG.RETRY_DELAY
|
|
): Promise<T> => {
|
|
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<string> => {
|
|
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<ArrayBuffer> => {
|
|
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<Blob> => {
|
|
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)
|
|
})
|
|
}
|