fix(h5/code-pay): 合并码牌路由至 CodePay 父级修复首屏白屏

码牌入口 /h/:code 跳转 /h/alipay/:code 等环境页是跨顶层路由跳转,
触发 App.vue transition mode=out-in 整页 remount Layout, 与 onMounted 内
router.replace 竞争导致首次扫码白屏、刷新才正常 (支付宝 webview 尤为明显)。
与聚合支付同源 bug (commits da82489 / 8138d93), 照搬修复范式。

- paths.ts: 新增 CODE_PAY_GROUP='/h' (PC 仍用各 CODE_PAY_* 顶层路由)
- modules.ts: 入口 + wechat/alipay/union-pay/douyin 合并到 CodePay 父级
  下作子路由, 静态段在 :code 之前避免被吞; matched[0].name 恒为 CodePay
- router-guards.ts: ENV name 改子路由名 (CodePayAlipayPage 等);
  守卫检查 CodePayPage → CodePayEntry
- code-pay/index.vue: 组件名 CodePayDispatch → CodePayEntry;
  onMounted → onBeforeMount; ENV_ROUTE_NAME 改子路由名
- 4 个环境页 defineOptions name 对齐路由名 (CodePayAlipayPage 等)

URL 契约不变: /h/:code、/h/{env}/:code
This commit is contained in:
bootx
2026-07-20 21:59:48 +08:00
parent f92c5f9613
commit 89da35d1a4
8 changed files with 69 additions and 88 deletions

View File

