弱网核销功能

This commit is contained in:
2026-07-12 12:00:55 +08:00
parent de01d36cdb
commit 06b1cb22e0
24 changed files with 1434 additions and 42 deletions
+49 -16
View File
@@ -1,6 +1,8 @@
import { useEffect, useState } from 'react';
import { useNavigate, useSearchParams } from 'react-router-dom';
import WeakNetFallbackPanel from '../components/WeakNetFallbackPanel';
import { request } from '../lib/api';
import { reportRedeemFailure } from '../lib/redeem-failure';
function formatAmount(n: number) {
return n.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
@@ -23,6 +25,8 @@ export default function RedeemConfirmPage() {
const [storeName, setStoreName] = useState('');
const [preview, setPreview] = useState<Preview | null>(null);
const [storeClosed, setStoreClosed] = useState(false);
const [failCount, setFailCount] = useState(0);
const [showWeakNet, setShowWeakNet] = useState(false);
useEffect(() => {
request<Record<string, unknown>>('SHOP_H5', '/shop/store')
@@ -52,9 +56,16 @@ export default function RedeemConfirmPage() {
body: JSON.stringify({ token }),
})
.then(setPreview)
.catch((e) => {
.catch(async (e) => {
setPreview(null);
setMsg(e instanceof Error ? e.message : '无法预览核销码');
const report = await reportRedeemFailure(token, 'preview', e);
if (report?.thresholdReached) {
setFailCount(report.failCount);
setShowWeakNet(true);
} else if (report) {
setFailCount(report.failCount);
}
});
}, [token]);
@@ -78,6 +89,13 @@ export default function RedeemConfirmPage() {
navigate('/redeem/success', { state: { result, storeName, user: preview?.user } });
} catch (e) {
setMsg(e instanceof Error ? e.message : '核销失败');
const report = await reportRedeemFailure(token, 'confirm', e);
if (report?.thresholdReached) {
setFailCount(report.failCount);
setShowWeakNet(true);
} else if (report) {
setFailCount(report.failCount);
}
} finally {
setLoading(false);
}
@@ -139,8 +157,10 @@ export default function RedeemConfirmPage() {
<div className="shop-redeem-details">
<div className="shop-redeem-detail-row">
<span></span>
<span>{token ? `${token.slice(-8)}` : '扫码后显示'}</span>
<span> ID</span>
<span style={{ wordBreak: 'break-all', maxWidth: '60%', textAlign: 'right' }}>
{token || '扫码后显示'}
</span>
</div>
<div className="shop-redeem-detail-row">
<span></span>
@@ -152,26 +172,39 @@ export default function RedeemConfirmPage() {
<span></span>
</div>
)}
{failCount > 0 && !showWeakNet && (
<div className="shop-redeem-detail-row">
<span></span>
<span>{failCount} / 5 </span>
</div>
)}
</div>
{msg && <p className="shop-redeem-error">{msg}</p>}
<button
type="button"
className={`shop-redeem-confirm-btn${loading ? ' success' : ''}`}
disabled={loading || storeClosed || !preview}
onClick={confirm}
>
<span className="material-symbols-outlined shop-fill-icon">
{loading ? 'sync' : 'check_circle'}
</span>
<span>{loading ? '正在核销...' : preview ? `确认核销 ¥${formatAmount(previewAmount)}` : '加载中…'}</span>
</button>
<p className="shop-redeem-hint"></p>
{!showWeakNet && (
<>
<button
type="button"
className={`shop-redeem-confirm-btn${loading ? ' success' : ''}`}
disabled={loading || storeClosed || !preview}
onClick={() => void confirm()}
>
<span className="material-symbols-outlined shop-fill-icon">
{loading ? 'sync' : 'check_circle'}
</span>
<span>{loading ? '正在核销...' : preview ? `确认核销 ¥${formatAmount(previewAmount)}` : '加载中…'}</span>
</button>
<p className="shop-redeem-hint"></p>
</>
)}
</div>
</section>
{showWeakNet && token && (
<WeakNetFallbackPanel redeemToken={token} failCount={failCount} />
)}
<div className="shop-redeem-ornament">
<span className="material-symbols-outlined" style={{ fontSize: 64, color: 'var(--color-heritage-red)' }}>
wine_bar