From dedc1d25da1b0382a57dcd47bf4d8a4c836ac0ab Mon Sep 17 00:00:00 2001 From: bootx Date: Mon, 20 Jul 2026 18:34:11 +0800 Subject: [PATCH] =?UTF-8?q?refactor(h5):=20=E7=BB=9F=E4=B8=80=E6=94=AF?= =?UTF-8?q?=E4=BB=98=E9=A1=B5=20loading=20=E8=A7=86=E8=A7=89=EF=BC=8C?= =?UTF-8?q?=E6=B6=88=E9=99=A4=20OAuth=20=E8=B7=B3=E8=BD=AC=E9=97=AA?= =?UTF-8?q?=E7=83=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 InitLoadingMask 组件统一全屏遮罩(聚合/收银台/码牌/回调页共用) - 聚合/收银台/码牌引入 ready 状态,业务内容在就绪前不渲染 - 路由守卫拦截 entry 入口,导航阶段直接 replace 到环境页 - 订单数据 sessionStorage 缓存,OAuth 回跳后快速恢复 - 回调页删除成功态分支,init 提前到 setup,中性文案 common.processing - 新增 OrderCardSkeleton 骨架屏组件(暂未接入业务页,留作扩展) --- src/mobile/router/router-guards.ts | 75 ++++- src/mobile/views/aggregate/alipay/Index.vue | 39 ++- src/mobile/views/aggregate/douyin/Index.vue | 35 ++- .../views/aggregate/union-pay/Index.vue | 35 ++- src/mobile/views/aggregate/wechat/Index.vue | 38 ++- src/mobile/views/auth/alipay/index.vue | 181 ++--------- src/mobile/views/auth/douyin/index.vue | 181 ++--------- src/mobile/views/auth/wechat/index.vue | 183 ++--------- src/mobile/views/cashier/CashierEnvPage.vue | 29 +- src/mobile/views/code-pay/alipay/index.vue | 38 ++- src/mobile/views/code-pay/douyin/index.vue | 38 ++- .../views/code-pay/shared/useCodePayPage.ts | 13 + src/mobile/views/code-pay/union-pay/index.vue | 38 ++- src/mobile/views/code-pay/wechat/index.vue | 38 ++- src/pc/views/auth/wechat/Index.vue | 296 +++--------------- src/pc/views/cashier/Index.vue | 8 +- src/shared/components/pay/InitLoadingMask.vue | 134 ++++++++ .../components/pay/OrderCardSkeleton.vue | 105 +++++++ src/shared/locales/en-US/common.json | 3 +- src/shared/locales/id-ID/common.json | 3 +- src/shared/locales/ja-JP/common.json | 3 +- src/shared/locales/ko-KR/common.json | 3 +- src/shared/locales/ms-MY/common.json | 3 +- src/shared/locales/th-TH/common.json | 3 +- src/shared/locales/vi-VN/common.json | 3 +- src/shared/locales/zh-CN/common.json | 3 +- src/shared/locales/zh-HK/common.json | 3 +- src/shared/locales/zh-TW/common.json | 3 +- src/shared/pay/use-aggregate-pay.ts | 34 ++ src/shared/utils/order-cache.ts | 76 +++++ 30 files changed, 799 insertions(+), 845 deletions(-) create mode 100644 src/shared/components/pay/InitLoadingMask.vue create mode 100644 src/shared/components/pay/OrderCardSkeleton.vue create mode 100644 src/shared/utils/order-cache.ts diff --git a/src/mobile/router/router-guards.ts b/src/mobile/router/router-guards.ts index 55549cc..259cf74 100644 --- a/src/mobile/router/router-guards.ts +++ b/src/mobile/router/router-guards.ts @@ -1,13 +1,79 @@ -import type { Router } from 'vue-router' +import type { RouteLocationNormalized, Router } from 'vue-router' import NProgress from 'nprogress' import { watch } from 'vue' import { isNavigationFailure } from 'vue-router' import i18n, { t } from '@/shared/locales' import { useRouteStoreWithOut } from '@/shared/store/modules/route' +import { + detectAggregateClientEnv, + detectClientEnv, +} from '@/shared/utils/client-env' import 'nprogress/nprogress.css' NProgress.configure({ parent: '#app' }) +/** 聚合 clientEnv → 路由 name(指向 Aggregate 父级下的环境页子路由) */ +const AGGREGATE_ENV_ROUTE_NAME: Record = { + wechat: 'AggregateWechatPage', + alipay: 'AggregateAlipayPage', + union_pay: 'AggregateUnionPage', + douyin: 'AggregateDouyinPage', +} + +/** 码牌 clientEnv → 路由 name */ +const CODE_PAY_ENV_ROUTE_NAME: Record = { + wechat: 'CodePayWechat', + alipay: 'CodePayAlipay', + union_pay: 'CodePayUnion', + douyin: 'CodePayDouyin', +} + +/** + * entry 探测:在导航阶段直接 replace 到目标环境页, + * 避免 entry 组件 mount/unmount 带来的额外视觉切换(消除"闪一下") + * + * 与 entry.vue 的 onBeforeMount replace 形成兜底:守卫拦截成功则 entry 组件不会挂载; + * 守卫若因故未拦到(return true),entry.vue 仍会兜底执行同样的探测逻辑 + */ +function resolveEntryRedirect(to: RouteLocationNormalized): RouteLocationNormalized | Record | null { + // 收银台入口 → 环境页(detectClientEnv 总有返回值,含 browser) + if (to.name === 'CashierEntry') { + const orderNo = to.params.orderNo as string + if (orderNo) { + const clientEnv = detectClientEnv() + return { name: 'CashierEnvPage', params: { orderNo, clientEnv }, replace: true } + } + } + // 聚合入口 → 各宿主环境页 / 非宿主提示页 + if (to.name === 'AggregateEntry') { + const orderNo = to.params.orderNo as string + if (orderNo) { + const clientEnv = detectAggregateClientEnv() + if (!clientEnv) { + // 非宿主:提示页 + return { name: 'AggregateUnsupportedPage', query: { orderNo }, replace: true } + } + const routeName = AGGREGATE_ENV_ROUTE_NAME[clientEnv] + if (routeName) { + return { name: routeName, params: { orderNo }, replace: true } + } + } + } + // 码牌分发入口 → 各宿主端页(非钱包宿主留在 CodePayPage 显示扫码提示) + if (to.name === 'CodePayPage') { + const code = to.params.code as string + if (code) { + const env = detectClientEnv() + const routeName = CODE_PAY_ENV_ROUTE_NAME[env] + if (routeName) { + return { name: routeName, params: { code }, replace: true } + } + // browser 等非钱包宿主:留在 CodePayPage(onMounted 显示"请用钱包扫码") + } + } + return null +} + /** 根据路由 meta.title(i18n key)设置浏览器标题 */ function applyRouteDocumentTitle(router: Router) { const key = router.currentRoute.value.meta?.title as string | undefined @@ -18,8 +84,13 @@ function applyRouteDocumentTitle(router: Router) { } export function createRouterGuards(router: Router) { - router.beforeEach(() => { + router.beforeEach((to) => { NProgress.start() + // entry 探测:拦截入口路由,导航阶段直接 replace 到环境页 + const redirect = resolveEntryRedirect(to) + if (redirect) { + return redirect + } // 鉴权逻辑已移除:当前为开放式访问,待业务需要时在此补充登录/权限校验 return true }) diff --git a/src/mobile/views/aggregate/alipay/Index.vue b/src/mobile/views/aggregate/alipay/Index.vue index 511d85f..d74bf77 100644 --- a/src/mobile/views/aggregate/alipay/Index.vue +++ b/src/mobile/views/aggregate/alipay/Index.vue @@ -8,6 +8,7 @@ import { showNotify, showSuccessToast } from 'vant' import { computed, onMounted } from 'vue' import { useI18n } from 'vue-i18n' import { useRoute } from 'vue-router' +import InitLoadingMask from '@/shared/components/pay/InitLoadingMask.vue' import QrCodeDisplay from '@/shared/components/pay/QrCodeDisplay.vue' import { useAggregatePay } from '@/shared/pay/use-aggregate-pay' import AggregateResultCard from '../components/AggregateResultCard.vue' @@ -22,7 +23,7 @@ const route = useRoute() const orderNo = route.params.orderNo as string const { - loading, + ready, paying, authorizing, loadError, @@ -54,6 +55,19 @@ const { }, }) +/** + * InitLoadingMask 阶段文案 + */ +const maskTipKey = computed(() => { + if (authorizing.value) { + return 'aggregate.authorizing' + } + if (paying.value) { + return 'aggregate.paying' + } + return '' +}) + /** 终态(非成功)的状态映射,供 AggregateResultCard 渲染 */ const terminalState = computed(() => { const s = order.value.status @@ -99,25 +113,20 @@ onMounted(() => {