Files
jurong_circle_frontdesk/src/views/PayFailed.vue

538 lines
12 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-failed-page">
<!-- 导航栏 -->
<nav class="navbar">
<div class="nav-left">
<el-button
type="text"
@click="$router.push({ name: 'Shop' })"
class="back-btn"
>
<el-icon><ArrowLeft /></el-icon>
</el-button>
</div>
<div class="nav-center">
<h1 class="nav-title">{{ orderExpired ? '交易关闭' : '继续付款' }}</h1>
</div>
<div class="nav-right">
<!-- 占位元素保持标题居中 -->
</div>
</nav>
<div v-loading="loading" class="page-content">
<!-- 订单信息 -->
<div class="order-info-section">
<h3 class="section-title">订单信息</h3>
<div class="info-item">
<span class="label">订单号</span>
<span class="value">{{ orderData.orderNumber || '-' }}</span>
</div>
<div class="info-item">
<span class="label">创建时间</span>
<span class="value">{{ formatDateTime(orderData.createTime) || '-' }}</span>
</div>
</div>
<!-- 收货地址 -->
<div class="address-section">
<h3 class="section-title">收货地址</h3>
<div class="address-card">
<div class="address-header">
<span class="recipient">{{ orderData.address?.recipient || '-' }}</span>
<span class="phone">{{ orderData.address?.phone || '-' }}</span>
</div>
<div class="address-detail">
{{ formatAddress(orderData.address) || '暂无地址信息' }}
</div>
</div>
</div>
<!-- 商品清单 -->
<div class="products-section">
<h3 class="section-title">商品清单</h3>
<div class="product-list">
<div
v-for="item in orderData.cartItems"
:key="item.id"
class="product-item"
>
<img :src="item.image" :alt="item.name" class="product-image" />
<div class="product-info">
<div class="product-name">{{ item.name }}</div>
<div class="product-spec">{{ item.specification || '默认规格' }}</div>
<div class="product-price">¥{{ item.price }} × {{ item.quantity }}</div>
</div>
<div class="product-total">
¥{{ (item.price * item.quantity).toFixed(2) }}
</div>
</div>
</div>
</div>
<!-- 费用明细 -->
<div class="cost-section">
<h3 class="section-title">费用明细</h3>
<div class="cost-item">
<span class="label">商品总价</span>
<span class="value">¥{{ orderData.subtotal || 0 }}</span>
</div>
<div class="cost-item">
<span class="label">运费</span>
<span class="value">¥{{ orderData.shippingFee || 0 }}</span>
</div>
<div class="cost-item total">
<span class="label">总计</span>
<span class="value">¥{{ orderData.totalAmount || 0 }}</span>
</div>
</div>
</div>
<!-- 底部操作按钮 -->
<div class="bottom-actions" v-if="!orderExpired">
<el-button
size="large"
class="cancel-btn"
@click="cancelOrder"
>
取消订单
</el-button>
<el-button
v-if="!orderExpired"
type="primary"
size="large"
class="pay-btn"
@click="continuePay"
:loading="paying"
>
{{ paying ? '处理中...' : '继续付款' }}
</el-button>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import {
ArrowLeft,
Warning
} 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 orderExpired = ref(false) // 订单是否超时
const orderData = ref({
orderNumber: '', // 订单编号
createTime: '', // 订单创建时间
totalAmount: 0, // 订单总金额
subtotal: 0, // 商品小计金额(不含运费)
shippingFee: 0, // 运费
address: { // 收货地址信息
recipient: '', // 收件人姓名
phone: '', // 收件人电话
province: '', // 省份
city: '', // 城市
district: '', // 区/县
detail: '' // 详细地址
},
cartItems: [] // 购物车商品列表
})
// 方法
const formatDateTime = (dateTime) => {
if (!dateTime) return ''
const date = new Date(dateTime)
return date.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
})
}
const formatAddress = (address) => {
if (!address) return ''
const { province, city, district, detail } = address
return `${province || ''}${city || ''}${district || ''}${detail || ''}`
}
const fetchOrderData = async () => {
try {
loading.value = true
const cartId = route.query.cartId
console.log('cartId:', cartId)
// 检查cartId是否有效
if (!cartId || cartId === 'undefined' || cartId === 'null' || cartId === '???') {
console.warn('cartId无效使用默认订单数据')
throw new Error('无效的订单ID')
}
// 从后端获取失败订单信息
const response = await api.get(`/order/failed/${cartId}`)
console.log('API响应:', response)
if (response.data.success) {
const data = response.data.data
orderData.value = {
orderNumber: data.orderNumber,
createTime: data.createTime,
totalAmount: data.totalAmount,
subtotal: data.subtotal,
shippingFee: data.shippingFee,
address: data.address,
cartItems: data.cartItems
}
} else {
throw new Error(response.data.message || '获取订单信息失败')
}
} catch (error) {
ElMessage.error(error.message || '获取订单信息失败')
// 如果获取失败,使用默认数据
orderData.value = {
orderNumber: 'ORD' + Date.now(),
createTime: new Date().toISOString(),
totalAmount: 0,
subtotal: 0,
shippingFee: 0,
address: {
recipient: '张三',
phone: '13888888888',
province: '浙江省',
city: '宁波市',
district: '鄞州区',
detail: '宁波外经合作大厦'
},
cartItems: [
{
id: 1,
name: '示例商品1',
image: '/imgs/loading.png',
specification: '默认规格',
price: 199.00,
quantity: 1
},
{
id: 2,
name: '示例商品2',
image: '/imgs/loading.png',
specification: '标准版',
price: 90.00,
quantity: 1
}
]
}
} finally {
loading.value = false
}
}
const continuePay = async () => {
try {
paying.value = true
// 校验订单状态
const cartId = route.query.cartId
if (cartId) {
const response = await api.get(`/order/status/${cartId}`)
if (response.data.success) {
const orderStatus = response.data.data.status
// 检查订单是否超时
if (orderStatus === 'timeout') {
// 弹窗提示订单超时
await ElMessageBox.alert(
'很抱歉,该订单已超时,无法继续付款。',
'订单超时提醒',
{
confirmButtonText: '确定',
type: 'warning'
}
)
// 设置订单超时状态
orderExpired.value = true
return
}
}
}
// 跳转回支付页面
if (cartId) {
router.push(`/pay?cartId=${cartId}`)
} else {
router.push('/pay')
}
} catch (error) {
ElMessage.error(error.message || '检查订单状态失败')
} finally {
paying.value = false
}
}
const cancelOrder = async () => {
try {
await ElMessageBox.confirm(
'确定要取消这个订单吗?取消后将无法恢复。',
'取消订单',
{
confirmButtonText: '确定取消',
cancelButtonText: '我再想想',
type: 'warning'
}
)
// 发送取消订单请求
const cartId = route.query.cartId
if (cartId) {
const response = await api.post(`/order/cancel/${cartId}`)
if (response.data.success) {
ElMessage.success('订单已取消')
orderExpired.value = true
} else {
throw new Error(response.data.message || '取消订单失败')
}
} else {
ElMessage.success('订单已取消')
orderExpired.value = true
}
} catch (error) {
if (error !== 'cancel') {
ElMessage.error(error.message || '取消订单失败')
}
}
}
// 生命周期
onMounted(() => {
fetchOrderData()
})
</script>
<style scoped>
.pay-failed-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;
}
.order-info-section,
.address-section,
.products-section,
.cost-section {
background: white;
padding: 16px;
margin-bottom: 8px;
}
.section-title {
font-size: 16px;
font-weight: 500;
margin: 0 0 12px 0;
color: #333;
}
.info-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 0;
border-bottom: 1px solid #f5f5f5;
}
.info-item:last-child {
border-bottom: none;
}
.label {
font-size: 14px;
color: #666;
}
.value {
font-size: 14px;
color: #333;
font-weight: 500;
}
.address-card {
background: #f8f9fa;
padding: 12px;
border-radius: 8px;
}
.address-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
.recipient {
font-size: 14px;
font-weight: 500;
color: #333;
}
.phone {
font-size: 14px;
color: #666;
}
.address-detail {
font-size: 14px;
color: #666;
line-height: 1.4;
}
.product-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.product-item {
display: flex;
align-items: center;
gap: 12px;
padding: 12px;
background: #f8f9fa;
border-radius: 8px;
}
.product-image {
width: 60px;
height: 60px;
object-fit: cover;
border-radius: 6px;
}
.product-info {
flex: 1;
}
.product-name {
font-size: 14px;
font-weight: 500;
color: #333;
margin-bottom: 4px;
}
.product-spec {
font-size: 12px;
color: #999;
margin-bottom: 4px;
}
.product-price {
font-size: 12px;
color: #666;
}
.product-total {
font-size: 14px;
font-weight: 500;
color: #ff4757;
}
.cost-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 0;
}
.cost-item.total {
border-top: 1px solid #eee;
margin-top: 8px;
padding-top: 12px;
font-weight: 500;
}
.cost-item.total .value {
color: #ff4757;
font-size: 16px;
}
.bottom-actions {
padding: 16px;
background: white;
border-top: 1px solid #eee;
display: flex;
gap: 12px;
}
.cancel-btn {
flex: 1;
height: 48px;
border: 1px solid #ddd;
color: #666;
background: white;
border-radius: 24px;
font-size: 16px;
}
.pay-btn {
flex: 1;
height: 48px;
background: #ffae00;
border: none;
border-radius: 24px;
font-size: 16px;
font-weight: 500;
}
.pay-btn:hover {
background: #e69900;
}
</style>