From b506417f5e6eb738d9a8bb483d34a1eb0c75eefb Mon Sep 17 00:00:00 2001 From: jacy-dukang Date: Thu, 9 Jul 2026 17:26:24 +0800 Subject: [PATCH] =?UTF-8?q?=E5=90=88=E4=BC=99=E4=BA=BA=E7=AB=AF=E5=BC=80?= =?UTF-8?q?=E5=BA=97=E9=80=89=E7=85=A7=E7=89=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/OssUploadField.tsx | 10 +- packages/weixin-sdk/src/chooseImage.ts | 112 ++++++++++++++---- packages/weixin-sdk/src/index.ts | 2 +- 3 files changed, 97 insertions(+), 27 deletions(-) diff --git a/apps/h5-partner/src/components/OssUploadField.tsx b/apps/h5-partner/src/components/OssUploadField.tsx index 1ac2406..12e8d74 100644 --- a/apps/h5-partner/src/components/OssUploadField.tsx +++ b/apps/h5-partner/src/components/OssUploadField.tsx @@ -7,6 +7,7 @@ import { 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'; @@ -28,12 +29,11 @@ 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,并刷新页面后重试'; } - if (/permission|denied|拒绝/i.test(msg)) { - return '微信选图权限被拒绝,请在微信设置中允许相册/相机访问后重试'; - } return msg; } @@ -116,9 +116,10 @@ export default function OssUploadField({ } async function pickWechatImage() { + await weixinSdk.init(); const files = await weixinSdk.chooseImages({ count: 1, - sourceType: ['album', 'camera'], + sourceType: ['album'], }); if (files?.[0]) { await uploadSelectedFile(files[0]); @@ -143,7 +144,6 @@ export default function OssUploadField({ const msg = e instanceof Error ? e.message : '无法打开相册'; if (/cancel/i.test(msg)) return; setError(formatWechatUploadError(e)); - openNativeFilePicker(); } return; } diff --git a/packages/weixin-sdk/src/chooseImage.ts b/packages/weixin-sdk/src/chooseImage.ts index f360ad4..39734a5 100644 --- a/packages/weixin-sdk/src/chooseImage.ts +++ b/packages/weixin-sdk/src/chooseImage.ts @@ -1,5 +1,5 @@ -import { getRuntimePlatform } from './env'; -import { ensureJssdkReady, normalizeJssdkPageUrl, getJssdkSignUrl } from './jssdk'; +import { getRuntimePlatform, isIosDevice, isWechatDevTools } from './env'; +import { ensureJssdkReady, normalizeJssdkPageUrl } from './jssdk'; import type { WeixinSdkConfig } from './types'; export type ChooseWechatImageOptions = { @@ -7,6 +7,38 @@ export type ChooseWechatImageOptions = { sourceType?: Array<'album' | 'camera'>; }; +function delay(ms: number): Promise { + return new Promise((resolve) => window.setTimeout(resolve, ms)); +} + +/** 将微信 chooseImage / getLocalImgData fail 的 errMsg 转为用户可读文案 */ +export function formatChooseImageFailMessage(errMsg: string): string { + const msg = errMsg.trim() || '无法打开相册'; + if (/cancel/i.test(msg)) return ''; + + if (/offline verifying|permission value is offline/i.test(msg)) { + return '微信权限验证中,请稍候再试或刷新页面后重新选择图片'; + } + + if (/invalid signature|config:fail|signature/i.test(msg)) { + return '微信 JSSDK 签名校验失败,请刷新页面后重试'; + } + + if (/photo.*denied|photos.*denied|相册.*权限|无法访问相册|无相册权限/i.test(msg)) { + return '相册权限未开启,请在 iPhone「设置 → 隐私与安全性 → 照片」中允许微信访问相册'; + } + + if (/system.*camera|camera.*not.*allowed|无法访问相机|无相机权限/i.test(msg)) { + return '相机权限未开启,请在 iPhone「设置 → 隐私与安全性 → 相机」中允许微信使用摄像头'; + } + + if (/permission|auth|denied|授权|拒绝/i.test(msg)) { + return `微信选图接口未就绪(${msg}),请刷新页面后重试`; + } + + return msg; +} + async function reportChooseImageEvent( config: WeixinSdkConfig, payload: { @@ -67,7 +99,34 @@ function localIdToFile(localId: string): Promise { reject(e instanceof Error ? e : new Error('图片解析失败')); } }, - fail: (err) => reject(new Error(err.errMsg || '读取图片失败')), + fail: (err) => { + const raw = err.errMsg || '读取图片失败'; + const formatted = formatChooseImageFailMessage(raw); + reject(new Error(formatted || raw)); + }, + }); + }); +} + +function invokeChooseImage( + count: number, + sourceType: Array<'album' | 'camera'>, +): Promise { + return new Promise((resolve, reject) => { + window.wx!.chooseImage!({ + count, + sizeType: ['compressed'], + sourceType, + success: (res) => resolve(res.localIds ?? []), + fail: (err) => { + const raw = err.errMsg || '无法打开相册'; + if (/cancel/i.test(raw)) { + resolve([]); + return; + } + const formatted = formatChooseImageFailMessage(raw); + reject(new Error(formatted || raw)); + }, }); }); } @@ -84,7 +143,6 @@ export async function chooseWechatImages( const sourceType = options.sourceType ?? ['album', 'camera']; if (platform === 'wechat-h5') { - const pageUrl = typeof window !== 'undefined' ? getJssdkSignUrl() : ''; const sourceTypeKey = sourceType.join(','); try { await ensureJssdkReady({ @@ -104,26 +162,38 @@ export async function chooseWechatImages( 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 (isIosDevice() && !isWechatDevTools()) { + await delay(500); } + let localIds: string[] = []; + const maxAttempts = 2; + let lastError: Error | null = null; + for (let attempt = 0; attempt < maxAttempts; attempt++) { + if (attempt > 0) { + await delay(800); + } + try { + localIds = await invokeChooseImage(count, sourceType); + lastError = null; + break; + } catch (e) { + lastError = e instanceof Error ? e : new Error('无法打开相册'); + const raw = lastError.message; + const retryable = /offline verifying|权限验证中|接口未就绪/i.test(raw); + if (attempt < maxAttempts - 1 && retryable) continue; + void reportChooseImageEvent(config, { + status: 'fail', + errMsg: raw, + sourceType: sourceTypeKey, + stage: 'choose', + }); + throw lastError; + } + } + if (lastError) throw lastError; + if (!localIds.length) { - const errMsg = '未选择图片'; - void reportChooseImageEvent(config, { status: 'fail', errMsg, sourceType: sourceTypeKey, stage: 'empty' }); return []; } diff --git a/packages/weixin-sdk/src/index.ts b/packages/weixin-sdk/src/index.ts index 7f66347..393730f 100644 --- a/packages/weixin-sdk/src/index.ts +++ b/packages/weixin-sdk/src/index.ts @@ -19,7 +19,7 @@ export { export type { WechatLocationOutcome } from './location'; export { scanQrCode } from './scan'; export { invokeWechatPay } from './pay'; -export { chooseWechatImages, canUseWechatChooseImage } from './chooseImage'; +export { chooseWechatImages, canUseWechatChooseImage, formatChooseImageFailMessage } from './chooseImage'; export type { ChooseWechatImageOptions } from './chooseImage'; export { setWechatShareData,