You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

393 lines
8.9 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<view class="container">
<!-- 支付结果显示 -->
<view class="result-section">
<!-- 加载中状态 -->
<view v-if="paymentStatus === 'loading'" class="status-loading">
<view class="loading-icon">
<uv-loading-icon mode="circle" color="#4285f4" size="60"></uv-loading-icon>
</view>
<view class="status-title">支付处理中...</view>
<view class="status-desc">请稍候,正在确认支付结果</view>
<view class="countdown-text">{{ countdownText }}</view>
</view>
<!-- 支付成功 -->
<view v-else-if="paymentStatus === 'success'" class="status-success">
<view class="success-icon">
<uv-icon name="checkmark-circle-fill" color="#52c41a" size="80"></uv-icon>
</view>
<view class="status-title">支付成功</view>
<view class="status-desc">恭喜您,会员服务已开通</view>
</view>
<!-- 支付失败 -->
<view v-else-if="paymentStatus === 'failed'" class="status-failed">
<view class="failed-icon">
<uv-icon name="close-circle-fill" color="#ff4d4f" size="80"></uv-icon>
</view>
<view class="status-title">支付失败</view>
<view class="status-desc">{{ failedReason || '支付过程中出现问题,请重试' }}</view>
</view>
<!-- 支付取消 -->
<view v-else-if="paymentStatus === 'cancelled'" class="status-cancelled">
<view class="cancelled-icon">
<uv-icon name="info-circle-fill" color="#faad14" size="80"></uv-icon>
</view>
<view class="status-title">支付已取消</view>
<view class="status-desc">您已取消本次支付</view>
</view>
<!-- 超时状态 -->
<view v-else-if="paymentStatus === 'timeout'" class="status-timeout">
<view class="timeout-icon">
<uv-icon name="clock-fill" color="#faad14" size="80"></uv-icon>
</view>
<view class="status-title">查询超时</view>
<view class="status-desc">支付结果查询超时,请手动查看订单状态</view>
</view>
</view>
<!-- 订单信息 -->
<view class="order-info" v-if="orderInfo">
<view class="info-title">订单信息</view>
<view class="info-item">
<text class="label">订单号:</text>
<text class="value">{{ orderNo }}</text>
</view>
<view class="info-item" v-if="orderInfo.plan_name">
<text class="label">商品:</text>
<text class="value">{{ orderInfo.plan_name }}</text>
</view>
<view class="info-item" v-if="orderInfo.amount">
<text class="label">金额:</text>
<text class="value price">¥{{ orderInfo.amount }}</text>
</view>
<view class="info-item" v-if="orderInfo.pay_time">
<text class="label">支付时间:</text>
<text class="value">{{ formatTime(orderInfo.pay_time) }}</text>
</view>
</view>
<!-- 底部按钮 -->
<view class="bottom-section">
<view class="button-group">
<!-- 支付成功时的按钮 -->
<template v-if="paymentStatus === 'success'">
<uv-button type="primary" @click="goToMine">
返回会员中心
</uv-button>
</template>
<!-- 支付失败时的按钮 -->
<template v-else-if="paymentStatus === 'failed'">
<uv-button type="primary" @click="goToMine" customStyle="margin-right: 12px; flex: 1;">
返回会员中心
</uv-button>
<!-- <uv-button type="primary" @click="retryPayment" customStyle="flex: 1;">
重新支付
</uv-button> -->
</template>
<!-- 其他状态的按钮 -->
<template v-else>
<uv-button type="primary" plain @click="goToMine">
</uv-button>
<uv-button type="primary" @click="checkOrderStatus" >
查看订单
</uv-button>
</template>
</view>
</view>
</view>
</template>
<script setup>
import {
ref,
onMounted,
onUnmounted
} from "vue";
import {
onLoad
} from "@dcloudio/uni-app";
import {
useUserStore
} from "@/store";
import RPC from "@/utils/request";
import dayjs from "dayjs";
const userStore = useUserStore();
// 页面参数
const orderNo = ref('');
// 响应式数据
const paymentStatus = ref('loading'); // loading, success, failed, cancelled, timeout
const failedReason = ref('');
const orderInfo = ref(null);
const countdown = ref(60); // 查询倒计时60秒
const countdownText = ref('');
// 定时器
let queryTimer = null;
let countdownTimer = null;
// 页面加载时获取参数
onLoad((options) => {
orderNo.value = options.order_no || '';
if (orderNo.value) {
startQueryPaymentStatus();
} else {
paymentStatus.value = 'failed';
failedReason.value = '订单号参数缺失';
}
});
// 开始查询支付状态
const startQueryPaymentStatus = () => {
// 开始倒计时
startCountdown();
// 立即查询一次
queryPaymentStatus();
// 设置定时查询每3秒查询一次
queryTimer = setInterval(() => {
queryPaymentStatus();
}, 3000);
};
// 查询支付状态
const queryPaymentStatus = async () => {
try {
const res = await RPC.post('/membership/order/status', {
order_no: orderNo.value
});
orderInfo.value = res;
// 根据订单状态设置页面状态
if (res.status === 'paid') {
paymentStatus.value = 'success';
clearTimers();
} else if (res.status === 'failed') {
paymentStatus.value = 'failed';
failedReason.value = res.fail_reason || '支付失败';
clearTimers();
} else if (res.status === 'canceled' || res.status === 'cancelled') {
paymentStatus.value = 'cancelled';
clearTimers();
} else if (res.status === 'created' || res.status === 'pending') {
// 继续等待
paymentStatus.value = 'loading';
}
} catch (error) {
console.error('查询支付状态失败:', error);
// 如果是网络错误,继续查询;如果是其他错误,显示失败
if (countdown.value <= 0) {
paymentStatus.value = 'timeout';
clearTimers();
}
}
};
// 开始倒计时
const startCountdown = () => {
updateCountdownText();
countdownTimer = setInterval(() => {
countdown.value--;
updateCountdownText();
if (countdown.value <= 0) {
paymentStatus.value = 'timeout';
clearTimers();
}
}, 1000);
};
// 更新倒计时文本
const updateCountdownText = () => {
countdownText.value = `查询超时倒计时:${countdown.value}`;
};
// 清除定时器
const clearTimers = () => {
if (queryTimer) {
clearInterval(queryTimer);
queryTimer = null;
}
if (countdownTimer) {
clearInterval(countdownTimer);
countdownTimer = null;
}
};
// 格式化时间
const formatTime = (timestamp) => {
if (!timestamp) return '';
return dayjs(timestamp * 1000).format('YYYY-MM-DD HH:mm:ss');
};
// 返回会员中心
const goToMine = () => {
uni.switchTab({
url: '/pages/mine/mine'
});
};
// 重新支付
const retryPayment = () => {
// 返回支付页面,重新发起支付
uni.navigateBack({
delta: 1
});
};
// 查看订单
const checkOrderStatus = () => {
uni.switchTab({
url: '/pages/order/order'
});
};
// 页面卸载时清除定时器
onUnmounted(() => {
clearTimers();
});
</script>
<style lang="scss" scoped>
.container {
font-size: 14px;
background-color: #f8f8f8;
min-height: 100vh;
padding: 20px 16px;
box-sizing: border-box;
}
.result-section {
background: white;
border-radius: 12px;
padding: 40px 20px;
text-align: center;
margin-bottom: 20px;
.loading-icon,
.success-icon,
.failed-icon,
.cancelled-icon,
.timeout-icon {
margin-bottom: 20px;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
}
.status-title {
font-size: 20px;
font-weight: bold;
color: #333;
margin-bottom: 8px;
}
.status-desc {
font-size: 14px;
color: #666;
margin-bottom: 16px;
}
.countdown-text {
font-size: 12px;
color: #999;
}
.status-success {
.status-title {
color: #52c41a;
}
}
.status-failed {
.status-title {
color: #ff4d4f;
}
}
.status-cancelled {
.status-title {
color: #faad14;
}
}
.status-timeout {
.status-title {
color: #faad14;
}
}
}
.order-info {
background: white;
border-radius: 12px;
padding: 16px;
margin-bottom: 20px;
.info-title {
font-size: 16px;
font-weight: 500;
color: #333;
margin-bottom: 16px;
}
.info-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 0;
border-bottom: 1px solid #f0f0f0;
&:last-child {
border-bottom: none;
}
.label {
font-size: 14px;
color: #666;
}
.value {
font-size: 14px;
color: #333;
&.price {
color: #ff3333;
font-weight: 500;
}
}
}
}
.bottom-section {
// position: fixed;
// bottom: 0;
// left: 0;
// right: 0;
background: white;
padding: 16px;
border-radius: 10px;
// border-top: 1px solid #e5e5e5;
/* #ifdef H5 */
// bottom: 50px;
/* H5环境下给tabBar留出空间 */
/* #endif */
.button-group {
display: flex;
gap: 30px;
}
}
</style>