diff --git a/src/app/pay/page.tsx b/src/app/pay/page.tsx index 7a14098..39132e5 100644 --- a/src/app/pay/page.tsx +++ b/src/app/pay/page.tsx @@ -486,6 +486,7 @@ function PayContent() { onBack={handleBack} dark={isDark} isEmbedded={isEmbedded} + isMobile={isMobile} /> )} diff --git a/src/components/PaymentQRCode.tsx b/src/components/PaymentQRCode.tsx index 4dea432..bfea193 100644 --- a/src/components/PaymentQRCode.tsx +++ b/src/components/PaymentQRCode.tsx @@ -18,6 +18,7 @@ interface PaymentQRCodeProps { onBack: () => void; dark?: boolean; isEmbedded?: boolean; + isMobile?: boolean; } const TEXT_EXPIRED = '\u8BA2\u5355\u5DF2\u8D85\u65F6'; @@ -26,6 +27,7 @@ const TEXT_GO_PAY = '\u70B9\u51FB\u524D\u5F80\u652F\u4ED8'; const TEXT_SCAN_PAY = '\u8BF7\u4F7F\u7528\u652F\u4ED8\u5E94\u7528\u626B\u7801\u652F\u4ED8'; const TEXT_BACK = '\u8FD4\u56DE'; const TEXT_CANCEL_ORDER = '\u53D6\u6D88\u8BA2\u5355'; +const TEXT_H5_HINT = '\u652F\u4ED8\u5B8C\u6210\u540E\u8BF7\u8FD4\u56DE\u6B64\u9875\u9762\uFF0C\u7CFB\u7EDF\u5C06\u81EA\u52A8\u786E\u8BA4'; const TERMINAL_STATUSES = new Set(['COMPLETED', 'FAILED', 'CANCELLED', 'EXPIRED', 'REFUNDED', 'REFUND_FAILED']); export default function PaymentQRCode({ @@ -43,6 +45,7 @@ export default function PaymentQRCode({ onBack, dark = false, isEmbedded = false, + isMobile = false, }: PaymentQRCodeProps) { const displayAmount = payAmountProp ?? amount; const hasFeeDiff = payAmountProp !== undefined && payAmountProp !== amount; @@ -416,6 +419,21 @@ export default function PaymentQRCode({ )} + ) : isMobile && payUrl ? ( + <> + + {channelLabel} + {`打开${channelLabel}支付`} + +

+ {TEXT_H5_HINT} +

+ ) : ( <> {qrDataUrl && ( diff --git a/src/lib/pay-utils.ts b/src/lib/pay-utils.ts index 00896e9..c019fe3 100644 --- a/src/lib/pay-utils.ts +++ b/src/lib/pay-utils.ts @@ -38,12 +38,19 @@ export const FILTER_OPTIONS: { key: OrderStatusFilter; label: string }[] = [ export function detectDeviceIsMobile(): boolean { if (typeof window === 'undefined') return false; + // 1. 现代 API(Chromium 系浏览器,最准确) + const uad = (navigator as Navigator & { userAgentData?: { mobile: boolean } }).userAgentData; + if (uad !== undefined) return uad.mobile; + + // 2. UA 正则兜底(Safari / Firefox 等) const ua = navigator.userAgent || ''; const mobileUA = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Windows Phone|Mobile/i.test(ua); + if (mobileUA) return true; + + // 3. 触控 + 小屏兜底(新版 iPad UA 伪装成 Mac 的情况) const smallPhysicalScreen = Math.min(window.screen.width, window.screen.height) <= 768; const touchCapable = navigator.maxTouchPoints > 1; - - return mobileUA || (touchCapable && smallPhysicalScreen); + return touchCapable && smallPhysicalScreen; } export function formatStatus(status: string): string {