import { useEffect, useRef, useState } from 'react'; import { uploadFileToOss, type OssMediaType } from '../lib/upload'; import { enqueueUpload } from '../lib/upload-lock'; import { formatChooseImageFailMessage } from '@dukang/weixin-sdk'; import { isWechatEnv, weixinSdk } from '../lib/weixin'; import { toastError } from '../lib/toast'; type OssUploadFieldProps = { value?: string; onChange?: (url: string) => void; bizType: string; mediaType?: OssMediaType; accept?: string; wide?: boolean; compact?: boolean; label?: string; /** 兼容现有调用:选图不再依赖 openId 绑定 */ wechatReady?: boolean; onWechatReadyChange?: (ready: boolean) => void; }; const DEFAULT_MAX_MB = 10; function isCancelError(msg: string): boolean { return /cancel|取消/i.test(msg); } function formatWechatUploadError(e: unknown): string { const msg = e instanceof Error ? e.message : '无法打开相册'; const formatted = formatChooseImageFailMessage(msg); if (formatted) return formatted; if (/invalid signature/i.test(msg)) { return '微信 JSSDK 签名校验失败:请确认公众号已配置 JS 接口安全域名,并刷新页面后重试'; } return msg; } function acceptsImages(accept: string) { return accept.includes('image'); } export default function OssUploadField({ value, onChange, bizType, mediaType = 'IMAGE', accept, wide, compact, label, }: OssUploadFieldProps) { const inputRef = useRef(null); const pickingRef = useRef(false); const [uploading, setUploading] = useState(false); const [error, setError] = useState(''); const [showAlbumFallback, setShowAlbumFallback] = useState(false); function showUploadError(text: string) { setError(text); toastError(text); } const resolvedAccept = accept ?? (mediaType === 'VIDEO' ? 'video/*' : mediaType === 'FILE' ? 'image/*,.pdf' : 'image/*'); const inWechat = isWechatEnv(); const useWechatPicker = inWechat && (mediaType === 'IMAGE' || (mediaType === 'FILE' && acceptsImages(resolvedAccept))); useEffect(() => { if (!useWechatPicker) return; // 仅预热,勿在每次点击时 reset,否则取消后再点常无法调起 void weixinSdk.init().catch(() => { /* 点击上传时会再次初始化 */ }); }, [useWechatPicker]); async function persistUpload(file: File) { if (!file.size) { showUploadError('图片读取失败,请重新选择'); return; } if (file.size > DEFAULT_MAX_MB * 1024 * 1024) { showUploadError(`文件不能超过 ${DEFAULT_MAX_MB}MB`); return; } const result = await uploadFileToOss(file, { bizType, mediaType }); onChange?.(result.url); } async function uploadSelectedFile(file: File) { setUploading(true); setError(''); try { await enqueueUpload(() => persistUpload(file)); } catch (e) { showUploadError(e instanceof Error ? e.message : '上传失败'); } finally { setUploading(false); if (inputRef.current) inputRef.current.value = ''; } } async function pickWechatImage() { if (pickingRef.current || uploading) return; pickingRef.current = true; setError(''); try { // 不要每次 reset:会打断 JSSDK,取消后再点经常无法调起相机/相册 await weixinSdk.init(); const files = await weixinSdk.chooseImages({ count: 1, sourceType: ['album', 'camera'], }); // 取消或未选图:直接结束,允许再次点击 if (!files?.length) return; setUploading(true); try { // 仅串行化实际上传,选图本身不占上传锁,避免取消后锁态异常 await enqueueUpload(() => persistUpload(files[0]!)); } finally { setUploading(false); } } catch (e) { const msg = e instanceof Error ? e.message : '无法打开相册'; if (isCancelError(msg)) return; throw e; } finally { pickingRef.current = false; } } async function pickFile() { if (uploading || pickingRef.current) return; setError(''); if (useWechatPicker) { try { await pickWechatImage(); } catch (e) { const msg = e instanceof Error ? e.message : '无法打开相册'; if (isCancelError(msg)) return; const formatted = formatWechatUploadError(e); setError(`${formatted},可改从系统相册选择`); setShowAlbumFallback(true); inputRef.current?.click(); } return; } inputRef.current?.click(); } const isImage = mediaType === 'IMAGE' && value; const isFile = mediaType === 'FILE' && value; const busy = uploading; const pickerLabel = label ?? (useWechatPicker ? '拍照 / 从相册选择' : '点击上传'); const triggerProps = { type: 'button' as const, disabled: busy, onClick: () => void pickFile(), }; return (
{ const file = e.target.files?.[0]; if (file) { setShowAlbumFallback(false); void uploadSelectedFile(file); } }} /> {isImage ? ( ) : isFile ? ( ) : ( )} {showAlbumFallback && useWechatPicker && ( )} {error &&

{error}

}
); }