awake
This commit is contained in:
184
src/api/utils/index.ts
Normal file
184
src/api/utils/index.ts
Normal file
@ -0,0 +1,184 @@
|
||||
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)
|
||||
})
|
||||
}
|
Reference in New Issue
Block a user