门店端调用相机功能提示修改

This commit is contained in:
2026-08-03 12:37:01 +08:00
parent 0621ac18fb
commit 23587418c1
3 changed files with 91 additions and 19 deletions
+12 -4
View File
@@ -27,7 +27,7 @@ function formatScanError(e: unknown): string {
return '微信 JSSDK 签名校验失败:请确认公众号已配置 JS 接口安全域名,并刷新页面后重试'; return '微信 JSSDK 签名校验失败:请确认公众号已配置 JS 接口安全域名,并刷新页面后重试';
} }
if (/offline verifying|权限验证中|接口未就绪/i.test(msg)) { if (/offline verifying|权限验证中|接口未就绪/i.test(msg)) {
return `${msg}(可在 URL 后加 ?wxdebug=1 开启 JSSDK 调试查看详情)`; return '微信权限校验尚未完成,请等待 1~2 秒后再次点击扫码(首次绑定微信时较常见)';
} }
return msg; return msg;
} }
@@ -73,7 +73,7 @@ export default function HomePage() {
}; };
}, [loadDashboard]); }, [loadDashboard]);
async function runScan() { async function runScan(opts?: { postAuthWarmup?: boolean }) {
if (!isWechatEnv()) { if (!isWechatEnv()) {
setScanMsg('请在微信内打开门店端进行扫码核销'); setScanMsg('请在微信内打开门店端进行扫码核销');
return; return;
@@ -81,8 +81,14 @@ export default function HomePage() {
setScanning(true); setScanning(true);
setScanMsg(''); setScanMsg('');
try { try {
// 授权回跳后强制重签,避免沿用带 code 的旧签名态
if (opts?.postAuthWarmup) {
weixinSdk.reset();
}
await weixinSdk.init(); await weixinSdk.init();
const raw = await weixinSdk.scanQrCode(); const raw = await weixinSdk.scanQrCode(
opts?.postAuthWarmup ? { postAuthWarmup: true } : undefined,
);
if (!raw) { if (!raw) {
void loadDashboard(); void loadDashboard();
return; return;
@@ -111,12 +117,14 @@ export default function HomePage() {
} }
setAuthModalOpen(false); setAuthModalOpen(false);
setAuthError(''); setAuthError('');
// 先清 OAuth 参数再扫,保证 JSSDK 签名 URL 与当前页一致
stripOAuthParamsFromLocation(); stripOAuthParamsFromLocation();
setSearchParams({}, { replace: true }); setSearchParams({}, { replace: true });
const shouldScan = sessionStorage.getItem(PENDING_SCAN_KEY) === '1'; const shouldScan = sessionStorage.getItem(PENDING_SCAN_KEY) === '1';
sessionStorage.removeItem(PENDING_SCAN_KEY); sessionStorage.removeItem(PENDING_SCAN_KEY);
if (shouldScan) { if (shouldScan) {
window.setTimeout(() => void runScan(), 0); // OAuth 回跳后微信权限离线校验未完成;稍候再扫,失败可再点一次
window.setTimeout(() => void runScan({ postAuthWarmup: true }), 400);
} }
}) })
.catch((e) => { .catch((e) => {
+1 -1
View File
@@ -70,7 +70,7 @@ export function createWeixinSdk(config: WeixinSdkConfig) {
getPhoneNumber: () => getWechatPhoneNumber(config), getPhoneNumber: () => getWechatPhoneNumber(config),
getLocation: () => getWechatLocation(config), getLocation: () => getWechatLocation(config),
getLocationDetailed: () => getWechatLocationDetailed(config), getLocationDetailed: () => getWechatLocationDetailed(config),
scanQrCode: () => scanQrCode(config), scanQrCode: (options?: Parameters<typeof scanQrCode>[1]) => scanQrCode(config, options),
chooseImages: (options?: Parameters<typeof chooseWechatImages>[1]) => chooseImages: (options?: Parameters<typeof chooseWechatImages>[1]) =>
chooseWechatImages(config, options), chooseWechatImages(config, options),
pay: (prepay: Parameters<typeof invokeWechatPay>[0]) => pay: (prepay: Parameters<typeof invokeWechatPay>[0]) =>
+78 -14
View File
@@ -1,5 +1,10 @@
import { getRuntimePlatform, isIosDevice, isWechatDevTools } from './env'; import { getRuntimePlatform, isIosDevice, isWechatDevTools } from './env';
import { ensureJssdkReady } from './jssdk'; import {
ensureJssdkReady,
getJssdkSignUrl,
initWechatJssdk,
resetJssdkConfig,
} from './jssdk';
import type { WeixinSdkConfig } from './types'; import type { WeixinSdkConfig } from './types';
function delay(ms: number): Promise<void> { function delay(ms: number): Promise<void> {
@@ -11,7 +16,7 @@ export function formatScanFailMessage(errMsg: string): string {
const msg = errMsg.trim() || '扫码失败'; const msg = errMsg.trim() || '扫码失败';
if (/cancel/i.test(msg)) return ''; if (/cancel/i.test(msg)) return '';
// iOS 常见:JSSDK 权限离线校验尚未完成,并非系统相机权限 // iOS / 授权回调后常见:JSSDK 权限离线校验尚未完成,并非系统相机权限
if (/offline verifying|permission value is offline/i.test(msg)) { if (/offline verifying|permission value is offline/i.test(msg)) {
return '微信权限验证中,请稍候再试或刷新页面后重新扫码'; return '微信权限验证中,请稍候再试或刷新页面后重新扫码';
} }
@@ -33,6 +38,47 @@ export function formatScanFailMessage(errMsg: string): string {
return msg; return msg;
} }
function isScanPermissionWarmupError(msg: string): boolean {
return /offline verifying|permission value is offline|权限验证中|接口未就绪|invalid signature|config:fail|signature/i.test(
msg,
);
}
function checkJsApi(apiName: string): Promise<boolean> {
return new Promise((resolve) => {
if (!window.wx?.checkJsApi) {
resolve(false);
return;
}
window.wx.checkJsApi({
jsApiList: [apiName],
success: (res) => resolve(!!res.checkResult?.[apiName]),
fail: () => resolve(false),
});
});
}
async function ensureScanJssdk(config: WeixinSdkConfig): Promise<void> {
const jssdkOptions = {
apiBase: config.apiBase ?? '/api/v1',
clientApp: config.clientApp,
getAccessToken: config.getAccessToken,
jsApiList: ['scanQRCode', 'checkJsApi'],
};
await ensureJssdkReady(jssdkOptions);
let ready = await checkJsApi('scanQRCode');
if (!ready) {
resetJssdkConfig();
await initWechatJssdk({ ...jssdkOptions, url: getJssdkSignUrl() });
ready = await checkJsApi('scanQRCode');
}
if (!ready) {
throw new Error('微信扫码接口未授权,请刷新页面后重试');
}
}
function invokeScanQrCode(): Promise<string | null> { function invokeScanQrCode(): Promise<string | null> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
window.wx!.scanQRCode!({ window.wx!.scanQRCode!({
@@ -52,8 +98,16 @@ function invokeScanQrCode(): Promise<string | null> {
}); });
} }
/** 调起扫码(返回二维码/条码内容,失败返回 null) */ export type ScanQrCodeOptions = {
export async function scanQrCode(config: WeixinSdkConfig): Promise<string | null> { /** OAuth / 首次绑定后权限离线校验更慢,加长预热 */
postAuthWarmup?: boolean;
};
/** 调起扫码(返回二维码/条码内容,取消返回 null) */
export async function scanQrCode(
config: WeixinSdkConfig,
options: ScanQrCodeOptions = {},
): Promise<string | null> {
const platform = getRuntimePlatform(); const platform = getRuntimePlatform();
if (platform === 'mini' && window.wx?.scanCode) { if (platform === 'mini' && window.wx?.scanCode) {
@@ -68,32 +122,42 @@ export async function scanQrCode(config: WeixinSdkConfig): Promise<string | null
} }
if (platform === 'wechat-h5') { if (platform === 'wechat-h5') {
await ensureJssdkReady({ const jssdkOptions = {
apiBase: config.apiBase ?? '/api/v1', apiBase: config.apiBase ?? '/api/v1',
clientApp: config.clientApp, clientApp: config.clientApp,
getAccessToken: config.getAccessToken, getAccessToken: config.getAccessToken,
}); jsApiList: ['scanQRCode', 'checkJsApi'],
};
await ensureScanJssdk(config);
if (!window.wx?.scanQRCode) { if (!window.wx?.scanQRCode) {
throw new Error('当前微信版本不支持扫码,请升级微信后重试'); throw new Error('当前微信版本不支持扫码,请升级微信后重试');
} }
// iOS 在 wx.ready 后立即调 scanQRCode 可能触发 offline verifying // wx.ready ≠ 权限离线校验完成;授权回跳后更明显
if (isIosDevice() && !isWechatDevTools()) { const warmupMs = options.postAuthWarmup
await delay(500); ? 1200
} : isIosDevice() && !isWechatDevTools()
? 800
: 300;
await delay(warmupMs);
const maxAttempts = 2; const maxAttempts = options.postAuthWarmup ? 3 : 2;
let lastError: Error | null = null; let lastError: Error | null = null;
for (let attempt = 0; attempt < maxAttempts; attempt++) { for (let attempt = 0; attempt < maxAttempts; attempt++) {
if (attempt > 0) { if (attempt > 0) {
await delay(800); if (lastError && isScanPermissionWarmupError(lastError.message)) {
resetJssdkConfig();
await initWechatJssdk({ ...jssdkOptions, url: getJssdkSignUrl() });
await checkJsApi('scanQRCode');
}
await delay(700 + attempt * 500);
} }
try { try {
return await invokeScanQrCode(); return await invokeScanQrCode();
} catch (e) { } catch (e) {
lastError = e instanceof Error ? e : new Error('扫码失败'); lastError = e instanceof Error ? e : new Error('扫码失败');
const raw = lastError.message; const retryable = isScanPermissionWarmupError(lastError.message);
const retryable = /offline verifying|权限验证中|接口未就绪/i.test(raw);
if (attempt < maxAttempts - 1 && retryable) continue; if (attempt < maxAttempts - 1 && retryable) continue;
throw lastError; throw lastError;
} }