Files
tyc-webview-v2/src/views/PaymentResult.vue
2026-01-22 16:03:28 +08:00

530 lines
16 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="payment-result-container flex flex-col items-center p-6">
<!-- 加载动画验证支付结果时显示 -->
<div v-if="isLoading" class="w-full">
<div class="flex flex-col items-center justify-center py-10">
<van-loading size="48px" color="#1989fa" />
<p class="mt-4 text-gray-600 text-lg">正在处理支付结果...</p>
</div>
</div>
<!-- 支付结果展示 -->
<div v-else class="w-full">
<!-- 支付成功 -->
<div v-if="paymentStatus === 'paid'" class="success-result">
<div class="success-animation mb-6">
<van-icon name="checked" size="64" color="#07c160" />
<div class="success-ring"></div>
</div>
<h1 class="text-2xl font-bold text-center text-gray-800 mb-4">
支付成功
</h1>
<div class="payment-info bg-white rounded-lg shadow-md p-6 mb-6 w-full">
<div class="flex justify-between mb-4">
<span class="text-gray-600">订单编号</span>
<span class="text-gray-800">{{ orderNo }}</span>
</div>
<div class="flex justify-between">
<span class="text-gray-600">支付类型</span>
<span class="text-gray-800">{{
paymentType === "agent_vip"
? "代理会员"
: "查询服务"
}}</span>
</div>
</div>
<div v-if="paymentType === 'agent_vip'" class="text-center text-gray-600 mb-4">恭喜你成为高级代理会员享受更多权益</div>
<div class="action-buttons grid grid-cols-1 gap-4">
<van-button block type="primary" class="rounded-lg" @click="handleNavigation">
{{
paymentType === "agent_vip"
? "查看会员权益"
: "查看查询结果"
}}
</van-button>
</div>
</div>
<!-- 退款状态 -->
<div v-else-if="paymentStatus === 'refunded'" class="refund-result">
<div v-if="paymentType === 'query'" class="success-animation mb-6">
<van-icon name="checked" size="64" color="#07c160" />
<div class="success-ring"></div>
</div>
<div v-else class="info-animation mb-6">
<van-icon name="info-o" size="64" color="#1989fa" />
<div class="info-ring"></div>
</div>
<h1 class="text-2xl font-bold text-center text-gray-800 mb-4">
{{ paymentType === "query" ? "已处理" : "订单已退款" }}
</h1>
<div class="payment-info bg-white rounded-lg shadow-md p-6 mb-6 w-full">
<div class="flex justify-between mb-4">
<span class="text-gray-600">订单编号</span>
<span class="text-gray-800">{{ orderNo }}</span>
</div>
<div class="flex justify-between mb-4">
<span class="text-gray-600">支付类型</span>
<span class="text-gray-800">{{
paymentType === "agent_vip"
? "代理会员"
: "查询服务"
}}</span>
</div>
<div class="flex justify-between">
<span class="text-gray-600">订单状态</span>
<span class="text-blue-600">已退款</span>
</div>
</div>
<div v-if="paymentType === 'query'" class="action-buttons grid grid-cols-1 gap-4">
<van-button block type="primary" class="rounded-lg" @click="handleNavigation">
查看查询结果
</van-button>
</div>
<div v-else class="message-box p-4 bg-blue-50 rounded-lg mb-6">
<p class="text-center text-blue-800">
您的代理会员费用已退款如有疑问请联系客服
</p>
</div>
<div v-if="paymentType === 'agent_vip'" class="action-buttons grid grid-cols-1 gap-4">
<van-button block type="primary" class="rounded-lg" @click="contactService">
联系客服
</van-button>
</div>
</div>
<!-- 其他状态待支付失败关闭 -->
<div v-else class="other-result">
<div class="info-animation mb-6">
<van-icon :name="getStatusIcon" size="64" :color="getStatusColor" />
<div class="info-ring" :class="getRingClass"></div>
</div>
<h1 class="text-2xl font-bold text-center text-gray-800 mb-4">
{{ statusText }}
</h1>
<!-- 添加轮询状态提示 -->
<div v-if="paymentStatus === 'pending'" class="text-center text-gray-500 mb-4">
<p>正在等待支付结果请稍候...</p>
<p class="text-sm mt-1">
已等待
{{
Math.floor(
(pollingCount * getPollingInterval) / 1000
)
}}
</p>
</div>
<div class="payment-info bg-white rounded-lg shadow-md p-6 mb-6 w-full">
<div class="flex justify-between mb-4">
<span class="text-gray-600">订单编号</span>
<span class="text-gray-800">{{ orderNo }}</span>
</div>
<div v-if="!isApiError" class="flex justify-between mb-4">
<span class="text-gray-600">支付类型</span>
<span class="text-gray-800">{{
paymentType === "agent_vip"
? "代理会员"
: "查询服务"
}}</span>
</div>
<div class="flex justify-between">
<span class="text-gray-600">订单状态</span>
<span :class="getStatusTextClass">{{
statusText
}}</span>
</div>
</div>
<div class="message-box p-4 bg-blue-50 rounded-lg mb-6">
<p class="text-center" :class="getMessageClass">
{{ statusMessage }}
</p>
</div>
<div class="action-buttons grid grid-cols-2 gap-4">
<van-button block type="default" class="rounded-lg" @click="goHome">
返回首页
</van-button>
<van-button block type="primary" class="rounded-lg" @click="contactService">
联系客服
</van-button>
</div>
</div>
</div>
</div>
</template>
<script setup>
import {
ref,
computed,
onBeforeMount,
defineExpose,
onBeforeUnmount,
} from "vue";
import { useRoute, useRouter } from "vue-router";
import { useAgentStore } from "@/stores/agentStore";
import { useUserStore } from "@/stores/userStore";
const route = useRoute();
const router = useRouter();
const orderNo = ref("");
const agentStore = useAgentStore();
const userStore = useUserStore();
// 状态变量
const isLoading = ref(true);
const paymentResult = ref(null);
const paymentType = ref("");
const paymentStatus = ref("");
const isApiError = ref(false);
const pollingInterval = ref(null);
const pollingCount = ref(0);
const maxPollingCount = 30; // 最大轮询次数
const baseInterval = 2000; // 基础轮询间隔2秒
// 计算属性
const statusText = computed(() => {
if (isApiError.value) {
return "系统繁忙";
}
switch (paymentStatus.value) {
case "pending":
return "正在支付";
case "failed":
return "支付失败";
case "closed":
return "订单已关闭";
default:
return "处理中";
}
});
const statusMessage = computed(() => {
if (isApiError.value) {
return "系统正在维护或网络繁忙,请稍后再试,或联系客服确认订单状态。";
}
switch (paymentStatus.value) {
case "pending":
return "您的订单正在支付,请稍后";
case "failed":
return "支付未成功,您可以返回重新支付,或联系客服确认详情。";
case "closed":
return "订单已关闭,如有疑问请联系客服。";
default:
return "系统正在处理您的订单,如有疑问请联系客服。";
}
});
// 状态图标
const getStatusIcon = computed(() => {
if (isApiError.value) {
return "warning-o";
}
return paymentStatus.value === "pending" ? "clock-o" : "close";
});
// 状态颜色
const getStatusColor = computed(() => {
if (isApiError.value) {
return "#ff9800"; // 橙色警告
}
return paymentStatus.value === "pending" ? "#ff976a" : "#ee0a24";
});
// 环形样式类
const getRingClass = computed(() => {
if (isApiError.value) {
return "api-error-ring";
}
return {
"pending-ring": paymentStatus.value === "pending",
"failed-ring": paymentStatus.value === "failed",
"closed-ring": paymentStatus.value === "closed",
};
});
// 状态文本样式
const getStatusTextClass = computed(() => {
if (isApiError.value) {
return "text-amber-600";
}
return {
"text-orange-500": paymentStatus.value === "pending",
"text-red-500":
paymentStatus.value === "failed" ||
paymentStatus.value === "closed",
};
});
// 消息文本样式
const getMessageClass = computed(() => {
if (isApiError.value) {
return "text-amber-800";
}
return {
"text-orange-700": paymentStatus.value === "pending",
"text-red-700":
paymentStatus.value === "failed" ||
paymentStatus.value === "closed",
};
});
// 计算轮询间隔时间(渐进式增加)
const getPollingInterval = computed(() => {
// 每5次轮询增加1秒最大间隔10秒
const increment = Math.floor(pollingCount.value / 5);
return Math.min(baseInterval + increment * 1000, 10000);
});
// 检查支付状态
const checkPaymentStatus = async () => {
if (pollingCount.value >= maxPollingCount) {
// 超过最大轮询次数,停止轮询
stopPolling();
return;
}
try {
const { data, error } = await useApiFetch(`/pay/check`)
.post({
order_no: orderNo.value,
})
.json();
if (data.value && !error.value) {
paymentResult.value = data.value.data;
paymentType.value = data.value.data.type || "";
const newStatus = data.value.data.status || "";
// 状态发生变化时更新
if (paymentStatus.value !== newStatus) {
paymentStatus.value = newStatus;
// 对于查询类型,如果状态是已支付或已退款,直接跳转
if (
paymentType.value === "query" &&
(newStatus === "paid" || newStatus === "refunded")
) {
stopPolling();
router.replace({
path: "/report",
query: { orderNo: orderNo.value },
});
return;
}
// 如果状态不是 pending停止轮询
if (newStatus !== "pending") {
stopPolling();
}
}
} else {
console.error("API调用失败:", error.value);
// 不要立即停止轮询,继续尝试
}
} catch (err) {
console.error("验证支付状态失败:", err);
// 不要立即停止轮询,继续尝试
} finally {
pollingCount.value++;
isLoading.value = false;
}
};
// 开始轮询
const startPolling = () => {
if (pollingInterval.value) return;
pollingCount.value = 0;
const poll = () => {
checkPaymentStatus();
if (
paymentStatus.value === "pending" &&
pollingCount.value < maxPollingCount
) {
pollingInterval.value = setTimeout(poll, getPollingInterval.value);
}
};
poll();
};
// 停止轮询
const stopPolling = () => {
if (pollingInterval.value) {
clearTimeout(pollingInterval.value);
pollingInterval.value = null;
}
};
// 在组件挂载前验证支付结果
onBeforeMount(async () => {
const query = new URLSearchParams(window.location.search);
orderNo.value = query.get("out_trade_no");
if (!orderNo.value) {
orderNo.value = route.query.orderNo;
}
if (!orderNo.value) {
router.push("/");
return;
}
// 检测是否为 iOS 浏览器且没有上级页面
const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent);
const hasHistory = window.history.length > 1;
if (isIOS && !hasHistory) {
// iOS 浏览器且没有上级页面,关闭当前标签页
window.close();
return;
}
// 首次检查支付状态
await checkPaymentStatus();
// 如果状态是 pending开始轮询
if (paymentStatus.value === "pending") {
startPolling();
}
});
// 组件卸载前清理轮询
onBeforeUnmount(() => {
stopPolling();
});
// 处理导航逻辑
function handleNavigation() {
if (paymentType.value === "agent_vip") {
// 跳转到代理会员页面
router.replace("/agent");
agentStore.fetchAgentStatus();
userStore.fetchUserInfo();
} else {
// 跳转到查询结果页面
router.replace({
path: "/report",
query: { orderNo: orderNo.value },
});
}
}
// 返回首页
function goHome() {
router.replace("/");
}
// 联系客服
function contactService() {
// 可以替换为实际的客服联系逻辑,如打开聊天窗口或跳转到客服页面
window.location.href =
"https://work.weixin.qq.com/kfid/kfc5c19b2b93a5e73b9"; // 跳转到客服页面
}
// 暴露方法和数据供父组件或路由调用
defineExpose({
paymentResult,
paymentType,
paymentStatus,
handleNavigation,
stopPolling, // 暴露停止轮询方法
});
</script>
<style scoped>
.payment-result-container {
min-height: 80vh;
background-color: #f8f9fa;
}
.success-animation,
.info-animation {
position: relative;
display: flex;
justify-content: center;
align-items: center;
margin: 2rem auto;
}
.success-ring,
.info-ring,
.pending-ring,
.failed-ring,
.closed-ring,
.api-error-ring {
position: absolute;
width: 80px;
height: 80px;
border-radius: 50%;
animation: pulse 1.5s infinite;
}
.success-ring {
border: 2px solid #07c160;
}
.info-ring {
border: 2px solid #1989fa;
}
.pending-ring {
border: 2px solid #ff976a;
}
.failed-ring,
.closed-ring {
border: 2px solid #ee0a24;
}
.api-error-ring {
border: 2px solid #ff9800;
/* 橙色警告 */
}
@keyframes pulse {
0% {
transform: scale(0.95);
opacity: 0.8;
}
70% {
transform: scale(1.1);
opacity: 0.3;
}
100% {
transform: scale(0.95);
opacity: 0.8;
}
}
.success-result,
.refund-result,
.other-result {
animation: fadeIn 0.5s ease-in-out;
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
</style>