门店端调用相机功能提示修改
This commit is contained in:
@@ -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) => {
|
||||||
|
|||||||
@@ -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]) =>
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user