用户端调整

2.首页商品详情增加点击区域
3.浓香型 酱香型 点击增加提示:暂未开放
5.首页餐券2字更换为“好客权益”
6.门店页面地址筛选功能 (三级菜单,省市区)
7.好客权益:去使用点击交互调整为核销页面,单张好客权益点击核销页面带参数和金额
7.核销金额限制为可用余额最大数,不是500
9.收货地址电话号码增加校验,长度限制,地址或者未填提示,地区调整为省市区三级菜单
10.微信授权位置调整在手机验证码下侧
This commit is contained in:
2026-07-01 00:19:08 +08:00
parent 6e047dc0a5
commit c43defca3f
18 changed files with 772 additions and 190 deletions
+51 -17
View File
@@ -1,8 +1,7 @@
import { useEffect, useRef, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { useNavigate, useSearchParams } from 'react-router-dom';
import { request } from '../lib/api';
const REDEEM_MAX_AMOUNT = 500;
const REDEEM_TOKEN_TTL_SECONDS = 300;
type BenefitSummary = {
@@ -23,7 +22,12 @@ function formatTimer(seconds: number) {
export default function RedeemPage() {
const navigate = useNavigate();
const [searchParams] = useSearchParams();
const couponId = searchParams.get('couponId') ?? undefined;
const presetAmount = searchParams.get('amount');
const [summary, setSummary] = useState<BenefitSummary | null>(null);
const [couponBalance, setCouponBalance] = useState<number | null>(null);
const [amountInput, setAmountInput] = useState('');
const [msg, setMsg] = useState('');
const [loading, setLoading] = useState(false);
@@ -32,12 +36,40 @@ export default function RedeemPage() {
const [confirmAmount, setConfirmAmount] = useState(0);
const [timerSec, setTimerSec] = useState(REDEEM_TOKEN_TTL_SECONDS);
const timerRef = useRef<number | null>(null);
const presetApplied = useRef(false);
const redeemableMax = couponId
? (couponBalance ?? 0)
: (summary?.totalBalance ?? 0);
useEffect(() => {
request<BenefitSummary>('USER_H5', '/benefit/summary').then(setSummary).catch(() => {});
return () => stopTimer();
}, []);
useEffect(() => {
if (!couponId) {
setCouponBalance(null);
return;
}
request<Array<Record<string, unknown>>>('USER_H5', '/benefit/coupons')
.then((list) => {
const found = list.find((c) => String(c.id) === couponId);
if (found) {
setCouponBalance(Number(found.balance));
}
})
.catch(() => {});
}, [couponId]);
useEffect(() => {
if (presetApplied.current) return;
if (presetAmount && Number(presetAmount) > 0) {
setAmountInput(formatMoney(Number(presetAmount)));
presetApplied.current = true;
}
}, [presetAmount]);
function stopTimer() {
if (timerRef.current != null) {
window.clearInterval(timerRef.current);
@@ -76,33 +108,34 @@ export default function RedeemPage() {
}
function fillMaxAmount() {
if (!summary) return;
setAmountInput(formatMoney(summary.maxRedeemAmount));
if (redeemableMax <= 0) return;
setAmountInput(formatMoney(redeemableMax));
setMsg('');
}
async function submit() {
const amount = parseAmount();
if (!summary) return;
const maxAllowed = couponId ? (couponBalance ?? 0) : summary.totalBalance;
if (amount <= 0) {
setMsg('请输入核销金额');
return;
}
if (amount > summary.totalBalance) {
setMsg('核销金额不能超过可用余额');
return;
}
if (amount > REDEEM_MAX_AMOUNT) {
setMsg(`单次核销不能超过 ¥${REDEEM_MAX_AMOUNT}`);
if (amount > maxAllowed) {
setMsg(couponId ? '核销金额不能超过该权益可用余额' : '核销金额不能超过可用余额');
return;
}
setLoading(true);
setMsg('');
try {
const body: { amount: number; couponId?: string } = { amount };
if (couponId) body.couponId = couponId;
const data = await request<{ token: string; amount: number }>('USER_H5', '/redeem/tokens', {
method: 'POST',
body: JSON.stringify({ amount }),
body: JSON.stringify(body),
});
setToken(data.token);
setConfirmAmount(data.amount);
@@ -117,7 +150,6 @@ export default function RedeemPage() {
}
}
const maxHint = summary ? Math.min(REDEEM_MAX_AMOUNT, summary.totalBalance) : REDEEM_MAX_AMOUNT;
const qrUrl = token
? `https://api.qrserver.com/v1/create-qr-code/?size=192x192&data=${encodeURIComponent(token)}`
: '';
@@ -133,7 +165,7 @@ export default function RedeemPage() {
type="button"
className="redeem-header-btn"
aria-label="帮助"
onClick={() => window.alert('单次核销上限 ¥500,核销码 5 分钟内有效。')}
onClick={() => window.alert('核销金额不超过可用余额,核销码 5 分钟内有效。')}
>
<span className="material-symbols-outlined">help_outline</span>
</button>
@@ -143,11 +175,13 @@ export default function RedeemPage() {
<section className="redeem-balance-card">
<div className="redeem-balance-pattern" aria-hidden />
<div className="redeem-balance-inner">
<span className="redeem-balance-label"></span>
<span className="redeem-balance-label">
{couponId ? '当前权益可用余额' : '当前好客权益可用余额'}
</span>
<div className="redeem-balance-amount">
<span className="redeem-balance-symbol">¥</span>
<span className="redeem-balance-value">
{summary ? formatMoney(summary.totalBalance) : '--'}
{redeemableMax > 0 ? formatMoney(redeemableMax) : '--'}
</span>
</div>
</div>
@@ -175,7 +209,7 @@ export default function RedeemPage() {
/>
</div>
<div className="redeem-amount-foot">
<span> ¥{formatMoney(maxHint)}</span>
<span> ¥{formatMoney(redeemableMax)}</span>
<button type="button" className="redeem-fill-max" onClick={fillMaxAmount}>
</button>
@@ -187,7 +221,7 @@ export default function RedeemPage() {
<button
type="button"
className="redeem-submit-btn"
disabled={loading || !summary || summary.totalBalance <= 0}
disabled={loading || redeemableMax <= 0}
onClick={submit}
>
<span className="material-symbols-outlined">qr_code_2</span>