321 lines
7.5 KiB
Vue
321 lines
7.5 KiB
Vue
<template>
|
||
<view v-if="visible" class="payment-popup-mask" @tap="handleMaskClick">
|
||
<view class="payment-popup-content" @tap.stop>
|
||
<!-- 顶部提示 -->
|
||
<view class="risk-warning">
|
||
<text>盲盒具有随机性,请理性消费,购买即表示同意</text>
|
||
<text class="agreement-link" @tap="openAgreement">《购买协议》</text>
|
||
</view>
|
||
|
||
<view class="popup-header">
|
||
<text class="popup-title">确认支付</text>
|
||
<view class="close-icon" @tap="handleClose">×</view>
|
||
</view>
|
||
|
||
<view class="popup-body">
|
||
<view class="amount-section" v-if="amount !== undefined && amount !== null">
|
||
<text class="label">支付金额</text>
|
||
<text class="amount">¥{{ amount }}</text>
|
||
</view>
|
||
|
||
<view class="form-item">
|
||
<text class="label">优惠券</text>
|
||
<picker
|
||
mode="selector"
|
||
:range="coupons"
|
||
range-key="name"
|
||
@change="onCouponChange"
|
||
:value="couponIndex"
|
||
:disabled="!coupons || coupons.length === 0"
|
||
>
|
||
<view class="picker-display">
|
||
<text v-if="selectedCoupon" class="selected-text">{{ selectedCoupon.name }} (-¥{{ selectedCoupon.amount }})</text>
|
||
<text v-else-if="!coupons || coupons.length === 0" class="placeholder">暂无优惠券可用</text>
|
||
<text v-else class="placeholder">请选择优惠券</text>
|
||
<text class="arrow"></text>
|
||
</view>
|
||
</picker>
|
||
</view>
|
||
|
||
<view class="form-item" v-if="showCards">
|
||
<text class="label">道具卡</text>
|
||
<picker
|
||
mode="selector"
|
||
:range="propCards"
|
||
range-key="name"
|
||
@change="onCardChange"
|
||
:value="cardIndex"
|
||
:disabled="!propCards || propCards.length === 0"
|
||
>
|
||
<view class="picker-display">
|
||
<text v-if="selectedCard" class="selected-text">{{ selectedCard.name }}</text>
|
||
<text v-else-if="!propCards || propCards.length === 0" class="placeholder">暂无道具卡可用</text>
|
||
<text v-else class="placeholder">请选择道具卡</text>
|
||
<text class="arrow"></text>
|
||
</view>
|
||
</picker>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="popup-footer">
|
||
<button class="btn-cancel" @tap="handleClose">取消</button>
|
||
<button class="btn-confirm" @tap="handleConfirm">确认支付</button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, computed, watch } from 'vue'
|
||
|
||
const props = defineProps({
|
||
visible: { type: Boolean, default: false },
|
||
amount: { type: [Number, String], default: 0 },
|
||
coupons: { type: Array, default: () => [] },
|
||
propCards: { type: Array, default: () => [] },
|
||
showCards: { type: Boolean, default: true }
|
||
})
|
||
|
||
const emit = defineEmits(['update:visible', 'confirm', 'cancel'])
|
||
|
||
const couponIndex = ref(-1)
|
||
const cardIndex = ref(-1)
|
||
|
||
const selectedCoupon = computed(() => {
|
||
if (couponIndex.value >= 0 && props.coupons[couponIndex.value]) {
|
||
return props.coupons[couponIndex.value]
|
||
}
|
||
return null
|
||
})
|
||
|
||
const selectedCard = computed(() => {
|
||
if (cardIndex.value >= 0 && props.propCards[cardIndex.value]) {
|
||
return props.propCards[cardIndex.value]
|
||
}
|
||
return null
|
||
})
|
||
|
||
watch(() => props.visible, (val) => {
|
||
if (val) {
|
||
couponIndex.value = -1
|
||
cardIndex.value = -1
|
||
}
|
||
})
|
||
|
||
function onCouponChange(e) {
|
||
couponIndex.value = e.detail.value
|
||
}
|
||
|
||
function onCardChange(e) {
|
||
cardIndex.value = e.detail.value
|
||
}
|
||
|
||
function openAgreement() {
|
||
uni.navigateTo({
|
||
url: '/pages/agreement/purchase' // 假设协议页面路径,如果没有请替换为实际路径
|
||
})
|
||
}
|
||
|
||
function handleMaskClick() {
|
||
handleClose()
|
||
}
|
||
|
||
function handleClose() {
|
||
emit('update:visible', false)
|
||
emit('cancel')
|
||
}
|
||
|
||
function handleConfirm() {
|
||
emit('confirm', {
|
||
coupon: selectedCoupon.value,
|
||
card: props.showCards ? selectedCard.value : null
|
||
})
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
/* ============================================
|
||
奇盒潮玩 - 支付弹窗组件
|
||
采用暖橙色调的底部弹窗设计
|
||
============================================ */
|
||
|
||
.payment-popup-mask {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background-color: rgba(0, 0, 0, 0.55);
|
||
z-index: 999;
|
||
display: flex;
|
||
align-items: flex-end;
|
||
}
|
||
|
||
.payment-popup-content {
|
||
width: 100%;
|
||
background: #FFFFFF;
|
||
border-radius: 32rpx 32rpx 0 0;
|
||
padding: 32rpx;
|
||
padding-bottom: calc(32rpx + constant(safe-area-inset-bottom));
|
||
padding-bottom: calc(32rpx + env(safe-area-inset-bottom));
|
||
animation: slideUp 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||
}
|
||
@keyframes slideUp {
|
||
from { transform: translateY(100%); }
|
||
to { transform: translateY(0); }
|
||
}
|
||
|
||
.risk-warning {
|
||
background: linear-gradient(135deg, #FFF8F3, #FFF4E6);
|
||
color: #B45309;
|
||
font-size: 24rpx;
|
||
padding: 20rpx 24rpx;
|
||
border-radius: 16rpx;
|
||
margin-bottom: 24rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
flex-wrap: wrap;
|
||
line-height: 1.5;
|
||
text-align: center;
|
||
border: 1rpx solid rgba(255, 159, 67, 0.2);
|
||
}
|
||
|
||
.agreement-link {
|
||
color: #FF9F43;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.popup-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 32rpx;
|
||
position: relative;
|
||
}
|
||
.popup-title {
|
||
font-size: 36rpx;
|
||
font-weight: 700;
|
||
color: #1F2937;
|
||
}
|
||
.close-icon {
|
||
position: absolute;
|
||
right: 0;
|
||
top: 50%;
|
||
transform: translateY(-50%);
|
||
font-size: 48rpx;
|
||
color: #9CA3AF;
|
||
line-height: 1;
|
||
padding: 10rpx;
|
||
transition: color 0.2s ease;
|
||
}
|
||
.close-icon:active {
|
||
color: #6B7280;
|
||
}
|
||
|
||
.popup-body {
|
||
padding: 16rpx 0 24rpx;
|
||
}
|
||
|
||
.amount-section {
|
||
text-align: center;
|
||
margin-bottom: 40rpx;
|
||
padding: 24rpx;
|
||
background: linear-gradient(145deg, #FFFFFF, #FFF8F3);
|
||
border-radius: 20rpx;
|
||
border: 1rpx solid rgba(255, 159, 67, 0.1);
|
||
}
|
||
.amount-section .label {
|
||
font-size: 28rpx;
|
||
color: #6B7280;
|
||
margin-right: 10rpx;
|
||
}
|
||
.amount-section .amount {
|
||
font-size: 56rpx;
|
||
font-weight: 800;
|
||
background: linear-gradient(135deg, #FF6B35, #FF9F43);
|
||
-webkit-background-clip: text;
|
||
-webkit-text-fill-color: transparent;
|
||
background-clip: text;
|
||
}
|
||
|
||
.form-item {
|
||
margin-bottom: 24rpx;
|
||
}
|
||
.form-item .label {
|
||
display: block;
|
||
font-size: 28rpx;
|
||
color: #1F2937;
|
||
font-weight: 600;
|
||
margin-bottom: 16rpx;
|
||
}
|
||
|
||
.picker-display {
|
||
border: 2rpx solid #E5E7EB;
|
||
border-radius: 16rpx;
|
||
padding: 24rpx;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
font-size: 28rpx;
|
||
background: #F9FAFB;
|
||
transition: all 0.2s ease;
|
||
}
|
||
.picker-display:active {
|
||
border-color: #FF9F43;
|
||
background: #FFF8F3;
|
||
}
|
||
|
||
.selected-text {
|
||
color: #1F2937;
|
||
font-weight: 500;
|
||
}
|
||
.placeholder {
|
||
color: #9CA3AF;
|
||
}
|
||
.arrow {
|
||
color: #9CA3AF;
|
||
width: 16rpx;
|
||
height: 16rpx;
|
||
border-right: 3rpx solid #9CA3AF;
|
||
border-bottom: 3rpx solid #9CA3AF;
|
||
transform: rotate(-45deg);
|
||
margin-right: 8rpx;
|
||
}
|
||
|
||
.popup-footer {
|
||
display: flex;
|
||
gap: 20rpx;
|
||
margin-top: 16rpx;
|
||
}
|
||
.btn-cancel, .btn-confirm {
|
||
flex: 1;
|
||
border: none;
|
||
border-radius: 24rpx;
|
||
font-size: 32rpx;
|
||
padding: 24rpx 0;
|
||
line-height: 1.5;
|
||
font-weight: 600;
|
||
transition: all 0.2s ease;
|
||
}
|
||
.btn-cancel::after, .btn-confirm::after {
|
||
border: none;
|
||
}
|
||
.btn-cancel {
|
||
color: #6B7280;
|
||
background: #F3F4F6;
|
||
}
|
||
.btn-cancel:active {
|
||
background: #E5E7EB;
|
||
}
|
||
.btn-confirm {
|
||
color: #FFFFFF;
|
||
background: linear-gradient(135deg, #FF9F43, #FF6B35);
|
||
box-shadow: 0 8rpx 24rpx rgba(255, 107, 53, 0.35);
|
||
}
|
||
.btn-confirm:active {
|
||
transform: scale(0.97);
|
||
box-shadow: 0 4rpx 12rpx rgba(255, 107, 53, 0.25);
|
||
}
|
||
</style>
|