核销金额:最低 0.01 元;输入最多两位小数;低于该值会 toast 提示,无法生成核销码。
底部提示:左右留白 + 内边距,浅底圆角块,文案改为最低 0.01、余额上限、3 分钟到店出示。 新增地址:进入页会尝试定位,自动选中当前省市区(失败则保留默认郑州);编辑地址不重定位。
This commit is contained in:
@@ -5,9 +5,15 @@ import PageShell from '../../components/PageShell';
|
||||
import SubPageHeader from '../../components/SubPageHeader';
|
||||
import RegionPicker from '../../components/RegionPicker';
|
||||
import { buildAddressListUrl, readCheckoutContext } from '../../lib/checkout-nav';
|
||||
import { DEFAULT_REGION, formatRegion, type RegionSelection } from '../../lib/region-data';
|
||||
import {
|
||||
DEFAULT_REGION,
|
||||
REGION_ALL,
|
||||
formatRegion,
|
||||
type RegionSelection,
|
||||
} from '../../lib/region-data';
|
||||
import { normalizePhoneInput, validateMobilePhone } from '../../lib/phone';
|
||||
import { getStoredUserPhone, resolveDefaultUserPhone } from '../../lib/user-phone';
|
||||
import { resolveUserCity } from '../../lib/user-location';
|
||||
import { request, toast, type UserProfile } from '../../lib/api';
|
||||
|
||||
type AddressForm = {
|
||||
@@ -27,6 +33,7 @@ export default function AddressEditPage() {
|
||||
const checkoutCtx = readCheckoutContext(router.params);
|
||||
const [pickerOpen, setPickerOpen] = useState(false);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [locating, setLocating] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
const [form, setForm] = useState<AddressForm>(() => ({
|
||||
receiverName: '',
|
||||
@@ -49,6 +56,35 @@ export default function AddressEditPage() {
|
||||
.catch(() => {});
|
||||
}, [id]);
|
||||
|
||||
useEffect(() => {
|
||||
if (id) return;
|
||||
let cancelled = false;
|
||||
setLocating(true);
|
||||
void resolveUserCity(true)
|
||||
.then((resolved) => {
|
||||
if (cancelled) return;
|
||||
const district =
|
||||
resolved.region.district && resolved.region.district !== REGION_ALL
|
||||
? resolved.region.district
|
||||
: resolved.district && resolved.district !== REGION_ALL
|
||||
? resolved.district
|
||||
: DEFAULT_REGION.district;
|
||||
setForm((prev) => ({
|
||||
...prev,
|
||||
province: resolved.region.province || prev.province,
|
||||
city: resolved.region.city || prev.city,
|
||||
district: district || prev.district,
|
||||
}));
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
if (!cancelled) setLocating(false);
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [id]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!id) return;
|
||||
request<Array<Record<string, unknown>>>('/user/addresses').then((list) => {
|
||||
@@ -161,7 +197,11 @@ export default function AddressEditPage() {
|
||||
style={{ display: 'flex', alignItems: 'center' }}
|
||||
onClick={() => setPickerOpen(true)}
|
||||
>
|
||||
<Text>{regionText || '请选择省市区'}</Text>
|
||||
<Text>
|
||||
{locating && !isEdit
|
||||
? '定位中…'
|
||||
: regionText || '请选择省市区'}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View className="address-form-field">
|
||||
|
||||
@@ -11,10 +11,26 @@ type BenefitSummary = {
|
||||
maxRedeemAmount: number;
|
||||
};
|
||||
|
||||
const MIN_REDEEM_AMOUNT = 0.01;
|
||||
|
||||
function formatMoney(amount: number) {
|
||||
return amount.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
||||
}
|
||||
|
||||
/** 核销金额输入:最多两位小数,禁止非法字符 */
|
||||
function sanitizeRedeemAmountInput(raw: string): string {
|
||||
let next = raw.replace(/[^\d.]/g, '');
|
||||
const firstDot = next.indexOf('.');
|
||||
if (firstDot >= 0) {
|
||||
next =
|
||||
next.slice(0, firstDot + 1) + next.slice(firstDot + 1).replace(/\./g, '');
|
||||
const [intPart, decPart = ''] = next.split('.');
|
||||
next = `${intPart}.${decPart.slice(0, 2)}`;
|
||||
}
|
||||
if (next.startsWith('.')) next = `0${next}`;
|
||||
return next;
|
||||
}
|
||||
|
||||
export default function RedeemPage() {
|
||||
const router = useRouter();
|
||||
const couponId = router.params.couponId;
|
||||
@@ -50,14 +66,14 @@ export default function RedeemPage() {
|
||||
}, [couponId]);
|
||||
|
||||
function fillMaxAmount() {
|
||||
if (redeemableMax <= 0) return;
|
||||
if (redeemableMax < MIN_REDEEM_AMOUNT) return;
|
||||
setAmount(String(redeemableMax));
|
||||
}
|
||||
|
||||
async function submit() {
|
||||
const value = Math.round(Number(amount) * 100) / 100;
|
||||
if (!(value > 0)) {
|
||||
toast('请输入核销金额');
|
||||
if (!Number.isFinite(value) || value < MIN_REDEEM_AMOUNT) {
|
||||
toast(`核销金额不能低于 ${MIN_REDEEM_AMOUNT.toFixed(2)} 元`);
|
||||
return;
|
||||
}
|
||||
if (value > redeemableMax) {
|
||||
@@ -103,7 +119,7 @@ export default function RedeemPage() {
|
||||
placeholder="输入核销金额"
|
||||
placeholderClass="redeem-input-placeholder"
|
||||
value={amount}
|
||||
onInput={(e) => setAmount(e.detail.value)}
|
||||
onInput={(e) => setAmount(sanitizeRedeemAmountInput(e.detail.value))}
|
||||
style={{ textAlign: 'center' }}
|
||||
/>
|
||||
</View>
|
||||
@@ -115,12 +131,14 @@ export default function RedeemPage() {
|
||||
全部核销
|
||||
</Text>
|
||||
</View>
|
||||
<Text className="redeem-tips">
|
||||
直接核销:金额须大于 0 且不超过全部可用权益余额。核销码有效期 3 分钟,请到店出示。
|
||||
<View className="redeem-tips">
|
||||
<Text className="redeem-tips-text">
|
||||
核销金额最低 0.01 元,且不超过可用权益余额。核销码有效期 3 分钟,请到店出示给收银员扫码。
|
||||
</Text>
|
||||
</View>
|
||||
<View
|
||||
className={`redeem-submit${loading || redeemableMax <= 0 ? ' redeem-submit--disabled' : ''}`}
|
||||
onClick={loading || redeemableMax <= 0 ? undefined : submit}
|
||||
className={`redeem-submit${loading || redeemableMax < MIN_REDEEM_AMOUNT ? ' redeem-submit--disabled' : ''}`}
|
||||
onClick={loading || redeemableMax < MIN_REDEEM_AMOUNT ? undefined : submit}
|
||||
>
|
||||
<Text>{loading ? '生成中...' : '生成核销码'}</Text>
|
||||
</View>
|
||||
|
||||
@@ -77,12 +77,18 @@
|
||||
.redeem-tips {
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 0 var(--space-page);
|
||||
margin: 8px var(--space-page) 0;
|
||||
padding: 12px 16px;
|
||||
border-radius: var(--radius-md);
|
||||
background: rgba(0, 0, 0, 0.03);
|
||||
}
|
||||
|
||||
.redeem-tips-text {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
color: var(--color-subtle-gray);
|
||||
line-height: 1.6;
|
||||
line-height: 1.7;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.redeem-amount-hint {
|
||||
|
||||
Reference in New Issue
Block a user