init
This commit is contained in:
@@ -0,0 +1,204 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
import BrandLogo from '@dukang/shared-ui/BrandLogo';
|
||||
import { request } from '../lib/api';
|
||||
|
||||
type BenefitSummary = {
|
||||
totalBalance: number;
|
||||
maxRedeemAmount: number;
|
||||
activeCouponCount: number;
|
||||
};
|
||||
|
||||
type CouponItem = {
|
||||
id: string;
|
||||
couponNo: string;
|
||||
totalAmount: number;
|
||||
usedAmount: number;
|
||||
balance: number;
|
||||
status: string;
|
||||
sourceProduct: string;
|
||||
};
|
||||
|
||||
function formatMoney(amount: number) {
|
||||
return amount.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
||||
}
|
||||
|
||||
function formatCouponNo(no: string) {
|
||||
const tail = no.replace(/^BC/i, '').slice(-6);
|
||||
return `NO. DK${tail}`;
|
||||
}
|
||||
|
||||
function usagePercent(coupon: CouponItem) {
|
||||
const total = Number(coupon.totalAmount);
|
||||
if (total <= 0) return 0;
|
||||
return Math.min(100, Math.round((Number(coupon.usedAmount) / total) * 100));
|
||||
}
|
||||
|
||||
export default function BenefitPage() {
|
||||
const navigate = useNavigate();
|
||||
const listRef = useRef<HTMLElement>(null);
|
||||
const [summary, setSummary] = useState<BenefitSummary | null>(null);
|
||||
const [coupons, setCoupons] = useState<CouponItem[]>([]);
|
||||
const [tab, setTab] = useState<'available' | 'history'>('available');
|
||||
|
||||
useEffect(() => {
|
||||
Promise.all([
|
||||
request<BenefitSummary>('USER_H5', '/benefit/summary'),
|
||||
request<CouponItem[]>('USER_H5', '/benefit/coupons'),
|
||||
])
|
||||
.then(([s, list]) => {
|
||||
setSummary(s);
|
||||
setCoupons(
|
||||
list.map((c) => ({
|
||||
id: String(c.id),
|
||||
couponNo: String(c.couponNo),
|
||||
totalAmount: Number(c.totalAmount),
|
||||
usedAmount: Number(c.usedAmount),
|
||||
balance: Number(c.balance),
|
||||
status: String(c.status),
|
||||
sourceProduct: String(c.sourceProduct),
|
||||
})),
|
||||
);
|
||||
})
|
||||
.catch(() => {});
|
||||
}, []);
|
||||
|
||||
const available = coupons.filter((c) => c.status === 'ACTIVE' && c.balance > 0);
|
||||
const history = coupons.filter((c) => c.status === 'USED_UP' || c.status === 'VOID');
|
||||
const visible = tab === 'available' ? available : history;
|
||||
|
||||
function scrollToList() {
|
||||
listRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="benefit-page">
|
||||
<header className="benefit-header">
|
||||
<button type="button" className="benefit-header-city">
|
||||
<span className="material-symbols-outlined">location_on</span>
|
||||
<span>郑州市</span>
|
||||
</button>
|
||||
<h1 className="app-page-title">好客权益</h1>
|
||||
<button
|
||||
type="button"
|
||||
className="benefit-header-btn"
|
||||
aria-label="通知"
|
||||
onClick={() => window.alert('preV1:消息通知即将开放')}
|
||||
>
|
||||
<span className="material-symbols-outlined">notifications</span>
|
||||
</button>
|
||||
</header>
|
||||
|
||||
<main className="benefit-main">
|
||||
<section className="benefit-hero">
|
||||
<div className="benefit-hero-top">
|
||||
<div>
|
||||
<p className="benefit-hero-label">当前好客权益余额</p>
|
||||
<div className="benefit-hero-amount">
|
||||
<span className="benefit-hero-symbol">¥</span>
|
||||
<span className="benefit-hero-value">
|
||||
{summary ? formatMoney(summary.totalBalance) : '--'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<BrandLogo className="benefit-hero-logo" />
|
||||
</div>
|
||||
<button type="button" className="benefit-hero-link" onClick={scrollToList}>
|
||||
<span>查看权益明细</span>
|
||||
<span className="material-symbols-outlined">chevron_right</span>
|
||||
</button>
|
||||
</section>
|
||||
|
||||
<section className="benefit-action">
|
||||
<button type="button" className="benefit-use-btn" onClick={() => navigate('/stores')}>
|
||||
<span className="material-symbols-outlined filled">storefront</span>
|
||||
去使用
|
||||
</button>
|
||||
</section>
|
||||
|
||||
<nav className="benefit-tabs" ref={listRef}>
|
||||
<button
|
||||
type="button"
|
||||
className={`benefit-tab${tab === 'available' ? ' active' : ''}`}
|
||||
onClick={() => setTab('available')}
|
||||
>
|
||||
待使用
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`benefit-tab${tab === 'history' ? ' active' : ''}`}
|
||||
onClick={() => setTab('history')}
|
||||
>
|
||||
已用完/已过期
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
{visible.length > 0 ? (
|
||||
<div className="benefit-list">
|
||||
{visible.map((c) => (
|
||||
<article key={c.id} className="benefit-card">
|
||||
<div className="benefit-card-inner">
|
||||
<div className="benefit-card-value">
|
||||
<span className="benefit-card-value-label">好客权益</span>
|
||||
<div className="benefit-card-value-amount">
|
||||
<span>¥</span>
|
||||
<span>{Math.round(c.totalAmount)}</span>
|
||||
</div>
|
||||
<span className="benefit-card-notch" aria-hidden />
|
||||
</div>
|
||||
<div className="benefit-card-body">
|
||||
<div className="benefit-card-main">
|
||||
<div className="benefit-card-title-row">
|
||||
<h3 className="benefit-card-title">{c.sourceProduct}</h3>
|
||||
<span className="benefit-card-badge">永久有效</span>
|
||||
</div>
|
||||
<p className="benefit-card-desc">适用于合作酒店餐饮消费,到店核销使用</p>
|
||||
<div className="benefit-card-progress-wrap">
|
||||
<div className="benefit-card-progress-labels">
|
||||
<span>已使用 ¥{formatMoney(c.usedAmount)}</span>
|
||||
<span>未使用 ¥{formatMoney(c.balance)}</span>
|
||||
</div>
|
||||
<div className="benefit-card-progress">
|
||||
<div
|
||||
className="benefit-card-progress-bar"
|
||||
style={{ width: `${usagePercent(c)}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="benefit-card-foot">
|
||||
<Link to={`/benefit/${c.id}`} className="benefit-card-no">
|
||||
{formatCouponNo(c.couponNo)}
|
||||
</Link>
|
||||
<button
|
||||
type="button"
|
||||
className="benefit-card-redeem"
|
||||
onClick={() => navigate('/redeem')}
|
||||
>
|
||||
立即核销
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="benefit-empty">
|
||||
{tab === 'available' ? (
|
||||
<>
|
||||
<span className="material-symbols-outlined">card_giftcard</span>
|
||||
<p>暂无可用权益,购酒后自动发放</p>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<span className="material-symbols-outlined">history_edu</span>
|
||||
<p>暂无过期或已使用的权益记录</p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user