feat(mini): v4.0.8 客服卡片主包落地、定位误报修复,合伙人微信内预览保存海报
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -130,10 +130,12 @@ export function toast(title: string, icon: 'success' | 'error' | 'none' = 'none'
|
||||
const text = title.trim();
|
||||
if (!text) return;
|
||||
if (icon !== 'success' && showFloatingToast(text)) {
|
||||
void Taro.hideToast();
|
||||
void Promise.resolve(Taro.hideToast() as void | Promise<unknown>).catch(() => {});
|
||||
return;
|
||||
}
|
||||
Taro.showToast({ title: text, icon, duration: 1800 });
|
||||
void Promise.resolve(
|
||||
Taro.showToast({ title: text, icon, duration: 1800 }) as void | Promise<unknown>,
|
||||
).catch(() => {});
|
||||
}
|
||||
|
||||
export type SessionPayload = {
|
||||
|
||||
@@ -21,6 +21,14 @@ export type ClientErrorPayload = {
|
||||
extra?: Record<string, unknown>;
|
||||
};
|
||||
|
||||
/** 微信预期失败,不应按 P1 未捕获上报 */
|
||||
const IGNORED_REJECTION =
|
||||
/getLocation:fail|hideToast:fail:toast can't be found|showToast:fail|authorize:fail auth deny/i;
|
||||
|
||||
function shouldIgnoreRejection(message: string): boolean {
|
||||
return IGNORED_REJECTION.test(message);
|
||||
}
|
||||
|
||||
function currentPagePath(): string | undefined {
|
||||
try {
|
||||
const pages = Taro.getCurrentPages();
|
||||
@@ -97,6 +105,7 @@ export function installClientErrorReporting(): void {
|
||||
: typeof reason === 'string'
|
||||
? reason
|
||||
: JSON.stringify(reason);
|
||||
if (shouldIgnoreRejection(message || '')) return;
|
||||
const stack = reason instanceof Error ? reason.stack : undefined;
|
||||
reportClientError({
|
||||
level: 'error',
|
||||
@@ -121,15 +130,17 @@ export function installClientErrorReporting(): void {
|
||||
});
|
||||
window.addEventListener('unhandledrejection', (ev) => {
|
||||
const reason = ev.reason;
|
||||
const message =
|
||||
reason instanceof Error
|
||||
? reason.message
|
||||
: typeof reason === 'string'
|
||||
? reason
|
||||
: 'unhandledrejection';
|
||||
if (shouldIgnoreRejection(message)) return;
|
||||
reportClientError({
|
||||
level: 'error',
|
||||
category: 'unhandled_rejection',
|
||||
message:
|
||||
reason instanceof Error
|
||||
? reason.message
|
||||
: typeof reason === 'string'
|
||||
? reason
|
||||
: 'unhandledrejection',
|
||||
message,
|
||||
stack: reason instanceof Error ? reason.stack : undefined,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,13 +14,7 @@ export async function tryGetClientGpsLocation(): Promise<ClientGpsLocation | nul
|
||||
if (process.env.TARO_ENV === 'weapp') {
|
||||
try {
|
||||
const Taro = (await import('@tarojs/taro')).default;
|
||||
const loc = await new Promise<{ latitude: number; longitude: number }>((resolve, reject) => {
|
||||
Taro.getLocation({
|
||||
type: 'gcj02',
|
||||
success: resolve,
|
||||
fail: reject,
|
||||
});
|
||||
});
|
||||
const loc = await Taro.getLocation({ type: 'gcj02' });
|
||||
return { latitude: loc.latitude, longitude: loc.longitude };
|
||||
} catch {
|
||||
return null;
|
||||
|
||||
@@ -11,13 +11,7 @@ export type ClientGpsLocation = {
|
||||
|
||||
export async function tryGetClientGpsLocation(): Promise<ClientGpsLocation | null> {
|
||||
try {
|
||||
const loc = await new Promise<{ latitude: number; longitude: number }>((resolve, reject) => {
|
||||
Taro.getLocation({
|
||||
type: 'gcj02',
|
||||
success: resolve,
|
||||
fail: reject,
|
||||
});
|
||||
});
|
||||
const loc = await Taro.getLocation({ type: 'gcj02' });
|
||||
return { latitude: loc.latitude, longitude: loc.longitude };
|
||||
} catch {
|
||||
return null;
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
import Taro from '@tarojs/taro';
|
||||
import { fetchClientConfig } from './pay-wechat';
|
||||
|
||||
/** 与 package.json version 同步,供服务端 minClientVersion 比对 */
|
||||
export const APP_VERSION = '3.5.16';
|
||||
/** 与 package.json version 同步(Taro defineConstants 注入),供 minClientVersion 比对 */
|
||||
export const APP_VERSION =
|
||||
(typeof TARO_APP_VERSION !== 'undefined' && String(TARO_APP_VERSION).trim()) || '4.0.4';
|
||||
|
||||
export const APP_VERSION_LABEL = `v${APP_VERSION}`;
|
||||
export const APP_VERSION_LABEL = `v${APP_VERSION.replace(/^v/i, '')}`;
|
||||
|
||||
function normalizeVersion(v: string): string {
|
||||
return String(v || '').trim().replace(/^v/i, '');
|
||||
}
|
||||
|
||||
function parseSemver(v: string): number[] {
|
||||
return v.split('.').map((n) => parseInt(n, 10) || 0);
|
||||
return normalizeVersion(v).split('.').map((n) => parseInt(n, 10) || 0);
|
||||
}
|
||||
|
||||
export function compareSemver(a: string, b: string): number {
|
||||
@@ -89,7 +94,7 @@ async function enforceMinClientVersion(min: string) {
|
||||
export async function checkClientVersionGate() {
|
||||
try {
|
||||
const config = await fetchClientConfig();
|
||||
const min = config.minClientVersion?.trim();
|
||||
const min = normalizeVersion(config.minClientVersion ?? '');
|
||||
if (min && compareSemver(APP_VERSION, min) < 0) {
|
||||
await enforceMinClientVersion(min);
|
||||
}
|
||||
|
||||
@@ -59,6 +59,23 @@ function clearLocationDenied() {
|
||||
}
|
||||
}
|
||||
|
||||
function wxErrorMessage(err: unknown): string {
|
||||
if (!err) return '';
|
||||
if (typeof err === 'string') return err;
|
||||
if (err instanceof Error) return err.message;
|
||||
if (typeof err === 'object') {
|
||||
const o = err as { errMsg?: unknown; message?: unknown };
|
||||
if (typeof o.errMsg === 'string' && o.errMsg) return o.errMsg;
|
||||
if (typeof o.message === 'string' && o.message) return o.message;
|
||||
try {
|
||||
return JSON.stringify(err);
|
||||
} catch {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
return String(err);
|
||||
}
|
||||
|
||||
function isDenyMessage(errMsg?: string): boolean {
|
||||
return /auth deny|authorize|permission|denied|拒绝|用户拒绝|getLocation:fail/i.test(
|
||||
errMsg || '',
|
||||
@@ -179,14 +196,13 @@ async function promptLocationAuthOnce() {
|
||||
}).catch(() => {});
|
||||
}
|
||||
|
||||
function getMiniLocation(): Promise<Taro.getLocation.SuccessCallbackResult> {
|
||||
return new Promise((resolve, reject) => {
|
||||
Taro.getLocation({
|
||||
type: 'gcj02',
|
||||
success: resolve,
|
||||
fail: reject,
|
||||
});
|
||||
});
|
||||
async function getMiniLocation(): Promise<Taro.getLocation.SuccessCallbackResult> {
|
||||
const setting = await Taro.getSetting().catch(() => null);
|
||||
if (setting?.authSetting?.['scope.userLocation'] === false) {
|
||||
throw new Error('getLocation:fail auth deny');
|
||||
}
|
||||
// 只用返回的 Promise,避免 callback + Promise 双重 reject 变成未捕获
|
||||
return Taro.getLocation({ type: 'gcj02' });
|
||||
}
|
||||
|
||||
async function resolveViaH5Jssdk(): Promise<ResolvedUserCity | null> {
|
||||
@@ -263,7 +279,7 @@ export async function resolveUserCity(force = false): Promise<ResolvedUserCity>
|
||||
return resolved;
|
||||
}
|
||||
} catch (err) {
|
||||
const errMsg = err instanceof Error ? err.message : String(err);
|
||||
const errMsg = wxErrorMessage(err);
|
||||
const denied = isDenyMessage(errMsg);
|
||||
if (denied && !isLocationDenied()) {
|
||||
await promptLocationAuthOnce();
|
||||
|
||||
@@ -57,6 +57,23 @@ function clearLocationDenied() {
|
||||
}
|
||||
}
|
||||
|
||||
function wxErrorMessage(err: unknown): string {
|
||||
if (!err) return '';
|
||||
if (typeof err === 'string') return err;
|
||||
if (err instanceof Error) return err.message;
|
||||
if (typeof err === 'object') {
|
||||
const o = err as { errMsg?: unknown; message?: unknown };
|
||||
if (typeof o.errMsg === 'string' && o.errMsg) return o.errMsg;
|
||||
if (typeof o.message === 'string' && o.message) return o.message;
|
||||
try {
|
||||
return JSON.stringify(err);
|
||||
} catch {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
return String(err);
|
||||
}
|
||||
|
||||
function isDenyMessage(errMsg?: string): boolean {
|
||||
return /auth deny|authorize|permission|denied|拒绝|用户拒绝|getLocation:fail/i.test(
|
||||
errMsg || '',
|
||||
@@ -175,14 +192,13 @@ async function promptLocationAuthOnce() {
|
||||
}).catch(() => {});
|
||||
}
|
||||
|
||||
function getMiniLocation(): Promise<Taro.getLocation.SuccessCallbackResult> {
|
||||
return new Promise((resolve, reject) => {
|
||||
Taro.getLocation({
|
||||
type: 'gcj02',
|
||||
success: resolve,
|
||||
fail: reject,
|
||||
});
|
||||
});
|
||||
async function getMiniLocation(): Promise<Taro.getLocation.SuccessCallbackResult> {
|
||||
const setting = await Taro.getSetting().catch(() => null);
|
||||
if (setting?.authSetting?.['scope.userLocation'] === false) {
|
||||
throw new Error('getLocation:fail auth deny');
|
||||
}
|
||||
// 只用返回的 Promise,避免 callback + Promise 双重 reject 变成未捕获
|
||||
return Taro.getLocation({ type: 'gcj02' });
|
||||
}
|
||||
|
||||
export async function resolveUserCity(force = false): Promise<ResolvedUserCity> {
|
||||
@@ -211,7 +227,7 @@ export async function resolveUserCity(force = false): Promise<ResolvedUserCity>
|
||||
return resolved;
|
||||
}
|
||||
} catch (err) {
|
||||
const errMsg = err instanceof Error ? err.message : String(err);
|
||||
const errMsg = wxErrorMessage(err);
|
||||
const denied = isDenyMessage(errMsg);
|
||||
if (denied && !isLocationDenied()) {
|
||||
await promptLocationAuthOnce();
|
||||
|
||||
@@ -4,6 +4,17 @@ export type WecomCsSessionContext = {
|
||||
from?: string;
|
||||
};
|
||||
|
||||
/** 客服消息卡片必须落主包页;分包(订单详情)作启动页会白屏 */
|
||||
export function buildCsSharePath(orderId?: string): string {
|
||||
const id = (orderId || '').trim();
|
||||
return id ? `pages/open/index?id=${encodeURIComponent(id)}` : 'pages/home/index';
|
||||
}
|
||||
|
||||
export function buildCsShareTitle(orderNo?: string): string {
|
||||
const no = (orderNo || '').trim();
|
||||
return no ? `订单 ${no}` : '杜康好客';
|
||||
}
|
||||
|
||||
type OpenCsChatOption = {
|
||||
extInfo: { url: string };
|
||||
corpId: string;
|
||||
@@ -97,16 +108,10 @@ export async function openWecomCustomerServiceChat(params: {
|
||||
const option: OpenCsChatOption = {
|
||||
extInfo: { url },
|
||||
corpId,
|
||||
showMessageCard: true,
|
||||
sendMessageTitle: buildCsShareTitle(params.session?.orderNo),
|
||||
sendMessagePath: buildCsSharePath(params.session?.orderId),
|
||||
};
|
||||
if (params.session?.orderNo || params.session?.orderId) {
|
||||
option.showMessageCard = true;
|
||||
option.sendMessageTitle = params.session.orderNo
|
||||
? `订单 ${params.session.orderNo}`
|
||||
: '订单咨询';
|
||||
if (params.session.orderId) {
|
||||
option.sendMessagePath = `pages/order-detail/index?id=${params.session.orderId}`;
|
||||
}
|
||||
}
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
wxApi({
|
||||
|
||||
Reference in New Issue
Block a user