Files
gym-manage/gym-manage-uniapp/pages/memberInfo/confirmPayment.vue
T

1393 lines
40 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="confirm-payment-page">
<view class="cp-scroll-wrap">
<scroll-view scroll-y class="cp-scroll">
<view class="cp-body">
<!-- 订单信息 -->
<view class="cp-section">
<text class="cp-section__title">订单信息</text>
<view class="cp-order">
<view class="cp-order__item">
<text class="cp-order__label">订单编号</text>
<text class="cp-order__value">{{ orderNo }}</text>
</view>
<view class="cp-order__item">
<text class="cp-order__label">下单时间</text>
<text class="cp-order__value">{{ orderTime }}</text>
</view>
</view>
</view>
<!-- 商品信息 -->
<view class="cp-section" v-if="!isRecharge">
<text class="cp-section__title">购买商品</text>
<view class="cp-goods">
<view class="cp-goods__item" v-for="item in cart" :key="item.card.id">
<view class="cp-goods__icon-wrap">
<uni-icons type="gift" size="32rpx" color="#FF6B35" />
</view>
<view class="cp-goods__info">
<text class="cp-goods__name">{{ item.card.cardName }}</text>
<text class="cp-goods__desc">{{ getCardTypeName(item.card.cardType) }} · {{ getCardDesc(item.card) }}</text>
</view>
<view class="cp-goods__right">
<text class="cp-goods__count">×{{ item.count }}</text>
<text class="cp-goods__price">¥{{ (item.card.price * item.count).toFixed(2) }}</text>
</view>
</view>
</view>
</view>
<!-- 充值信息 -->
<view class="cp-section" v-else>
<text class="cp-section__title">充值信息</text>
<view class="cp-recharge">
<view class="cp-recharge__icon-wrap">
<uni-icons type="wallet" size="32rpx" color="#FFFFFF" />
</view>
<view class="cp-recharge__info">
<text class="cp-recharge__name">储值卡充值</text>
<text class="cp-recharge__amount">¥{{ totalPrice.toFixed(2) }}</text>
</view>
</view>
</view>
<!-- 支付方式 -->
<view class="cp-section">
<text class="cp-section__title">支付方式</text>
<view class="cp-payment">
<view
class="cp-payment__item"
:class="{ 'cp-payment__item--selected': selectedPayment === 'alipay' }"
@tap="selectPayment('alipay')"
>
<view class="cp-payment__icon-wrap cp-payment__icon-wrap--alipay">
<image class="cp-payment__icon-img" src="https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/images/alipay.png" mode="aspectFit" />
</view>
<view class="cp-payment__info">
<text class="cp-payment__name">支付宝支付</text>
<text class="cp-payment__desc">推荐使用安全快捷</text>
</view>
<view class="cp-payment__radio">
<view class="cp-payment__radio-inner" :class="{ 'cp-payment__radio-inner--checked': selectedPayment === 'alipay' }"></view>
</view>
</view>
<view
class="cp-payment__item"
:class="{
'cp-payment__item--selected': selectedPayment === 'stored',
'cp-payment__item--disabled': storedBalance < totalPrice
}"
v-if="!isRecharge"
@tap="storedBalance >= totalPrice && selectPayment('stored')"
>
<view class="cp-payment__icon-wrap cp-payment__icon-wrap--stored">
<uni-icons type="wallet" size="32rpx" color="#FFFFFF" />
</view>
<view class="cp-payment__info">
<text class="cp-payment__name">储值卡支付</text>
<text class="cp-payment__desc">余额: ¥{{ storedBalance.toFixed(2) }}</text>
</view>
<view class="cp-payment__radio">
<view class="cp-payment__radio-inner" :class="{ 'cp-payment__radio-inner--checked': selectedPayment === 'stored' }"></view>
</view>
<view class="cp-payment__tag" v-if="storedBalance < totalPrice">
<text>余额不足</text>
</view>
</view>
</view>
</view>
<!-- 金额明细 -->
<view class="cp-section">
<text class="cp-section__title">金额明细</text>
<view class="cp-amount">
<view class="cp-amount__item">
<text class="cp-amount__label">{{ isRecharge ? '充值金额' : '商品金额' }}</text>
<text class="cp-amount__value">¥{{ totalPrice.toFixed(2) }}</text>
</view>
<view class="cp-amount__item" v-if="!isRecharge && selectedPayment === 'stored' && storedBalance > 0">
<text class="cp-amount__label">储值抵扣</text>
<text class="cp-amount__value cp-amount__value--discount">-¥{{ Math.min(storedBalance, totalPrice).toFixed(2) }}</text>
</view>
<view class="cp-amount__item cp-amount__item--total">
<text class="cp-amount__label cp-amount__label--total">实付金额</text>
<text class="cp-amount__value cp-amount__value--total">¥{{ actualPay.toFixed(2) }}</text>
</view>
</view>
</view>
</view>
</scroll-view>
</view>
<!-- 底部支付栏 -->
<view class="cp-bottom">
<view class="cp-bottom__left">
<text class="cp-bottom__label">实付金额</text>
<text class="cp-bottom__price">¥{{ actualPay.toFixed(2) }}</text>
</view>
<view
class="cp-bottom__btn"
:class="{ 'cp-bottom__btn--disabled': !canPay || loading }"
@tap="handlePay()"
>
<text class="cp-bottom__btn-text">{{ loading ? '处理中...' : '确认支付' }}</text>
</view>
</view>
<!-- 购买成功弹窗 -->
<view class="success-modal-overlay" v-if="showSuccessModalVisible" @tap="closeSuccessModal">
<view class="success-modal-dialog" @tap.stop>
<view class="success-modal__icon-wrap">
<uni-icons type="checkmarkempty" size="56rpx" color="#FFFFFF" />
</view>
<text class="success-modal__title">{{ isRecharge ? '充值成功' : '购买成功' }}</text>
<text class="success-modal__desc">{{ isRecharge ? '恭喜您,储值卡充值成功!' : '恭喜您,会员卡已成功开通!' }}</text>
<view class="success-modal__buttons">
<view class="success-modal__btn success-modal__btn--primary" @tap="handleGoToCardFromConfirm">
<text>{{ isRecharge ? '查看储值卡' : '去我的会员卡' }}</text>
</view>
<view class="success-modal__btn success-modal__btn--secondary" @tap="handleBackFromConfirm">
<text>返回上一页</text>
</view>
</view>
</view>
</view>
<!-- 旧订单提示弹窗 -->
<view class="pending-order-modal-overlay" v-if="showPendingOrderModal" @tap.stop>
<view class="pending-order-modal-dialog">
<view class="pending-order-modal__header">
<uni-icons type="info" size="40rpx" color="#F2994A" />
<text class="pending-order-modal__title">发现未完成订单</text>
</view>
<view class="pending-order-modal__body">
<text class="pending-order-modal__desc">您有一笔未支付的订单</text>
<view class="pending-order-modal__info" v-if="pendingOrder">
<text class="pending-order-modal__order-no">订单号{{ pendingOrder.orderId }}</text>
<text class="pending-order-modal__amount">金额¥{{ (pendingOrder.transAmt / 100 || pendingOrder.transAmt).toFixed(2) }}</text>
<text class="pending-order-modal__time">创建时间{{ pendingOrder.createdAt }}</text>
</view>
<text class="pending-order-modal__question">请选择您的操作</text>
</view>
<view class="pending-order-modal__buttons">
<view class="pending-order-modal__btn pending-order-modal__btn--primary" @tap="handlePayPendingOrder">
<text>支付旧订单</text>
</view>
<view class="pending-order-modal__btn pending-order-modal__btn--secondary" @tap="handleCreateNewOrder">
<text>创建新订单</text>
</view>
</view>
</view>
</view>
<!-- 支付密码验证弹窗 -->
<view class="pay-password-modal-overlay" v-if="showPayPasswordModal" @tap="closePayPasswordModal">
<view class="pay-password-modal-dialog" @tap.stop>
<view class="pay-password-modal__header">
<text class="pay-password-modal__header-title">验证支付密码</text>
<view class="pay-password-modal__header-close" @tap="closePayPasswordModal">
<uni-icons type="close" size="20rpx" color="#A0AEC0" />
</view>
</view>
<view class="pay-password-modal__body">
<text class="pay-password-modal__amount">¥{{ totalPrice.toFixed(2) }}</text>
<VerifyCodeInput
ref="payPasswordInputRef"
v-model="payPassword"
:length="6"
:mask="true"
type="box"
:errorMsg="payPasswordError"
@complete="handlePayPasswordComplete"
/>
<view class="pay-password-modal__forgot" @tap="goForgotPassword">
<text>忘记密码</text>
</view>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref, computed, onMounted, onUnmounted, nextTick } from 'vue'
import { onLoad, onShow } from '@dcloudio/uni-app'
import { getMyMemberCards, purchaseMemberCard, createPayment, createQrCodePayment, checkPayPasswordSet, verifyPayPassword, payWithStoredCard as storedCardPay, getStoredCardInfo, getPendingOrder, closeOrder } from '@/api/main.js'
import { getMemberId } from '@/utils/request.js'
import { getLoginMemberInfo } from '@/common/memberInfo/store.js'
import VerifyCodeInput from '@/components/global/VerifyCodeInput.vue'
const cart = ref([])
const selectedPayment = ref('alipay')
const storedBalance = ref(0)
const loading = ref(false)
const orderNo = ref('')
const orderTime = ref('')
const isRecharge = ref(false)
const rechargeDesc = ref('')
const rechargeRemark = ref('')
const returnPage = ref('')
const cardType = ref('')
const rechargeAmount = ref(0)
const pendingOrder = ref(null)
const showPendingOrderModal = ref(false)
const paymentResolved = ref(false)
const currentOrderId = ref(null)
const currentMerchantOrderId = ref(null)
const currentHfSeqId = ref(null)
const currentPayUrl = ref('')
const showSuccessModalVisible = ref(false)
let pollTimer = null
let pollCount = 0
const MAX_POLL_COUNT = 10
const showPayPasswordModal = ref(false)
const payPassword = ref('')
const payPasswordError = ref('')
const payPasswordInputRef = ref(null)
const totalPrice = computed(() => {
if (isRecharge.value) {
return rechargeAmount.value
}
return cart.value.reduce((sum, item) => sum + (item.card.price * item.count), 0)
})
const actualPay = computed(() => {
if (isRecharge.value) {
return rechargeAmount.value
}
if (selectedPayment.value === 'stored') {
return Math.max(0, totalPrice.value - storedBalance.value)
}
return totalPrice.value
})
const canPay = computed(() => {
if (isRecharge.value) {
if (selectedPayment.value === 'alipay') {
return true
}
return storedBalance.value >= totalPrice.value
}
if (cart.value.length === 0) return false
if (selectedPayment.value === 'alipay') {
return true
}
return storedBalance.value >= totalPrice.value
})
function getCardTypeName(type) {
const map = {
DURATION: '时长卡',
TIME_CARD: '时长卡',
COUNT: '次卡',
COUNT_CARD: '次卡',
BALANCE: '储值卡',
STORED_VALUE_CARD: '储值卡'
}
return map[type] || type
}
function getCardDesc(card) {
if (!card) return ''
if (card.validityDays) return `${card.validityDays}`
if (card.totalTimes) return `${card.totalTimes}`
if (card.amount) return `${card.amount}元储值`
return ''
}
async function selectPayment(type) {
if (type === 'stored') {
const memberId = getMemberId()
if (!memberId) {
uni.showToast({ title: '请先登录', icon: 'none' })
return
}
try {
const res = await getStoredCardInfo(memberId)
storedBalance.value = res.balance || res.totalAmount || 0
} catch (e) {
console.error('获取储值卡信息失败:', e)
storedBalance.value = 0
}
}
selectedPayment.value = type
}
async function loadStoredBalance() {
const memberId = getMemberId()
if (!memberId) return
try {
const res = await getStoredCardInfo(memberId)
storedBalance.value = res.balance || res.totalAmount || 0
} catch (e) {
console.error('获取储值卡余额失败:', e)
}
}
function handlePay() {
if (!canPay.value || loading.value) return
if (selectedPayment.value === 'alipay') {
createAlipayOrder()
} else if (selectedPayment.value === 'stored') {
showPayPasswordModal.value = true
}
}
async function createAlipayOrder() {
loading.value = true
try {
const memberId = getMemberId()
if (!memberId) {
uni.showToast({ title: '请先登录', icon: 'none' })
loading.value = false
return
}
let res
if (isRecharge.value) {
res = await createPayment({
memberId,
transAmt: String(Math.round(totalPrice.value * 100)),
orderType: 'STORED_CARD_RECHARGE',
goodsDesc: rechargeDesc.value || '储值卡充值',
remark: rechargeRemark.value || ''
})
} else {
const cardIds = cart.value.map(item => item.card.id)
const cardItem = cart.value[0]
const cardName = cardItem?.card?.cardName || '会员卡'
res = await createPayment({
memberId,
transAmt: String(Math.round(totalPrice.value * 100)),
orderType: 'MEMBER_CARD',
goodsDesc: cardName,
remark: ''
})
}
const payData = res && (res.data || res)
if (payData && (payData.payUrl || payData.qrCode || payData.qrCodeUrl)) {
currentOrderId.value = payData.orderId || payData.merchantOrderId
currentPayUrl.value = payData.payUrl || payData.qrCode || payData.qrCodeUrl
paymentResolved.value = false
loading.value = false
startPolling()
openAlipay(payData.payUrl || payData.qrCode || payData.qrCodeUrl)
} else {
uni.showToast({ title: '创建订单失败', icon: 'none' })
loading.value = false
}
} catch (e) {
console.error('创建订单失败:', e)
uni.showToast({ title: '创建订单失败', icon: 'none' })
loading.value = false
}
}
function openAlipay(payUrl) {
if (!payUrl) {
uni.showToast({ title: '支付链接为空', icon: 'none' })
return
}
console.log('[Alipay] 准备唤起支付宝:', payUrl)
if (typeof plus !== 'undefined') {
plus.runtime.openURL(payUrl, (err) => {
console.error('[Alipay] 唤起支付宝失败:', err)
uni.showToast({ title: '请安装支付宝App', icon: 'none' })
})
} else {
uni.showModal({ title: '提示', content: '请在 App 内完成支付', showCancel: false })
}
}
function closeSuccessModal() {
showSuccessModalVisible.value = false
handleBackFromConfirm()
}
function handleBackFromConfirm() {
const pages = getCurrentPages()
if (pages.length > 2) {
uni.navigateBack({ delta: 2 })
} else if (pages.length > 1) {
uni.navigateBack()
} else {
uni.switchTab({ url: '/pages/memberInfo/memberInfo' })
}
}
function handleGoToCardFromConfirm() {
showSuccessModalVisible.value = false
if (isRecharge.value) {
uni.redirectTo({ url: '/pages/memberInfo/rechargeStoredCard' })
} else {
uni.redirectTo({ url: '/pages/memberInfo/memberCard' })
}
}
function closePayPasswordModal() {
showPayPasswordModal.value = false
payPassword.value = ''
payPasswordError.value = ''
}
function handlePayPasswordComplete() {
handleStoredCardPay()
}
async function handleStoredCardPay() {
if (!payPassword.value || payPassword.value.length < 6) return
loading.value = true
try {
const memberId = getMemberId()
const password = payPassword.value
const res = await verifyPayPassword(memberId, password)
const verifyData = res && (res.data || res)
if (verifyData && (verifyData.code === 200 || verifyData.valid)) {
closePayPasswordModal()
const payRes = await storedCardPay(memberId, password, totalPrice.value)
const payData = payRes && (payRes.data || payRes)
if (payData && (payData.code === 200 || payData.success)) {
paymentResolved.value = true
if (isRecharge.value) {
showSuccessModalVisible.value = true
} else {
await purchaseCards()
}
} else {
uni.showToast({ title: payData?.message || '支付失败', icon: 'none' })
}
} else {
payPasswordError.value = '密码错误,请重试'
payPassword.value = ''
if (payPasswordInputRef.value) {
payPasswordInputRef.value.clear()
}
}
} catch (e) {
console.error('储值卡支付失败:', e)
uni.showToast({ title: '支付失败', icon: 'none' })
closePayPasswordModal()
} finally {
loading.value = false
}
}
function goForgotPassword() {
closePayPasswordModal()
uni.navigateTo({ url: '/pages/memberInfo/setPayPassword' })
}
async function purchaseCards() {
if (cart.value.length === 0) return
uni.showLoading({ title: '开通会员卡...' })
let successCount = 0
for (const item of cart.value) {
try {
const memberRes = await purchaseMemberCard({ memberCardId: item.card.id })
if (memberRes.code === 200 || memberRes.id || memberRes.memberCardRecordId) {
successCount += item.count
}
} catch (e) {
console.error('购买会员卡失败:', e)
}
}
uni.hideLoading()
if (successCount > 0) {
showSuccessModalVisible.value = true
} else {
uni.showToast({ title: '开通会员卡失败', icon: 'none' })
}
}
async function handlePayPendingOrder() {
showPendingOrderModal.value = false
if (pendingOrder.value) {
currentOrderId.value = pendingOrder.value.orderId
currentPayUrl.value = pendingOrder.value.payUrl
paymentResolved.value = false
startPolling()
openAlipay(pendingOrder.value.payUrl)
}
}
function handleCreateNewOrder() {
showPendingOrderModal.value = false
if (pendingOrder.value?.orderId) {
closeOrder(pendingOrder.value.orderId).catch(() => {})
}
pendingOrder.value = null
}
async function checkPendingOrder() {
try {
const memberId = getMemberId()
if (!memberId) return
const res = await getPendingOrder(memberId)
if (res && (res.orderId || res.data?.orderId)) {
pendingOrder.value = res.data || res
showPendingOrderModal.value = true
}
} catch (e) {
console.error('检查待支付订单失败:', e)
}
}
onLoad((options) => {
if (options && options.returnPage) {
returnPage.value = options.returnPage
}
if (options && options.cardType) {
cardType.value = options.cardType
}
if (options && options.orderType && options.orderType === 'STORED_CARD_RECHARGE') {
isRecharge.value = true
if (options.desc) {
rechargeDesc.value = decodeURIComponent(options.desc)
}
if (options.remark) {
rechargeRemark.value = decodeURIComponent(options.remark)
}
}
if (isRecharge.value && options && options.amount) {
const amt = parseFloat(options.amount)
if (!isNaN(amt)) {
rechargeAmount.value = amt
}
}
if (options && options.orderId) {
currentOrderId.value = options.orderId
setTimeout(() => {
checkPaymentStatus()
}, 500)
}
try {
if (options && options.cartData) {
cart.value = JSON.parse(decodeURIComponent(options.cartData))
} else if (options && options.cardInfo) {
const cardInfo = JSON.parse(decodeURIComponent(options.cardInfo))
const purchaseCount = parseInt(options.purchaseCount) || 1
cart.value = [{ card: cardInfo, count: purchaseCount }]
}
} catch (e) {
console.error('解析购物车数据失败:', e)
cart.value = []
}
const now = new Date()
orderNo.value = `PAY${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, '0')}${String(now.getDate()).padStart(2, '0')}${String(now.getHours()).padStart(2, '0')}${String(now.getMinutes()).padStart(2, '0')}${String(now.getSeconds()).padStart(2, '0')}`
orderTime.value = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')} ${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`
loadStoredBalance()
checkPendingOrder()
})
function handleAppResume() {
if (paymentResolved.value || !currentOrderId.value) {
return
}
console.log('[Payment] APP从后台返回,查询支付结果')
startPolling()
}
function startPolling() {
if (paymentResolved.value || !currentOrderId.value) {
return
}
stopPolling()
pollCount = 0
console.log('[Payment] 开始轮询支付状态')
checkPaymentStatus()
pollTimer = setInterval(() => {
pollCount++
console.log(`[Payment] 轮询第 ${pollCount}`)
if (pollCount >= MAX_POLL_COUNT) {
console.log('[Payment] 轮询次数达到上限,停止轮询')
stopPolling()
return
}
checkPaymentStatus(true)
}, 2000)
}
function stopPolling() {
if (pollTimer) {
clearInterval(pollTimer)
pollTimer = null
}
}
function checkPaymentStatus(silent = false) {
if (paymentResolved.value || !currentOrderId.value) {
stopPolling()
return
}
if (!silent) {
uni.showLoading({ title: '查询支付结果...' })
}
const queryOrderId = currentOrderId.value
import('@/api/main.js').then(({ getPaymentStatus }) => {
getPaymentStatus(queryOrderId)
.then(res => {
if (!silent) {
uni.hideLoading()
}
if (res.code === 200 && res.data) {
const status = res.data.status || res.data.payStatus
console.log('[Payment] 支付状态:', status)
if (status === 'SUCCESS') {
paymentResolved.value = true
stopPolling()
uni.showToast({ title: '支付成功', icon: 'success', duration: 2000 })
handlePaymentSuccess()
} else if (status === 'FAIL') {
paymentResolved.value = true
stopPolling()
uni.showToast({ title: '支付失败', icon: 'error' })
loading.value = false
} else if (status === 'CLOSED') {
paymentResolved.value = true
stopPolling()
uni.showToast({ title: '订单已关闭', icon: 'none' })
loading.value = false
}
} else {
if (!silent) {
uni.hideLoading()
}
}
})
.catch(() => {
if (!silent) {
uni.hideLoading()
}
console.error('[Payment] 查询支付状态失败')
})
})
}
async function handlePaymentSuccess() {
try {
const res = await getStoredCardInfo(getMemberId())
storedBalance.value = res.balance || 0
} catch (e) {
console.error('支付成功后更新余额失败:', e)
}
if (isRecharge.value) {
uni.showToast({ title: '充值成功', icon: 'success' })
showSuccessModalVisible.value = true
return
}
if (cart.value.length === 0) return
uni.showLoading({ title: '开通会员卡...' })
let successCount = 0
for (const item of cart.value) {
try {
const memberRes = await purchaseMemberCard({ memberCardId: item.card.id })
if (memberRes.code === 200 || memberRes.id || memberRes.memberCardRecordId) {
successCount += item.count
}
} catch (e) {
console.error('购买会员卡失败:', e)
}
}
uni.hideLoading()
if (successCount > 0) {
showSuccessModalVisible.value = true
}
}
onMounted(() => {
if (typeof plus !== 'undefined') {
plus.globalEvent.addEventListener('resume', handleAppResume)
}
})
onUnmounted(() => {
paymentResolved.value = true
stopPolling()
if (typeof plus !== 'undefined') {
plus.globalEvent.removeEventListener('resume', handleAppResume)
}
})
</script>
<style lang="scss" scoped>
@import '@/common/style/memberInfo/pages/page-reset.css';
@import '@/common/style/memberInfo/member-info-tap.css';
// ================== 页面布局 ==================
.confirm-payment-page {
display: flex;
flex-direction: column;
height: 100vh;
overflow: hidden;
background: #F5F7FA;
}
.cp-scroll-wrap {
flex: 1;
overflow: hidden;
}
.cp-scroll {
height: 100%;
overflow-y: auto;
}
.cp-body {
padding: 24rpx 32rpx;
padding-bottom: 160rpx;
}
// ================== 区块 ==================
.cp-section {
background: #FFFFFF;
border-radius: 16rpx;
padding: 24rpx 32rpx;
margin-bottom: 24rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04);
}
.cp-section__title {
font-size: 28rpx;
font-weight: 600;
color: #1E2A3A;
margin-bottom: 20rpx;
display: block;
}
// ================== 订单信息 ==================
.cp-order {
display: flex;
flex-direction: column;
gap: 16rpx;
}
.cp-order__item {
display: flex;
justify-content: space-between;
align-items: center;
}
.cp-order__label {
font-size: 26rpx;
color: #8A99B4;
}
.cp-order__value {
font-size: 26rpx;
color: #2D3748;
}
// ================== 商品信息 ==================
.cp-goods {
display: flex;
flex-direction: column;
gap: 16rpx;
}
.cp-goods__item {
display: flex;
align-items: center;
gap: 16rpx;
padding: 20rpx;
background: #F7FAFC;
border-radius: 12rpx;
}
.cp-goods__icon-wrap {
width: 64rpx;
height: 64rpx;
border-radius: 16rpx;
background: rgba(255, 107, 53, 0.1);
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.cp-goods__info {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 6rpx;
}
.cp-goods__name {
font-size: 28rpx;
font-weight: 600;
color: #2D3748;
}
.cp-goods__desc {
font-size: 24rpx;
color: #8A99B4;
}
.cp-goods__right {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 6rpx;
flex-shrink: 0;
}
.cp-goods__count {
font-size: 24rpx;
color: #A0AEC0;
}
.cp-goods__price {
font-size: 28rpx;
font-weight: 600;
color: #E53E3E;
}
// ================== 充值信息 ==================
.cp-recharge {
display: flex;
align-items: center;
gap: 20rpx;
}
.cp-recharge__icon-wrap {
width: 64rpx;
height: 64rpx;
border-radius: 16rpx;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.cp-recharge__info {
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
}
.cp-recharge__name {
font-size: 28rpx;
font-weight: 600;
color: #2D3748;
}
.cp-recharge__amount {
font-size: 28rpx;
font-weight: 600;
color: #E53E3E;
}
// ================== 支付方式 ==================
.cp-payment {
display: flex;
flex-direction: column;
gap: 16rpx;
}
.cp-payment__item {
display: flex;
align-items: center;
padding: 24rpx;
border: 2rpx solid #E2E8F0;
border-radius: 16rpx;
transition: all 0.2s;
position: relative;
gap: 16rpx;
}
.cp-payment__item--selected {
border-color: #1677FF;
background: #F0F7FF;
}
.cp-payment__item--disabled {
opacity: 0.55;
}
.cp-payment__icon-wrap {
width: 64rpx;
height: 64rpx;
border-radius: 16rpx;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.cp-payment__icon-wrap--alipay {
background: #1677FF;
}
.cp-payment__icon-img {
width: 40rpx;
height: 40rpx;
}
.cp-payment__icon-wrap--stored {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.cp-payment__info {
flex: 1;
display: flex;
flex-direction: column;
gap: 6rpx;
}
.cp-payment__name {
font-size: 28rpx;
font-weight: 600;
color: #2D3748;
}
.cp-payment__desc {
font-size: 24rpx;
color: #8A99B4;
}
.cp-payment__radio {
width: 36rpx;
height: 36rpx;
border: 3rpx solid #CBD5E0;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.cp-payment__item--selected .cp-payment__radio {
border-color: #1677FF;
}
.cp-payment__radio-inner {
width: 20rpx;
height: 20rpx;
border-radius: 50%;
background: transparent;
transition: all 0.2s;
}
.cp-payment__radio-inner--checked {
background: #1677FF;
}
.cp-payment__tag {
position: absolute;
right: 32rpx;
top: 50%;
transform: translateY(-50%);
padding: 8rpx 20rpx;
background: #FED7D7;
border-radius: 20rpx;
}
.cp-payment__tag text {
font-size: 22rpx;
color: #E53E3E;
}
// ================== 金额明细 ==================
.cp-amount {
display: flex;
flex-direction: column;
gap: 16rpx;
}
.cp-amount__item {
display: flex;
justify-content: space-between;
align-items: center;
}
.cp-amount__item--total {
padding-top: 16rpx;
border-top: 1rpx dashed #E2E8F0;
margin-top: 8rpx;
}
.cp-amount__label {
font-size: 26rpx;
color: #8A99B4;
}
.cp-amount__label--total {
font-size: 28rpx;
font-weight: 600;
color: #1E2A3A;
}
.cp-amount__value {
font-size: 26rpx;
color: #2D3748;
}
.cp-amount__value--discount {
color: #38A169;
}
.cp-amount__value--total {
font-size: 32rpx;
font-weight: 700;
color: #E53E3E;
}
// ================== 底部支付栏 ==================
.cp-bottom {
position: fixed;
bottom: 0;
left: 0;
right: 0;
display: flex;
align-items: center;
justify-content: space-between;
padding: 24rpx 32rpx;
padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
background: #FFFFFF;
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.05);
z-index: 100;
}
.cp-bottom__left {
display: flex;
align-items: baseline;
gap: 12rpx;
}
.cp-bottom__label {
font-size: 26rpx;
color: #8A99B4;
}
.cp-bottom__price {
font-size: 40rpx;
font-weight: 700;
color: #E53E3E;
line-height: 1;
}
.cp-bottom__btn {
background: linear-gradient(135deg, #1677FF 0%, #0958D9 100%);
border-radius: 48rpx;
padding: 24rpx 56rpx;
transition: all 0.2s;
&:active:not(.cp-bottom__btn--disabled) {
transform: scale(0.97);
opacity: 0.9;
}
}
.cp-bottom__btn--disabled {
background: #A0AEC0;
}
.cp-bottom__btn-text {
font-size: 30rpx;
font-weight: 600;
color: #FFFFFF;
}
// ================== 成功弹窗 ==================
.success-modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 2000;
animation: fadeIn 0.3s ease;
}
.success-modal-dialog {
width: 600rpx;
max-width: 600rpx;
background: #FFFFFF;
border-radius: 32rpx;
padding: 56rpx 40rpx;
display: flex;
flex-direction: column;
align-items: center;
animation: modalPop 0.3s ease;
}
.success-modal__icon-wrap {
width: 96rpx;
height: 96rpx;
border-radius: 48rpx;
background: linear-gradient(135deg, #4CAF50 0%, #66BB6A 100%);
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 24rpx;
}
.success-modal__title {
font-size: 36rpx;
font-weight: 700;
color: #1A202C;
margin-bottom: 12rpx;
}
.success-modal__desc {
font-size: 28rpx;
color: #8A99B4;
margin-bottom: 40rpx;
}
.success-modal__buttons {
display: flex;
flex-direction: column;
gap: 16rpx;
width: 100%;
}
.success-modal__btn {
padding: 24rpx;
border-radius: 48rpx;
text-align: center;
font-size: 30rpx;
font-weight: 600;
}
.success-modal__btn--primary {
background: linear-gradient(135deg, #1677FF 0%, #0958D9 100%);
color: #FFFFFF;
}
.success-modal__btn--secondary {
background: #F5F7FA;
color: #4A5568;
}
// ================== 旧订单弹窗 ==================
.pending-order-modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
animation: fadeIn 0.2s ease-out;
}
.pending-order-modal-dialog {
width: 600rpx;
background: #FFFFFF;
border-radius: 32rpx;
padding: 40rpx;
animation: slideUp 0.3s ease-out;
}
.pending-order-modal__header {
display: flex;
flex-direction: column;
align-items: center;
gap: 16rpx;
margin-bottom: 24rpx;
}
.pending-order-modal__title {
font-size: 32rpx;
font-weight: 600;
color: #1A202C;
}
.pending-order-modal__body {
display: flex;
flex-direction: column;
gap: 16rpx;
margin-bottom: 32rpx;
}
.pending-order-modal__desc {
font-size: 26rpx;
color: #8A99B4;
}
.pending-order-modal__info {
background: #F7FAFC;
border-radius: 16rpx;
padding: 24rpx;
display: flex;
flex-direction: column;
gap: 12rpx;
}
.pending-order-modal__order-no {
font-size: 24rpx;
color: #2D3748;
}
.pending-order-modal__amount {
font-size: 28rpx;
font-weight: 600;
color: #E53E3E;
}
.pending-order-modal__time {
font-size: 24rpx;
color: #8A99B4;
}
.pending-order-modal__question {
font-size: 26rpx;
color: #2D3748;
font-weight: 500;
}
.pending-order-modal__buttons {
display: flex;
gap: 16rpx;
}
.pending-order-modal__btn {
flex: 1;
padding: 24rpx;
border-radius: 48rpx;
text-align: center;
font-size: 28rpx;
font-weight: 600;
}
.pending-order-modal__btn--primary {
background: linear-gradient(135deg, #1677FF 0%, #0958D9 100%);
color: #FFFFFF;
}
.pending-order-modal__btn--secondary {
background: #F7FAFC;
color: #2D3748;
border: 2rpx solid #E2E8F0;
}
// ================== 支付密码弹窗 ==================
.pay-password-modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 2000;
animation: fadeIn 0.3s ease;
}
.pay-password-modal-dialog {
width: 700rpx;
max-width: 95vw;
background: #FFFFFF;
border-radius: 32rpx;
overflow: visible;
animation: modalPop 0.3s ease;
}
.pay-password-modal__header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 32rpx 36rpx 20rpx;
}
.pay-password-modal__header-title {
font-size: 32rpx;
font-weight: 600;
color: #1A202C;
}
.pay-password-modal__header-close {
width: 48rpx;
height: 48rpx;
display: flex;
align-items: center;
justify-content: center;
}
.pay-password-modal__body {
padding: 0 24rpx 48rpx;
display: flex;
flex-direction: column;
align-items: center;
}
.pay-password-modal__amount {
font-size: 48rpx;
font-weight: 700;
color: #E53E3E;
margin-bottom: 32rpx;
}
.pay-password-modal__forgot {
margin-top: 24rpx;
width: 100%;
text-align: right;
}
.pay-password-modal__forgot text {
font-size: 26rpx;
color: #1677FF;
}
// ================== 动画 ==================
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes modalPop {
from {
opacity: 0;
transform: scale(0.9);
}
to {
opacity: 1;
transform: scale(1);
}
}
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(40rpx);
}
to {
opacity: 1;
transform: translateY(0);
}
}
</style>