import { useEffect, useId, useRef, useState } from 'react'; import { uploadFileToOss, type OssMediaType } from '../lib/upload'; import { toastError } from '../lib/toast'; import { authorizePartnerWechat, fetchClientConfig, fetchPartnerProfile, needsWechatAuth, type PartnerProfile, } from '../lib/wechat-auth'; import { formatChooseImageFailMessage } from '@dukang/weixin-sdk'; import { isWechatEnv, weixinSdk } from '../lib/weixin'; import type { ClientRuntimeConfig } from '@dukang/shared-types'; type OssUploadFieldProps = { value?: string; onChange?: (url: string) => void; bizType: string; mediaType?: OssMediaType; accept?: string; wide?: boolean; compact?: boolean; label?: string; /** 父级已确认微信授权时可跳过检查 */ wechatReady?: boolean; onWechatReadyChange?: (ready: boolean) => void; }; const DEFAULT_MAX_MB = 10; 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 接口安全域名为 user.runxian.top,并刷新页面后重试'; } return msg; } export default function OssUploadField({ value, onChange, bizType, mediaType = 'IMAGE', accept, wide, compact, label, wechatReady, onWechatReadyChange, }: OssUploadFieldProps) { const inputId = useId(); const inputRef = useRef(null); const [uploading, setUploading] = useState(false); const [authorizing, setAuthorizing] = useState(false); const [error, setError] = useState(''); const [profile, setProfile] = useState(null); const [clientConfig, setClientConfig] = useState(null); const resolvedAccept = accept ?? (mediaType === 'VIDEO' ? 'video/*' : mediaType === 'FILE' ? 'image/*,.pdf' : 'image/*'); const useWechatPicker = isWechatEnv() && mediaType === 'IMAGE'; const needsAuth = useWechatPicker && needsWechatAuth(profile, clientConfig) && wechatReady !== true; useEffect(() => { if (!useWechatPicker) return; void Promise.all([fetchPartnerProfile(), fetchClientConfig()]) .then(([me, config]) => { setProfile(me); setClientConfig(config); onWechatReadyChange?.(!!me.hasWechat); }) .catch(() => { /* 未登录等场景由上传接口报错 */ }); }, [useWechatPicker, onWechatReadyChange]); useEffect(() => { if (!useWechatPicker || needsAuth) return; void weixinSdk.init().catch(() => { /* 点击上传时会再次初始化 */ }); }, [useWechatPicker, needsAuth]); async function uploadSelectedFile(file: File) { if (file.size > DEFAULT_MAX_MB * 1024 * 1024) { const text = `文件不能超过 ${DEFAULT_MAX_MB}MB`; setError(text); toastError(text); return; } setUploading(true); setError(''); try { const result = await uploadFileToOss(file, { bizType, mediaType }); onChange?.(result.url); } catch (e) { setError(e instanceof Error ? e.message : '上传失败'); } finally { setUploading(false); if (inputRef.current) inputRef.current.value = ''; } } async function startWechatAuth() { setAuthorizing(true); setError(''); try { await authorizePartnerWechat(); } catch (e) { const text = e instanceof Error ? e.message : '微信授权失败'; setError(text); toastError(text); setAuthorizing(false); } } function openNativeFilePicker() { inputRef.current?.click(); } async function pickWechatImage() { await weixinSdk.init(); const files = await weixinSdk.chooseImages({ count: 1, sourceType: ['album'], }); if (files?.[0]) { await uploadSelectedFile(files[0]); return; } throw new Error('未能获取图片,请重试'); } async function pickFile() { if (uploading || authorizing) return; setError(''); if (useWechatPicker && needsAuth) { const text = '请先完成微信授权后再上传照片'; setError(text); toastError(text); return; } if (useWechatPicker) { try { await pickWechatImage(); } catch (e) { const msg = e instanceof Error ? e.message : '无法打开相册'; if (/cancel/i.test(msg)) return; setError(formatWechatUploadError(e)); } return; } openNativeFilePicker(); } const isImage = mediaType === 'IMAGE' && value; const isFile = mediaType === 'FILE' && value; const busy = uploading || authorizing; const triggerProps = { type: 'button' as const, disabled: busy || needsAuth, onClick: () => void pickFile(), }; return (
{needsAuth && (

上传照片需先完成微信授权

)} { const file = e.target.files?.[0]; if (file) void uploadSelectedFile(file); }} /> {isImage ? ( ) : isFile ? ( ) : ( )} {error &&

{error}

}
); }