fix(cashier): 优化收银台图标与 H5/PC 布局配色

使用真实品牌 SVG 替换手写假图标;PC 恢复横向卡片并加大主面板;
H5 改为中间滚动+底栏固定避免矮屏遮挡;金额红/倒计时橙/按钮蓝分层。
This commit is contained in:
bootx
2026-07-17 08:57:41 +08:00
parent c8cd503906
commit ea3eb464d3
9 changed files with 421 additions and 86 deletions

View File

@@ -482,7 +482,8 @@ onUnmounted(() => {
@click="selectId = item.id"
>
<div class="cashier__item-info">
<PayMethodIcon :icon="item.icon" />
<!-- 品牌 SVG 图标略大于默认以提升可识别度 -->
<PayMethodIcon :icon="item.icon" :size="28" />
<div class="cashier__pay-name">
{{ methodName(item) }}
<span v-if="item.recommend" class="cashier__recommend">{{ t('cashier.recommend') }}</span>
@@ -518,11 +519,16 @@ onUnmounted(() => {
@border: #eee;
.cashier {
min-height: 100vh;
// 三区布局: header 固定 / 中间滚动 / footer 贴底,矮屏不压按钮
height: 100vh;
height: 100dvh;
max-height: 100vh;
max-height: 100dvh;
background: @bg;
display: flex;
flex-direction: column;
padding-bottom: 80px;
overflow: hidden;
box-sizing: border-box;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
&__error {
@@ -541,12 +547,15 @@ onUnmounted(() => {
&__result {
flex: 1;
min-height: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 32px 24px;
padding: 32px 24px calc(24px + env(safe-area-inset-bottom, 0px));
text-align: center;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
animation: cashier-result-up 0.5s ease-out;
}
@@ -608,16 +617,17 @@ onUnmounted(() => {
}
}
// 金额红:与 PC 一致,比品牌蓝更醒目
&__result-amount {
color: @primary;
color: @danger;
font-weight: 600;
}
&__result-btn {
position: fixed;
bottom: 20px;
left: 20px;
right: 20px;
// 流式底栏,不再 fixed避免被内容遮挡
margin-top: 32px;
width: 100%;
max-width: 320px;
height: 48px;
background: linear-gradient(135deg, @primary 0%, @primary-dark 100%);
color: #fff;
@@ -626,6 +636,7 @@ onUnmounted(() => {
font-size: 16px;
font-weight: 600;
box-shadow: 0 4px 12px rgba(93, 157, 254, 0.3);
flex-shrink: 0;
&:active {
opacity: 0.9;
@@ -634,6 +645,7 @@ onUnmounted(() => {
}
&__header {
flex-shrink: 0;
background: linear-gradient(180deg, #f0f6ff 0%, #ffffff 100%);
padding: 24px 20px 16px;
display: flex;
@@ -642,10 +654,11 @@ onUnmounted(() => {
box-shadow: 0 2px 10px rgb(0 0 0 / 5%);
}
// 应付金额用红色,主按钮保持品牌蓝,信息层级更清晰
&__price {
display: flex;
align-items: baseline;
color: @primary;
color: @danger;
margin-bottom: 8px;
}
@@ -658,15 +671,16 @@ onUnmounted(() => {
font-size: 44px;
font-weight: 800;
letter-spacing: -1px;
text-shadow: 0 2px 8px rgba(93, 157, 254, 0.2);
text-shadow: 0 2px 8px rgba(255, 77, 79, 0.15);
}
// 倒计时用橙:与金额红区分(红=应付 / 橙=时限 / 蓝=操作)
&__countdown {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 20px;
background: #fff1f0;
background: #fff7e6;
padding: 6px 16px;
border-radius: 100px;
@@ -677,11 +691,11 @@ onUnmounted(() => {
&__countdown-label {
font-size: 12px;
color: @danger;
color: #fa8c16;
}
&__countdown-time {
color: @danger;
color: #fa8c16;
font-weight: 700;
font-size: 14px;
}
@@ -707,8 +721,13 @@ onUnmounted(() => {
}
}
// 中间唯一滚动区:支付项再多也不压底栏
&__body {
padding: 24px 20px;
flex: 1;
min-height: 0;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
padding: 20px 20px 16px;
}
&__section-title {
@@ -769,6 +788,7 @@ onUnmounted(() => {
border: 2px solid #d9d9d9;
border-radius: 50%;
transition: all 0.2s;
flex-shrink: 0;
&--checked {
border-color: @primary;
@@ -794,12 +814,11 @@ onUnmounted(() => {
text-align: center;
}
// 文档流底栏,不再 fixed天然不被列表遮挡
&__footer {
position: fixed;
bottom: 0;
left: 0;
right: 0;
padding: 12px 20px 20px;
flex-shrink: 0;
z-index: 10;
padding: 12px 20px calc(16px + env(safe-area-inset-bottom, 0px));
background: @card;
box-shadow: 0 -2px 10px rgb(0 0 0 / 5%);
}

View File

@@ -252,8 +252,10 @@ onUnmounted(() => {
<template>
<div class="pc-cashier">
<div class="pc-cashier__box">
<div v-if="loading" class="pc-cashier__content">
{{ t('cashier.paying') }}
<!-- 加载态 -->
<div v-if="loading" class="pc-cashier__loading">
<div class="pc-cashier__spinner" />
<span>{{ t('cashier.paying') }}</span>
</div>
<!-- 结果态订单已关闭/支付失败/已过期/加载失败 -->
<div v-else-if="resultState" class="pc-cashier__result">
@@ -347,28 +349,34 @@ onUnmounted(() => {
<!-- 内容区 -->
<div class="pc-cashier__content">
<div v-if="paid" class="pc-cashier__method-name">
<div v-if="paid" class="pc-cashier__paid">
{{ t('cashier.paid') }}
</div>
<div v-else-if="payError" class="pc-cashier__error">
{{ payError }}
</div>
<!-- 支付方式网格 -->
<div v-else-if="!showQrcode" class="pc-cashier__grid">
<div
v-for="item in payMethods"
:key="item.id"
class="pc-cashier__method"
:class="{ 'pc-cashier__method--active': item.id === selectId }"
@click="selectId = item.id"
>
<PayMethodIcon :icon="item.icon" :size="22" />
<span class="pc-cashier__method-name">{{ methodName(item) }}</span>
<span v-if="item.recommend" class="pc-cashier__recommend">{{ t('cashier.recommend') }}</span>
<!-- 支付方式选择 -->
<div v-else-if="!showQrcode" class="pc-cashier__methods">
<div class="pc-cashier__section-title">
{{ t('cashier.selectMethod') }}
</div>
<div v-if="!payMethods.length" class="pc-cashier__empty">
{{ t('cashier.emptyItems') }}
</div>
<div v-else class="pc-cashier__grid">
<div
v-for="item in payMethods"
:key="item.id"
class="pc-cashier__method"
:class="{ 'pc-cashier__method--active': item.id === selectId }"
@click="selectId = item.id"
>
<!-- 横向卡片: 图标 + 名称 + 推荐选中靠边框/底色 -->
<PayMethodIcon :icon="item.icon" :size="28" />
<span class="pc-cashier__method-name">{{ methodName(item) }}</span>
<span v-if="item.recommend" class="pc-cashier__recommend">{{ t('cashier.recommend') }}</span>
</div>
</div>
</div>
<!-- 二维码支付 -->
@@ -391,14 +399,20 @@ onUnmounted(() => {
</div>
</div>
<button
v-if="!showQrcode && !paid"
class="pc-cashier__pay-btn"
:disabled="paying || !selectId || expired"
@click="pay"
>
{{ paying ? t('cashier.paying') : t('cashier.payNow') }}
</button>
<!-- 底部操作栏流式布局避免 absolute 压住支付项 -->
<div v-if="!showQrcode && !paid" class="pc-cashier__footer">
<div class="pc-cashier__footer-amount">
<span class="pc-cashier__footer-label">{{ t('cashier.payableAmount') }}</span>
<span class="pc-cashier__footer-value"><em></em>{{ amountYuan }}</span>
</div>
<button
class="pc-cashier__pay-btn"
:disabled="paying || !selectId || expired"
@click="pay"
>
{{ paying ? t('cashier.paying') : t('cashier.payNow') }}
</button>
</div>
</template>
</div>
</div>
@@ -418,99 +432,142 @@ onUnmounted(() => {
}
.pc-cashier__box {
width: 1000px;
width: 1100px;
max-width: 100%;
background: #fff;
border-radius: 12px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.08);
border-radius: 16px;
box-shadow: 0 12px 40px rgba(15, 23, 42, 0.08);
overflow: hidden;
position: relative;
display: flex;
flex-direction: column;
min-height: 420px;
min-height: 520px;
}
.pc-cashier__loading {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 16px;
min-height: 320px;
color: #64748b;
font-size: 15px;
}
.pc-cashier__spinner {
width: 36px;
height: 36px;
border: 3px solid #e2e8f0;
border-top-color: #5d9dfe;
border-radius: 50%;
animation: pc-cashier-spin 0.8s linear infinite;
}
.pc-cashier__header {
background: linear-gradient(135deg, #eaf2fe 0%, #f5f9ff 100%);
padding: 30px 40px;
padding: 28px 40px 24px;
border-bottom: 1px solid #edf2f7;
position: relative;
}
.pc-cashier__countdown {
position: absolute;
right: 30px;
top: 30px;
right: 32px;
top: 28px;
display: flex;
align-items: center;
gap: 8px;
background: rgba(255, 255, 255, 0.8);
padding: 6px 12px;
background: rgba(255, 255, 255, 0.9);
padding: 6px 14px;
border-radius: 20px;
border: 1px solid rgba(0, 0, 0, 0.05);
box-shadow: 0 2px 8px rgba(15, 23, 42, 0.04);
}
.pc-cashier__countdown-label {
font-size: 14px;
font-size: 13px;
color: #64748b;
}
.pc-cashier__countdown-time {
font-size: 16px;
font-size: 15px;
font-weight: 600;
color: #ff4d4f;
font-family: 'Monaco', 'Menlo', 'Consolas', monospace;
}
.pc-cashier__title {
font-size: 18px;
font-size: 17px;
font-weight: 600;
color: #1e293b;
padding-right: 180px;
line-height: 1.4;
}
.pc-cashier__price {
margin-top: 32px;
margin-bottom: 16px;
margin-top: 20px;
margin-bottom: 12px;
}
.pc-cashier__price-label {
font-size: 16px;
font-size: 14px;
color: #64748b;
}
.pc-cashier__price p {
font-size: 48px;
font-size: 44px;
color: #ff4d4f;
font-weight: 800;
line-height: 1;
margin: 8px 0 0;
margin: 6px 0 0;
letter-spacing: -1px;
}
.pc-cashier__price p span {
font-size: 24px;
margin-right: 4px;
font-size: 22px;
margin-right: 2px;
}
.pc-cashier__order-no {
font-size: 14px;
font-size: 13px;
color: #94a3b8;
}
.pc-cashier__content {
flex: 1;
background: #fff;
padding: 40px;
padding: 28px 40px 20px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 240px;
min-height: 200px;
box-sizing: border-box;
}
.pc-cashier__methods {
width: 100%;
}
.pc-cashier__section-title {
font-size: 15px;
font-weight: 600;
color: #1e293b;
margin-bottom: 16px;
}
.pc-cashier__paid {
font-size: 18px;
font-weight: 600;
color: #07c160;
text-align: center;
padding: 40px 0;
}
.pc-cashier__error {
color: #ff4d4f;
font-size: 14px;
text-align: center;
padding: 24px 0;
}
.pc-cashier__result {
@@ -613,17 +670,18 @@ onUnmounted(() => {
}
.pc-cashier__empty {
grid-column: 1 / -1;
color: #94a3b8;
text-align: center;
padding: 24px;
padding: 32px 16px;
font-size: 14px;
}
/* 4 列横向卡片(与原布局一致) */
.pc-cashier__grid {
width: 100%;
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 20px;
gap: 16px;
}
.pc-cashier__method {
@@ -654,26 +712,34 @@ onUnmounted(() => {
}
.pc-cashier__method-name {
font-size: 16px;
font-size: 15px;
font-weight: 500;
color: #1e293b;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
min-width: 0;
}
.pc-cashier__recommend {
margin-left: auto;
font-size: 12px;
color: #f59e0b;
background: #fffbeb;
border: 1px solid #fef3c7;
padding: 2px 8px;
border-radius: 4px;
margin-left: auto;
white-space: nowrap;
flex-shrink: 0;
}
.pc-cashier__qrcode {
display: flex;
align-items: center;
justify-content: center;
gap: 40px;
padding: 16px 0 24px;
width: 100%;
}
.pc-cashier__qrcode-box {
@@ -742,35 +808,91 @@ onUnmounted(() => {
margin: 0;
}
/* 底部操作栏:流式布局,不压支付项 */
.pc-cashier__footer {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 24px;
padding: 16px 40px 28px;
border-top: 1px solid #f1f5f9;
background: #fff;
}
.pc-cashier__footer-amount {
display: flex;
align-items: baseline;
gap: 8px;
}
.pc-cashier__footer-label {
font-size: 13px;
color: #94a3b8;
}
.pc-cashier__footer-value {
font-size: 28px;
font-weight: 800;
color: #ff4d4f;
line-height: 1;
}
.pc-cashier__footer-value em {
font-style: normal;
font-size: 16px;
font-weight: 700;
margin-right: 1px;
}
.pc-cashier__pay-btn {
position: absolute;
right: 40px;
bottom: 40px;
width: 200px;
height: 54px;
font-size: 18px;
height: 48px;
font-size: 16px;
font-weight: 600;
color: #fff;
background: #5d9dfe;
background: linear-gradient(135deg, #5d9dfe 0%, #4a87e0 100%);
border: none;
border-radius: 8px;
border-radius: 10px;
cursor: pointer;
transition: all 0.2s;
box-shadow: 0 4px 12px rgba(93, 157, 254, 0.3);
box-shadow: 0 4px 14px rgba(93, 157, 254, 0.32);
flex-shrink: 0;
}
.pc-cashier__pay-btn:hover:not(:disabled) {
background: #4a87e0;
transform: translateY(-1px);
box-shadow: 0 6px 16px rgba(93, 157, 254, 0.4);
box-shadow: 0 6px 18px rgba(93, 157, 254, 0.4);
}
.pc-cashier__pay-btn:disabled {
opacity: 0.6;
opacity: 0.55;
cursor: not-allowed;
box-shadow: none;
}
@media (max-width: 768px) {
.pc-cashier__box {
border-radius: 12px;
}
.pc-cashier__header,
.pc-cashier__content,
.pc-cashier__footer {
padding-left: 20px;
padding-right: 20px;
}
.pc-cashier__title {
padding-right: 0;
margin-bottom: 8px;
}
.pc-cashier__countdown {
position: static;
margin-bottom: 12px;
width: fit-content;
}
.pc-cashier__grid {
grid-template-columns: repeat(2, 1fr);
}
@@ -780,10 +902,24 @@ onUnmounted(() => {
gap: 20px;
}
.pc-cashier__footer {
flex-direction: column;
align-items: stretch;
gap: 16px;
}
.pc-cashier__footer-amount {
justify-content: space-between;
}
.pc-cashier__pay-btn {
position: static;
width: 100%;
margin: 20px 0 0;
}
}
@keyframes pc-cashier-spin {
to {
transform: rotate(360deg);
}
}

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

@@ -0,0 +1 @@
<svg t="1781512036127" class="icon" viewBox="0 0 1651 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8092" width="200" height="200"><path d="M1005.700129 518.077935c0 271.624258-223.430194 491.81729-498.98529 491.817291-275.588129 0-498.98529-220.193032-498.985291-491.817291C7.729548 246.453677 231.12671 26.227613 506.714839 26.227613c275.555097 0 498.98529 220.226065 498.98529 491.850322" fill="#ED0002" p-id="8093"></path><path d="M1637.07871 518.077935c0 271.624258-223.430194 491.81729-498.985291 491.817291-275.588129 0-498.98529-220.193032-498.98529-491.817291 0-271.624258 223.397161-491.850323 498.98529-491.850322 275.555097 0 498.98529 220.226065 498.985291 491.850322" fill="#F9A000" p-id="8094"></path></svg>

After

Width:  |  Height:  |  Size: 738 B

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1767701266625" class="icon" viewBox="0 0 1025 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8499" xmlns:xlink="http://www.w3.org/1999/xlink" width="200.1953125" height="200"><path d="M512 0a512 512 0 1 0 512 512A512 512 0 0 0 512 0z m46.788512 867.592689h-122.986945v-133.681462h122.986945zM749.953003 570.819843H558.788512v118.976502h-122.986945v-240.626632h191.16449V282.067885H398.370757v124.32376h-122.986945V159.08094H749.953003z" fill="#F9A90A" p-id="8500"></path></svg>

After

Width:  |  Height:  |  Size: 634 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200" class="icon" viewBox="0 0 1024 1024"><path fill="#00908C" d="M283.776 257.12h712c23.296 6.752 30.048 22.048 27.456 42.112l-212.8 492.224c-11.712 15.904-28.256 26.496-51.872 29.248H21.12c-19.392-1.92-25.28-18.4-17.664-49.472l205.696-475.68c17.408-23.456 42.368-36.128 74.624-38.464z"/><path fill="#E60012" d="M284.48 257.12h210.56c-32.256 2.368-57.248 15.04-74.624 38.496L214.72 771.2c-7.648 31.04-1.76 47.488 17.664 49.408H21.824C2.4 818.72-3.456 802.272 4.16 771.2l205.728-475.648c17.408-23.456 42.368-36.16 74.592-38.4z"/><path fill="#00508E" d="M495.04 257.12h259.104c-32.224 2.368-57.216 15.04-74.592 38.496L473.856 771.2c-7.648 31.04-1.76 47.488 17.664 49.408H232.384c-19.392-1.888-25.28-18.336-17.632-49.408l205.664-475.68c17.408-23.456 42.368-36.128 74.624-38.464v.032z"/><path fill="#FFF" d="M311.296 460.896h36.544l.416 16.224h32.64v31.456h-48c-7.808 13.12-23.136 18.368-39.296 14.4l-.16-25.376c13.536-1.6 19.104-12.832 17.824-36.704zm458.048 54.976h-27.136l-22.56-33.504c-1.984 1.152-2.528 5.12-4.48 6.24-3.296 1.856-6.24.48-9.536 2.336l.224 146.784c9.408 6.08 6.304 13.856 7.008 23.2 7.52-1.504 12.288.8 19.808-.704 4.288-14.496 13.856-31.168 18.816-46.592 1.152-3.552-.544-7.36.448-10.72h-30.08l.544-28.384 35.456.32c0-5.92.672-17.376.672-23.328H721.92v-28.928l106.496 1.728-.352 27.296-35.104 1.184-.704 23.52 33.536-.512.192 25.76-35.488.48c-3.168 0-1.152 6.88-3.104 9.824v2.24c4.544.576 9.984-1.664 14.112 1.76 3.84 3.136 1.472 10.976 4.608 16.16 5.472 8.992 7.2 20.896 22.816 22.496l-.192 33.28h-23.264c-11.2-14.496-22.528-22.88-27.104-44.736l-4.512 2.24a90.43 90.43 0 0 1-24.832 42.432h-33.888c-1.28-2.88.64-8.736.128-9.984-.864-2.048-4.256 1.28-9.184-1.12-1.44 8.096 1.216 6.816-2.4 11.168-10.976-.096-21.024.32-30.304-1.152-5.76-.896.32-8.256-5.44-9.312-14.816-2.72-44.032-.64-65.728-.704v-29.12c8.192-1.408 6.88 1.728 11.296-1.824V466.752c9.76.768 19.552.128 29.344.864.768 3.744-.576 9.856.16 13.632 1.888-1.248 7.456 2.56 7.68.224 1.344-14.08 30.496-17.696 58.336-14.912 5.984.608-.512-4.928 6.08-5.248 10.816-.544 22.304-1.824 33.888-1.152 5.12 11.52 7.872 19.68 20.32 24.576v31.232l.064-.096zm29.344-53.568h31.616v4.48c7.232 19.456-11.2 37.248-20.672 47.872l-26.56.16-.192-29.664c11.456-2.08 11.936-4.096 15.808-11.648zm-399.488 2.24h25.248l.832 17.888c18.08-17.632 42.848-19.52 89.024-16.448l.256 93.568c0 12.704-11.648 17.696-12.864 19.424l-80.064-.8.352 67.328c2.24 3.904 5.024 6.944 8.64 6.912l22.944-.16-.32 31.232-49.28-.192c-4.384-.544-5.76-4.064-5.76-8.896l1.024-209.92-.032.096zm250.688 28.064c-5.44 11.328-5.856 23.808-5.44 42.688 13.216-1.408 20.64-6.016 27.776-13.408-.768-9.664.64-19.264-.128-28.928-6.784 0-15.424-.352-22.208-.352m-221.92 4.736v28.224c2.304-2.24 5.568-3.2 7.808-5.44 18.624-15.552 26.048-7.424 41.92-11.648v-11.168h-49.728zM291.36 531.52H384v24.544h-36.16v26.912h33.888v28.896h-15.872c-7.264 13.856-19.04 30.08-15.808 46.88h24.832v26.784l-42.88 2.24c-21.6-12.224-19.52-41.632-18.08-75.872h-24.832v-28.96h24.832v-26.912h-22.592v-24.544zm186.336 23.552.544-13.344-48.192.192v13.44l47.616-.32zm166.464-3.392v37.856c9.76-2.272 18.336-4.48 28.096-6.72v-31.264zm-148.224 35.584h22.336c-.768 10.4-.224 22.624-.992 33.024-8.704 3.616-19.104 3.2-33.344 2.944v6.944c3.52 11.04 1.12 21.376 12.48 25.76 4.8-4.832 10.304-3.936 20.64-3.968-.736 10.432.32 20.832-.448 31.264-18.72.448-33.6 1.92-42.368-3.84-23.168-15.168-17.504-53.312-28.416-87.68.768-1.472 2.304-2.976 3.04-4.48h24.832c2.304 5.984 2.208 12.448 4.48 18.368 10.176-.48 11.904-2.08 17.792-4.96v-13.376zm150.08 20.096v35.712h24.864c-.768-11.904 0-23.2-.704-35.104-7.552 0-16.64-.64-24.128-.64h-.064z"/></svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@@ -0,0 +1 @@
<svg t="1781511995794" class="icon" viewBox="0 0 3112 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7030" width="200" height="200"><path d="M1096.704 969.85088L1254.1952 16.71168h251.98592l-157.61408 953.1392h-251.904M2262.4256 40.1408c-49.9712-19.33312-128.12288-40.05888-225.77152-40.05888-249.0368 0-424.38656 129.31072-425.90208 314.5728-1.39264 136.97024 125.21472 213.31968 220.7744 258.94912 98.05824 46.6944 131.072 76.51328 130.58048 118.25152-0.65536 63.8976-78.31552 93.10208-150.7328 93.10208-100.80256 0-154.37824-14.45888-237.1584-50.05312l-32.48128-15.11424-35.34848 213.31968c58.85952 26.624 167.69024 49.68448 280.65792 50.87232 264.92928 0 436.79744-127.7952 438.80448-325.632 0.90112-108.46208-66.23232-190.95552-211.5584-258.99008-88.064-44.032-142.04928-73.56416-141.47584-118.21056 0-39.60832 45.6704-81.92 144.34304-81.92 64.7168-1.47456 128.98304 10.97728 188.53888 36.49536l22.60992 10.97728 34.16064-206.56128M2908.52864 17.6128h-194.72384c-60.33408 0-105.43104 16.91648-131.93216 78.97088l-374.1696 873.59488h264.6016s43.2128-117.47328 53.00224-143.27808l322.68288 0.4096c7.53664 33.3824 30.67904 142.86848 30.67904 142.86848h233.71776L2908.5696 17.6128m-310.72256 614.35904c20.76672-54.8864 100.352-266.4448 100.352-266.4448-1.47456 2.49856 20.6848-55.21408 33.3824-90.9312l17.03936 82.1248 58.368 275.2512h-209.14176M885.30944 17.36704l-246.70208 649.99424-26.2144-132.13696c-45.8752-152.24832-188.94848-317.2352-348.93824-399.7696l225.4848 833.536 266.56768-0.24576L1152.28672 17.36704h-266.8544" fill="#1A1F71" p-id="7031"></path><path d="M409.8048 16.7936H3.56352L0.28672 36.4544c316.17024 78.88896 525.27104 269.59872 612.10624 498.72896L524.0832 97.15712C508.76416 36.90496 464.52736 18.92352 409.76384 16.7936" fill="#1A1F71" p-id="7032"></path></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1,85 @@
<script lang="ts" setup>
/**
* 支付方式图标(彩色品牌 SVG与配置 icon / PayProvider 编码对齐)
* 未知编码使用中性占位,禁止回落为伪微信图标
*/
import { computed } from 'vue'
import aggregatePaySvg from '@/shared/assets/icons/channel/aggregate_pay.svg'
import alipaySvg from '@/shared/assets/icons/channel/alipay.svg'
import douyinSvg from '@/shared/assets/icons/channel/douyin.svg'
import mastercardSvg from '@/shared/assets/icons/channel/mastercard.svg'
import otherSvg from '@/shared/assets/icons/channel/other.svg'
import unionPaySvg from '@/shared/assets/icons/channel/union_pay.svg'
import visaSvg from '@/shared/assets/icons/channel/visa.svg'
import wechatSvg from '@/shared/assets/icons/channel/wechat.svg'
const props = withDefaults(defineProps<{
/** 图标编码: wechat / alipay / union_pay / douyin / aggregate_pay / visa / mastercard */
icon?: string
/** 图标显示边长(px),容器略大以留内边距 */
size?: number
}>(), {
icon: '',
size: 28,
})
/** icon code → 品牌 SVG 资源 */
const ICON_MAP: Record<string, string> = {
wechat: wechatSvg,
alipay: alipaySvg,
union_pay: unionPaySvg,
douyin: douyinSvg,
aggregate_pay: aggregatePaySvg,
visa: visaSvg,
mastercard: mastercardSvg,
}
const src = computed(() => {
const code = (props.icon || '').trim()
if (code && ICON_MAP[code]) {
return ICON_MAP[code]
}
// 未知编码: 中性占位,不伪装成微信
return otherSvg
})
const containerSize = computed(() => props.size + 12)
</script>
<template>
<span
class="pay-method-icon"
:style="{ width: `${containerSize}px`, height: `${containerSize}px` }"
:title="icon || undefined"
>
<img
class="pay-method-icon__img"
:src="src"
:width="size"
:height="size"
alt=""
draggable="false"
>
</span>
</template>
<style scoped>
.pay-method-icon {
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 10px;
background: #f5f7fa;
flex-shrink: 0;
overflow: hidden;
box-sizing: border-box;
}
.pay-method-icon__img {
display: block;
object-fit: contain;
user-select: none;
pointer-events: none;
}
</style>

View File

@@ -0,0 +1,90 @@
<script lang="ts" setup>
/**
* 二维码展示:将文本内容渲染为 canvas 二维码
* 使用动态 import('qrcode'),未安装时回退展示原文
*/
import { onMounted, ref, watch } from 'vue'
const props = withDefaults(defineProps<{
/** 二维码内容 */
content: string
/** 边长 px */
size?: number
}>(), {
size: 200,
})
const canvasRef = ref<HTMLCanvasElement | null>(null)
const fallback = ref(false)
const errorMsg = ref('')
async function render() {
fallback.value = false
errorMsg.value = ''
if (!props.content || !canvasRef.value) {
return
}
try {
// 动态加载 qrcode 包
const mod = await import('qrcode')
const QRCode = (mod as any).default ?? mod
await QRCode.toCanvas(canvasRef.value, props.content, {
width: props.size,
margin: 1,
color: {
dark: '#000000',
light: '#ffffff',
},
})
}
catch (e: any) {
// 无 qrcode 依赖或渲染失败时回退文本
fallback.value = true
errorMsg.value = e?.message || 'qr render failed'
}
}
onMounted(render)
watch(() => props.content, render)
watch(() => props.size, render)
</script>
<template>
<div class="qr-code-display" :style="{ width: `${size}px`, height: `${size}px` }">
<canvas
v-show="!fallback"
ref="canvasRef"
class="qr-code-display__canvas"
/>
<p v-if="fallback" class="qr-code-display__fallback">
{{ content }}
</p>
</div>
</template>
<style scoped>
.qr-code-display {
display: flex;
align-items: center;
justify-content: center;
background: #fff;
box-sizing: border-box;
}
.qr-code-display__canvas {
display: block;
max-width: 100%;
max-height: 100%;
}
.qr-code-display__fallback {
margin: 0;
padding: 8px;
font-size: 12px;
word-break: break-all;
color: #64748b;
text-align: center;
max-height: 100%;
overflow: auto;
}
</style>