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 | 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 = (file as any)?.blob || (file as any)?.buffer || file; // Expo ImagePicker 返回 { uri } 时,转换为 Blob if (!body && (file as any)?.uri) { const resp = await fetch((file as any).uri); body = await resp.blob(); } const res = await uploadWithRetry({ key, body, contentType: finalOptions.contentType || (file as any)?.type, signal: controller.signal, onProgress: ({ percent }) => setProgress(percent), }); const url = buildPublicUrl(res.key); return { key: res.key, url }; } finally { setUploading(false); } }, [defaultOptions] ); return useMemo(() => ({ upload, cancel, progress, uploading }), [upload, cancel, progress, uploading]); }