@@ -100,82 +100,49 @@ const routeModuleList: Array<RouteRecordRaw> = [
},
],
},
// 码牌支付-微信端(静态段须在 /h/:code 之前注册)
// 码牌支付:入口 + 各宿主环境页 统一在 CodePay 父级下作为子路由
// 让 App.vue 顶层 transition keymatched[0].name在 entry → 环境页跳转时保持不变(都是 CodePay
// 避免整页 remount 与 Entry 的 onBeforeMount router.replace 竞争导致白屏
// (曾发生在支付宝 webview跨顶层 replace 与 <transition mode="out-in" appear> 的 enter 死锁,
// 与聚合 Aggregate 同源 bug修复范式见 commits da82489 / 8138d93
// 注意静态段wechat/alipay/union-pay/douyin必须排在 :code 之前,否则会被当 code 吞掉
// URL 契约不变,仍是 /h/:code、/h/{env}/:code
{
path: RoutePath.CODE_PAY_WECHAT,
name: 'CodePayWechat',
component: Layout,
meta: {
title: 'route.codePay', // 码牌支付
},
children: [
{
path: '',
name: 'CodePayWechatPage',
component: () => import('@/mobile/views/code-pay/wechat/index.vue'),
},
],
},
// 码牌支付-支付宝端
{
path: RoutePath.CODE_PAY_ALIPAY,
name: 'CodePayAlipay',
component: Layout,
meta: {
title: 'route.codePay', // 码牌支付
},
children: [
{
path: '',
name: 'CodePayAlipayPage',
component: () => import('@/mobile/views/code-pay/alipay/index.vue'),
},
],
},
// 码牌支付-云闪付端(静态段须在 /h/:code 之前注册)
{
path: RoutePath.CODE_PAY_UNION,
name: 'CodePayUnion',
component: Layout,
meta: {
title: 'route.codePay', // 码牌支付
},
children: [
{
path: '',
name: 'CodePayUnionPage',
component: () => import('@/mobile/views/code-pay/union-pay/index.vue'),
},
],
},
// 码牌支付-抖音端
{
path: RoutePath.CODE_PAY_DOUYIN,
name: 'CodePayDouyin',
component: Layout,
meta: {
title: 'route.codePay', // 码牌支付
},
children: [
{
path: '',
name: 'CodePayDouyinPage',
component: () => import('@/mobile/views/code-pay/douyin/index.vue'),
},
],
},
// 码牌支付入口分发跨端PC 为扫码引导页)
{
path: RoutePath.CODE_PAY,
path: RoutePath.CODE_PAY_GROUP,
name: 'CodePay',
component: Layout,
meta: {
title: 'route.codePay', // 码牌支付
},
children: [
// 微信环境页
{
path: '',
name: 'CodePayPage',
path: 'wechat/:code',
name: 'CodePayWechatPage',
component: () => import('@/mobile/views/code-pay/wechat/index.vue'),
},
// 支付宝环境页
{
path: 'alipay/:code',
name: 'CodePayAlipayPage',
component: () => import('@/mobile/views/code-pay/alipay/index.vue'),
},
// 云闪付环境页
{
path: 'union-pay/:code',
name: 'CodePayUnionPage',
component: () => import('@/mobile/views/code-pay/union-pay/index.vue'),
},
// 抖音环境页
{
path: 'douyin/:code',
name: 'CodePayDouyinPage',
component: () => import('@/mobile/views/code-pay/douyin/index.vue'),
},
// 入口UA 探测后 replace 到环境页(静态段已注册完毕,:code 兜底匹配编码)
{
path: ':code',
name: 'CodePayEntry',
component: () => import('@/mobile/views/code-pay/index.vue'),
},
],

View File

@@ -20,12 +20,12 @@ const AGGREGATE_ENV_ROUTE_NAME: Record<string, string> = {
douyin: 'AggregateDouyinPage',
}
/** 码牌 clientEnv → 路由 name */
/** 码牌 clientEnv → 路由 name(指向 CodePay 父级下的环境页子路由,避免跨顶层触发 transition remount */
const CODE_PAY_ENV_ROUTE_NAME: Record<string, string> = {
wechat: 'CodePayWechat',
alipay: 'CodePayAlipay',
union_pay: 'CodePayUnion',
douyin: 'CodePayDouyin',
wechat: 'CodePayWechatPage',
alipay: 'CodePayAlipayPage',
union_pay: 'CodePayUnionPage',
douyin: 'CodePayDouyinPage',
}
/**
@@ -59,8 +59,8 @@ function resolveEntryRedirect(to: RouteLocationNormalized): RouteLocationNormali
}
}
}
// 码牌分发入口 → 各宿主端页(非钱包宿主留在 CodePayPage 显示扫码提示)
if (to.name === 'CodePayPage') {
// 码牌分发入口 → 各宿主端页(非钱包宿主留在 CodePayEntry 显示扫码提示)
if (to.name === 'CodePayEntry') {
const code = to.params.code as string
if (code) {
const env = detectClientEnv()
@@ -68,7 +68,7 @@ function resolveEntryRedirect(to: RouteLocationNormalized): RouteLocationNormali
if (routeName) {
return { name: routeName, params: { code }, replace: true }
}
// browser 等非钱包宿主:留在 CodePayPageonMounted 显示"请用钱包扫码"
// browser 等非钱包宿主:留在 CodePayEntryonBeforeMount 显示"请用钱包扫码"
}
}
return null

View File

@@ -9,7 +9,7 @@ import InitLoadingMask from '@/shared/components/pay/InitLoadingMask.vue'
import CodePayShell from '../shared/CodePayShell.vue'
import { useCodePayPage } from '../shared/useCodePayPage'
defineOptions({ name: 'CodePayAlipay' })
defineOptions({ name: 'CodePayAlipayPage' })
// 支付宝品牌色
const BRAND_COLOR = '#1677ff'

View File

@@ -9,7 +9,7 @@ import InitLoadingMask from '@/shared/components/pay/InitLoadingMask.vue'
import CodePayShell from '../shared/CodePayShell.vue'
import { useCodePayPage } from '../shared/useCodePayPage'
defineOptions({ name: 'CodePayDouyin' })
defineOptions({ name: 'CodePayDouyinPage' })
// 抖音黑白主视觉
const BRAND_COLOR = '#161823'

View File

@@ -5,8 +5,12 @@
* 二维码恒为 /h/:code; 按 UA replace 到微信/支付宝/云闪付/抖音端页,
* 其它环境browser 等)提示用钱包扫码。
* 不支持页:四钱包图标 + 加粗标题 + 小号副文案(与聚合 unsupported 一致)。
*
* 注:跳转须用 onBeforeMount不用 onMounted与 App.vue <transition mode="out-in" appear>
* 的 enter 动画竞争onMounted 跳转曾导致首屏白屏、刷新才正常,与聚合 unsupported 同源 bug
* 同时各环境页须与入口同属 CodePay 父级子路由(见 modules.ts保证 matched[0].name 不变。
*/
import { onMounted, ref } from 'vue'
import { onBeforeMount, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRoute, useRouter } from 'vue-router'
import alipaySvg from '@/shared/assets/icons/channel/alipay.svg'
@@ -15,7 +19,7 @@ import unionPaySvg from '@/shared/assets/icons/channel/union_pay.svg'
import wechatSvg from '@/shared/assets/icons/channel/wechat.svg'
import { detectClientEnv } from '@/shared/utils/client-env'
defineOptions({ name: 'CodePayDispatch' })
defineOptions({ name: 'CodePayEntry' })
const { t } = useI18n()
const route = useRoute()
@@ -33,15 +37,15 @@ const walletIcons = [
{ src: douyinSvg, altKey: 'codePay.wallet.douyin' },
]
/** clientEnv → 码牌分端路由 name */
/** clientEnv → 码牌分端路由 name(指向 CodePay 父级下的环境页子路由,不跨顶层以避免 transition 竞争) */
const ENV_ROUTE_NAME: Record<string, string> = {
wechat: 'CodePayWechat',
alipay: 'CodePayAlipay',
union_pay: 'CodePayUnion',
douyin: 'CodePayDouyin',
wechat: 'CodePayWechatPage',
alipay: 'CodePayAlipayPage',
union_pay: 'CodePayUnionPage',
douyin: 'CodePayDouyinPage',
}
onMounted(() => {
onBeforeMount(() => {
const env = detectClientEnv()
const routeName = ENV_ROUTE_NAME[env]
if (routeName) {

View File

@@ -9,7 +9,7 @@ import InitLoadingMask from '@/shared/components/pay/InitLoadingMask.vue'
import CodePayShell from '../shared/CodePayShell.vue'
import { useCodePayPage } from '../shared/useCodePayPage'
defineOptions({ name: 'CodePayUnion' })
defineOptions({ name: 'CodePayUnionPage' })
// 云闪付品牌红
const BRAND_COLOR = '#e21836'

View File

@@ -9,7 +9,7 @@ import InitLoadingMask from '@/shared/components/pay/InitLoadingMask.vue'
import CodePayShell from '../shared/CodePayShell.vue'
import { useCodePayPage } from '../shared/useCodePayPage'
defineOptions({ name: 'CodePayWechat' })
defineOptions({ name: 'CodePayWechatPage' })
// 微信品牌色
const BRAND_COLOR = '#07c160'

View File

@@ -43,6 +43,16 @@ export const RoutePath = {
AGGREGATE_GROUP: '/aggregate',
/** 聚合-不支持的宿主环境提示Mobile + PC 注册用) */
AGGREGATE_UNSUPPORTED: '/aggregate/unsupported',
/**
* 码牌支付父级分组(仅 Mobile 路由注册用)
* Mobile 把入口 + 各宿主环境页wechat/alipay/union-pay/douyin全部合并到此父级下作为
* 子路由,让 App.vue 顶层 transition keymatched[0].name在 entry → 环境页跳转时保持不变
* (都是 CodePay避免整页 remount 与 Entry 的 onBeforeMount router.replace 竞争导致白屏
* (曾发生在支付宝 webview跨顶层 replace 与 <transition mode="out-in" appear> 的 enter 死锁)。
* URL 契约不变:/h/{code}、/h/{env}/{code}。
* PC 仍用各 CODE_PAY_*/h/:code、/h/wechat/:code 等)作为单层顶层路由。
*/
CODE_PAY_GROUP: '/h',
/** 码牌支付入口跨端Mobile 分发/收款PC 扫码引导path 与后端 getCodeLink /h/{code} 一致) */
CODE_PAY: '/h/:code',
/** 码牌支付-微信端Mobile 收款PC 同入口扫码引导) */