export function isWechatBrowser(userAgent = navigator.userAgent) { const ua = userAgent || ''; return /MicroMessenger/i.test(ua) || /WindowsWechat/i.test(ua); } export function isMobileBrowser(userAgent = navigator.userAgent) { return /Android|iPhone|iPad|iPod|Mobile/i.test(userAgent || ''); } export function isIosBrowser(userAgent = navigator.userAgent) { return /iPhone|iPad|iPod/i.test(userAgent || ''); } /** 手机外部浏览器(Safari / Chrome 等),不在微信内 */ export function isMobileExternalBrowser(options?: { userAgent?: string; search?: string; serverInWechat?: boolean; }) { return isMobileBrowser(options?.userAgent) && !isWechatContext(options); } /** 构建「在微信中打开」后应访问的链接(带上 from=wechat 以展示一键登录) */ export function buildPageUrlForWechatOpen( search = window.location.search, pathname = window.location.pathname, ) { const params = new URLSearchParams(search); params.set('from', 'wechat'); if (!params.get('utm_source')) params.set('utm_source', 'wechat'); const query = params.toString(); return `${window.location.origin}${pathname}${query ? `?${query}` : ''}${window.location.hash}`; } /** PC 兜底二维码:扫码后直接进入微信 OAuth,手机上会出现授权/确认页 */ export function buildWechatAuthorizePageUrl( search = window.location.search, ) { const params = new URLSearchParams(search); const returnTo = params.get('return_to'); const authPath = buildWechatAuthorizeUrl({ returnTo: returnTo ?? undefined, utmSource: params.get('utm_source') ?? params.get('from') ?? 'wechat', utmMedium: params.get('utm_medium') ?? undefined, utmCampaign: params.get('utm_campaign') ?? undefined, intent: 'login', }); return `${window.location.origin}${authPath}`; } export async function copyWechatOpenLink(): Promise { const url = buildPageUrlForWechatOpen(); try { await navigator.clipboard.writeText(url); return true; } catch { try { const input = document.createElement('textarea'); input.value = url; input.setAttribute('readonly', 'true'); input.style.position = 'fixed'; input.style.left = '-9999px'; document.body.appendChild(input); input.select(); const ok = document.execCommand('copy'); document.body.removeChild(input); return ok; } catch { return false; } } } /** 从服务号链接进入时带的参数(即使 UA 识别失败也展示微信登录) */ export function isWechatEntryUrl(search = window.location.search) { const params = new URLSearchParams(search); const from = (params.get('from') || '').toLowerCase(); const utm = (params.get('utm_source') || '').toLowerCase(); return from === 'wechat' || utm === 'wechat'; } export function isWechatContext(options?: { userAgent?: string; search?: string; serverInWechat?: boolean; }) { if (options?.serverInWechat) return true; if (isWechatBrowser(options?.userAgent)) return true; return isWechatEntryUrl(options?.search); } /** 手机 H5 或微信内置浏览器:导航会话标签采用下方悬浮条布局 */ export function isH5OrWechatClient(options?: { userAgent?: string; search?: string; serverInWechat?: boolean; }) { return isMobileBrowser(options?.userAgent) || isWechatContext(options); } export function buildWechatAuthorizeUrl(options?: { returnTo?: string; utmSource?: string; utmMedium?: string; utmCampaign?: string; intent?: 'login' | 'register' | 'bind'; authMode?: 'mp' | 'open'; }) { const params = new URLSearchParams(); const returnTo = options?.returnTo ?? `${window.location.pathname}${window.location.search}${window.location.hash}`; if (returnTo && returnTo !== '/') { params.set('return_to', returnTo); } if (options?.utmSource) params.set('utm_source', options.utmSource); if (options?.utmMedium) params.set('utm_medium', options.utmMedium); if (options?.utmCampaign) params.set('utm_campaign', options.utmCampaign); if (options?.intent) params.set('intent', options.intent); if (options?.authMode) params.set('auth_mode', options.authMode); const query = params.toString(); return query ? `/auth/wechat/authorize?${query}` : '/auth/wechat/authorize'; } export function readWechatPendingToken(): string | null { const params = new URLSearchParams(window.location.search); const pending = params.get('wechat_pending'); if (!pending) return null; params.delete('wechat_pending'); const nextSearch = params.toString(); const nextUrl = `${window.location.pathname}${nextSearch ? `?${nextSearch}` : ''}${window.location.hash}`; window.history.replaceState({}, '', nextUrl); return pending; } export function readWechatAuthError(): string | null { const params = new URLSearchParams(window.location.search); const error = params.get('wechat_error'); if (!error) return null; params.delete('wechat_error'); const nextSearch = params.toString(); const nextUrl = `${window.location.pathname}${nextSearch ? `?${nextSearch}` : ''}${window.location.hash}`; window.history.replaceState({}, '', nextUrl); try { return decodeURIComponent(error); } catch { return error; } }