Files
dukang/packages/weixin-sdk/src/chooseImage.ts
T

149 lines
4.8 KiB
TypeScript

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<string, string> = {
'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<File> {
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<File[] | null> {
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<string[]>((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';
}