核销成功提示
This commit is contained in:
@@ -1,53 +1,128 @@
|
||||
import { useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
import PageHeader from '@dukang/shared-ui/PageHeader';
|
||||
import { request } from '../lib/api';
|
||||
|
||||
type RedeemRecord = {
|
||||
id: string;
|
||||
redeemNo: string;
|
||||
amount: number;
|
||||
storeId: string;
|
||||
storeName: string;
|
||||
createdAt: string;
|
||||
};
|
||||
|
||||
function formatMoney(amount: number) {
|
||||
return amount.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
||||
}
|
||||
|
||||
function StarRating({
|
||||
label,
|
||||
value,
|
||||
onChange,
|
||||
}: {
|
||||
label: string;
|
||||
value: number;
|
||||
onChange: (score: number) => void;
|
||||
}) {
|
||||
return (
|
||||
<div className="redeem-rating-row">
|
||||
<span className="redeem-rating-label">{label}</span>
|
||||
<div className="redeem-star-row" role="group" aria-label={label}>
|
||||
{[1, 2, 3, 4, 5].map((score) => (
|
||||
<button
|
||||
key={score}
|
||||
type="button"
|
||||
className={`redeem-star-btn${score <= value ? ' active' : ''}`}
|
||||
aria-label={`${score} 星`}
|
||||
onClick={() => onChange(score)}
|
||||
>
|
||||
★
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function RedeemSuccessPage() {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const [serviceScore, setServiceScore] = useState(5);
|
||||
const [envScore, setEnvScore] = useState(5);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const record = useMemo<RedeemRecord | null>(() => {
|
||||
const fromState = (location.state as { record?: RedeemRecord })?.record;
|
||||
if (fromState) return fromState;
|
||||
try {
|
||||
const cached = sessionStorage.getItem('lastRedeemResult');
|
||||
return cached ? (JSON.parse(cached) as RedeemRecord) : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}, [location.state]);
|
||||
|
||||
const amount = Number(record?.amount ?? 0);
|
||||
const storeName = record?.storeName || '门店';
|
||||
const redeemNo = record?.redeemNo || '—';
|
||||
const redeemedAt = record?.createdAt
|
||||
? new Date(record.createdAt).toLocaleString('zh-CN')
|
||||
: new Date().toLocaleString('zh-CN');
|
||||
|
||||
async function submit() {
|
||||
setLoading(true);
|
||||
try {
|
||||
const redeemRecordId = sessionStorage.getItem('lastRedeemRecordId');
|
||||
const redeemRecordId = record?.id || sessionStorage.getItem('lastRedeemRecordId');
|
||||
if (redeemRecordId) {
|
||||
await request('USER_H5', '/redeem/ratings', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
redeemRecordId,
|
||||
serviceScore,
|
||||
environmentScore: envScore,
|
||||
envScore,
|
||||
}),
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
/* preV1: allow navigate even if rating fails */
|
||||
/* 评价失败不阻塞返回 */
|
||||
} finally {
|
||||
setLoading(false);
|
||||
navigate('/benefit');
|
||||
sessionStorage.removeItem('lastRedeemRecordId');
|
||||
sessionStorage.removeItem('lastRedeemResult');
|
||||
navigate('/benefit', { replace: true });
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="page-no-tab redeem-success-page">
|
||||
<PageHeader title="核销成功" onBack={() => navigate('/benefit')} />
|
||||
<div style={{ textAlign: 'center', padding: '24px 0' }}>
|
||||
<PageHeader title="核销成功" onBack={() => navigate('/benefit', { replace: true })} />
|
||||
<div className="redeem-success-hero">
|
||||
<div className="success-icon">✓</div>
|
||||
<h2 className="headline-lg text-primary">核销成功</h2>
|
||||
<p className="text-variant body-md" style={{ marginTop: 8 }}>请为门店服务评分</p>
|
||||
<p className="redeem-success-amount">¥ {formatMoney(amount)}</p>
|
||||
<p className="text-variant body-md">已在 {storeName} 完成核销</p>
|
||||
</div>
|
||||
<div className="card">
|
||||
<div className="form-group">
|
||||
<label>服务评分 (1-5)</label>
|
||||
<input type="number" min={1} max={5} value={serviceScore} onChange={(e) => setServiceScore(Number(e.target.value))} />
|
||||
|
||||
<div className="card redeem-success-details">
|
||||
<div className="redeem-success-detail-row">
|
||||
<span>核销门店</span>
|
||||
<span>{storeName}</span>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>环境评分 (1-5)</label>
|
||||
<input type="number" min={1} max={5} value={envScore} onChange={(e) => setEnvScore(Number(e.target.value))} />
|
||||
<div className="redeem-success-detail-row">
|
||||
<span>核销时间</span>
|
||||
<span>{redeemedAt}</span>
|
||||
</div>
|
||||
<div className="redeem-success-detail-row">
|
||||
<span>核销单号</span>
|
||||
<span className="redeem-success-mono">{redeemNo}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="card redeem-success-rating">
|
||||
<p className="redeem-success-rating-title">为门店服务评分</p>
|
||||
<StarRating label="服务态度" value={serviceScore} onChange={setServiceScore} />
|
||||
<StarRating label="用餐环境" value={envScore} onChange={setEnvScore} />
|
||||
<button type="button" className="btn btn-primary btn-block" disabled={loading} onClick={submit}>
|
||||
{loading ? '提交中...' : '完成'}
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user