Files
jurong_circle_frontdesk/src/views/Pay.vue

614 lines
13 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>
<div class="pay-page">
<!-- 导航栏 -->
<nav class="navbar">
<div class="nav-left">
<el-button
type="text"
@click="handleGoBack"
class="back-btn"
>
<el-icon><ArrowLeft /></el-icon>
</el-button>
</div>
<div class="nav-center">
<h1 class="nav-title">确认支付</h1>
</div>
<div class="nav-right">
<!-- 占位元素保持标题居中 -->
</div>
</nav>
<div v-loading="loading" class="page-content">
<!-- 支付倒计时 -->
<div class="countdown-section">
<div class="countdown-header">
<el-icon class="clock-icon"><Clock /></el-icon>
<span class="countdown-label">支付剩余时间</span>
</div>
<div class="countdown-display">
<div class="time-block">
<span class="time-number">{{ formatTime(minutes) }}</span>
<span class="time-label"></span>
</div>
<div class="time-separator">:</div>
<div class="time-block">
<span class="time-number">{{ formatTime(seconds) }}</span>
<span class="time-label"></span>
</div>
</div>
<div class="countdown-tip">
<span v-if="timeLeft > 0">请在规定时间内完成支付</span>
<span v-else class="timeout-text">支付超时请重新下单</span>
</div>
</div>
<!-- 支付金额 -->
<div class="amount-section">
<h3 class="section-title">支付金额</h3>
<div class="amount-display">
<div class="total-amount-large">
<span class="currency-symbol">¥</span>
<span class="amount-number">{{ paymentData.totalAmount || 0 }}</span>
</div>
<div class="amount-breakdown" v-if="paymentData.pointsAmount > 0 || paymentData.beansAmount > 0">
<div v-if="paymentData.pointsAmount > 0" class="breakdown-item">
<el-icon><Coin /></el-icon>
<span>积分{{ paymentData.pointsAmount }}</span>
</div>
<div v-if="paymentData.beansAmount > 0" class="breakdown-item">
<el-icon><Orange /></el-icon>
<span>融豆{{ paymentData.beansAmount }}</span>
</div>
</div>
</div>
</div>
<!-- 支付方式选择 -->
<div class="payment-method-section">
<h3 class="section-title">支付方式</h3>
<div class="payment-options">
<div
class="payment-option"
:class="{ active: selectedPaymentMethod === 'beans' }"
@click="selectPaymentMethod('beans')"
>
<el-icon class="payment-icon"><Orange /></el-icon>
<div class="payment-info">
<div class="payment-name">融豆支付</div>
<div class="payment-desc">使用账户融豆进行支付</div>
</div>
<el-icon class="check-icon" v-if="selectedPaymentMethod === 'beans'"><Check /></el-icon>
</div>
<div
class="payment-option"
:class="{ active: selectedPaymentMethod === 'points' }"
@click="selectPaymentMethod('points')"
>
<el-icon class="payment-icon"><Coin /></el-icon>
<div class="payment-info">
<div class="payment-name">积分支付</div>
<div class="payment-desc">使用账户积分进行支付</div>
</div>
<el-icon class="check-icon" v-if="selectedPaymentMethod === 'points'"><Check /></el-icon>
</div>
<div
class="payment-option"
:class="{ active: selectedPaymentMethod === 'mixed' }"
@click="selectPaymentMethod('mixed')"
>
<div class="payment-icon-group">
<el-icon class="payment-icon"><Coin /></el-icon>
<span class="plus-sign">+</span>
<el-icon class="payment-icon"><Orange /></el-icon>
</div>
<div class="payment-info">
<div class="payment-name">积分+融豆</div>
<div class="payment-desc">使用积分和融豆组合支付</div>
</div>
<el-icon class="check-icon" v-if="selectedPaymentMethod === 'mixed'"><Check /></el-icon>
</div>
</div>
</div>
</div>
<!-- 底部支付按钮 -->
<div class="bottom-actions">
<div class="payment-summary">
<div class="total-amount">
<span>实付</span>
<span class="amount">¥{{ paymentData.totalAmount || 0 }}</span>
</div>
</div>
<el-button
type="primary"
size="large"
class="pay-button"
@click="confirmPayment"
:disabled="timeLeft <= 0 || paying || !selectedPaymentMethod"
:loading="paying"
>
{{ timeLeft <= 0 ? '支付超时' : paying ? '支付中...' : '确认支付' }}
</el-button>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted, onUnmounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import {
ArrowLeft,
Clock,
Coin,
Orange,
Check
} from '@element-plus/icons-vue'
import api from '@/utils/api'
const route = useRoute()
const router = useRouter()
// 响应式数据
const loading = ref(false)
const paying = ref(false)
const timeLeft = ref(0) // 从后端获取倒计时时间
const timer = ref(null)
const selectedPaymentMethod = ref('') // 当前选择的支付方式
const paymentData = ref({
totalAmount: 0,
pointsAmount: 0,
beansAmount: 0,
cartId: null
})
// 计算属性
const minutes = computed(() => Math.floor(timeLeft.value / 60))
const seconds = computed(() => timeLeft.value % 60)
// 方法
const formatTime = (time) => {
return time.toString().padStart(2, '0')
}
const selectPaymentMethod = (method) => {
selectedPaymentMethod.value = method
}
const startCountdown = () => {
timer.value = setInterval(() => {
if (timeLeft.value > 0) {
timeLeft.value--
} else {
clearInterval(timer.value)
ElMessage.error('支付超时,请重新下单')
}
}, 1000)
}
const fetchPaymentData = async () => {
try {
loading.value = true
const cartId = route.query.cartId
if (!cartId) {
// 允许直接访问,使用默认数据
ElMessage.warning('未指定购物车信息,使用默认支付数据')
paymentData.value = {
totalAmount: 0,
pointsAmount: 0,
beansAmount: 0,
cartId: null
}
timeLeft.value = 900 // 默认15分钟
startCountdown()
loading.value = false
return
}
// 获取支付信息
const response = await api.get(`/payment/info/${cartId}`)
if (response.data.success) {
const data = response.data.data
paymentData.value = {
totalAmount: data.totalAmount || 0,
pointsAmount: data.pointsAmount || 0,
beansAmount: data.beansAmount || 0,
cartId: cartId
}
// 设置倒计时时间(从后端获取,单位:秒)
timeLeft.value = data.remainingTime || 900 // 默认15分钟
// 开始倒计时
startCountdown()
} else {
throw new Error(response.data.message || '获取支付信息失败')
}
} catch (error) {
ElMessage.error(error.message || '获取支付信息失败')
router.go(-1)
} finally {
loading.value = false
}
}
const handleGoBack = async () => {
// 如果倒计时还在进行中,显示确认弹窗
if (timeLeft.value > 0) {
try {
await ElMessageBox.confirm(
'确认要放弃付款吗?',
'提示',
{
confirmButtonText: '狠心离开',
cancelButtonText: '继续付款',
type: 'warning'
}
)
// 用户确认放弃付款跳转到PayFailed页面
router.push('/payfailed')
} catch {
// 用户取消,什么都不做,留在当前页面
}
} else {
// 倒计时已结束,直接返回
router.go(-1)
}
}
const confirmPayment = async () => {
if (timeLeft.value <= 0) {
ElMessage.error('支付超时,请重新下单')
return
}
if (!selectedPaymentMethod.value) {
ElMessage.error('请选择支付方式')
return
}
try {
await ElMessageBox.confirm(
`确定要支付 ¥${paymentData.value.totalAmount} 吗?`,
'确认支付',
{
confirmButtonText: '确定支付',
cancelButtonText: '取消',
type: 'warning'
}
)
paying.value = true
// 构建支付数据
const submitData = {
cartId: paymentData.value.cartId,
paymentMethod: selectedPaymentMethod.value,
totalAmount: paymentData.value.totalAmount
}
// 发送支付请求到后端
const response = await api.post('/payment/confirm', submitData)
if (response.data.success) {
ElMessage.success('支付成功!')
// 清除定时器
if (timer.value) {
clearInterval(timer.value)
}
// 跳转到订单页面
router.push('/shop')
} else {
throw new Error(response.data.message || '支付失败')
}
} catch (error) {
if (error !== 'cancel') {
ElMessage.error(error.message || '支付失败,请重试')
}
} finally {
paying.value = false
}
}
// 生命周期
onMounted(() => {
fetchPaymentData()
})
onUnmounted(() => {
if (timer.value) {
clearInterval(timer.value)
}
})
</script>
<style scoped>
.pay-page {
min-height: 100vh;
background: #f5f5f5;
display: flex;
flex-direction: column;
}
.navbar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 16px;
background: white;
border-bottom: 1px solid #eee;
position: relative;
}
.nav-left,
.nav-right {
width: 48px;
display: flex;
justify-content: center;
}
.nav-center {
position: absolute;
left: 50%;
transform: translateX(-50%);
}
.nav-title {
font-size: 18px;
font-weight: 500;
margin: 0;
}
.back-btn {
color: #333;
padding: 0;
}
.page-content {
flex: 1;
padding: 0;
}
.countdown-section {
background: white;
padding: 24px 16px;
margin-bottom: 8px;
text-align: center;
}
.countdown-header {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
margin-bottom: 16px;
}
.clock-icon {
color: #ff4757;
font-size: 18px;
}
.countdown-label {
font-size: 16px;
font-weight: 500;
color: #333;
}
.countdown-display {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
margin-bottom: 12px;
}
.time-block {
display: flex;
flex-direction: column;
align-items: center;
background: #ff4757;
color: white;
padding: 12px 16px;
border-radius: 8px;
min-width: 60px;
}
.time-number {
font-size: 24px;
font-weight: bold;
line-height: 1;
}
.time-label {
font-size: 12px;
margin-top: 4px;
}
.time-separator {
font-size: 24px;
font-weight: bold;
color: #ff4757;
}
.countdown-tip {
font-size: 14px;
color: #666;
}
.timeout-text {
color: #ff4757;
font-weight: 500;
}
.amount-section,
.payment-method-section {
background: white;
padding: 16px;
margin-bottom: 8px;
}
.section-title {
font-size: 16px;
font-weight: 500;
margin: 0 0 12px 0;
color: #333;
}
.amount-display {
text-align: center;
padding: 20px 0;
}
.total-amount-large {
display: flex;
align-items: baseline;
justify-content: center;
gap: 4px;
margin-bottom: 16px;
}
.currency-symbol {
font-size: 20px;
color: #ff4757;
font-weight: 500;
}
.amount-number {
font-size: 36px;
font-weight: bold;
color: #ff4757;
}
.amount-breakdown {
display: flex;
justify-content: center;
gap: 16px;
flex-wrap: wrap;
}
.breakdown-item {
display: flex;
align-items: center;
gap: 4px;
font-size: 14px;
color: #666;
background: #f8f9fa;
padding: 6px 12px;
border-radius: 12px;
}
.payment-options {
display: flex;
flex-direction: column;
gap: 12px;
}
.payment-option {
display: flex;
align-items: center;
gap: 12px;
padding: 16px;
border: 1px solid #eee;
border-radius: 8px;
cursor: pointer;
transition: all 0.2s;
}
.payment-option.active {
border-color: #ffae00;
background: #fff7e6;
}
.payment-icon {
color: #ffae00;
font-size: 20px;
}
.payment-icon-group {
display: flex;
align-items: center;
gap: 4px;
}
.plus-sign {
font-size: 14px;
color: #666;
font-weight: bold;
}
.payment-info {
flex: 1;
}
.payment-name {
font-size: 14px;
font-weight: 500;
color: #333;
margin-bottom: 4px;
}
.payment-desc {
font-size: 12px;
color: #666;
}
.check-icon {
color: #ffae00;
font-size: 18px;
}
.bottom-actions {
padding: 16px;
background: white;
border-top: 1px solid #eee;
display: flex;
align-items: center;
gap: 16px;
}
.payment-info {
flex: 1;
}
.payment-summary {
flex: 1;
}
.total-amount {
display: flex;
align-items: center;
gap: 4px;
font-size: 16px;
}
.amount {
color: #ff4757;
font-size: 18px;
font-weight: bold;
}
.pay-button {
min-width: 120px;
height: 48px;
background: #ffae00;
border: none;
border-radius: 24px;
font-size: 16px;
font-weight: 500;
}
.pay-button:hover {
background: #e69900;
}
.pay-button:disabled {
background: #ccc;
cursor: not-allowed;
}
</style>