拆镜
This commit is contained in:
@@ -167,7 +167,7 @@ const AppLayout: React.FC = () => {
|
||||
const countdownTimerRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
||||
const currentOrderNoRef = useRef<string | null>(null);
|
||||
const [enabledMethods, setEnabledMethods] = useState<{ alipay: boolean; wechat: boolean }>({ alipay: false, wechat: false });
|
||||
|
||||
|
||||
// LocalStorage keys
|
||||
const PENDING_ORDER_KEY = 'pending_payment_order';
|
||||
|
||||
@@ -177,12 +177,12 @@ const AppLayout: React.FC = () => {
|
||||
getSiteInfo().then(info => {
|
||||
const name = info.siteName || '民众智创';
|
||||
const logo = info.siteLogo || '';
|
||||
|
||||
|
||||
if (name !== siteName) {
|
||||
setSiteName(name);
|
||||
document.title = name;
|
||||
}
|
||||
|
||||
|
||||
if (logo && logo !== siteLogo) {
|
||||
setSiteLogo(logo);
|
||||
let faviconLink = document.querySelector('link[rel="icon"]') as HTMLLinkElement;
|
||||
@@ -194,15 +194,15 @@ const AppLayout: React.FC = () => {
|
||||
faviconLink.href = logo;
|
||||
faviconLink.type = 'image/png';
|
||||
}
|
||||
|
||||
|
||||
localStorage.setItem('siteInfo', JSON.stringify({ siteName: name, siteLogo: logo }));
|
||||
}).catch(() => {});
|
||||
}).catch(() => { });
|
||||
}, []);
|
||||
|
||||
const loadUnreadCount = () => {
|
||||
getUnreadCount().then(count => {
|
||||
setUnreadCount(count);
|
||||
}).catch(() => {});
|
||||
}).catch(() => { });
|
||||
};
|
||||
|
||||
// 检查并恢复待处理的支付订单
|
||||
@@ -229,7 +229,7 @@ const AppLayout: React.FC = () => {
|
||||
const timeoutSeconds = savedOrder.timeoutSeconds || 180;
|
||||
const elapsedSeconds = Math.floor((now - createdAt) / 1000);
|
||||
const remainingSeconds = Math.max(0, timeoutSeconds - elapsedSeconds);
|
||||
|
||||
|
||||
if (remainingSeconds > 0) {
|
||||
setQrCodeModalOpen(true);
|
||||
startPolling(savedOrder.orderNo, remainingSeconds);
|
||||
@@ -252,7 +252,7 @@ const AppLayout: React.FC = () => {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
checkPendingOrder();
|
||||
}, []);
|
||||
|
||||
@@ -274,16 +274,16 @@ const AppLayout: React.FC = () => {
|
||||
});
|
||||
}
|
||||
setMenuItems(items);
|
||||
}).catch(() => {});
|
||||
}).catch(() => { });
|
||||
getRechargePackages().then(data => {
|
||||
setRechargeOptions(data.filter((p: any) => p.is_active !== false && p.isActive !== false));
|
||||
}).catch(() => {});
|
||||
}).catch(() => { });
|
||||
getPaymentMethods().then(data => {
|
||||
setEnabledMethods(data);
|
||||
// Auto-select the first enabled method
|
||||
if (data.alipay) setPaymentMethod('alipay');
|
||||
else if (data.wechat) setPaymentMethod('wechat');
|
||||
}).catch(() => {});
|
||||
}).catch(() => { });
|
||||
loadUnreadCount();
|
||||
}, [user]);
|
||||
|
||||
@@ -344,7 +344,7 @@ const AppLayout: React.FC = () => {
|
||||
const startPolling = useCallback((orderNo: string, timeoutSeconds: number = 180) => {
|
||||
stopPolling();
|
||||
setCountdown(timeoutSeconds);
|
||||
|
||||
|
||||
// 订单状态轮询(每2秒查询一次,只查询当前订单
|
||||
const pollingTimer = setInterval(async () => {
|
||||
try {
|
||||
@@ -368,7 +368,7 @@ const AppLayout: React.FC = () => {
|
||||
}
|
||||
}, 2000);
|
||||
pollingTimerRef.current = pollingTimer;
|
||||
|
||||
|
||||
// 倒计时
|
||||
const countdownTimer = setInterval(() => {
|
||||
setCountdown(prev => {
|
||||
@@ -376,7 +376,7 @@ const AppLayout: React.FC = () => {
|
||||
// 超时自动取消
|
||||
stopPolling();
|
||||
if (currentOrderNoRef.current) {
|
||||
cancelPaymentOrder(currentOrderNoRef.current).catch(() => {});
|
||||
cancelPaymentOrder(currentOrderNoRef.current).catch(() => { });
|
||||
currentOrderNoRef.current = null;
|
||||
}
|
||||
localStorage.removeItem(PENDING_ORDER_KEY);
|
||||
@@ -474,8 +474,8 @@ const AppLayout: React.FC = () => {
|
||||
background: isActive ? 'linear-gradient(135deg, rgba(99, 102, 241, 0.1) 0%, rgba(139, 92, 246, 0.08) 100%)' : 'transparent',
|
||||
transition: 'all 0.2s ease',
|
||||
}}>
|
||||
<span style={{
|
||||
fontSize: depth > 0 ? 14 : 16,
|
||||
<span style={{
|
||||
fontSize: depth > 0 ? 14 : 16,
|
||||
flexShrink: 0,
|
||||
color: isActive ? '#6366f1' : '#64748b',
|
||||
}}>{menuIcon}</span>
|
||||
@@ -521,13 +521,13 @@ const AppLayout: React.FC = () => {
|
||||
boxShadow: '0 4px 16px rgba(99, 102, 241, 0.4)',
|
||||
transition: 'all 0.3s ease',
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.background = 'linear-gradient(135deg, #4f46e5 0%, #7c3aed 100%)';
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.background = 'linear-gradient(135deg, #4f46e5 0%, #7c3aed 100%)';
|
||||
e.currentTarget.style.transform = 'translateY(-1px)';
|
||||
e.currentTarget.style.boxShadow = '0 6px 20px rgba(99, 102, 241, 0.5)';
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.background = 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)';
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.background = 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)';
|
||||
e.currentTarget.style.transform = 'translateY(0)';
|
||||
e.currentTarget.style.boxShadow = '0 4px 16px rgba(99, 102, 241, 0.4)';
|
||||
}}
|
||||
@@ -544,7 +544,7 @@ const AppLayout: React.FC = () => {
|
||||
justifyContent: 'flex-start',
|
||||
gap: 12,
|
||||
padding: '10px 14px',
|
||||
borderRadius: 14, cursor: 'pointer',
|
||||
borderRadius: 14, cursor: 'pointer',
|
||||
transition: 'all 0.25s ease',
|
||||
background: 'linear-gradient(135deg, rgba(248, 250, 252, 0.9) 0%, rgba(241, 245, 249, 0.9) 100%)',
|
||||
boxShadow: '0 2px 12px rgba(0, 0, 0, 0.04)',
|
||||
@@ -559,8 +559,8 @@ const AppLayout: React.FC = () => {
|
||||
}}
|
||||
>
|
||||
<Avatar size={36} icon={<UserOutlined />}
|
||||
style={{
|
||||
background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)',
|
||||
style={{
|
||||
background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)',
|
||||
flexShrink: 0,
|
||||
boxShadow: '0 4px 12px rgba(99, 102, 241, 0.3)',
|
||||
}} />
|
||||
@@ -568,9 +568,9 @@ const AppLayout: React.FC = () => {
|
||||
<div style={{ color: '#1e293b', fontSize: 14, fontWeight: 600, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', letterSpacing: -0.01 }}>
|
||||
{user?.username}
|
||||
</div>
|
||||
<div style={{
|
||||
color: '#6366f1',
|
||||
fontSize: 12,
|
||||
<div style={{
|
||||
color: '#6366f1',
|
||||
fontSize: 12,
|
||||
fontWeight: 500,
|
||||
letterSpacing: 0,
|
||||
background: 'rgba(99, 102, 241, 0.08)',
|
||||
@@ -586,18 +586,21 @@ const AppLayout: React.FC = () => {
|
||||
|
||||
{/* Main Content */}
|
||||
<div className="desktop-content" style={{
|
||||
marginLeft: sidebarW + 48,
|
||||
marginLeft: sidebarW + 48,
|
||||
marginRight: 32,
|
||||
marginTop: 16,
|
||||
marginBottom: 16,
|
||||
flex: 1,
|
||||
minHeight: 'calc(100vh - 32px)',
|
||||
flex: 1,
|
||||
// height: '100%',
|
||||
minHeight: 'calc(100vh - 32px)',
|
||||
background: 'transparent',
|
||||
padding: 0,
|
||||
padding: 0,
|
||||
transition: 'margin-left 0.25s ease',
|
||||
}}>
|
||||
<div style={{
|
||||
background: '#ffffff',
|
||||
boxSizing: 'border-box',
|
||||
height: '100%',
|
||||
background: '#ffffffff',
|
||||
borderRadius: '20px',
|
||||
boxShadow: '0 4px 32px rgba(0, 0, 0, 0.06), 0 1px 8px rgba(0, 0, 0, 0.04)',
|
||||
minHeight: '100%',
|
||||
@@ -672,33 +675,33 @@ const AppLayout: React.FC = () => {
|
||||
const g = GRADIENTS[idx % GRADIENTS.length];
|
||||
const totalCredits = (opt.credits || 0) + (opt.bonus_credits || opt.bonusCredits || 0);
|
||||
return (
|
||||
<div key={opt.id} onClick={() => setSelectedPlan(opt.id)} style={{
|
||||
flex: '1 1 45%', minWidth: 200, borderRadius: 16, padding: '20px 16px',
|
||||
background: selectedPlan === opt.id ? 'rgba(99,102,241,0.04)' : '#fafbff',
|
||||
border: selectedPlan === opt.id ? '2px solid #6366f1' : '1px solid #f0f0f5',
|
||||
cursor: 'pointer', position: 'relative', transition: 'all 0.2s',
|
||||
}}>
|
||||
{opt.description && (
|
||||
<Tag color="purple" style={{ position: 'absolute', top: -10, left: '50%', transform: 'translateX(-50%)', borderRadius: 8, fontSize: 11 }}>{opt.description}</Tag>
|
||||
)}
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
||||
<div style={{
|
||||
width: 44, height: 44, borderRadius: 12, flexShrink: 0,
|
||||
background: g.gradient, display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
fontSize: 18, color: '#fff', boxShadow: `0 6px 16px ${g.shadow}`,
|
||||
}}>{g.icon}</div>
|
||||
<div style={{ flex: 1 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'baseline', gap: 6 }}>
|
||||
<Typography.Text strong style={{ fontSize: 15 }}>{opt.name}</Typography.Text>
|
||||
<Typography.Text style={{ fontSize: 12, color: '#94a3b8' }}>{totalCredits.toLocaleString()} 积分</Typography.Text>
|
||||
</div>
|
||||
<div key={opt.id} onClick={() => setSelectedPlan(opt.id)} style={{
|
||||
flex: '1 1 45%', minWidth: 200, borderRadius: 16, padding: '20px 16px',
|
||||
background: selectedPlan === opt.id ? 'rgba(99,102,241,0.04)' : '#fafbff',
|
||||
border: selectedPlan === opt.id ? '2px solid #6366f1' : '1px solid #f0f0f5',
|
||||
cursor: 'pointer', position: 'relative', transition: 'all 0.2s',
|
||||
}}>
|
||||
{opt.description && (
|
||||
<Tag color="purple" style={{ position: 'absolute', top: -10, left: '50%', transform: 'translateX(-50%)', borderRadius: 8, fontSize: 11 }}>{opt.description}</Tag>
|
||||
)}
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
||||
<div style={{
|
||||
fontSize: 22, fontWeight: 800, marginTop: 4,
|
||||
background: g.gradient, WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent',
|
||||
}}>¥{opt.price}</div>
|
||||
width: 44, height: 44, borderRadius: 12, flexShrink: 0,
|
||||
background: g.gradient, display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
fontSize: 18, color: '#fff', boxShadow: `0 6px 16px ${g.shadow}`,
|
||||
}}>{g.icon}</div>
|
||||
<div style={{ flex: 1 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'baseline', gap: 6 }}>
|
||||
<Typography.Text strong style={{ fontSize: 15 }}>{opt.name}</Typography.Text>
|
||||
<Typography.Text style={{ fontSize: 12, color: '#94a3b8' }}>{totalCredits.toLocaleString()} 积分</Typography.Text>
|
||||
</div>
|
||||
<div style={{
|
||||
fontSize: 22, fontWeight: 800, marginTop: 4,
|
||||
background: g.gradient, WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent',
|
||||
}}>¥{opt.price}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
@@ -763,7 +766,7 @@ const AppLayout: React.FC = () => {
|
||||
setRechargeModalOpen(false);
|
||||
setQrCodeModalOpen(true);
|
||||
currentOrderNoRef.current = order.orderNo;
|
||||
|
||||
|
||||
// 保存到 localStorage
|
||||
localStorage.setItem(PENDING_ORDER_KEY, JSON.stringify({
|
||||
orderNo: order.orderNo,
|
||||
@@ -774,7 +777,7 @@ const AppLayout: React.FC = () => {
|
||||
createdAt: order.createdAt || new Date().toISOString(),
|
||||
timeoutSeconds: 180,
|
||||
}));
|
||||
|
||||
|
||||
// Start polling for payment status
|
||||
startPolling(order.orderNo);
|
||||
} else {
|
||||
@@ -808,7 +811,7 @@ const AppLayout: React.FC = () => {
|
||||
stopPolling();
|
||||
// Mark order as cancelled if it's still pending
|
||||
if (currentOrderNoRef.current) {
|
||||
try { await cancelPaymentOrder(currentOrderNoRef.current); } catch {}
|
||||
try { await cancelPaymentOrder(currentOrderNoRef.current); } catch { }
|
||||
currentOrderNoRef.current = null;
|
||||
}
|
||||
localStorage.removeItem(PENDING_ORDER_KEY);
|
||||
@@ -934,7 +937,7 @@ const AppLayout: React.FC = () => {
|
||||
onClick={async () => {
|
||||
stopPolling();
|
||||
if (currentOrderNoRef.current) {
|
||||
try { await cancelPaymentOrder(currentOrderNoRef.current); } catch {}
|
||||
try { await cancelPaymentOrder(currentOrderNoRef.current); } catch { }
|
||||
currentOrderNoRef.current = null;
|
||||
}
|
||||
localStorage.removeItem(PENDING_ORDER_KEY);
|
||||
|
||||
Reference in New Issue
Block a user