1025 lines
29 KiB
Vue
1025 lines
29 KiB
Vue
<template>
|
||
<view class="payment-panel">
|
||
<!-- 支付方式 -->
|
||
<view class="pp-section">
|
||
<text class="pp-section__title">支付方式</text>
|
||
<view class="pp-payment">
|
||
<view
|
||
class="pp-payment__item"
|
||
:class="{ 'pp-payment__item--selected': selectedPayment === 'alipay' }"
|
||
@tap="selectPayment('alipay')"
|
||
>
|
||
<view class="pp-payment__icon pp-payment__icon--alipay">
|
||
<image src="https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/images/alipay.png" mode="aspectFit" />
|
||
</view>
|
||
<view class="pp-payment__info">
|
||
<text class="pp-payment__name">支付宝支付</text>
|
||
<text class="pp-payment__desc">推荐使用,安全快捷</text>
|
||
</view>
|
||
<view class="pp-payment__check">
|
||
<view class="pp-payment__check-inner" :class="{ 'pp-payment__check-inner--checked': selectedPayment === 'alipay' }"></view>
|
||
</view>
|
||
</view>
|
||
|
||
<view
|
||
v-if="showStoredCard"
|
||
class="pp-payment__item"
|
||
:class="{ 'pp-payment__item--selected': selectedPayment === 'stored' }"
|
||
@tap="selectPayment('stored')"
|
||
>
|
||
<view class="pp-payment__icon pp-payment__icon--stored">💰</view>
|
||
<view class="pp-payment__info">
|
||
<text class="pp-payment__name">储值卡支付</text>
|
||
<text class="pp-payment__desc">余额: ¥{{ storedBalance.toFixed(2) }}</text>
|
||
</view>
|
||
<view class="pp-payment__check">
|
||
<view class="pp-payment__check-inner" :class="{ 'pp-payment__check-inner--checked': selectedPayment === 'stored' }"></view>
|
||
</view>
|
||
<view class="pp-payment__disabled" v-if="storedBalance < totalAmount">
|
||
<text>余额不足</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 金额明细 -->
|
||
<view class="pp-section">
|
||
<text class="pp-section__title">金额明细</text>
|
||
<view class="pp-amount">
|
||
<view class="pp-amount__item">
|
||
<text class="pp-amount__label">{{ amountLabel }}</text>
|
||
<text class="pp-amount__value">¥{{ totalAmount.toFixed(2) }}</text>
|
||
</view>
|
||
<view class="pp-amount__item" v-if="showStoredCard && selectedPayment === 'stored' && storedBalance > 0">
|
||
<text class="pp-amount__label">储值抵扣</text>
|
||
<text class="pp-amount__value pp-amount__value--discount">-¥{{ Math.min(storedBalance, totalAmount).toFixed(2) }}</text>
|
||
</view>
|
||
<view class="pp-amount__item pp-amount__item--total">
|
||
<text class="pp-amount__label">实付金额</text>
|
||
<text class="pp-amount__value pp-amount__value--total">¥{{ actualPay.toFixed(2) }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 底部支付栏 -->
|
||
<view class="pp-bottom">
|
||
<view class="pp-bottom__left">
|
||
<text class="pp-bottom__label">实付金额</text>
|
||
<text class="pp-bottom__price">¥{{ actualPay.toFixed(2) }}</text>
|
||
</view>
|
||
<view
|
||
class="pp-bottom__btn"
|
||
:class="{ 'pp-bottom__btn--disabled': !canPay || loading }"
|
||
@tap="handlePay"
|
||
>
|
||
<text class="pp-bottom__btn-text">{{ loading ? '处理中...' : '确认支付' }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 支付成功弹窗 -->
|
||
<view class="success-modal-overlay" v-if="showSuccessModal" @tap="closeSuccessModal">
|
||
<view class="success-modal-dialog" @tap.stop>
|
||
<view class="success-modal__icon">🎉</view>
|
||
<text class="success-modal__title">{{ successTitle }}</text>
|
||
<text class="success-modal__desc">{{ successDesc }}</text>
|
||
<view class="success-modal__buttons">
|
||
<view class="success-modal__btn success-modal__btn--secondary" @tap="handleBack">
|
||
<text>返回上一页</text>
|
||
</view>
|
||
<view class="success-modal__btn success-modal__btn--primary" @tap="handleGoToResult">
|
||
<text>{{ successBtnText }}</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__title">验证支付密码</text>
|
||
<view class="pay-password-modal__close" @tap="closePayPasswordModal">
|
||
<text>✕</text>
|
||
</view>
|
||
</view>
|
||
<view class="pay-password-modal__body">
|
||
<text class="pay-password-modal__amount">¥{{ totalAmount.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 { onShow } from '@dcloudio/uni-app'
|
||
import { checkPayPasswordSet, verifyPayPassword, payWithStoredCard as storedCardPay, createPayment, createQrCodePayment, getPaymentStatus, getPendingOrder, closeOrder } from '@/api/main.js'
|
||
import VerifyCodeInput from '@/components/global/VerifyCodeInput.vue'
|
||
|
||
const props = defineProps({
|
||
orderType: {
|
||
type: String,
|
||
required: true
|
||
},
|
||
goodsDesc: {
|
||
type: String,
|
||
required: true
|
||
},
|
||
remark: {
|
||
type: String,
|
||
default: ''
|
||
},
|
||
totalAmount: {
|
||
type: Number,
|
||
required: true
|
||
},
|
||
showStoredCard: {
|
||
type: Boolean,
|
||
default: false
|
||
},
|
||
storedBalance: {
|
||
type: Number,
|
||
default: 0
|
||
},
|
||
memberId: {
|
||
type: [Number, String],
|
||
required: true
|
||
},
|
||
amountLabel: {
|
||
type: String,
|
||
default: '商品金额'
|
||
},
|
||
successTitle: {
|
||
type: String,
|
||
default: '支付成功'
|
||
},
|
||
successDesc: {
|
||
type: String,
|
||
default: '恭喜您,支付成功!'
|
||
},
|
||
successBtnText: {
|
||
type: String,
|
||
default: '查看详情'
|
||
},
|
||
showSuccessModalOnSuccess: {
|
||
type: Boolean,
|
||
default: true
|
||
}
|
||
})
|
||
|
||
const emit = defineEmits(['success', 'fail', 'cancel', 'storedBalanceUpdate', 'goToResult'])
|
||
|
||
const selectedPayment = ref('alipay')
|
||
const loading = ref(false)
|
||
const paymentResolved = ref(false)
|
||
const currentOrderId = ref(null)
|
||
const currentPayUrl = ref('')
|
||
const showSuccessModal = 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 actualPay = computed(() => {
|
||
if (selectedPayment.value === 'stored') {
|
||
return Math.max(0, props.totalAmount - props.storedBalance)
|
||
}
|
||
return props.totalAmount
|
||
})
|
||
|
||
const canPay = computed(() => {
|
||
if (props.totalAmount <= 0) return false
|
||
if (selectedPayment.value === 'alipay') {
|
||
return true
|
||
}
|
||
if (selectedPayment.value === 'stored') {
|
||
return props.storedBalance >= props.totalAmount
|
||
}
|
||
return false
|
||
})
|
||
|
||
function selectPayment(type) {
|
||
if (type === 'stored') {
|
||
if (props.storedBalance < props.totalAmount) {
|
||
uni.showToast({ title: '储值余额不足', icon: 'none' })
|
||
return
|
||
}
|
||
|
||
uni.showLoading({ title: '检查支付密码...' })
|
||
checkPayPasswordSet(props.memberId)
|
||
.then(res => {
|
||
uni.hideLoading()
|
||
if (!res.data?.isSet) {
|
||
uni.showModal({
|
||
title: '提示',
|
||
content: '您尚未设置支付密码,请先设置支付密码',
|
||
showCancel: false,
|
||
success: () => {
|
||
uni.navigateTo({ url: '/pages/memberInfo/setPayPassword' })
|
||
}
|
||
})
|
||
return
|
||
}
|
||
selectedPayment.value = type
|
||
})
|
||
.catch(e => {
|
||
uni.hideLoading()
|
||
console.error('检查支付密码失败:', e)
|
||
uni.showToast({ title: '检查失败', icon: 'none' })
|
||
})
|
||
return
|
||
}
|
||
selectedPayment.value = type
|
||
}
|
||
|
||
function handlePay() {
|
||
if (!canPay.value || loading.value) return
|
||
loading.value = true
|
||
|
||
if (selectedPayment.value === 'alipay') {
|
||
payWithAlipay()
|
||
} else if (selectedPayment.value === 'stored') {
|
||
payWithStoredCard()
|
||
}
|
||
}
|
||
|
||
function payWithStoredCard() {
|
||
if (props.storedBalance < props.totalAmount) {
|
||
uni.showToast({
|
||
title: `余额不足,当前余额:¥${props.storedBalance.toFixed(2)}`,
|
||
icon: 'none',
|
||
duration: 3000
|
||
})
|
||
loading.value = false
|
||
return
|
||
}
|
||
|
||
payPassword.value = ''
|
||
payPasswordError.value = ''
|
||
showPayPasswordModal.value = true
|
||
nextTick(() => {
|
||
payPasswordInputRef.value?.focusInput()
|
||
})
|
||
}
|
||
|
||
function closePayPasswordModal() {
|
||
showPayPasswordModal.value = false
|
||
payPassword.value = ''
|
||
payPasswordError.value = ''
|
||
if (loading.value) {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
function handlePayPasswordComplete(password) {
|
||
payPasswordError.value = ''
|
||
doPayWithStoredCard(password)
|
||
}
|
||
|
||
function goForgotPassword() {
|
||
uni.showToast({ title: '请联系客服重置密码', icon: 'none' })
|
||
}
|
||
|
||
async function doPayWithStoredCard(password) {
|
||
uni.showLoading({ title: '处理中...' })
|
||
try {
|
||
const payRes = await storedCardPay(props.memberId, password, props.totalAmount)
|
||
|
||
if (payRes.code !== 200) {
|
||
uni.hideLoading()
|
||
const msg = payRes.message || '支付失败'
|
||
if (msg.includes('密码错误') || msg.includes('密码')) {
|
||
payPasswordError.value = msg
|
||
payPassword.value = ''
|
||
nextTick(() => {
|
||
payPasswordInputRef.value?.focusInput()
|
||
})
|
||
} else {
|
||
uni.showToast({ title: msg, icon: 'none' })
|
||
loading.value = false
|
||
closePayPasswordModal()
|
||
emit('fail', payRes)
|
||
}
|
||
return
|
||
}
|
||
|
||
const newBalance = payRes.data?.balance || 0
|
||
emit('storedBalanceUpdate', newBalance)
|
||
|
||
closePayPasswordModal()
|
||
paymentResolved.value = true
|
||
uni.hideLoading()
|
||
uni.showToast({ title: '支付成功', icon: 'success' })
|
||
|
||
if (props.showSuccessModalOnSuccess) {
|
||
showSuccessModal.value = true
|
||
}
|
||
emit('success', {
|
||
orderId: null,
|
||
payType: 'stored',
|
||
balance: newBalance
|
||
})
|
||
} catch (e) {
|
||
uni.hideLoading()
|
||
console.error('储值卡支付失败:', e)
|
||
uni.showToast({ title: '支付失败,请稍后重试', icon: 'none' })
|
||
loading.value = false
|
||
closePayPasswordModal()
|
||
emit('fail', e)
|
||
}
|
||
}
|
||
|
||
function payWithAlipay() {
|
||
const transAmt = String(Math.round(props.totalAmount * 100))
|
||
const confirmContent = `确认支付 ¥${props.totalAmount.toFixed(2)} ${props.goodsDesc}?`
|
||
|
||
uni.showModal({
|
||
title: '确认支付',
|
||
content: confirmContent,
|
||
confirmText: '确认支付',
|
||
confirmColor: '#1677FF',
|
||
success: async (modalRes) => {
|
||
if (!modalRes.confirm) {
|
||
loading.value = false
|
||
emit('cancel')
|
||
return
|
||
}
|
||
|
||
try {
|
||
const pendingRes = await getPendingOrder(props.memberId, props.orderType)
|
||
|
||
if (pendingRes.code === 200 && pendingRes.data) {
|
||
const pendingOrder = pendingRes.data
|
||
const pendingAmount = pendingOrder.transAmt || (pendingOrder.amount ? (pendingOrder.amount / 100).toFixed(2) : '0.00')
|
||
uni.showModal({
|
||
title: '存在未支付订单',
|
||
content: `您有一笔¥${pendingAmount}的${props.goodsDesc}订单尚未支付,是否继续支付该订单?\n\n点击"支付旧订单"继续支付\n点击"创建新订单"关闭旧订单并重新创建`,
|
||
confirmText: '支付旧订单',
|
||
cancelText: '创建新订单',
|
||
confirmColor: '#1677FF',
|
||
success: async (chooseRes) => {
|
||
if (chooseRes.confirm) {
|
||
useExistingOrder(pendingOrder)
|
||
} else {
|
||
try {
|
||
const orderId = pendingOrder.orderId || pendingOrder.merchantOrderId || pendingOrder.orderNo
|
||
await closeOrder(orderId)
|
||
createNewPayment(transAmt)
|
||
} catch (e) {
|
||
console.error('[Payment] 关闭旧订单失败:', e)
|
||
uni.showToast({ title: '关闭旧订单失败,请重试', icon: 'none' })
|
||
loading.value = false
|
||
}
|
||
}
|
||
},
|
||
fail: () => {
|
||
loading.value = false
|
||
}
|
||
})
|
||
} else {
|
||
createNewPayment(transAmt)
|
||
}
|
||
} catch (e) {
|
||
console.error('[Payment] 查询未支付订单失败:', e)
|
||
createNewPayment(transAmt)
|
||
}
|
||
},
|
||
fail: () => {
|
||
loading.value = false
|
||
emit('cancel')
|
||
}
|
||
})
|
||
}
|
||
|
||
function useExistingOrder(order) {
|
||
currentOrderId.value = order.orderId || order.merchantOrderId || order.orderNo
|
||
currentPayUrl.value = order.payUrl || order.payInfo || order.h5PayUrl
|
||
paymentResolved.value = false
|
||
|
||
const payUrl = currentPayUrl.value
|
||
const isWebEnv = typeof navigator !== 'undefined' && typeof plus === 'undefined'
|
||
|
||
if (isWebEnv && !payUrl) {
|
||
uni.showToast({ title: '请扫码支付', icon: 'none' })
|
||
startPolling()
|
||
return
|
||
}
|
||
|
||
if (payUrl && typeof plus !== 'undefined') {
|
||
const encodedUrl = encodeURIComponent(payUrl)
|
||
const alipayScheme = `alipays://platformapi/startapp?appId=20000067&url=${encodedUrl}`
|
||
plus.runtime.openURL(alipayScheme, function(err) {
|
||
console.log('[Alipay] 唤起支付宝App失败,尝试H5支付:', err)
|
||
plus.runtime.openURL(payUrl, function(h5Err) {
|
||
console.error('[Alipay] H5支付也失败:', h5Err)
|
||
uni.showModal({ title: '提示', content: '无法唤起支付宝,请确认已安装', showCancel: false })
|
||
loading.value = false
|
||
})
|
||
})
|
||
startPolling()
|
||
} else if (payUrl) {
|
||
window.location.href = payUrl
|
||
startPolling()
|
||
}
|
||
}
|
||
|
||
async function createNewPayment(transAmt) {
|
||
const isWebEnv = typeof navigator !== 'undefined' && typeof plus === 'undefined'
|
||
|
||
try {
|
||
let payRes
|
||
if (isWebEnv) {
|
||
payRes = await createQrCodePayment({
|
||
memberId: props.memberId,
|
||
orderType: props.orderType,
|
||
goodsDesc: props.goodsDesc,
|
||
transAmt: transAmt,
|
||
tradeType: 'QRCODE',
|
||
remark: props.remark
|
||
})
|
||
} else {
|
||
payRes = await createPayment({
|
||
memberId: props.memberId,
|
||
orderType: props.orderType,
|
||
goodsDesc: props.goodsDesc,
|
||
transAmt: transAmt,
|
||
tradeType: 'ALIPAY',
|
||
remark: props.remark
|
||
})
|
||
}
|
||
|
||
if (payRes.code !== 200) {
|
||
uni.showToast({ title: payRes.message || '创建订单失败', icon: 'none' })
|
||
loading.value = false
|
||
emit('fail', payRes)
|
||
return
|
||
}
|
||
|
||
currentOrderId.value = payRes.data.orderId || payRes.data.merchantOrderId || payRes.data.tradeNo
|
||
currentPayUrl.value = payRes.data.payUrl || payRes.data.payInfo || payRes.data.h5PayUrl
|
||
paymentResolved.value = false
|
||
|
||
if (isWebEnv) {
|
||
uni.showToast({ title: '请扫码支付', icon: 'none' })
|
||
} else {
|
||
const payUrl = currentPayUrl.value
|
||
if (payUrl && typeof plus !== 'undefined') {
|
||
const encodedUrl = encodeURIComponent(payUrl)
|
||
const alipayScheme = `alipays://platformapi/startapp?appId=20000067&url=${encodedUrl}`
|
||
plus.runtime.openURL(alipayScheme, function(err) {
|
||
console.log('[Alipay] 唤起支付宝App失败,尝试H5支付:', err)
|
||
plus.runtime.openURL(payUrl, function(h5Err) {
|
||
console.error('[Alipay] H5支付也失败:', h5Err)
|
||
uni.showModal({ title: '提示', content: '无法唤起支付宝,请确认已安装', showCancel: false })
|
||
loading.value = false
|
||
})
|
||
})
|
||
} else if (payUrl) {
|
||
window.location.href = payUrl
|
||
}
|
||
}
|
||
|
||
startPolling()
|
||
} catch (e) {
|
||
console.error('[Payment] 创建订单失败:', e)
|
||
uni.showToast({ title: '创建订单失败', icon: 'none' })
|
||
loading.value = false
|
||
emit('fail', e)
|
||
}
|
||
}
|
||
|
||
function handleAppResume() {
|
||
if (paymentResolved.value || !currentOrderId.value) {
|
||
return
|
||
}
|
||
console.log('[PaymentPanel] APP从后台返回,查询支付结果')
|
||
startPolling()
|
||
}
|
||
|
||
function startPolling() {
|
||
if (paymentResolved.value || !currentOrderId.value) {
|
||
return
|
||
}
|
||
stopPolling()
|
||
pollCount = 0
|
||
console.log('[PaymentPanel] 开始轮询支付状态')
|
||
checkPaymentStatus()
|
||
pollTimer = setInterval(() => {
|
||
pollCount++
|
||
console.log(`[PaymentPanel] 轮询第 ${pollCount} 次`)
|
||
if (pollCount >= MAX_POLL_COUNT) {
|
||
console.log('[PaymentPanel] 轮询次数达到上限,停止轮询')
|
||
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
|
||
getPaymentStatus(queryOrderId)
|
||
.then(res => {
|
||
if (!silent) {
|
||
uni.hideLoading()
|
||
}
|
||
if (res.code === 200 && res.data) {
|
||
const status = res.data.status || res.data.payStatus
|
||
|
||
console.log('[PaymentPanel] 支付状态:', status)
|
||
|
||
if (status === 'SUCCESS') {
|
||
paymentResolved.value = true
|
||
stopPolling()
|
||
uni.showToast({ title: '支付成功', icon: 'success', duration: 2000 })
|
||
if (props.showSuccessModalOnSuccess) {
|
||
showSuccessModal.value = true
|
||
}
|
||
emit('success', {
|
||
orderId: queryOrderId,
|
||
payType: 'alipay',
|
||
orderInfo: res.data
|
||
})
|
||
} else if (status === 'FAIL') {
|
||
paymentResolved.value = true
|
||
stopPolling()
|
||
uni.showToast({ title: '支付失败', icon: 'error' })
|
||
loading.value = false
|
||
emit('fail', res.data)
|
||
} else if (status === 'CLOSED') {
|
||
paymentResolved.value = true
|
||
stopPolling()
|
||
uni.showToast({ title: '订单已关闭', icon: 'none' })
|
||
loading.value = false
|
||
emit('fail', res.data)
|
||
}
|
||
} else {
|
||
if (!silent) {
|
||
uni.hideLoading()
|
||
}
|
||
}
|
||
})
|
||
.catch(() => {
|
||
if (!silent) {
|
||
uni.hideLoading()
|
||
}
|
||
console.error('[PaymentPanel] 查询支付状态失败')
|
||
})
|
||
}
|
||
|
||
function closeSuccessModal() {
|
||
showSuccessModal.value = false
|
||
}
|
||
|
||
function handleBack() {
|
||
showSuccessModal.value = false
|
||
uni.navigateBack()
|
||
}
|
||
|
||
function handleGoToResult() {
|
||
showSuccessModal.value = false
|
||
emit('goToResult')
|
||
}
|
||
|
||
onMounted(() => {
|
||
if (typeof plus !== 'undefined') {
|
||
plus.globalEvent.addEventListener('resume', handleAppResume)
|
||
}
|
||
})
|
||
|
||
onShow(() => {
|
||
if (currentOrderId.value && !paymentResolved.value) {
|
||
console.log('[PaymentPanel] 页面显示,检查支付状态')
|
||
startPolling()
|
||
}
|
||
})
|
||
|
||
onUnmounted(() => {
|
||
paymentResolved.value = true
|
||
stopPolling()
|
||
if (typeof plus !== 'undefined') {
|
||
plus.globalEvent.removeEventListener('resume', handleAppResume)
|
||
}
|
||
})
|
||
|
||
defineExpose({
|
||
getCurrentOrderId: () => currentOrderId.value,
|
||
isPaymentResolved: () => paymentResolved.value,
|
||
resetPayment: () => {
|
||
paymentResolved.value = false
|
||
currentOrderId.value = null
|
||
currentPayUrl.value = ''
|
||
stopPolling()
|
||
}
|
||
})
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.payment-panel {
|
||
display: flex;
|
||
flex-direction: column;
|
||
flex: 1;
|
||
}
|
||
|
||
.pp-section {
|
||
background: #fff;
|
||
border-radius: 12rpx;
|
||
padding: 24rpx;
|
||
margin-bottom: 20rpx;
|
||
}
|
||
|
||
.pp-section__title {
|
||
font-size: 30rpx;
|
||
font-weight: 600;
|
||
color: #333;
|
||
margin-bottom: 20rpx;
|
||
display: block;
|
||
}
|
||
|
||
.pp-payment {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 16rpx;
|
||
}
|
||
|
||
.pp-payment__item {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 20rpx;
|
||
border-radius: 12rpx;
|
||
border: 2rpx solid #eee;
|
||
position: relative;
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.pp-payment__item--selected {
|
||
border-color: #1677FF;
|
||
background: #F0F7FF;
|
||
}
|
||
|
||
.pp-payment__icon {
|
||
width: 60rpx;
|
||
height: 60rpx;
|
||
border-radius: 12rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 32rpx;
|
||
margin-right: 20rpx;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.pp-payment__icon--alipay {
|
||
background: #1677FF;
|
||
image {
|
||
width: 40rpx;
|
||
height: 40rpx;
|
||
}
|
||
}
|
||
|
||
.pp-payment__icon--stored {
|
||
background: #FFD700;
|
||
}
|
||
|
||
.pp-payment__info {
|
||
flex: 1;
|
||
}
|
||
|
||
.pp-payment__name {
|
||
font-size: 28rpx;
|
||
font-weight: 500;
|
||
color: #333;
|
||
display: block;
|
||
}
|
||
|
||
.pp-payment__desc {
|
||
font-size: 24rpx;
|
||
color: #999;
|
||
margin-top: 6rpx;
|
||
display: block;
|
||
}
|
||
|
||
.pp-payment__check {
|
||
width: 40rpx;
|
||
height: 40rpx;
|
||
border-radius: 50%;
|
||
border: 2rpx solid #ddd;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.pp-payment__check-inner {
|
||
width: 24rpx;
|
||
height: 24rpx;
|
||
border-radius: 50%;
|
||
background: transparent;
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.pp-payment__check-inner--checked {
|
||
background: #1677FF;
|
||
}
|
||
|
||
.pp-payment__disabled {
|
||
position: absolute;
|
||
right: 80rpx;
|
||
top: 50%;
|
||
transform: translateY(-50%);
|
||
font-size: 24rpx;
|
||
color: #FF4D4F;
|
||
background: rgba(255, 77, 79, 0.1);
|
||
padding: 6rpx 12rpx;
|
||
border-radius: 8rpx;
|
||
}
|
||
|
||
.pp-amount {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 16rpx;
|
||
}
|
||
|
||
.pp-amount__item {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
|
||
.pp-amount__label {
|
||
font-size: 28rpx;
|
||
color: #666;
|
||
}
|
||
|
||
.pp-amount__value {
|
||
font-size: 28rpx;
|
||
color: #333;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.pp-amount__value--discount {
|
||
color: #1677FF;
|
||
}
|
||
|
||
.pp-amount__item--total {
|
||
padding-top: 16rpx;
|
||
border-top: 1rpx solid #f0f0f0;
|
||
}
|
||
|
||
.pp-amount__item--total .pp-amount__label {
|
||
font-size: 30rpx;
|
||
font-weight: 600;
|
||
color: #333;
|
||
}
|
||
|
||
.pp-amount__item--total .pp-amount__value--total {
|
||
font-size: 36rpx;
|
||
font-weight: 700;
|
||
color: #FF4D4F;
|
||
}
|
||
|
||
.pp-bottom {
|
||
position: fixed;
|
||
bottom: 0;
|
||
left: 0;
|
||
right: 0;
|
||
background: #fff;
|
||
padding: 20rpx 24rpx;
|
||
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
|
||
display: flex;
|
||
align-items: center;
|
||
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.05);
|
||
z-index: 100;
|
||
}
|
||
|
||
.pp-bottom__left {
|
||
flex: 1;
|
||
}
|
||
|
||
.pp-bottom__label {
|
||
font-size: 24rpx;
|
||
color: #999;
|
||
display: block;
|
||
}
|
||
|
||
.pp-bottom__price {
|
||
font-size: 40rpx;
|
||
font-weight: 700;
|
||
color: #FF4D4F;
|
||
margin-top: 4rpx;
|
||
display: block;
|
||
}
|
||
|
||
.pp-bottom__btn {
|
||
background: #1677FF;
|
||
border-radius: 48rpx;
|
||
padding: 20rpx 60rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.pp-bottom__btn--disabled {
|
||
background: #B8D2FF;
|
||
}
|
||
|
||
.pp-bottom__btn-text {
|
||
color: #fff;
|
||
font-size: 30rpx;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.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: 1000;
|
||
}
|
||
|
||
.success-modal-dialog {
|
||
width: 600rpx;
|
||
background: #fff;
|
||
border-radius: 24rpx;
|
||
padding: 60rpx 40rpx 40rpx;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
}
|
||
|
||
.success-modal__icon {
|
||
font-size: 100rpx;
|
||
margin-bottom: 20rpx;
|
||
}
|
||
|
||
.success-modal__title {
|
||
font-size: 36rpx;
|
||
font-weight: 700;
|
||
color: #333;
|
||
margin-bottom: 12rpx;
|
||
}
|
||
|
||
.success-modal__desc {
|
||
font-size: 28rpx;
|
||
color: #999;
|
||
text-align: center;
|
||
margin-bottom: 40rpx;
|
||
}
|
||
|
||
.success-modal__buttons {
|
||
display: flex;
|
||
gap: 20rpx;
|
||
width: 100%;
|
||
}
|
||
|
||
.success-modal__btn {
|
||
flex: 1;
|
||
padding: 24rpx;
|
||
border-radius: 48rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
text {
|
||
font-size: 28rpx;
|
||
font-weight: 500;
|
||
}
|
||
}
|
||
|
||
.success-modal__btn--secondary {
|
||
background: #f5f5f5;
|
||
text {
|
||
color: #666;
|
||
}
|
||
}
|
||
|
||
.success-modal__btn--primary {
|
||
background: #1677FF;
|
||
text {
|
||
color: #fff;
|
||
}
|
||
}
|
||
|
||
.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: 85%;
|
||
max-width: 360px;
|
||
background: #FFFFFF;
|
||
border-radius: 20px;
|
||
overflow: hidden;
|
||
animation: modalPop 0.3s ease;
|
||
}
|
||
|
||
.pay-password-modal__header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 20px 24px 12px;
|
||
}
|
||
|
||
.pay-password-modal__title {
|
||
font-size: 18px;
|
||
font-weight: 600;
|
||
color: #1A202C;
|
||
}
|
||
|
||
.pay-password-modal__close {
|
||
width: 24px;
|
||
height: 24px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
color: #A0AEC0;
|
||
font-size: 16px;
|
||
}
|
||
|
||
.pay-password-modal__body {
|
||
padding: 0 24px 32px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
}
|
||
|
||
.pay-password-modal__amount {
|
||
font-size: 32px;
|
||
font-weight: 700;
|
||
color: #E53E3E;
|
||
margin-bottom: 24px;
|
||
}
|
||
|
||
.pay-password-modal__forgot {
|
||
margin-top: 16px;
|
||
text-align: right;
|
||
width: 100%;
|
||
|
||
text {
|
||
font-size: 13px;
|
||
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);
|
||
}
|
||
}
|
||
</style>
|