feat(h5): 新增小程序入口引导页并按UA分流钱包内提示

- 新增码牌 /m/:code 引导页(mobile/pc),消除浏览器直开404

- 引导页按UA分流:钱包内显示小程序暂不可用并高亮当前钱包图标

- 避免在微信内提示用微信扫码的矛盾,抽取 useMiniGuideEnv 统一处理

- 顺带统一视图文件命名(index.vue)与组件命名
This commit is contained in:
daxpay
2026-07-31 12:12:27 +08:00
parent 3735193e08
commit fb234182d3
36 changed files with 722 additions and 47 deletions

View File

@@ -19,7 +19,7 @@ routeStore.setRouters(constantRouter.concat(routeModuleList))
const pcOnlyStubs: RouteRecordRaw[] = otherDeviceExclusives('mobile').map(r => ({
path: r.path,
name: `${r.name}__stub`,
component: () => import('@/mobile/views/device-only.vue'),
component: () => import('@/mobile/views/DeviceOnly.vue'),
}))
const router = createRouter({

View File

@@ -37,7 +37,7 @@ const routeModuleList: Array<RouteRecordRaw> = [
// 入口UA 探测后 replace 到环境页
path: '',
name: 'CashierEntry',
component: () => import('@/mobile/views/cashier/entry.vue'),
component: () => import('@/mobile/views/cashier/Entry.vue'),
},
{
// 环境页clientEnv 白名单在页内校验(不缓存:收银台为一次性支付页,每次须重新探测与加载)
@@ -66,31 +66,31 @@ const routeModuleList: Array<RouteRecordRaw> = [
{
path: 'unsupported',
name: 'AggregateUnsupportedPage',
component: () => import('@/mobile/views/aggregate/unsupported.vue'),
component: () => import('@/mobile/views/aggregate/Unsupported.vue'),
},
// 微信环境页
{
path: 'wechat/:orderNo',
name: 'AggregateWechatPage',
component: () => import('@/mobile/views/aggregate/wechat/Index.vue'),
component: () => import('@/mobile/views/aggregate/wechat/index.vue'),
},
// 支付宝环境页
{
path: 'alipay/:orderNo',
name: 'AggregateAlipayPage',
component: () => import('@/mobile/views/aggregate/alipay/Index.vue'),
component: () => import('@/mobile/views/aggregate/alipay/index.vue'),
},
// 云闪付环境页
{
path: 'union-pay/:orderNo',
name: 'AggregateUnionPage',
component: () => import('@/mobile/views/aggregate/union-pay/Index.vue'),
component: () => import('@/mobile/views/aggregate/union-pay/index.vue'),
},
// 抖音环境页
{
path: 'douyin/:orderNo',
name: 'AggregateDouyinPage',
component: () => import('@/mobile/views/aggregate/douyin/Index.vue'),
component: () => import('@/mobile/views/aggregate/douyin/index.vue'),
},
// 入口UA 探测后 replace 到环境页(静态段已注册完毕,:orderNo 兜底匹配订单号)
{
@@ -100,6 +100,24 @@ const routeModuleList: Array<RouteRecordRaw> = [
},
],
},
// 聚合小程序引导页(跨端:浏览器直接打开 /am/:orderNo 时展示扫码提示,不发起支付)
{
path: RoutePath.AGGREGATE_MINI,
name: 'AggregateMiniGuide',
component: () => import('@/mobile/views/aggregate-mini/index.vue'),
meta: {
title: 'route.cashier', // 收银台
},
},
// 码牌小程序引导页(跨端:浏览器直接打开 /m/:code 时展示扫码提示,不发起支付)
{
path: RoutePath.CODE_PAY_MINI,
name: 'CodePayMiniGuide',
component: () => import('@/mobile/views/code-pay-mini/index.vue'),
meta: {
title: 'route.codePay', // 码牌支付
},
},
// 码牌支付:入口 + 各宿主环境页 统一在 CodePay 父级下作为子路由
// 让 App.vue 顶层 transition keymatched[0].name在 entry → 环境页跳转时保持不变(都是 CodePay
// 避免整页 remount 与 Entry 的 onBeforeMount router.replace 竞争导致白屏

View File

@@ -0,0 +1,140 @@
<script lang="ts" setup>
/**
* 聚合小程序引导页(/am/:orderNo
* 系统浏览器打开:展示"请扫码支付";钱包 App 内打开(小程序未拉起):展示"小程序暂不可用"。
* UA 分流由 useMiniGuideEnv 处理。微信/支付宝扫码正常时由「普通链接二维码」规则拉起小程序。
* 视觉结构与 aggregate/unsupported.vue 对齐(品牌条 + 上浮卡)。
*/
import { useI18n } from 'vue-i18n'
import { useRoute } from 'vue-router'
import { closeWebview } from '@/shared/pay/close-webview'
import { useMiniGuideEnv } from '@/shared/pay/use-mini-guide-env'
defineOptions({ name: 'AggregateMiniGuidePage' })
const { t } = useI18n()
const route = useRoute()
// 订单号来自路由参数
const orderNo = (route.params.orderNo as string) || ''
// UA 感知:钱包内显示"小程序暂不可用"+当前钱包图标;否则"请扫码支付"+四钱包
const { icons, titleKey, descKey } = useMiniGuideEnv()
</script>
<template>
<div class="agg-mini">
<!-- 品牌顶栏与聚合环境页一致 -->
<div class="agg-mini__brand" />
<div class="agg-mini__panel">
<!-- 四钱包图标仅图标横排 -->
<div class="agg-mini__wallets">
<img
v-for="item in icons"
:key="item.altKey"
class="agg-mini__wallet-img"
:src="item.src"
:alt="t(item.altKey)"
width="40"
height="40"
>
</div>
<!-- 标题(UA 感知:钱包内为"小程序暂不可用") -->
<h1 class="agg-mini__title">
{{ t(titleKey) }}
</h1>
<!-- 描述(UA 感知) -->
<p class="agg-mini__desc">
{{ t(descKey) }}
</p>
<!-- 订单号弱展示 -->
<p v-if="orderNo" class="agg-mini__order">
{{ t('aggregate.orderNo') }} {{ orderNo }}
</p>
<!-- 关闭页面 -->
<van-button
class="agg-mini__close"
round
block
type="primary"
@click="closeWebview"
>
{{ t('aggregate.closePage') }}
</van-button>
</div>
</div>
</template>
<style scoped>
.agg-mini {
min-height: 100%;
padding-bottom: 40px;
background: var(--h5-bg-page);
box-sizing: border-box;
}
.agg-mini__brand {
height: 120px;
background: linear-gradient(135deg, var(--h5-brand-cashier), var(--h5-brand-cashier-deep));
}
.agg-mini__panel {
margin: -48px 16px 0;
padding: 28px 20px 24px;
background: var(--h5-bg-card);
border-radius: 12px;
box-shadow: var(--h5-shadow-card);
text-align: center;
box-sizing: border-box;
}
.agg-mini__wallets {
display: flex;
align-items: center;
justify-content: center;
gap: 20px;
margin-bottom: 18px;
}
.agg-mini__wallet-img {
display: block;
width: 40px;
height: 40px;
object-fit: contain;
border-radius: 10px;
}
.agg-mini__title {
margin: 0 0 8px;
font-size: 17px;
font-weight: 600;
color: var(--h5-text-primary);
line-height: 1.4;
}
.agg-mini__desc {
margin: 0;
font-size: 13px;
color: var(--h5-text-secondary);
line-height: 1.65;
padding: 0 4px;
}
.agg-mini__order {
margin: 16px 0 0;
font-size: 12px;
color: var(--h5-text-placeholder);
word-break: break-all;
line-height: 1.5;
}
.agg-mini__close {
margin-top: 20px;
--van-button-primary-background: var(--h5-brand-cashier);
--van-button-primary-border-color: var(--h5-brand-cashier);
color: #fff;
font-weight: 600;
}
</style>

View File

@@ -22,7 +22,7 @@ const ENV_ROUTE_NAME: Record<string, string> = {
// onBeforeMount 探测并 replace挂载前跳走避免与 App.vue <transition mode="out-in" appear> 的 enter 动画
// 发生竞争onMounted 跳转曾导致 /aggregate/unsupported 首次打开白屏、刷新才正常)。
// 不用 setup 同步 replace以免在当前导航未确认时触发新导航引发竞态与 cashier/entry.vue 同源结论)。
// 不用 setup 同步 replace以免在当前导航未确认时触发新导航引发竞态与 cashier/Entry.vue 同源结论)。
onBeforeMount(() => {
const orderNo = route.params.orderNo as string
if (!orderNo) {

View File

@@ -10,7 +10,7 @@
/**
* 聚合扫码 - 结果状态卡片
* 统一展示支付终态:成功 / 失败 / 关闭 / 过期 / 加载失败
* 图标 + 颜色 + 标题 + 副提示,与 PC 端视觉语言对齐(见 pc/views/aggregate/Index.vue resultMeta
* 图标 + 颜色 + 标题 + 副提示,与 PC 端视觉语言对齐(见 pc/views/aggregate/index.vue resultMeta
* 4 个聚合环境页共用;成功色优先继承父级 --agg-brand深色已压亮
*
* 成功态state='paid')特殊处理:

View File

@@ -1,24 +0,0 @@
<script lang="ts" setup>
/**
* 兼容旧引用:收银台逻辑已拆为 entry + CashierEnvPage
* 若有静态 import 本文件,重定向到入口
*/
import { onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
defineOptions({ name: 'CashierPageLegacy' })
const route = useRoute()
const router = useRouter()
onMounted(() => {
router.replace({
name: 'CashierEntry',
params: { orderNo: route.params.orderNo as string },
})
})
</script>
<template>
<div />
</template>

View File

@@ -0,0 +1,140 @@
<script lang="ts" setup>
/**
* 码牌小程序引导页(/m/:code
* 系统浏览器打开:展示"请扫码支付";钱包 App 内打开(小程序未拉起):展示"小程序暂不可用"。
* UA 分流由 useMiniGuideEnv 处理。微信/支付宝扫码正常时由「普通链接二维码」规则拉起小程序。
* 视觉结构与 aggregate-mini/index.vue 对齐(品牌条 + 上浮卡)。
*/
import { useI18n } from 'vue-i18n'
import { useRoute } from 'vue-router'
import { closeWebview } from '@/shared/pay/close-webview'
import { useMiniGuideEnv } from '@/shared/pay/use-mini-guide-env'
defineOptions({ name: 'CodePayMiniGuidePage' })
const { t } = useI18n()
const route = useRoute()
// 码牌编码来自路由参数
const code = (route.params.code as string) || ''
// UA 感知:钱包内显示"小程序暂不可用"+当前钱包图标;否则"请扫码支付"+四钱包
const { icons, titleKey, descKey } = useMiniGuideEnv()
</script>
<template>
<div class="code-mini">
<!-- 品牌顶栏与码牌环境页一致 -->
<div class="code-mini__brand" />
<div class="code-mini__panel">
<!-- 四钱包图标仅图标横排 -->
<div class="code-mini__wallets">
<img
v-for="item in icons"
:key="item.altKey"
class="code-mini__wallet-img"
:src="item.src"
:alt="t(item.altKey)"
width="40"
height="40"
>
</div>
<!-- 标题(UA 感知:钱包内为"小程序暂不可用") -->
<h1 class="code-mini__title">
{{ t(titleKey) }}
</h1>
<!-- 描述(UA 感知) -->
<p class="code-mini__desc">
{{ t(descKey) }}
</p>
<!-- 码牌编号弱展示 -->
<p v-if="code" class="code-mini__code">
{{ t('codePay.codeLabel') }} {{ code }}
</p>
<!-- 关闭页面 -->
<van-button
class="code-mini__close"
round
block
type="primary"
@click="closeWebview"
>
{{ t('aggregate.closePage') }}
</van-button>
</div>
</div>
</template>
<style scoped>
.code-mini {
min-height: 100%;
padding-bottom: 40px;
background: var(--h5-bg-page);
box-sizing: border-box;
}
.code-mini__brand {
height: 120px;
background: linear-gradient(135deg, var(--h5-brand-cashier), var(--h5-brand-cashier-deep));
}
.code-mini__panel {
margin: -48px 16px 0;
padding: 28px 20px 24px;
background: var(--h5-bg-card);
border-radius: 12px;
box-shadow: var(--h5-shadow-card);
text-align: center;
box-sizing: border-box;
}
.code-mini__wallets {
display: flex;
align-items: center;
justify-content: center;
gap: 20px;
margin-bottom: 18px;
}
.code-mini__wallet-img {
display: block;
width: 40px;
height: 40px;
object-fit: contain;
border-radius: 10px;
}
.code-mini__title {
margin: 0 0 8px;
font-size: 17px;
font-weight: 600;
color: var(--h5-text-primary);
line-height: 1.4;
}
.code-mini__desc {
margin: 0;
font-size: 13px;
color: var(--h5-text-secondary);
line-height: 1.65;
padding: 0 4px;
}
.code-mini__code {
margin: 16px 0 0;
font-size: 12px;
color: var(--h5-text-placeholder);
word-break: break-all;
line-height: 1.5;
}
.code-mini__close {
margin-top: 20px;
--van-button-primary-background: var(--h5-brand-cashier);
--van-button-primary-border-color: var(--h5-brand-cashier);
color: #fff;
font-weight: 600;
}
</style>

View File

@@ -9,7 +9,7 @@ import { RoutePath } from '@/shared/router/paths'
const mobileOnlyStubs: RouteRecordRaw[] = otherDeviceExclusives('pc').map(r => ({
path: r.path,
name: `${r.name}__stub`,
component: () => import('@/pc/views/device-only.vue'),
component: () => import('@/pc/views/DeviceOnly.vue'),
}))
// PC 端路由树(独立于移动端,按需在此扩展业务路由,例如收银台 PC 版)
@@ -24,7 +24,7 @@ const routes: RouteRecordRaw[] = [
{
path: RoutePath.CASHIER,
name: 'PcCashier',
component: () => import('@/pc/views/cashier/Index.vue'),
component: () => import('@/pc/views/cashier/index.vue'),
meta: { title: 'route.cashier' }, // 收银台
},
// 聚合「非宿主」提示:须在 /aggregate/:orderNo 之前注册,避免 path 段 "unsupported" 被当成 orderNo 查单
@@ -38,59 +38,73 @@ const routes: RouteRecordRaw[] = [
{
path: RoutePath.AGGREGATE,
name: 'PcAggregate',
component: () => import('@/pc/views/aggregate/Index.vue'),
component: () => import('@/pc/views/aggregate/index.vue'),
meta: { title: 'route.cashier' }, // 收银台
},
// 聚合小程序引导页(跨端:浏览器直接打开 /am/:orderNo 时展示扫码提示,不发起支付)
{
path: RoutePath.AGGREGATE_MINI,
name: 'PcAggregateMini',
component: () => import('@/pc/views/aggregate-mini/index.vue'),
meta: { title: 'route.cashier' }, // 收银台
},
// 码牌小程序引导页(跨端:浏览器直接打开 /m/:code 时展示扫码提示,不发起支付)
{
path: RoutePath.CODE_PAY_MINI,
name: 'PcCodePayMini',
component: () => import('@/pc/views/code-pay-mini/index.vue'),
meta: { title: 'route.codePay' }, // 码牌支付
},
// 商户对账单PC 独占:移动端由注册表派生 device-only 存根)
{
path: RoutePath.MERCHANT_STATEMENT,
name: 'MerchantStatement',
component: () => import('@/pc/views/statement/Index.vue'),
component: () => import('@/pc/views/statement/index.vue'),
meta: { title: 'route.merchantStatement' }, // 商户对账单
},
// 协议展示页(跨端:与移动端同 path 各指各 view链接可独立分享
{
path: RoutePath.PROTOCOL,
name: 'PcProtocol',
component: () => import('@/pc/views/protocol/Index.vue'),
component: () => import('@/pc/views/protocol/index.vue'),
meta: { title: 'route.protocol' }, // 协议
},
// 微信认证落地页(跨端:与移动端同 path 各指各 viewPC 微信内置浏览器可访问)
{
path: RoutePath.AUTH_WECHAT,
name: 'PcWechatAuth',
component: () => import('@/pc/views/auth/wechat/Index.vue'),
component: () => import('@/pc/views/auth/wechat/index.vue'),
meta: { title: 'route.wechatAuth' }, // 微信认证
},
// 码牌支付PC 仅展示扫码引导(静态段须在 /h/:code 之前)
{
path: RoutePath.CODE_PAY_WECHAT,
name: 'PcCodePayWechat',
component: () => import('@/pc/views/code-pay/Index.vue'),
component: () => import('@/pc/views/code-pay/index.vue'),
meta: { title: 'route.codePay' }, // 码牌支付
},
{
path: RoutePath.CODE_PAY_ALIPAY,
name: 'PcCodePayAlipay',
component: () => import('@/pc/views/code-pay/Index.vue'),
component: () => import('@/pc/views/code-pay/index.vue'),
meta: { title: 'route.codePay' }, // 码牌支付
},
{
path: RoutePath.CODE_PAY_UNION,
name: 'PcCodePayUnion',
component: () => import('@/pc/views/code-pay/Index.vue'),
component: () => import('@/pc/views/code-pay/index.vue'),
meta: { title: 'route.codePay' }, // 码牌支付
},
{
path: RoutePath.CODE_PAY_DOUYIN,
name: 'PcCodePayDouyin',
component: () => import('@/pc/views/code-pay/Index.vue'),
component: () => import('@/pc/views/code-pay/index.vue'),
meta: { title: 'route.codePay' }, // 码牌支付
},
{
path: RoutePath.CODE_PAY,
name: 'PcCodePay',
component: () => import('@/pc/views/code-pay/Index.vue'),
component: () => import('@/pc/views/code-pay/index.vue'),
meta: { title: 'route.codePay' }, // 码牌支付
},
...mobileOnlyStubs,

View File

@@ -0,0 +1,138 @@
<script lang="ts" setup>
/**
* PC 聚合小程序引导页(/am/:orderNo
* 系统浏览器打开:展示"请扫码支付";钱包 App 内打开(小程序未拉起):展示"小程序暂不可用"。
* UA 分流由 useMiniGuideEnv 处理。微信/支付宝扫码正常时由「普通链接二维码」规则拉起小程序。
* 视觉结构与 PC aggregate/Unsupported.vue 对齐Web 白卡风格scoped 原生 px
*/
import { useI18n } from 'vue-i18n'
import { useRoute } from 'vue-router'
import { closeWebview } from '@/shared/pay/close-webview'
import { useMiniGuideEnv } from '@/shared/pay/use-mini-guide-env'
defineOptions({ name: 'PcAggregateMiniGuide' })
const { t } = useI18n()
const route = useRoute()
// 订单号来自路由参数
const orderNo = (route.params.orderNo as string) || ''
// UA 感知:钱包内显示"小程序暂不可用"+当前钱包图标;否则"请扫码支付"+四钱包
const { icons, titleKey, descKey } = useMiniGuideEnv()
</script>
<template>
<div class="pc-agg-mini">
<div class="pc-agg-mini__card">
<div class="pc-agg-mini__icons">
<img
v-for="icon in icons"
:key="icon.altKey"
:src="icon.src"
:alt="t(icon.altKey)"
class="pc-agg-mini__icon"
width="40"
height="40"
>
</div>
<!-- 标题(UA 感知:钱包内为"小程序暂不可用") -->
<h1 class="pc-agg-mini__title">
{{ t(titleKey) }}
</h1>
<!-- 描述(UA 感知) -->
<p class="pc-agg-mini__desc">
{{ t(descKey) }}
</p>
<p v-if="orderNo" class="pc-agg-mini__order">
{{ t('aggregate.orderNo') }} {{ orderNo }}
</p>
<!-- 关闭页面 -->
<button type="button" class="pc-agg-mini__close" @click="closeWebview">
{{ t('aggregate.closePage') }}
</button>
</div>
</div>
</template>
<style scoped>
/* PCscoped 原生 px禁止 UnoCSS 长度类 */
.pc-agg-mini {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 40px 24px;
box-sizing: border-box;
background: var(--h5-bg-page);
}
.pc-agg-mini__card {
width: 100%;
max-width: 480px;
padding: 40px 36px 32px;
background: var(--h5-bg-card);
border-radius: 12px;
box-shadow: 0 4px 24px rgb(0 0 0 / 8%);
text-align: center;
box-sizing: border-box;
}
.pc-agg-mini__icons {
display: flex;
align-items: center;
justify-content: center;
gap: 18px;
margin-bottom: 20px;
}
.pc-agg-mini__icon {
display: block;
width: 40px;
height: 40px;
object-fit: contain;
}
.pc-agg-mini__title {
margin: 0 0 10px;
font-size: 20px;
font-weight: 600;
color: var(--h5-text-primary);
line-height: 1.4;
}
.pc-agg-mini__desc {
margin: 0;
font-size: 14px;
color: var(--h5-text-secondary);
line-height: 1.7;
}
.pc-agg-mini__order {
margin: 20px 0 0;
padding-top: 16px;
border-top: 1px solid var(--h5-border);
font-size: 12px;
color: var(--h5-text-placeholder);
word-break: break-all;
line-height: 1.5;
}
.pc-agg-mini__close {
margin-top: 24px;
width: 100%;
max-width: 280px;
height: 44px;
border-radius: 22px;
border: none;
background: linear-gradient(135deg, var(--h5-brand-cashier) 0%, var(--h5-brand-cashier-deep) 100%);
color: #fff;
font-size: 15px;
font-weight: 600;
cursor: pointer;
box-shadow: 0 4px 12px rgba(93, 157, 254, 0.3);
}
.pc-agg-mini__close:hover {
opacity: 0.92;
}
</style>

View File

@@ -0,0 +1,138 @@
<script lang="ts" setup>
/**
* PC 码牌小程序引导页(/m/:code
* 系统浏览器打开:展示"请扫码支付";钱包 App 内打开(小程序未拉起):展示"小程序暂不可用"。
* UA 分流由 useMiniGuideEnv 处理。微信/支付宝扫码正常时由「普通链接二维码」规则拉起小程序。
* 视觉结构与 PC aggregate-mini/index.vue 对齐Web 白卡风格scoped 原生 px
*/
import { useI18n } from 'vue-i18n'
import { useRoute } from 'vue-router'
import { closeWebview } from '@/shared/pay/close-webview'
import { useMiniGuideEnv } from '@/shared/pay/use-mini-guide-env'
defineOptions({ name: 'PcCodePayMiniGuide' })
const { t } = useI18n()
const route = useRoute()
// 码牌编码来自路由参数
const code = (route.params.code as string) || ''
// UA 感知:钱包内显示"小程序暂不可用"+当前钱包图标;否则"请扫码支付"+四钱包
const { icons, titleKey, descKey } = useMiniGuideEnv()
</script>
<template>
<div class="pc-code-mini">
<div class="pc-code-mini__card">
<div class="pc-code-mini__icons">
<img
v-for="icon in icons"
:key="icon.altKey"
:src="icon.src"
:alt="t(icon.altKey)"
class="pc-code-mini__icon"
width="40"
height="40"
>
</div>
<!-- 标题(UA 感知:钱包内为"小程序暂不可用") -->
<h1 class="pc-code-mini__title">
{{ t(titleKey) }}
</h1>
<!-- 描述(UA 感知) -->
<p class="pc-code-mini__desc">
{{ t(descKey) }}
</p>
<p v-if="code" class="pc-code-mini__code">
{{ t('codePay.codeLabel') }} {{ code }}
</p>
<!-- 关闭页面 -->
<button type="button" class="pc-code-mini__close" @click="closeWebview">
{{ t('aggregate.closePage') }}
</button>
</div>
</div>
</template>
<style scoped>
/* PCscoped 原生 px禁止 UnoCSS 长度类 */
.pc-code-mini {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 40px 24px;
box-sizing: border-box;
background: var(--h5-bg-page);
}
.pc-code-mini__card {
width: 100%;
max-width: 480px;
padding: 40px 36px 32px;
background: var(--h5-bg-card);
border-radius: 12px;
box-shadow: 0 4px 24px rgb(0 0 0 / 8%);
text-align: center;
box-sizing: border-box;
}
.pc-code-mini__icons {
display: flex;
align-items: center;
justify-content: center;
gap: 18px;
margin-bottom: 20px;
}
.pc-code-mini__icon {
display: block;
width: 40px;
height: 40px;
object-fit: contain;
}
.pc-code-mini__title {
margin: 0 0 10px;
font-size: 20px;
font-weight: 600;
color: var(--h5-text-primary);
line-height: 1.4;
}
.pc-code-mini__desc {
margin: 0;
font-size: 14px;
color: var(--h5-text-secondary);
line-height: 1.7;
}
.pc-code-mini__code {
margin: 20px 0 0;
padding-top: 16px;
border-top: 1px solid var(--h5-border);
font-size: 12px;
color: var(--h5-text-placeholder);
word-break: break-all;
line-height: 1.5;
}
.pc-code-mini__close {
margin-top: 24px;
width: 100%;
max-width: 280px;
height: 44px;
border-radius: 22px;
border: none;
background: linear-gradient(135deg, var(--h5-brand-cashier) 0%, var(--h5-brand-cashier-deep) 100%);
color: #fff;
font-size: 15px;
font-weight: 600;
cursor: pointer;
box-shadow: 0 4px 12px rgba(93, 157, 254, 0.3);
}
.pc-code-mini__close:hover {
opacity: 0.92;
}
</style>

View File

@@ -24,6 +24,10 @@
"scanQr": "Scan the QR code to pay",
"unsupportedTitle": "Open in a wallet app",
"unsupportedDesc": "Please scan the QR code with WeChat, Alipay, UnionPay or Douyin",
"miniScanTitle": "Scan to Pay",
"miniScanDesc": "Please scan the QR code with WeChat, Alipay, etc. to complete payment",
"miniInWalletTitle": "Cashier mini-program unavailable",
"miniInWalletDesc": "The merchant may not have configured mini-program payment, or the mini-program hasn't been published. Please contact the merchant.",
"useMobileScan": "Please scan with your phone to pay",
"scanWithWallet": "Scan with WeChat, Alipay, UnionPay or Douyin to pay",
"remainingTime": "Time remaining",

View File

@@ -24,6 +24,10 @@
"scanQr": "Scan the QR code to pay",
"unsupportedTitle": "Open in a wallet app",
"unsupportedDesc": "Please scan the QR code with WeChat, Alipay, UnionPay or Douyin",
"miniScanTitle": "Pindai untuk Bayar",
"miniScanDesc": "Silakan pindai kode QR dengan WeChat, Alipay, dll. untuk menyelesaikan pembayaran",
"miniInWalletTitle": "Mini-program pembayaran tidak tersedia sementara",
"miniInWalletDesc": "Merchant mungkin belum mengonfigurasi pembayaran mini-program atau mini-program belum dipublikasikan. Silakan hubungi merchant.",
"useMobileScan": "Please scan with your phone to pay",
"scanWithWallet": "Pindai dengan WeChat, Alipay, UnionPay, atau Douyin untuk membayar",
"remainingTime": "Sisa waktu bayar",

View File

@@ -24,6 +24,10 @@
"scanQr": "QRコードをスキャンして支払ってください",
"unsupportedTitle": "ウォレットでスキャンしてください",
"unsupportedDesc": "WeChat、Alipay、銀聯またはDouyinでQRコードをスキャンしてください",
"miniScanTitle": "スキャンして支払い",
"miniScanDesc": "WeChat、AlipayなどでQRコードをスキャンして支払いを完了してください",
"miniInWalletTitle": "収納ミニプログラムは現在利用できません",
"miniInWalletDesc": "加盟店がミニプログラム決済を未設定、またはミニプログラムが未公開の可能性があります。加盟店にお問い合わせください。",
"useMobileScan": "スマートフォンでスキャンして支払ってください",
"scanWithWallet": "WeChat、Alipay、銀聯またはDouyinでスキャンして支払ってください",
"remainingTime": "支払い残り時間",

View File

@@ -24,6 +24,10 @@
"scanQr": "QR 코드를 스캔하여 결제하세요",
"unsupportedTitle": "지갑 앱으로 스캔하세요",
"unsupportedDesc": "위챗, 알리페이, 유니온페이 또는 도우인으로 QR 코드를 스캔하세요",
"miniScanTitle": "스캔하여 결제",
"miniScanDesc": "WeChat, Alipay 등으로 QR 코드를 스캔하여 결제를 완료해 주세요",
"miniInWalletTitle": "결제 미니프로그램을 일시적으로 사용할 수 없습니다",
"miniInWalletDesc": "가맹점이 미니프로그램 결제를 미설정했거나 미니프로그램이 미출시되었을 수 있습니다. 가맹점에 문의해 주세요.",
"useMobileScan": "휴대폰으로 스캔하여 결제하세요",
"scanWithWallet": "위챗, 알리페이, 유니온페이 또는 도우인으로 스캔하여 결제하세요",
"remainingTime": "남은 결제 시간",

View File

@@ -24,6 +24,10 @@
"scanQr": "Scan the QR code to pay",
"unsupportedTitle": "Open in a wallet app",
"unsupportedDesc": "Please scan the QR code with WeChat, Alipay, UnionPay or Douyin",
"miniScanTitle": "Imbas untuk Bayar",
"miniScanDesc": "Sila imbas kod QR dengan WeChat, Alipay, dll. untuk menyelesaikan pembayaran",
"miniInWalletTitle": "Mini-program pembayaran tidak tersedia buat sementara waktu",
"miniInWalletDesc": "Pedagang mungkin belum mengkonfigurasi pembayaran mini-program atau mini-program belum diterbitkan. Sila hubungi pedagang.",
"useMobileScan": "Please scan with your phone to pay",
"scanWithWallet": "Imbas dengan WeChat, Alipay, UnionPay atau Douyin untuk bayar",
"remainingTime": "Masa berbaki",

View File

@@ -24,6 +24,10 @@
"scanQr": "Scan the QR code to pay",
"unsupportedTitle": "Open in a wallet app",
"unsupportedDesc": "Please scan the QR code with WeChat, Alipay, UnionPay or Douyin",
"miniScanTitle": "สแกนเพื่อชำระเงิน",
"miniScanDesc": "กรุณาสแกนคิวอาร์โค้ดด้วย WeChat, Alipay ฯลฯ เพื่อให้การชำระเงินเสร็จสมบูรณ์",
"miniInWalletTitle": "มินิโปรแกรมรับชำระไม่พร้อมใช้งานชั่วคราว",
"miniInWalletDesc": "อาจเป็นไปได้ว่าร้านค้ายังไม่ได้ตั้งค่าการชำระผ่านมินิโปรแกรมหรือมินิโปรแกรมยังไม่เผยแพร่ โปรดติดต่อร้านค้า",
"useMobileScan": "Please scan with your phone to pay",
"scanWithWallet": "สแกนด้วย WeChat, Alipay, UnionPay หรือ Douyin เพื่อชำระเงิน",
"remainingTime": "เวลาที่เหลือ",

View File

@@ -24,6 +24,10 @@
"scanQr": "Scan the QR code to pay",
"unsupportedTitle": "Open in a wallet app",
"unsupportedDesc": "Please scan the QR code with WeChat, Alipay, UnionPay or Douyin",
"miniScanTitle": "Quét để thanh toán",
"miniScanDesc": "Vui lòng quét mã QR bằng WeChat, Alipay, v.v. để hoàn tất thanh toán",
"miniInWalletTitle": "Mini-program thanh toán tạm thời không khả dụng",
"miniInWalletDesc": "Có thể merchant chưa cấu hình thanh toán mini-program hoặc mini-program chưa được phát hành. Vui lòng liên hệ merchant.",
"useMobileScan": "Please scan with your phone to pay",
"scanWithWallet": "Quét bằng WeChat, Alipay, UnionPay hoặc Douyin để thanh toán",
"remainingTime": "Thời gian còn lại",

View File

@@ -24,6 +24,10 @@
"scanQr": "请使用手机扫码完成支付",
"unsupportedTitle": "请使用钱包扫码",
"unsupportedDesc": "请使用微信、支付宝、云闪付或抖音扫描二维码完成支付",
"miniScanTitle": "请扫码支付",
"miniScanDesc": "请使用微信、支付宝等扫描二维码完成支付",
"miniInWalletTitle": "收款小程序暂不可用",
"miniInWalletDesc": "可能商户尚未配置小程序支付或小程序未发布,请联系商户处理",
"useMobileScan": "请使用手机扫码支付",
"scanWithWallet": "请使用微信、支付宝、云闪付或抖音扫一扫完成支付",
"remainingTime": "剩余支付时间",

View File

@@ -24,6 +24,10 @@
"scanQr": "請使用手機掃碼完成支付",
"unsupportedTitle": "請使用錢包掃碼",
"unsupportedDesc": "請使用微信、支付寶、雲閃付或抖音掃描二維碼完成支付",
"miniScanTitle": "請掃碼支付",
"miniScanDesc": "請用微信、支付寶等掃描二維碼完成付款",
"miniInWalletTitle": "收款小程式暫不可用",
"miniInWalletDesc": "可能商戶尚未設定小程式支付或小程式未發布,請聯絡商戶處理",
"useMobileScan": "請使用手機掃碼支付",
"scanWithWallet": "請使用微信、支付寶、雲閃付或抖音掃一掃完成支付",
"remainingTime": "剩餘支付時間",

View File

@@ -24,6 +24,10 @@
"scanQr": "請使用手機掃碼完成支付",
"unsupportedTitle": "請使用錢包掃碼",
"unsupportedDesc": "請使用微信、支付寶、雲閃付或抖音掃描二維碼完成支付",
"miniScanTitle": "請掃碼支付",
"miniScanDesc": "請使用微信、支付寶等掃描 QR Code 完成支付",
"miniInWalletTitle": "收款小程式暫不可用",
"miniInWalletDesc": "可能商戶尚未設定小程式支付或小程式未發布,請聯絡商戶處理",
"useMobileScan": "請使用手機掃碼支付",
"scanWithWallet": "請使用微信、支付寶、雲閃付或抖音掃一掃完成支付",
"remainingTime": "剩餘支付時間",

View File

@@ -0,0 +1,65 @@
/**
* 小程序入口引导页(/m/:code、/am/:orderNo)的 UA 感知文案与图标
*
* 钱包 App 内(微信/支付宝/云闪付/抖音)扫码本应由「普通链接二维码」规则拉起
* cashier 小程序;若规则未配/未匹配/小程序未发布,会落到本 H5 引导页。此时再显示
* "请用微信扫码"会自相矛盾(用户就在微信里),故按 UA 分流:
* - 钱包内:展示"收款小程序暂不可用"提示,并仅高亮当前钱包图标(聚焦)
* - 系统浏览器/PC:保持"请扫码支付"提示 + 四钱包图标
*
* UA 一次扫描即定(同一会话不变),无需响应式。
*/
import type { AggregateClientEnv } from '@/shared/utils/client-env'
import alipaySvg from '@/shared/assets/icons/channel/alipay.svg'
import douyinSvg from '@/shared/assets/icons/channel/douyin.svg'
import unionPaySvg from '@/shared/assets/icons/channel/union_pay.svg'
import wechatSvg from '@/shared/assets/icons/channel/wechat.svg'
import {
AGGREGATE_CLIENT_ENVS,
detectClientEnv,
isAggregateClientEnv,
} from '@/shared/utils/client-env'
/** 钱包图标项(alt 用 aggregate.clientEnv.* 统一翻译) */
export interface MiniGuideWalletIcon {
src: string
altKey: string
}
/** 宿主环境 → 钱包图标 */
const ENV_ICON_MAP: Record<AggregateClientEnv, MiniGuideWalletIcon> = {
wechat: { src: wechatSvg, altKey: 'aggregate.clientEnv.wechat' },
alipay: { src: alipaySvg, altKey: 'aggregate.clientEnv.alipay' },
union_pay: { src: unionPaySvg, altKey: 'aggregate.clientEnv.union' },
douyin: { src: douyinSvg, altKey: 'aggregate.clientEnv.douyin' },
}
export interface MiniGuideEnv {
/** 是否在钱包 App 内(微信/支付宝/云闪付/抖音) */
inWallet: boolean
/** 图标列表:钱包内仅当前钱包(高亮聚焦);系统浏览器为四钱包 */
icons: MiniGuideWalletIcon[]
/** 标题 i18n key */
titleKey: string
/** 描述 i18n key */
descKey: string
}
/**
* 探测当前 UA,返回引导页所需的文案 key 与图标列表
*/
export function useMiniGuideEnv(): MiniGuideEnv {
const env = detectClientEnv()
const inWallet = isAggregateClientEnv(env)
// inWallet 为 true 时,env 已收窄为 AggregateClientEnv(type guard)
const icons = inWallet
? [ENV_ICON_MAP[env]]
: AGGREGATE_CLIENT_ENVS.map(e => ENV_ICON_MAP[e])
return {
inWallet,
icons,
// 钱包内:小程序未拉起专属文案;否则:通用扫码文案
titleKey: inWallet ? 'aggregate.miniInWalletTitle' : 'aggregate.miniScanTitle',
descKey: inWallet ? 'aggregate.miniInWalletDesc' : 'aggregate.miniScanDesc',
}
}

View File

@@ -44,9 +44,9 @@ export const RoutePath = {
/** 聚合-不支持的宿主环境提示Mobile + PC 注册用) */
AGGREGATE_UNSUPPORTED: '/aggregate/unsupported',
/**
* 聚合小程序入口(跨端;linkForm=mini 预下单落地 URL 契约)
* 微信/支付宝扫码后由"普通链接二维码"规则拉起小程序dax-pay-cashier;
* H5/PC 浏览器直接打开时展示扫码引导(不发起支付)
* 聚合小程序入口(跨端;预下单同时返回的 miniUrl 落地 URL 契约)
* 微信/支付宝扫码后由普通链接二维码规则拉起各自小程序;
* 浏览器直接打开时展示扫码引导提示(不发起支付)
*/
AGGREGATE_MINI: '/am/:orderNo',
/**
@@ -69,6 +69,12 @@ export const RoutePath = {
CODE_PAY_UNION: '/h/union-pay/:code',
/** 码牌支付-抖音端Mobile 收款PC 同入口扫码引导) */
CODE_PAY_DOUYIN: '/h/douyin/:code',
/**
* 码牌小程序入口跨端programType=mini_app 码牌落地 URL 契约)
* 微信/支付宝扫码后由「普通链接二维码」规则拉起小程序dax-pay-cashier;
* H5/PC 浏览器直接打开时展示扫码引导不发起支付。path 与后端 CODE_MINI_PATH /m/{code} 一致
*/
CODE_PAY_MINI: '/m/:code',
/** 商户对账单PC 独占) */
MERCHANT_STATEMENT: '/merchant/statement',
/** 协议展示页(跨端:用户协议/隐私政策,链接可独立分享) */