cashier/pages/pay/checkout.vue

322 lines
6.0 KiB
Vue

<template>
<view class="checkout-page">
<view class="checkout-sheet">
<view class="countdown-pill">
<view class="countdown-pill-inner">
<text class="countdown-label">支付剩余时间</text>
<text class="countdown-value">{{ countdownText }}</text>
</view>
</view>
<view class="amount-block">
<text class="amount-currency">¥</text>
<text class="amount-value">{{ order.amount }}</text>
</view>
<view class="merchant-name">{{ order.merchantName }}</view>
<view class="info-card">
<view class="info-row">
<text class="info-label">备注信息</text>
<input
v-model="remark"
class="info-input"
placeholder="点击填写订单备注"
placeholder-class="info-placeholder"
:disabled="paying"
/>
</view>
<view class="info-row">
<text class="info-label">订单信息</text>
<text class="info-value">{{ order.orderDesc }}</text>
</view>
</view>
<view class="pay-method-card">
<view class="pay-method-title">支付方式</view>
<view class="pay-method-item">
<view class="pay-method-left">
<view class="wx-icon">
<text class="ri-wechat-pay-fill"></text>
</view>
<text class="pay-method-name">微信支付</text>
</view>
<text class="ri-checkbox-circle-fill pay-method-check"></text>
</view>
<view class="pay-method-tip">支付完成后,您需手动切回原应用</view>
</view>
<button
class="pay-button"
hover-class="pay-button-hover"
:loading="paying"
:disabled="expired"
@tap="handlePay"
>
确认支付 ¥{{ order.amount }}
</button>
<view class="provider-footer">卓享汇收银台 提供服务</view>
</view>
</view>
</template>
<script setup lang="ts">
import { computed, onUnmounted, ref } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
import { buildMockOrderFromOptions, MOCK_PAY_CHECKOUT_ORDER, type PayCheckoutOrder } from '@/mock/pay-checkout';
const order = ref<PayCheckoutOrder>({ ...MOCK_PAY_CHECKOUT_ORDER });
const remark = ref('');
const paying = ref(false);
const remainSeconds = ref(0);
let countdownTimer: ReturnType<typeof setInterval> | null = null;
const expired = computed(() => remainSeconds.value <= 0);
const countdownText = computed(() => {
const minutes = String(Math.floor(remainSeconds.value / 60)).padStart(2, '0');
const seconds = String(remainSeconds.value % 60).padStart(2, '0');
return `${minutes}:${seconds}`;
});
const clearCountdown = () => {
if (countdownTimer) {
clearInterval(countdownTimer);
countdownTimer = null;
}
};
const startCountdown = (expireAt: number) => {
clearCountdown();
const tick = () => {
const next = Math.max(0, Math.floor((expireAt - Date.now()) / 1000));
remainSeconds.value = next;
if (next <= 0) {
clearCountdown();
}
};
tick();
countdownTimer = setInterval(tick, 1000);
};
const handlePay = () => {
if (paying.value || expired.value) return;
paying.value = true;
setTimeout(() => {
paying.value = false;
uni.showToast({
title: '支付接口待对接',
icon: 'none'
});
}, 500);
};
onLoad((options: any) => {
order.value = buildMockOrderFromOptions(options || {});
remark.value = order.value.remark;
startCountdown(order.value.expireAt);
});
onUnmounted(() => {
clearCountdown();
});
</script>
<style lang="scss" scoped>
.checkout-page {
box-sizing: border-box;
min-height: 100vh;
padding: 24rpx 0 calc(32rpx + env(safe-area-inset-bottom));
background: #f3f4f6;
}
.checkout-sheet {
padding: 16rpx 32rpx 0;
}
.countdown-pill {
display: flex;
justify-content: center;
margin-bottom: 40rpx;
}
.countdown-pill-inner {
display: inline-flex;
align-items: center;
gap: 12rpx;
padding: 10rpx 24rpx;
border-radius: 999rpx;
background: #ffffff;
box-shadow: 0 4rpx 16rpx rgba(17, 24, 39, 0.06);
}
.countdown-label,
.countdown-value {
color: #6b7280;
font-size: 24rpx;
line-height: 1.2;
}
.countdown-value {
color: #111827;
font-weight: 700;
}
.amount-block {
display: flex;
align-items: baseline;
justify-content: center;
}
.amount-currency {
margin-right: 8rpx;
color: #111827;
font-size: 44rpx;
font-weight: 700;
line-height: 1;
}
.amount-value {
color: #111827;
font-size: 72rpx;
font-weight: 800;
line-height: 1;
}
.merchant-name {
margin-top: 20rpx;
text-align: center;
color: #6b7280;
font-size: 28rpx;
}
.info-card,
.pay-method-card {
margin-top: 40rpx;
border-radius: 16rpx;
background: #ffffff;
}
.info-card {
padding: 4rpx 0;
}
.info-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 24rpx;
padding: 28rpx 28rpx;
border-bottom: 1rpx solid #f0f2f5;
}
.info-row:last-child {
border-bottom: none;
}
.info-label {
flex-shrink: 0;
color: #6b7280;
font-size: 28rpx;
}
.info-input,
.info-value {
flex: 1;
color: #111827;
font-size: 28rpx;
text-align: right;
}
.info-placeholder {
color: #c2c7d0;
}
.pay-method-card {
padding: 28rpx 28rpx 24rpx;
}
.pay-method-title {
margin-bottom: 20rpx;
color: #6b7280;
font-size: 28rpx;
}
.pay-method-item {
display: flex;
align-items: center;
justify-content: space-between;
}
.pay-method-left {
display: flex;
align-items: center;
gap: 16rpx;
}
.wx-icon {
display: flex;
align-items: center;
justify-content: center;
width: 48rpx;
height: 48rpx;
border-radius: 50%;
background: #07c160;
color: #ffffff;
font-size: 36rpx;
line-height: 1;
}
.pay-method-name {
color: #111827;
font-size: 30rpx;
font-weight: 600;
}
.pay-method-check {
color: #07c160;
font-size: 36rpx;
line-height: 1;
}
.pay-method-tip {
margin-top: 20rpx;
color: #9ca3af;
font-size: 24rpx;
line-height: 1.5;
}
.pay-button {
display: flex;
align-items: center;
justify-content: center;
height: 92rpx;
margin-top: 48rpx;
padding: 0;
border-radius: 12rpx;
background: #1f5bd8;
color: #ffffff;
font-size: 32rpx;
font-weight: 700;
}
.pay-button::after {
border: none;
}
.pay-button-hover {
background: #174dc1;
}
.pay-button[disabled] {
background: #c8d3ee;
color: #ffffff;
}
.provider-footer {
margin-top: 28rpx;
text-align: center;
color: #9ca3af;
font-size: 24rpx;
}
</style>