refactor(h5/code-pay): 抽取码牌成功结果页为独立组件并对齐聚合上浮卡片风格

This commit is contained in:
bootx
2026-07-20 23:20:25 +08:00
parent f83b57c153
commit 51ebb5ecc2
12 changed files with 312 additions and 153 deletions

View File

@@ -0,0 +1,288 @@
<script lang="ts" setup>
/**
* 码牌支付 - 成功结果页组件
*
* 视觉对齐聚合 AggregateResultCard上浮卡片 + 实心彩色图标 + border-top 摘要表格),
* 保留码牌特色:大金额独占行 + 入场动画。
* 通道品牌色由父级注入(微信绿 / 支付宝蓝),图标/标题/按钮跟随通道色;
* 近黑色品牌(抖音)在深色下主按钮反色为浅底深字。
*/
import { computed, unref } from 'vue'
import { useI18n } from 'vue-i18n'
import { useDesignSetting } from '@/shared/hooks/setting/useDesignSetting'
defineOptions({ name: 'CodePayResultCard' })
const props = withDefaults(defineProps<{
/** 大金额(元字符串),独占一行展示 — 码牌特色 */
amount: string
/** 标题文案(已 $t留空走 i18n codePay.paySuccess */
title?: string
/** 副提示文案(已 $t留空走 i18n codePay.paidTip */
tip?: string
/** 收款商户名 */
merchantName?: string
/** 订单号 */
orderNo?: string
/** 品牌色(浅色) */
brandColor?: string
/** 品牌色(深色模式压亮,空则用 brandColor */
brandColorDark?: string
/** 关闭按钮文案(已 $t留空走 i18n codePay.closePage */
closeText?: string
}>(), {
title: '',
tip: '',
merchantName: '',
orderNo: '',
brandColor: '#5d9dfe',
brandColorDark: '',
closeText: '',
})
const emit = defineEmits<{
/** 完成/关闭页面(由父级处理 closeWebview */
close: []
}>()
const { t } = useI18n()
const { getDarkMode } = useDesignSetting()
/** 近黑色品牌(抖音):深色下主按钮需浅底深字 */
function isNearBlackBrand(hex?: string) {
if (!hex) {
return false
}
const h = hex.replace('#', '')
if (h.length !== 6) {
return false
}
const r = Number.parseInt(h.slice(0, 2), 16)
const g = Number.parseInt(h.slice(2, 4), 16)
const b = Number.parseInt(h.slice(4, 6), 16)
return (r + g + b) / 3 < 60
}
/** 当前生效品牌色(深色优先 brandColorDark用于图标/标题/按钮) */
const effectiveBrand = computed(() => {
if (unref(getDarkMode) === 'dark' && props.brandColorDark) {
return props.brandColorDark
}
return props.brandColor
})
/** 操作按钮色:深色 + 近黑品牌时反色为浅底 */
const effectiveAction = computed(() => {
if (unref(getDarkMode) === 'dark' && isNearBlackBrand(props.brandColor)) {
return '#f2f2f2'
}
return effectiveBrand.value
})
/** 操作按钮文字色 */
const effectiveActionText = computed(() => {
if (unref(getDarkMode) === 'dark' && isNearBlackBrand(props.brandColor)) {
return '#161823'
}
return '#ffffff'
})
/** 标题(默认走 i18n */
const titleText = computed(() => props.title || t('codePay.paySuccess'))
/** 副提示(默认走 i18n */
const tipText = computed(() => props.tip || t('codePay.paidTip'))
/** 关闭按钮文案(默认走 i18n */
const closeText = computed(() => props.closeText || t('codePay.closePage'))
</script>
<template>
<div
class="code-pay-result"
:style="{
'--result-color': effectiveBrand,
'--brand-action': effectiveAction,
'--brand-action-text': effectiveActionText,
}"
>
<!-- 状态图标实心彩色圆底 + 白色 SVG对齐聚合 -->
<div class="code-pay-result__icon">
<svg
viewBox="0 0 48 48"
width="28"
height="28"
fill="none"
stroke="#fff"
stroke-width="4"
stroke-linecap="round"
stroke-linejoin="round"
>
<!-- 对勾 -->
<path d="M14 24 L21 31 L34 16" />
</svg>
</div>
<!-- 支付成功 -->
<p class="code-pay-result__title">
{{ titleText }}
</p>
<!-- 副提示 -->
<p class="code-pay-result__tip">
{{ tipText }}
</p>
<!-- 大金额码牌特色独占行 -->
<div class="code-pay-result__amount">
<span class="code-pay-result__currency">¥</span>{{ amount }}
</div>
<!-- 订单摘要border-top 分割线表格对齐聚合 -->
<div v-if="merchantName || orderNo" class="code-pay-result__summary">
<div v-if="merchantName" class="code-pay-result__summary-row">
<!-- 收款商户 -->
<span class="code-pay-result__summary-label">{{ t('codePay.merchantDefault') }}</span>
<span class="code-pay-result__summary-value">{{ merchantName }}</span>
</div>
<div v-if="orderNo" class="code-pay-result__summary-row">
<!-- 订单号 -->
<span class="code-pay-result__summary-label">{{ t('codePay.orderNo') }}</span>
<span class="code-pay-result__summary-value">{{ orderNo }}</span>
</div>
</div>
<!-- 完成/关闭 -->
<van-button
class="code-pay-result__close"
round
block
type="primary"
color="var(--brand-action)"
@click="emit('close')"
>
{{ closeText }}
</van-button>
</div>
</template>
<style scoped lang="less">
// 结果状态卡片:上浮卡片风格,对齐聚合 AggregateResultCard
.code-pay-result {
margin: -32px 16px 16px;
padding: 24px 20px;
background: var(--h5-bg-card);
border-radius: 12px;
box-shadow: var(--h5-shadow-card);
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
text-align: center;
box-sizing: border-box;
animation: code-pay-result-up 0.45s ease-out;
}
// 实心彩色圆底图标(对齐聚合,原 80px 半透明底)
.code-pay-result__icon {
width: 48px;
height: 48px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
background: var(--result-color);
margin-bottom: 4px;
flex-shrink: 0;
animation: code-pay-result-pop 0.55s cubic-bezier(0.34, 1.56, 0.64, 1);
}
// 状态标题:跟随通道品牌色(对齐聚合)
.code-pay-result__title {
margin: 0;
font-size: 16px;
font-weight: 500;
color: var(--result-color);
line-height: 1.4;
}
// 副提示文案
.code-pay-result__tip {
margin: 0;
font-size: 13px;
color: var(--h5-text-secondary);
line-height: 1.6;
}
// 大金额(码牌特色,独占行)
.code-pay-result__amount {
font-size: 36px;
font-weight: 600;
color: var(--h5-text-primary);
line-height: 1.2;
margin: 12px 0 16px;
}
.code-pay-result__currency {
font-size: 20px;
margin-right: 4px;
font-weight: 500;
}
// 订单摘要区border-top 分割线表格(对齐聚合,原独立阴影卡)
.code-pay-result__summary {
width: 100%;
padding-top: 16px;
border-top: 1px solid var(--h5-border);
text-align: left;
box-sizing: border-box;
}
.code-pay-result__summary-row {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 16px;
padding: 6px 0;
font-size: 13px;
line-height: 1.5;
}
.code-pay-result__summary-label {
color: var(--h5-text-secondary);
flex-shrink: 0;
min-width: 72px;
}
.code-pay-result__summary-value {
color: var(--h5-text-primary);
text-align: right;
word-break: break-all;
}
// 关闭按钮:实心品牌色,对齐聚合 280px
.code-pay-result__close {
margin-top: 16px;
width: 100%;
max-width: 280px;
}
@keyframes code-pay-result-up {
from {
opacity: 0;
transform: translateY(16px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes code-pay-result-pop {
from {
opacity: 0;
transform: scale(0.6);
}
to {
opacity: 1;
transform: scale(1);
}
}
</style>

View File

@@ -8,6 +8,7 @@
import { computed, ref, unref } from 'vue'
import { useI18n } from 'vue-i18n'
import { useDesignSetting } from '@/shared/hooks/setting/useDesignSetting'
import CodePayResultCard from './CodePayResultCard.vue'
defineOptions({ name: 'CodePayShell' })
@@ -169,47 +170,21 @@ function onPay() {
'--brand-action-text': effectiveActionText,
}"
>
<!-- 支付成功结果态对标收银 cashier__result -->
<div v-if="paid" class="code-pay-shell__result">
<div class="code-pay-shell__result-icon">
<van-icon name="success" size="48" color="#07c160" />
</div>
<!-- 支付成功 -->
<div class="code-pay-shell__result-title">
{{ t('codePay.paySuccess') }}
</div>
<div class="code-pay-shell__result-amount">
<span class="code-pay-shell__result-currency">¥</span>{{ displayAmount }}
</div>
<div v-if="orderNo || merchantName" class="code-pay-shell__result-card">
<div v-if="merchantName" class="code-pay-shell__result-row">
<!-- 收款商户 -->
<span>{{ t('codePay.merchantDefault') }}</span>
<span :title="merchantName">{{ merchantName }}</span>
</div>
<div v-if="orderNo" class="code-pay-shell__result-row">
<!-- 订单号 -->
<span>{{ t('codePay.orderNo') }}</span>
<span :title="orderNo">{{ orderNo }}</span>
</div>
</div>
<!-- 完成/关闭 -->
<van-button
class="code-pay-shell__result-btn"
round
block
type="primary"
color="var(--brand-action)"
@click="emit('close')"
>
{{ t('codePay.closePage') }}
</van-button>
</div>
<!-- 品牌顶栏paid 和非 paid 都渲染作为结果卡/收款卡负 margin 叠放对象 -->
<div class="code-pay-shell__brand" />
<!-- 支付成功结果态独立组件对齐聚合上浮卡片风格 -->
<CodePayResultCard
v-if="paid"
:amount="displayAmount"
:merchant-name="merchantName"
:order-no="orderNo"
:brand-color="brandColor"
:brand-color-dark="brandColorNight || brandColor"
@close="emit('close')"
/>
<template v-else>
<!-- 品牌顶栏文档流配合上浮卡 -->
<div class="code-pay-shell__brand" />
<div class="code-pay-shell__card enter-y">
<div class="code-pay-shell__merchant">
<div class="code-pay-shell__avatar">
@@ -522,119 +497,5 @@ function onPay() {
margin: 0;
}
}
// 成功结果态
&__result {
flex: 1;
min-height: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 32px 24px calc(24px + env(safe-area-inset-bottom, 0px));
text-align: center;
animation: code-pay-result-up 0.45s ease-out;
}
&__result-icon {
display: flex;
align-items: center;
justify-content: center;
width: 80px;
height: 80px;
border-radius: 50%;
margin-bottom: 20px;
background: rgb(7 193 96 / 10%);
animation: code-pay-result-pop 0.55s cubic-bezier(0.34, 1.56, 0.64, 1);
}
&__result-title {
font-size: 22px;
font-weight: 600;
color: var(--h5-text-primary);
margin-bottom: 12px;
}
&__result-amount {
font-size: 36px;
font-weight: 600;
color: var(--h5-text-primary);
line-height: 1.2;
margin-bottom: 24px;
}
&__result-currency {
font-size: 20px;
margin-right: 4px;
font-weight: 500;
}
&__result-card {
width: 100%;
max-width: 320px;
margin-bottom: 28px;
padding: 16px 20px;
background: var(--h5-bg-card);
border-radius: 12px;
box-shadow: var(--h5-shadow-card);
text-align: left;
}
&__result-row {
display: flex;
justify-content: space-between;
align-items: center;
gap: 12px;
font-size: 13px;
margin-bottom: 10px;
&:last-child {
margin-bottom: 0;
}
span:first-child {
flex-shrink: 0;
color: var(--h5-text-secondary);
}
span:last-child {
flex: 1;
min-width: 0;
color: var(--h5-text-primary);
text-align: right;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
&__result-btn {
width: 100%;
max-width: 320px;
}
}
@keyframes code-pay-result-up {
from {
opacity: 0;
transform: translateY(16px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes code-pay-result-pop {
from {
opacity: 0;
transform: scale(0.6);
}
to {
opacity: 1;
transform: scale(1);
}
}
</style>

View File

@@ -13,6 +13,7 @@
"merchantDefault": "Merchant",
"payToMerchant": "Pay to {name}",
"paySuccess": "Payment successful",
"paidTip": "Payment completed. Thank you",
"payFail": "Payment failed, please retry",
"payLaunched": "Payment launched",
"payPending": "Order created, please complete payment",

View File

@@ -13,6 +13,7 @@
"merchantDefault": "Merchant",
"payToMerchant": "Bayar ke {name}",
"paySuccess": "Pembayaran berhasil",
"paidTip": "Pembayaran selesai. Terima kasih",
"payFail": "Pembayaran gagal, coba lagi",
"payLaunched": "Pembayaran diluncurkan",
"payPending": "Pesanan dibuat, selesaikan pembayaran",

View File

@@ -13,6 +13,7 @@
"merchantDefault": "加盟店",
"payToMerchant": "{name} へ支払い",
"paySuccess": "支払い成功",
"paidTip": "支払いが完了しました。ありがとうございます",
"payFail": "支払いに失敗しました。再試行してください",
"payLaunched": "支払いを開始しました",
"payPending": "注文が作成されました。支払いを完了してください",

View File

@@ -13,6 +13,7 @@
"merchantDefault": "가맹점",
"payToMerchant": "{name}에 결제",
"paySuccess": "결제 성공",
"paidTip": "결제가 완료되었습니다. 감사합니다",
"payFail": "결제 실패, 다시 시도하세요",
"payLaunched": "결제가 시작되었습니다",
"payPending": "주문이 생성되었습니다. 결제를 완료하세요",

View File

@@ -13,6 +13,7 @@
"merchantDefault": "Pedagang",
"payToMerchant": "Bayar kepada {name}",
"paySuccess": "Pembayaran berjaya",
"paidTip": "Pembayaran selesai. Terima kasih",
"payFail": "Pembayaran gagal, cuba lagi",
"payLaunched": "Pembayaran dilancarkan",
"payPending": "Pesanan dicipta, sila lengkapkan pembayaran",

View File

@@ -13,6 +13,7 @@
"merchantDefault": "ร้านค้า",
"payToMerchant": "ชำระให้ {name}",
"paySuccess": "ชำระเงินสำเร็จ",
"paidTip": "ชำระเงินสำเร็จ ขอบคุณที่ใช้บริการ",
"payFail": "ชำระเงินไม่สำเร็จ โปรดลองอีกครั้ง",
"payLaunched": "เปิดการชำระเงินแล้ว",
"payPending": "สร้างคำสั่งซื้อแล้ว โปรดชำระเงินให้เสร็จ",

View File

@@ -13,6 +13,7 @@
"merchantDefault": "Merchant",
"payToMerchant": "Thanh toán cho {name}",
"paySuccess": "Thanh toán thành công",
"paidTip": "Thanh toán hoàn tất. Cảm ơn bạn",
"payFail": "Thanh toán thất bại, vui lòng thử lại",
"payLaunched": "Đã mở thanh toán",
"payPending": "Đơn đã tạo, vui lòng hoàn tất thanh toán",

View File

@@ -13,6 +13,7 @@
"merchantDefault": "收款商户",
"payToMerchant": "向{name}付款",
"paySuccess": "支付成功",
"paidTip": "支付已完成,感谢使用",
"payFail": "支付失败, 请重试",
"payLaunched": "支付已拉起",
"payPending": "订单已创建, 请完成支付",

View File

@@ -13,6 +13,7 @@
"merchantDefault": "收款商戶",
"payToMerchant": "向{name}付款",
"paySuccess": "支付成功",
"paidTip": "支付已完成,感謝使用",
"payFail": "支付失敗,請重試",
"payLaunched": "支付已拉起",
"payPending": "訂單已建立,請完成支付",

View File

@@ -13,6 +13,7 @@
"merchantDefault": "收款商戶",
"payToMerchant": "向{name}付款",
"paySuccess": "支付成功",
"paidTip": "支付已完成,感謝使用",
"payFail": "支付失敗,請重試",
"payLaunched": "支付已拉起",
"payPending": "訂單已建立,請完成支付",