优化了支付选择界面 ui
This commit is contained in:
Vendored
+1
-1
@@ -6,5 +6,5 @@
|
|||||||
// biome-ignore lint: disable
|
// biome-ignore lint: disable
|
||||||
export {}
|
export {}
|
||||||
declare global {
|
declare global {
|
||||||
|
const ElMessageBox: typeof import('element-plus/es').ElMessageBox
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { readError } from '@/shared/utils/error'
|
|||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
import { computed, nextTick, onMounted, ref, watch } from 'vue'
|
import { computed, nextTick, onMounted, ref, watch } from 'vue'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import { Star, StarFilled } from '@element-plus/icons-vue'
|
import { ShoppingCart, Star, StarFilled } from '@element-plus/icons-vue'
|
||||||
|
|
||||||
import { fetchListing, type Listing } from '@/features/listings/api/listings'
|
import { fetchListing, type Listing } from '@/features/listings/api/listings'
|
||||||
import { useListingCollections } from '@/features/listings/composables/useListingCollections'
|
import { useListingCollections } from '@/features/listings/composables/useListingCollections'
|
||||||
@@ -468,12 +468,21 @@ function handleToggleFavorite() {
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<aside class="order-panel pc-order-card">
|
<aside class="order-panel pc-order-card">
|
||||||
|
<div class="order-card-head">
|
||||||
|
<div>
|
||||||
|
<span class="order-eyebrow">平台担保</span>
|
||||||
<h2>立即下单</h2>
|
<h2>立即下单</h2>
|
||||||
|
</div>
|
||||||
|
<span class="order-state" :class="{ 'is-busy': listing.in_transaction }">
|
||||||
|
{{ listing.in_transaction ? '交易中' : '可租' }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
<p class="order-safe-text">平台托管订单与押金,按平台交接流程完成账号使用。</p>
|
<p class="order-safe-text">平台托管订单与押金,按平台交接流程完成账号使用。</p>
|
||||||
<el-form label-position="top">
|
|
||||||
|
<el-form label-position="top" class="order-form">
|
||||||
<div class="order-total-box">
|
<div class="order-total-box">
|
||||||
<div class="order-total-head">
|
<div class="order-total-head">
|
||||||
<span>租赁价格</span>
|
<span>租赁合计</span>
|
||||||
<strong>¥{{ listingPrice(listing) }}</strong>
|
<strong>¥{{ listingPrice(listing) }}</strong>
|
||||||
</div>
|
</div>
|
||||||
<div class="order-price-breakdown">
|
<div class="order-price-breakdown">
|
||||||
@@ -486,8 +495,12 @@ function handleToggleFavorite() {
|
|||||||
<strong>¥{{ formatMoney(orderPriceBreakdown.consumable) }}</strong>
|
<strong>¥{{ formatMoney(orderPriceBreakdown.consumable) }}</strong>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<em>押金另付 ¥{{ formatCent(listing.deposit_amount_cent) }}</em>
|
<div class="order-deposit-row">
|
||||||
|
<span>押金另付</span>
|
||||||
|
<strong>¥{{ formatCent(listing.deposit_amount_cent) }}</strong>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<dl class="order-check-list">
|
<dl class="order-check-list">
|
||||||
<div>
|
<div>
|
||||||
<dt>账号区服</dt>
|
<dt>账号区服</dt>
|
||||||
@@ -502,14 +515,17 @@ function handleToggleFavorite() {
|
|||||||
<dd>{{ formatEstimatedRentalDuration(listing) }}</dd>
|
<dd>{{ formatEstimatedRentalDuration(listing) }}</dd>
|
||||||
</div>
|
</div>
|
||||||
</dl>
|
</dl>
|
||||||
|
|
||||||
|
<div class="order-action-group">
|
||||||
<el-button
|
<el-button
|
||||||
type="warning"
|
type="warning"
|
||||||
size="large"
|
size="large"
|
||||||
:loading="ordering || agreementsLoading"
|
:loading="ordering || agreementsLoading"
|
||||||
:disabled="listing.in_transaction"
|
:disabled="listing.in_transaction"
|
||||||
class="full-control"
|
class="full-control order-primary-btn"
|
||||||
@click="handleCreateOrder"
|
@click="handleCreateOrder"
|
||||||
>
|
>
|
||||||
|
<el-icon><ShoppingCart /></el-icon>
|
||||||
{{ listing.in_transaction ? '交易中' : '立即下单' }}
|
{{ listing.in_transaction ? '交易中' : '立即下单' }}
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
@@ -517,8 +533,12 @@ function handleToggleFavorite() {
|
|||||||
size="large"
|
size="large"
|
||||||
@click="handleToggleFavorite"
|
@click="handleToggleFavorite"
|
||||||
>
|
>
|
||||||
|
<el-icon>
|
||||||
|
<component :is="listingFavorited ? StarFilled : Star" />
|
||||||
|
</el-icon>
|
||||||
{{ listingFavorited ? '取消收藏' : '收藏账号' }}
|
{{ listingFavorited ? '取消收藏' : '收藏账号' }}
|
||||||
</el-button>
|
</el-button>
|
||||||
|
</div>
|
||||||
</el-form>
|
</el-form>
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
@@ -903,10 +923,78 @@ function handleToggleFavorite() {
|
|||||||
.pc-order-card {
|
.pc-order-card {
|
||||||
max-width: none;
|
max-width: none;
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
|
padding: 24px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-card-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 18px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-card-head h2 {
|
||||||
|
margin: 4px 0 0;
|
||||||
|
color: #17233d;
|
||||||
|
font-size: 22px;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-eyebrow {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 24px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: #f1f5f9;
|
||||||
|
padding: 0 10px;
|
||||||
|
color: #64748b;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 900;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-state {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-width: 58px;
|
||||||
|
min-height: 28px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: #ecfdf5;
|
||||||
|
color: #16a34a;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 900;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-state.is-busy {
|
||||||
|
background: #fff7ed;
|
||||||
|
color: #ea580c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-form {
|
||||||
|
display: grid;
|
||||||
|
gap: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-safe-text {
|
||||||
|
margin-bottom: 18px;
|
||||||
|
color: #7a89a5;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
.order-total-box {
|
.order-total-box {
|
||||||
margin-bottom: 14px;
|
position: relative;
|
||||||
|
display: grid;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 0;
|
||||||
|
border: 1px solid rgba(245, 158, 11, 0.2);
|
||||||
|
background:
|
||||||
|
linear-gradient(135deg, rgba(255, 251, 235, 0.96), rgba(255, 247, 237, 0.92)),
|
||||||
|
#fffbeb;
|
||||||
|
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.86);
|
||||||
}
|
}
|
||||||
|
|
||||||
.order-total-head {
|
.order-total-head {
|
||||||
@@ -914,12 +1002,12 @@ function handleToggleFavorite() {
|
|||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
padding-bottom: 12px;
|
padding-bottom: 14px;
|
||||||
border-bottom: 1px solid rgba(217, 119, 6, 0.14);
|
border-bottom: 1px solid rgba(217, 119, 6, 0.14);
|
||||||
}
|
}
|
||||||
|
|
||||||
.order-total-head span {
|
.order-total-head span {
|
||||||
color: #92400e;
|
color: #9a5a16;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 900;
|
font-weight: 900;
|
||||||
}
|
}
|
||||||
@@ -927,14 +1015,15 @@ function handleToggleFavorite() {
|
|||||||
.order-total-head strong {
|
.order-total-head strong {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: #ef4444;
|
color: #ef4444;
|
||||||
font-size: 32px;
|
font-size: 34px;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
.order-price-breakdown {
|
.order-price-breakdown {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 8px;
|
gap: 10px;
|
||||||
padding: 12px 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.order-price-breakdown div {
|
.order-price-breakdown div {
|
||||||
@@ -944,42 +1033,51 @@ function handleToggleFavorite() {
|
|||||||
gap: 12px;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.order-price-breakdown span {
|
.order-price-breakdown span,
|
||||||
|
.order-deposit-row span {
|
||||||
color: #8a5a12;
|
color: #8a5a12;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
font-weight: 900;
|
font-weight: 900;
|
||||||
}
|
}
|
||||||
|
|
||||||
.order-price-breakdown strong {
|
.order-price-breakdown strong,
|
||||||
|
.order-deposit-row strong {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: #17233d;
|
color: #17233d;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.order-total-box > em {
|
.order-deposit-row {
|
||||||
padding-top: 10px;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
padding-top: 12px;
|
||||||
border-top: 1px solid rgba(217, 119, 6, 0.14);
|
border-top: 1px solid rgba(217, 119, 6, 0.14);
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-deposit-row strong {
|
||||||
color: #92400e;
|
color: #92400e;
|
||||||
font-size: 13px;
|
font-size: 18px;
|
||||||
font-weight: 900;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.order-check-list {
|
.order-check-list {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
margin: 0 0 18px;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.order-check-list div {
|
.order-check-list div {
|
||||||
display: flex;
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 1fr) auto;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
min-height: 40px;
|
min-height: 44px;
|
||||||
padding: 0 12px;
|
border: 1px solid #eef2f7;
|
||||||
border-radius: 10px;
|
border-radius: 12px;
|
||||||
background: #f8fafc;
|
background: #f8fafc;
|
||||||
|
padding: 0 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.order-check-list dt {
|
.order-check-list dt {
|
||||||
@@ -994,16 +1092,57 @@ function handleToggleFavorite() {
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 900;
|
font-weight: 900;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
}
|
}
|
||||||
|
|
||||||
.full-control {
|
.full-control {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.order-action-group {
|
||||||
|
display: grid;
|
||||||
|
gap: 10px;
|
||||||
|
padding-top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-primary-btn {
|
||||||
|
min-height: 48px;
|
||||||
|
border: 0;
|
||||||
|
background: #ff8a00;
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 900;
|
||||||
|
box-shadow: 0 12px 24px rgba(255, 106, 0, 0.24);
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-primary-btn:hover,
|
||||||
|
.order-primary-btn:focus {
|
||||||
|
background: #ff7a00;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-primary-btn.is-disabled,
|
||||||
|
.order-primary-btn.is-disabled:hover {
|
||||||
|
background: #f3a94a;
|
||||||
|
color: rgba(255, 255, 255, 0.86);
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
.favorite-order-btn {
|
.favorite-order-btn {
|
||||||
margin-top: 10px;
|
min-height: 46px;
|
||||||
|
margin-left: 0;
|
||||||
border-color: #ff6a00;
|
border-color: #ff6a00;
|
||||||
|
background: #ffffff;
|
||||||
color: #ff6a00;
|
color: #ff6a00;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 900;
|
||||||
|
}
|
||||||
|
|
||||||
|
.favorite-order-btn:hover,
|
||||||
|
.favorite-order-btn:focus {
|
||||||
|
border-color: #ff6a00;
|
||||||
|
background: #fff7ed;
|
||||||
|
color: #ea580c;
|
||||||
}
|
}
|
||||||
|
|
||||||
.agreement-dialog-body {
|
.agreement-dialog-body {
|
||||||
|
|||||||
@@ -0,0 +1,164 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { PaymentPayWay } from '@/features/orders/api/orders'
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
show: boolean
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
'update:show': [value: boolean]
|
||||||
|
choose: [payWay: PaymentPayWay]
|
||||||
|
closed: []
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<van-popup
|
||||||
|
:show="show"
|
||||||
|
position="bottom"
|
||||||
|
round
|
||||||
|
class="mobile-payway-popup"
|
||||||
|
@update:show="emit('update:show', $event)"
|
||||||
|
@closed="emit('closed')"
|
||||||
|
>
|
||||||
|
<header class="payway-header">
|
||||||
|
<span>选择支付方式</span>
|
||||||
|
<button type="button" aria-label="关闭" @click="emit('update:show', false)">
|
||||||
|
<van-icon name="cross" :size="20" />
|
||||||
|
</button>
|
||||||
|
</header>
|
||||||
|
<p class="payway-tip">选择渠道后将生成对应的支付二维码</p>
|
||||||
|
<div class="payway-options">
|
||||||
|
<button type="button" class="payway-option wechat" @click="emit('choose', 'WXZF')">
|
||||||
|
<span class="payway-icon">
|
||||||
|
<van-icon name="wechat-pay" :size="28" />
|
||||||
|
</span>
|
||||||
|
<span class="payway-text">
|
||||||
|
<strong>微信支付</strong>
|
||||||
|
<small>使用微信扫码完成支付</small>
|
||||||
|
</span>
|
||||||
|
<van-icon name="arrow" class="payway-arrow" :size="16" />
|
||||||
|
</button>
|
||||||
|
<button type="button" class="payway-option alipay" @click="emit('choose', 'ZFBZF')">
|
||||||
|
<span class="payway-icon">
|
||||||
|
<van-icon name="alipay" :size="28" />
|
||||||
|
</span>
|
||||||
|
<span class="payway-text">
|
||||||
|
<strong>支付宝支付</strong>
|
||||||
|
<small>使用支付宝扫码完成支付</small>
|
||||||
|
</span>
|
||||||
|
<van-icon name="arrow" class="payway-arrow" :size="16" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</van-popup>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.mobile-payway-popup {
|
||||||
|
overflow: hidden;
|
||||||
|
padding-bottom: calc(20px + env(safe-area-inset-bottom));
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.payway-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 16px 18px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.payway-header span {
|
||||||
|
color: #111827;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.payway-header button {
|
||||||
|
display: grid;
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
place-items: center;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 18px;
|
||||||
|
background: #f8fafc;
|
||||||
|
color: #64748b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.payway-tip {
|
||||||
|
margin: 0 18px 14px;
|
||||||
|
color: #94a3b8;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.payway-options {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 0 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.payway-option {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 14px;
|
||||||
|
padding: 14px 16px;
|
||||||
|
background: #fff;
|
||||||
|
border: 2px solid #eef0f3;
|
||||||
|
border-radius: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
text-align: left;
|
||||||
|
transition: border-color 0.18s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.payway-option:active {
|
||||||
|
background: #f9fafb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.payway-option.wechat:active {
|
||||||
|
border-color: #07c160;
|
||||||
|
}
|
||||||
|
|
||||||
|
.payway-option.alipay:active {
|
||||||
|
border-color: #1677ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.payway-icon {
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
width: 44px;
|
||||||
|
height: 44px;
|
||||||
|
border-radius: 12px;
|
||||||
|
color: #fff;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.payway-option.wechat .payway-icon {
|
||||||
|
background: #07c160;
|
||||||
|
}
|
||||||
|
|
||||||
|
.payway-option.alipay .payway-icon {
|
||||||
|
background: #1677ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.payway-text {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 3px;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.payway-text strong {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1f2937;
|
||||||
|
}
|
||||||
|
|
||||||
|
.payway-text small {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #94a3b8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.payway-arrow {
|
||||||
|
color: #cbd5e1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
import type { PaymentPayWay } from '@/features/orders/api/orders'
|
||||||
|
|
||||||
|
// useMobilePayWaySelect 提供移动端"选择支付方式"底部弹层的状态与 Promise 化选择逻辑。
|
||||||
|
// 与 MobilePayWaySelectPopup 配合:select() 打开弹层并返回用户所选渠道,关闭即视为取消。
|
||||||
|
export function useMobilePayWaySelect() {
|
||||||
|
const visible = ref(false)
|
||||||
|
let resolver: ((value: PaymentPayWay | null) => void) | null = null
|
||||||
|
|
||||||
|
// select 打开弹层并返回 Promise,resolve 为所选渠道;取消则为 null。
|
||||||
|
function select(): Promise<PaymentPayWay | null> {
|
||||||
|
visible.value = true
|
||||||
|
return new Promise(resolve => {
|
||||||
|
resolver = resolve
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// choose 用户点选某渠道,关闭弹层并 resolve。
|
||||||
|
function choose(payWay: PaymentPayWay) {
|
||||||
|
resolver?.(payWay)
|
||||||
|
resolver = null
|
||||||
|
visible.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleClosed 弹层关闭(含遮罩/返回)时若未选择则按取消处理。
|
||||||
|
function handleClosed() {
|
||||||
|
resolver?.(null)
|
||||||
|
resolver = null
|
||||||
|
}
|
||||||
|
|
||||||
|
return { visible, select, choose, handleClosed }
|
||||||
|
}
|
||||||
@@ -4,8 +4,10 @@ import { useRouter } from 'vue-router'
|
|||||||
import { showDialog, showToast } from 'vant'
|
import { showDialog, showToast } from 'vant'
|
||||||
|
|
||||||
import MobilePaymentCashierPopup from '@/features/orders/components/MobilePaymentCashierPopup.vue'
|
import MobilePaymentCashierPopup from '@/features/orders/components/MobilePaymentCashierPopup.vue'
|
||||||
|
import MobilePayWaySelectPopup from '@/features/orders/components/MobilePayWaySelectPopup.vue'
|
||||||
import { useOrderActions, CONFIRMABLE_ACTIONS } from '@/features/orders/composables/useOrderActions'
|
import { useOrderActions, CONFIRMABLE_ACTIONS } from '@/features/orders/composables/useOrderActions'
|
||||||
import { useMobilePaymentCashier } from '@/features/orders/composables/useMobilePaymentCashier'
|
import { useMobilePaymentCashier } from '@/features/orders/composables/useMobilePaymentCashier'
|
||||||
|
import { useMobilePayWaySelect } from '@/features/orders/composables/useMobilePayWaySelect'
|
||||||
import {
|
import {
|
||||||
amountYuan,
|
amountYuan,
|
||||||
money,
|
money,
|
||||||
@@ -19,11 +21,14 @@ import {
|
|||||||
} from '@/features/orders'
|
} from '@/features/orders'
|
||||||
import { handoffStatusLabel, orderStatusLabel } from '@/shared/utils/statusLabels'
|
import { handoffStatusLabel, orderStatusLabel } from '@/shared/utils/statusLabels'
|
||||||
import { formatDateTime } from '@/shared/utils/time'
|
import { formatDateTime } from '@/shared/utils/time'
|
||||||
import type { PaymentPayWay } from '@/features/orders/api/orders'
|
|
||||||
|
|
||||||
// 移动端支付收银台(基于通用 useOrderPaymentCashier 的薄封装:注入 vant toast + App 浏览器跳转)。
|
// 移动端支付收银台(基于通用 useOrderPaymentCashier 的薄封装:注入 vant toast + App 浏览器跳转)。
|
||||||
const cashier = useMobilePaymentCashier()
|
const cashier = useMobilePaymentCashier()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
|
// 支付方式选择底部弹层(与 PC 端一致的渠道卡片体验)。
|
||||||
|
const payWaySelect = useMobilePayWaySelect()
|
||||||
|
const selectPayWay = payWaySelect.select
|
||||||
const {
|
const {
|
||||||
paymentPopupVisible,
|
paymentPopupVisible,
|
||||||
activePayment,
|
activePayment,
|
||||||
@@ -117,23 +122,6 @@ const showDisputePopup = ref(false)
|
|||||||
const showCounterPopup = ref(false)
|
const showCounterPopup = ref(false)
|
||||||
const showRejectPopup = ref(false)
|
const showRejectPopup = ref(false)
|
||||||
|
|
||||||
// selectPayWay 在创建支付单前让移动端用户选择支付宝或微信。
|
|
||||||
async function selectPayWay(): Promise<PaymentPayWay | null> {
|
|
||||||
try {
|
|
||||||
await showDialog({
|
|
||||||
title: '选择支付方式',
|
|
||||||
message: '选择后将生成对应渠道的支付二维码。',
|
|
||||||
showCancelButton: true,
|
|
||||||
confirmButtonText: '支付宝支付',
|
|
||||||
cancelButtonText: '微信支付',
|
|
||||||
closeOnClickOverlay: false,
|
|
||||||
})
|
|
||||||
return 'ZFBZF'
|
|
||||||
} catch (action) {
|
|
||||||
return action === 'cancel' ? 'WXZF' : null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 账号快照展示(PC 不用,保留在本视图)。
|
// 账号快照展示(PC 不用,保留在本视图)。
|
||||||
function readSnapshot() {
|
function readSnapshot() {
|
||||||
return readOrderSnapshot(order.value) as Record<string, any> | null
|
return readOrderSnapshot(order.value) as Record<string, any> | null
|
||||||
@@ -465,6 +453,12 @@ async function copyListingCode() {
|
|||||||
<van-empty image="search" description="订单不存在或已被删除" />
|
<van-empty image="search" description="订单不存在或已被删除" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<MobilePayWaySelectPopup
|
||||||
|
v-model:show="payWaySelect.visible.value"
|
||||||
|
@choose="payWaySelect.choose"
|
||||||
|
@closed="payWaySelect.handleClosed"
|
||||||
|
/>
|
||||||
|
|
||||||
<MobilePaymentCashierPopup
|
<MobilePaymentCashierPopup
|
||||||
v-model:show="paymentPopupVisible"
|
v-model:show="paymentPopupVisible"
|
||||||
:payment="activePayment"
|
:payment="activePayment"
|
||||||
|
|||||||
@@ -2,17 +2,18 @@
|
|||||||
import { readError } from '@/shared/utils/error'
|
import { readError } from '@/shared/utils/error'
|
||||||
import { onMounted, ref, computed } from 'vue'
|
import { onMounted, ref, computed } from 'vue'
|
||||||
import { useRouter, useRoute } from 'vue-router'
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
import { showDialog, showToast } from 'vant'
|
import { showToast } from 'vant'
|
||||||
import MobileBottomNav from '@/components/MobileBottomNav.vue'
|
import MobileBottomNav from '@/components/MobileBottomNav.vue'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
fetchOrders,
|
fetchOrders,
|
||||||
startOrderPayment,
|
startOrderPayment,
|
||||||
type Order,
|
type Order,
|
||||||
type PaymentPayWay,
|
|
||||||
} from '@/features/orders/api/orders'
|
} from '@/features/orders/api/orders'
|
||||||
import MobilePaymentCashierPopup from '@/features/orders/components/MobilePaymentCashierPopup.vue'
|
import MobilePaymentCashierPopup from '@/features/orders/components/MobilePaymentCashierPopup.vue'
|
||||||
|
import MobilePayWaySelectPopup from '@/features/orders/components/MobilePayWaySelectPopup.vue'
|
||||||
import { useMobilePaymentCashier } from '@/features/orders/composables/useMobilePaymentCashier'
|
import { useMobilePaymentCashier } from '@/features/orders/composables/useMobilePaymentCashier'
|
||||||
|
import { useMobilePayWaySelect } from '@/features/orders/composables/useMobilePayWaySelect'
|
||||||
import { fetchOrderChat } from '@/features/chats/api/chats'
|
import { fetchOrderChat } from '@/features/chats/api/chats'
|
||||||
import { centToYuan, formatMoney } from '@/shared/utils/money'
|
import { centToYuan, formatMoney } from '@/shared/utils/money'
|
||||||
import { useSessionStore } from '@/stores/session'
|
import { useSessionStore } from '@/stores/session'
|
||||||
@@ -37,6 +38,10 @@ const {
|
|||||||
refreshPaymentStatus,
|
refreshPaymentStatus,
|
||||||
} = useMobilePaymentCashier()
|
} = useMobilePaymentCashier()
|
||||||
|
|
||||||
|
// 支付方式选择底部弹层。
|
||||||
|
const payWaySelect = useMobilePayWaySelect()
|
||||||
|
const selectPayWay = payWaySelect.select
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loadOrders()
|
loadOrders()
|
||||||
if (route.query.tab) {
|
if (route.query.tab) {
|
||||||
@@ -123,22 +128,7 @@ async function openOrderChatAfterPayment(order: Order) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// selectPayWay 在列表快捷支付前让用户选择支付宝或微信。
|
// selectPayWay 在列表快捷支付前让用户选择支付宝或微信(见 payWaySelect)。
|
||||||
async function selectPayWay(): Promise<PaymentPayWay | null> {
|
|
||||||
try {
|
|
||||||
await showDialog({
|
|
||||||
title: '选择支付方式',
|
|
||||||
message: '选择后将生成对应渠道的支付二维码。',
|
|
||||||
showCancelButton: true,
|
|
||||||
confirmButtonText: '支付宝支付',
|
|
||||||
cancelButtonText: '微信支付',
|
|
||||||
closeOnClickOverlay: false,
|
|
||||||
})
|
|
||||||
return 'ZFBZF'
|
|
||||||
} catch (action) {
|
|
||||||
return action === 'cancel' ? 'WXZF' : null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function money(value: unknown) {
|
function money(value: unknown) {
|
||||||
return formatMoney(Number(value || 0))
|
return formatMoney(Number(value || 0))
|
||||||
@@ -293,6 +283,12 @@ async function copyListingCode(order: Order) {
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- 底部导航 -->
|
<!-- 底部导航 -->
|
||||||
|
<MobilePayWaySelectPopup
|
||||||
|
v-model:show="payWaySelect.visible.value"
|
||||||
|
@choose="payWaySelect.choose"
|
||||||
|
@closed="payWaySelect.handleClosed"
|
||||||
|
/>
|
||||||
|
|
||||||
<MobilePaymentCashierPopup
|
<MobilePaymentCashierPopup
|
||||||
v-model:show="paymentPopupVisible"
|
v-model:show="paymentPopupVisible"
|
||||||
:payment="activePayment"
|
:payment="activePayment"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import { ChatDotRound, CopyDocument, Loading } from '@element-plus/icons-vue'
|
import { ChatDotRound, CopyDocument, Loading } from '@element-plus/icons-vue'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -106,19 +106,27 @@ const paymentPayURL = cashier.payURL
|
|||||||
const activePayWayLabel = computedPayWayLabel(activePayment)
|
const activePayWayLabel = computedPayWayLabel(activePayment)
|
||||||
|
|
||||||
// selectPayWay 在创建支付单前让用户选择支付宝或微信。
|
// selectPayWay 在创建支付单前让用户选择支付宝或微信。
|
||||||
async function selectPayWay(): Promise<PaymentPayWay | null> {
|
// 通过自定义对话框展示两个平级渠道卡片,返回 null 表示用户取消。
|
||||||
try {
|
const payWayDialogVisible = ref(false)
|
||||||
await ElMessageBox.confirm('选择后将生成对应渠道的支付二维码。', '选择支付方式', {
|
let payWayResolver: ((value: PaymentPayWay | null) => void) | null = null
|
||||||
type: 'info',
|
|
||||||
confirmButtonText: '支付宝支付',
|
function selectPayWay(): Promise<PaymentPayWay | null> {
|
||||||
cancelButtonText: '微信支付',
|
payWayDialogVisible.value = true
|
||||||
distinguishCancelAndClose: true,
|
return new Promise(resolve => {
|
||||||
closeOnClickModal: false,
|
payWayResolver = resolve
|
||||||
})
|
})
|
||||||
return 'ZFBZF'
|
|
||||||
} catch (action) {
|
|
||||||
return action === 'cancel' ? 'WXZF' : null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function choosePayWay(payWay: PaymentPayWay) {
|
||||||
|
payWayResolver?.(payWay)
|
||||||
|
payWayResolver = null
|
||||||
|
payWayDialogVisible.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 对话框关闭(含点 X、按 ESC)时,未选择则视为取消。
|
||||||
|
function handlePayWayDialogClosed() {
|
||||||
|
payWayResolver?.(null)
|
||||||
|
payWayResolver = null
|
||||||
}
|
}
|
||||||
|
|
||||||
// computedPayWayLabel 根据当前支付单展示支付方式名称。
|
// computedPayWayLabel 根据当前支付单展示支付方式名称。
|
||||||
@@ -603,6 +611,38 @@ async function copyListingCode() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<el-dialog
|
||||||
|
v-model="payWayDialogVisible"
|
||||||
|
title="选择支付方式"
|
||||||
|
width="420px"
|
||||||
|
append-to-body
|
||||||
|
:z-index="4000"
|
||||||
|
class="pay-way-dialog"
|
||||||
|
@closed="handlePayWayDialogClosed"
|
||||||
|
>
|
||||||
|
<p class="pay-way-tip">选择渠道后将生成对应的支付二维码</p>
|
||||||
|
<div class="pay-way-options">
|
||||||
|
<button type="button" class="pay-way-option wechat" @click="choosePayWay('WXZF')">
|
||||||
|
<span class="pay-way-icon">
|
||||||
|
<van-icon name="wechat-pay" :size="30" />
|
||||||
|
</span>
|
||||||
|
<span class="pay-way-text">
|
||||||
|
<strong>微信支付</strong>
|
||||||
|
<small>使用微信扫码完成支付</small>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="pay-way-option alipay" @click="choosePayWay('ZFBZF')">
|
||||||
|
<span class="pay-way-icon">
|
||||||
|
<van-icon name="alipay" :size="30" />
|
||||||
|
</span>
|
||||||
|
<span class="pay-way-text">
|
||||||
|
<strong>支付宝支付</strong>
|
||||||
|
<small>使用支付宝扫码完成支付</small>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
<el-dialog
|
<el-dialog
|
||||||
v-model="paymentDialogVisible"
|
v-model="paymentDialogVisible"
|
||||||
title="订单支付"
|
title="订单支付"
|
||||||
@@ -1012,6 +1052,90 @@ async function copyListingCode() {
|
|||||||
gap: 20px;
|
gap: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pay-way-tip {
|
||||||
|
margin: 0 0 16px 0;
|
||||||
|
color: #64748b;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pay-way-options {
|
||||||
|
display: grid;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pay-way-option {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
padding: 16px 18px;
|
||||||
|
background: #ffffff;
|
||||||
|
border: 2px solid #e5e7eb;
|
||||||
|
border-radius: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
text-align: left;
|
||||||
|
transition: all 0.18s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pay-way-option:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pay-way-icon {
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
border-radius: 12px;
|
||||||
|
color: #ffffff;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pay-way-option.wechat:hover {
|
||||||
|
border-color: #07c160;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pay-way-option.wechat .pay-way-icon {
|
||||||
|
background: #07c160;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pay-way-option.alipay:hover {
|
||||||
|
border-color: #1677ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pay-way-option.alipay .pay-way-icon {
|
||||||
|
background: #1677ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pay-way-text {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pay-way-text strong {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1f2937;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pay-way-text small {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #94a3b8;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.pay-way-dialog) {
|
||||||
|
border-radius: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.pay-way-dialog .el-dialog__header) {
|
||||||
|
padding: 20px 24px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.pay-way-dialog .el-dialog__body) {
|
||||||
|
padding: 16px 24px 24px;
|
||||||
|
}
|
||||||
|
|
||||||
.pay-summary {
|
.pay-summary {
|
||||||
padding: 16px 20px;
|
padding: 16px 20px;
|
||||||
background: #f8fafc;
|
background: #f8fafc;
|
||||||
|
|||||||
Reference in New Issue
Block a user