Files
dukang/apps/mini-user/src/pages/redeem/index.tsx
T
2026-07-12 23:59:52 +08:00

129 lines
4.1 KiB
TypeScript

import { useEffect, useState } from 'react';
import { View, Text, Input } from '@tarojs/components';
import Taro, { useRouter } from '@tarojs/taro';
import PageShell from '../../components/PageShell';
import SubPageHeader from '../../components/SubPageHeader';
import { goLogin } from '../../lib/auth-nav';
import { isLoggedIn, request, toast } from '../../lib/api';
type BenefitSummary = {
totalBalance: number;
maxRedeemAmount: number;
};
function formatMoney(amount: number) {
return amount.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
}
export default function RedeemPage() {
const router = useRouter();
const couponId = router.params.couponId;
const initialAmount = router.params.amount ?? '';
const [balance, setBalance] = useState(0);
const [couponBalance, setCouponBalance] = useState<number | null>(null);
const [amount, setAmount] = useState(initialAmount);
const [loading, setLoading] = useState(false);
const redeemableMax = couponId ? (couponBalance ?? 0) : balance;
useEffect(() => {
if (!isLoggedIn()) {
goLogin('/pages/redeem/index');
return;
}
request<BenefitSummary>('/benefit/summary')
.then((s) => setBalance(Number(s.maxRedeemAmount ?? s.totalBalance ?? 0)))
.catch(() => {});
}, []);
useEffect(() => {
if (!couponId) {
setCouponBalance(null);
return;
}
request<Array<{ id: string; balance: number }>>('/benefit/coupons')
.then((list) => {
const found = list.find((c) => String(c.id) === couponId);
if (found) setCouponBalance(Number(found.balance));
})
.catch(() => {});
}, [couponId]);
function fillMaxAmount() {
if (redeemableMax <= 0) return;
setAmount(String(redeemableMax));
}
async function submit() {
const value = Math.round(Number(amount) * 100) / 100;
if (!(value > 0)) {
toast('请输入核销金额');
return;
}
if (value > redeemableMax) {
toast(couponId ? '核销金额不能超过该权益可用余额' : '超出可用余额');
return;
}
setLoading(true);
try {
const body: { amount: number; couponId?: string } = { amount: value };
if (couponId) body.couponId = couponId;
const data = await request<{ token: string; amount: number }>('/redeem/tokens', {
method: 'POST',
data: body,
});
Taro.navigateTo({
url: `/pages/redeem-code/index?token=${encodeURIComponent(data.token)}&amount=${data.amount}`,
});
} catch (e) {
toast(e instanceof Error ? e.message : '生成失败');
} finally {
setLoading(false);
}
}
return (
<PageShell variant="sub" className="redeem-page">
<SubPageHeader title="权益核销" />
<View className="sub-page-body">
<View className="redeem-hero">
<Text className="redeem-hero-label">
{couponId ? '当前权益可用余额' : '可用余额'}
</Text>
<Text className="redeem-hero-amount">
¥{redeemableMax > 0 ? formatMoney(redeemableMax) : '--'}
</Text>
</View>
<View className="redeem-input-wrap">
<Input
className="redeem-input"
type="digit"
placeholder="输入核销金额"
value={amount}
onInput={(e) => setAmount(e.detail.value)}
/>
</View>
<View className="redeem-amount-foot">
<Text className="redeem-tips" style={{ margin: 0 }}>
最高可核销 ¥{formatMoney(redeemableMax)}
</Text>
<Text className="redeem-fill-max" onClick={fillMaxAmount}>
全部核销
</Text>
</View>
<Text className="redeem-tips">
直接核销:金额须大于 0 且不超过全部可用权益余额。核销码有效期 3 分钟,请到店出示。
</Text>
<View
className={`redeem-submit${loading || redeemableMax <= 0 ? ' redeem-submit--disabled' : ''}`}
onClick={loading || redeemableMax <= 0 ? undefined : submit}
>
<Text>{loading ? '生成中...' : '生成核销码'}</Text>
</View>
</View>
</PageShell>
);
}