import { useCallback, useRef } from 'react'; import { uniqueId } from 'lodash-es'; const useUploader = () => { /** 上传结果观察者 */ const _uploaderObservers = useRef< { id: string; success: (file: File) => void; failure: (err: Error) => void; cancel: () => void; }[] >([]); /** 处理图像上传 */ const AfterFileUpload = useCallback( (id: string) => async (e: Event) => { const index = _uploaderObservers.current.findIndex((i) => i.id === id); if (index === -1) return; const observer = _uploaderObservers.current.splice(index, 1)[0]; const input = e.target as HTMLInputElement; /** 文件大小及格式检测 */ const check = (file: File) => { if (!input) return 'message.invalid-file'; const maxLength = Number(input.getAttribute('maxLength')); if (maxLength && file.size > maxLength) return 'message.file-size-exceeds'; const accepts = input.getAttribute('accept')?.split(',') ?? []; if (file.type && accepts.length && !accepts.includes(file.type)) return 'message.invalid-file'; return; }; try { let file = input?.files?.[0]; if (!file) { return observer.cancel(); } const warn = check(file); if (warn) { alert(warn); return observer.cancel(); } return observer.success(file); } catch (err) { observer.failure(err as Error); } finally { // 移除文件占用,防止无法连续上传相同文件 (input as any).value = null; } }, [], ); /** 处理图像取消上传 */ const AfterFileCancel = useCallback( (id: string) => async (e: Event) => { const index = _uploaderObservers.current.findIndex((i) => i.id === id); if (index === -1) return; const observer = _uploaderObservers.current.splice(index, 1)[0]; observer.cancel(); }, [], ); /** 上传文件 */ const uploadFile = useCallback(async (configs: { accept: string; maxLength: number }) => { const id = uniqueId(); const uploader = document.createElement('input'); uploader.setAttribute('type', 'file'); uploader.setAttribute('multiple', 'false'); uploader.setAttribute('hidden', 'hidden'); Object.keys(configs).forEach((attr) => { uploader.setAttribute(attr, configs[attr]); }); uploader.onchange = AfterFileUpload(id); uploader.oncancel = AfterFileCancel(id); uploader.click(); return new Promise((resolve, reject) => { _uploaderObservers.current?.push({ id, success: (file: File) => { resolve(file); }, failure: (err: Error) => { reject(err); }, cancel: () => { resolve(null); }, }); }); }, []); return uploadFile; }; export default useUploader;