import { buildCosKey, buildPublicUrl } from '@/constants/Cos'; import { uploadWithRetry } from '@/services/cos'; import { useCallback, useMemo, useRef, useState } from 'react'; export type UseCosUploadOptions = { prefix?: string; userId?: string; contentType?: string; }; export function useCosUpload(defaultOptions?: UseCosUploadOptions) { const abortRef = useRef(null); const [progress, setProgress] = useState(0); const [uploading, setUploading] = useState(false); const cancel = useCallback(() => { abortRef.current?.abort(); }, []); const upload = useCallback( async (file: { uri?: string; name?: string; type?: string; buffer?: any; blob?: Blob } | Blob | string | any, options?: UseCosUploadOptions) => { const finalOptions = { ...(defaultOptions || {}), ...(options || {}) }; const extGuess = (() => { const name = (file && (file.name || (file as any).filename)) || ''; const match = name.match(/\.([a-zA-Z0-9]+)$/); return match ? match[1] : undefined; })(); const key = buildCosKey({ prefix: finalOptions.prefix, userId: finalOptions.userId, ext: extGuess }); const controller = new AbortController(); abortRef.current = controller; setProgress(0); setUploading(true); try { let body: any = null; // 1) 直接可用类型:Blob 或 string if (typeof file === 'string') { body = file; } else if (typeof Blob !== 'undefined' && file instanceof Blob) { body = file; } else if ((file as any)?.blob && (typeof Blob === 'undefined' || (file as any).blob instanceof Blob || (file as any).blob?._data)) { // 2) 已提供 blob 字段 body = (file as any).blob; } else if ((file as any)?.buffer) { // 3) ArrayBuffer/TypedArray -> Blob const buffer = (file as any).buffer; body = new Blob([buffer], { type: (file as any)?.type || finalOptions.contentType || 'application/octet-stream' }); } else if ((file as any)?.uri) { // 4) Expo ImagePicker/文件:必须先转 Blob const resp = await fetch((file as any).uri); body = await resp.blob(); } else { // 兜底:尝试直接作为字符串,否则抛错 if (file && (typeof file === 'object')) { throw new Error('无效的上传体:请提供 Blob/String,或包含 uri 的对象'); } body = file; } const res = await uploadWithRetry({ key, body, contentType: finalOptions.contentType || (file as any)?.type, signal: controller.signal, onProgress: ({ percent }) => setProgress(percent), }); const url = (res as any).publicUrl || buildPublicUrl(res.key); return { key: res.key, url }; } finally { setUploading(false); } }, [defaultOptions] ); return useMemo(() => ({ upload, cancel, progress, uploading }), [upload, cancel, progress, uploading]); }