|
@@ -1,15 +1,15 @@
|
|
|
'use client';
|
|
'use client';
|
|
|
|
|
|
|
|
-import { useEffect, useState, useRef } from 'react';
|
|
|
|
|
|
|
+import { useEffect, useState, useRef, Suspense } from 'react'; // 1. 引入 Suspense
|
|
|
import { useSearchParams, useRouter } from 'next/navigation';
|
|
import { useSearchParams, useRouter } from 'next/navigation';
|
|
|
import api from '@/lib/api';
|
|
import api from '@/lib/api';
|
|
|
import { Loader2, CheckCircle, XCircle, ArrowLeft } from 'lucide-react';
|
|
import { Loader2, CheckCircle, XCircle, ArrowLeft } from 'lucide-react';
|
|
|
|
|
|
|
|
-export default function PaymentConfirmPage() {
|
|
|
|
|
|
|
+// 2. 将原来的主要逻辑提取到一个单独的组件中 (非 default export)
|
|
|
|
|
+function PaymentConfirmContent() {
|
|
|
const searchParams = useSearchParams();
|
|
const searchParams = useSearchParams();
|
|
|
const router = useRouter();
|
|
const router = useRouter();
|
|
|
|
|
|
|
|
- // 防止 React StrictMode 下执行两次
|
|
|
|
|
const hasFetched = useRef(false);
|
|
const hasFetched = useRef(false);
|
|
|
|
|
|
|
|
const [status, setStatus] = useState<'loading' | 'success' | 'error'>('loading');
|
|
const [status, setStatus] = useState<'loading' | 'success' | 'error'>('loading');
|
|
@@ -28,13 +28,11 @@ export default function PaymentConfirmPage() {
|
|
|
|
|
|
|
|
setPaymentId(pid);
|
|
setPaymentId(pid);
|
|
|
|
|
|
|
|
- // 核心逻辑:调用确认接口
|
|
|
|
|
const confirmPayment = async () => {
|
|
const confirmPayment = async () => {
|
|
|
if (hasFetched.current) return;
|
|
if (hasFetched.current) return;
|
|
|
hasFetched.current = true;
|
|
hasFetched.current = true;
|
|
|
|
|
|
|
|
try {
|
|
try {
|
|
|
- // API: GET /api/vas/payment/confirm?payment_id=1&token=1
|
|
|
|
|
await api.get('/api/vas/payment/confirm', {
|
|
await api.get('/api/vas/payment/confirm', {
|
|
|
params: {
|
|
params: {
|
|
|
payment_id: pid,
|
|
payment_id: pid,
|
|
@@ -47,7 +45,6 @@ export default function PaymentConfirmPage() {
|
|
|
} catch (error: any) {
|
|
} catch (error: any) {
|
|
|
console.error(error);
|
|
console.error(error);
|
|
|
setStatus('error');
|
|
setStatus('error');
|
|
|
- // 获取后端返回的错误信息
|
|
|
|
|
const errorMsg = error.response?.data?.message || error.message || '未知错误';
|
|
const errorMsg = error.response?.data?.message || error.message || '未知错误';
|
|
|
setMessage(`确认失败: ${errorMsg}`);
|
|
setMessage(`确认失败: ${errorMsg}`);
|
|
|
}
|
|
}
|
|
@@ -56,76 +53,93 @@ export default function PaymentConfirmPage() {
|
|
|
confirmPayment();
|
|
confirmPayment();
|
|
|
}, [searchParams]);
|
|
}, [searchParams]);
|
|
|
|
|
|
|
|
- // 渲染不同的状态 UI
|
|
|
|
|
return (
|
|
return (
|
|
|
- <div className="min-h-screen bg-slate-50 flex flex-col items-center justify-center p-4">
|
|
|
|
|
- <div className="bg-white rounded-2xl shadow-xl w-full max-w-md overflow-hidden border border-slate-100">
|
|
|
|
|
-
|
|
|
|
|
- {/* 顶部装饰条 */}
|
|
|
|
|
- <div className={`h-2 w-full ${
|
|
|
|
|
- status === 'loading' ? 'bg-blue-500 animate-pulse' :
|
|
|
|
|
- status === 'success' ? 'bg-green-500' : 'bg-red-500'
|
|
|
|
|
- }`} />
|
|
|
|
|
-
|
|
|
|
|
- <div className="p-8 text-center">
|
|
|
|
|
- {/* 图标状态 */}
|
|
|
|
|
- <div className="flex justify-center mb-6">
|
|
|
|
|
- {status === 'loading' && (
|
|
|
|
|
- <div className="w-20 h-20 bg-blue-50 rounded-full flex items-center justify-center">
|
|
|
|
|
- <Loader2 className="w-10 h-10 text-blue-600 animate-spin" />
|
|
|
|
|
- </div>
|
|
|
|
|
- )}
|
|
|
|
|
- {status === 'success' && (
|
|
|
|
|
- <div className="w-20 h-20 bg-green-50 rounded-full flex items-center justify-center animate-in zoom-in duration-300">
|
|
|
|
|
- <CheckCircle className="w-10 h-10 text-green-600" />
|
|
|
|
|
- </div>
|
|
|
|
|
- )}
|
|
|
|
|
- {status === 'error' && (
|
|
|
|
|
- <div className="w-20 h-20 bg-red-50 rounded-full flex items-center justify-center animate-in zoom-in duration-300">
|
|
|
|
|
- <XCircle className="w-10 h-10 text-red-600" />
|
|
|
|
|
- </div>
|
|
|
|
|
- )}
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- {/* 标题与描述 */}
|
|
|
|
|
- <h1 className="text-xl font-bold text-slate-900 mb-2">
|
|
|
|
|
- {status === 'loading' ? '处理中...' :
|
|
|
|
|
- status === 'success' ? '操作成功' : '操作失败'}
|
|
|
|
|
- </h1>
|
|
|
|
|
-
|
|
|
|
|
- <p className={`text-sm mb-6 leading-relaxed ${
|
|
|
|
|
- status === 'error' ? 'text-red-500' : 'text-slate-500'
|
|
|
|
|
- }`}>
|
|
|
|
|
- {message}
|
|
|
|
|
- </p>
|
|
|
|
|
-
|
|
|
|
|
- {/* 详情信息 (仅在非加载状态显示) */}
|
|
|
|
|
- {status !== 'loading' && paymentId && (
|
|
|
|
|
- <div className="bg-slate-50 rounded-lg p-3 mb-6 text-xs text-slate-400 font-mono">
|
|
|
|
|
- Payment ID: {paymentId}
|
|
|
|
|
|
|
+ <div className="bg-white rounded-2xl shadow-xl w-full max-w-md overflow-hidden border border-slate-100">
|
|
|
|
|
+ {/* 顶部装饰条 */}
|
|
|
|
|
+ <div className={`h-2 w-full ${
|
|
|
|
|
+ status === 'loading' ? 'bg-blue-500 animate-pulse' :
|
|
|
|
|
+ status === 'success' ? 'bg-green-500' : 'bg-red-500'
|
|
|
|
|
+ }`} />
|
|
|
|
|
+
|
|
|
|
|
+ <div className="p-8 text-center">
|
|
|
|
|
+ {/* 图标状态 */}
|
|
|
|
|
+ <div className="flex justify-center mb-6">
|
|
|
|
|
+ {status === 'loading' && (
|
|
|
|
|
+ <div className="w-20 h-20 bg-blue-50 rounded-full flex items-center justify-center">
|
|
|
|
|
+ <Loader2 className="w-10 h-10 text-blue-600 animate-spin" />
|
|
|
</div>
|
|
</div>
|
|
|
)}
|
|
)}
|
|
|
|
|
+ {status === 'success' && (
|
|
|
|
|
+ <div className="w-20 h-20 bg-green-50 rounded-full flex items-center justify-center animate-in zoom-in duration-300">
|
|
|
|
|
+ <CheckCircle className="w-10 h-10 text-green-600" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ )}
|
|
|
|
|
+ {status === 'error' && (
|
|
|
|
|
+ <div className="w-20 h-20 bg-red-50 rounded-full flex items-center justify-center animate-in zoom-in duration-300">
|
|
|
|
|
+ <XCircle className="w-10 h-10 text-red-600" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ )}
|
|
|
|
|
+ </div>
|
|
|
|
|
|
|
|
- {/* 底部按钮 */}
|
|
|
|
|
- <div className="space-y-3">
|
|
|
|
|
|
|
+ {/* 标题与描述 */}
|
|
|
|
|
+ <h1 className="text-xl font-bold text-slate-900 mb-2">
|
|
|
|
|
+ {status === 'loading' ? '处理中...' :
|
|
|
|
|
+ status === 'success' ? '操作成功' : '操作失败'}
|
|
|
|
|
+ </h1>
|
|
|
|
|
+
|
|
|
|
|
+ <p className={`text-sm mb-6 leading-relaxed ${
|
|
|
|
|
+ status === 'error' ? 'text-red-500' : 'text-slate-500'
|
|
|
|
|
+ }`}>
|
|
|
|
|
+ {message}
|
|
|
|
|
+ </p>
|
|
|
|
|
+
|
|
|
|
|
+ {/* 详情信息 */}
|
|
|
|
|
+ {status !== 'loading' && paymentId && (
|
|
|
|
|
+ <div className="bg-slate-50 rounded-lg p-3 mb-6 text-xs text-slate-400 font-mono">
|
|
|
|
|
+ Payment ID: {paymentId}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ )}
|
|
|
|
|
+
|
|
|
|
|
+ {/* 底部按钮 */}
|
|
|
|
|
+ <div className="space-y-3">
|
|
|
|
|
+ <button
|
|
|
|
|
+ onClick={() => router.push('/admin/orders')}
|
|
|
|
|
+ className="w-full flex items-center justify-center gap-2 bg-slate-900 text-white py-2.5 rounded-lg text-sm font-medium hover:bg-slate-800 transition shadow-sm"
|
|
|
|
|
+ >
|
|
|
|
|
+ <ArrowLeft size={16} /> 返回订单管理
|
|
|
|
|
+ </button>
|
|
|
|
|
+
|
|
|
|
|
+ {status === 'error' && (
|
|
|
<button
|
|
<button
|
|
|
- onClick={() => router.push('/admin/orders')}
|
|
|
|
|
- className="w-full flex items-center justify-center gap-2 bg-slate-900 text-white py-2.5 rounded-lg text-sm font-medium hover:bg-slate-800 transition shadow-sm"
|
|
|
|
|
|
|
+ onClick={() => window.location.reload()}
|
|
|
|
|
+ className="w-full py-2.5 text-slate-600 text-sm hover:bg-slate-50 rounded-lg transition"
|
|
|
>
|
|
>
|
|
|
- <ArrowLeft size={16} /> 返回订单管理
|
|
|
|
|
|
|
+ 重试
|
|
|
</button>
|
|
</button>
|
|
|
-
|
|
|
|
|
- {status === 'error' && (
|
|
|
|
|
- <button
|
|
|
|
|
- onClick={() => window.location.reload()}
|
|
|
|
|
- className="w-full py-2.5 text-slate-600 text-sm hover:bg-slate-50 rounded-lg transition"
|
|
|
|
|
- >
|
|
|
|
|
- 重试
|
|
|
|
|
- </button>
|
|
|
|
|
- )}
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
+ )}
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ );
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 3. 定义一个 Loading Fallback 组件 (在 Suspense 加载时显示)
|
|
|
|
|
+function PageFallback() {
|
|
|
|
|
+ return (
|
|
|
|
|
+ <div className="bg-white rounded-2xl shadow-xl w-full max-w-md p-12 flex flex-col items-center justify-center">
|
|
|
|
|
+ <Loader2 className="w-10 h-10 text-blue-600 animate-spin mb-4" />
|
|
|
|
|
+ <p className="text-slate-500 text-sm">正在加载支付信息...</p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ );
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 4. 默认导出主页面组件,包裹 Suspense
|
|
|
|
|
+export default function PaymentConfirmPage() {
|
|
|
|
|
+ return (
|
|
|
|
|
+ <div className="min-h-screen bg-slate-50 flex flex-col items-center justify-center p-4">
|
|
|
|
|
+ <Suspense fallback={<PageFallback />}>
|
|
|
|
|
+ <PaymentConfirmContent />
|
|
|
|
|
+ </Suspense>
|
|
|
|
|
|
|
|
<p className="mt-8 text-xs text-slate-400">
|
|
<p className="mt-8 text-xs text-slate-400">
|
|
|
Visafly Admin System © {new Date().getFullYear()}
|
|
Visafly Admin System © {new Date().getFullYear()}
|