import { getRuntimePlatform } from './env'; import { ensureJssdkReady, normalizeJssdkPageUrl, getJssdkSignUrl } from './jssdk'; import type { WeixinSdkConfig } from './types'; export type ChooseWechatImageOptions = { count?: number; sourceType?: Array<'album' | 'camera'>; }; async function reportChooseImageEvent( config: WeixinSdkConfig, payload: { status: 'fail'; errMsg: string; sourceType?: string; stage: 'jssdk' | 'choose' | 'read' | 'empty'; }, ) { if (/cancel/i.test(payload.errMsg)) return; try { const headers: Record = { 'Content-Type': 'application/json', 'X-Client-App': config.clientApp, }; const token = config.getAccessToken?.(); if (token) headers.Authorization = `Bearer ${token}`; await fetch(`${config.apiBase ?? '/api/v1'}/common/wechat/choose-image`, { method: 'POST', headers, body: JSON.stringify({ status: 'fail', errMsg: payload.errMsg, sourceType: payload.sourceType, stage: payload.stage, pageUrl: typeof window !== 'undefined' ? normalizeJssdkPageUrl(window.location.href) : undefined, }), }); } catch { /* 上报失败不影响主流程 */ } } function base64ToFile(base64: string, fileName: string): File { const normalized = base64.startsWith('data:') ? base64 : `data:image/jpeg;base64,${base64.replace(/\s/g, '')}`; const [header, body] = normalized.split(','); const mime = header.match(/:(.*?);/)?.[1] ?? 'image/jpeg'; const binary = atob(body); const bytes = new Uint8Array(binary.length); for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i); return new File([bytes], fileName, { type: mime }); } function localIdToFile(localId: string): Promise { return new Promise((resolve, reject) => { if (!window.wx?.getLocalImgData) { reject(new Error('微信 getLocalImgData 不可用')); return; } window.wx.getLocalImgData({ localId, success: (res) => { try { resolve(base64ToFile(res.localData, `wx-${Date.now()}.jpg`)); } catch (e) { reject(e instanceof Error ? e : new Error('图片解析失败')); } }, fail: (err) => reject(new Error(err.errMsg || '读取图片失败')), }); }); } /** 微信内选图(相册/拍照),返回 File 列表;非微信环境返回 null */ export async function chooseWechatImages( config: WeixinSdkConfig, options: ChooseWechatImageOptions = {}, ): Promise { const platform = getRuntimePlatform(); if (platform === 'browser') return null; const count = options.count ?? 1; const sourceType = options.sourceType ?? ['album', 'camera']; if (platform === 'wechat-h5') { const pageUrl = typeof window !== 'undefined' ? getJssdkSignUrl() : ''; const sourceTypeKey = sourceType.join(','); try { await ensureJssdkReady({ apiBase: config.apiBase ?? '/api/v1', clientApp: config.clientApp, getAccessToken: config.getAccessToken, jsApiList: ['chooseImage', 'getLocalImgData'], }); } catch (e) { const errMsg = e instanceof Error ? e.message : 'JSSDK 初始化失败'; void reportChooseImageEvent(config, { status: 'fail', errMsg, sourceType: sourceTypeKey, stage: 'jssdk' }); throw e; } if (!window.wx?.chooseImage) { const errMsg = '微信选图接口不可用,请刷新页面后重试'; void reportChooseImageEvent(config, { status: 'fail', errMsg, sourceType: sourceTypeKey, stage: 'jssdk' }); throw new Error(errMsg); } let localIds: string[]; try { localIds = await new Promise((resolve, reject) => { window.wx!.chooseImage!({ count, sizeType: ['compressed'], sourceType, success: (res) => resolve(res.localIds ?? []), fail: (err) => reject(new Error(err.errMsg || '无法打开相册')), }); }); } catch (e) { const errMsg = e instanceof Error ? e.message : '无法打开相册'; void reportChooseImageEvent(config, { status: 'fail', errMsg, sourceType: sourceTypeKey, stage: 'choose' }); throw e; } if (!localIds.length) { const errMsg = '未选择图片'; void reportChooseImageEvent(config, { status: 'fail', errMsg, sourceType: sourceTypeKey, stage: 'empty' }); return []; } const files: File[] = []; for (const localId of localIds) { try { files.push(await localIdToFile(localId)); } catch (e) { const errMsg = e instanceof Error ? e.message : '读取图片失败'; void reportChooseImageEvent(config, { status: 'fail', errMsg, sourceType: sourceTypeKey, stage: 'read' }); throw e; } } return files; } return null; } export function canUseWechatChooseImage(): boolean { return getRuntimePlatform() !== 'browser'; }