核销金额:最低 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 SubPageHeader from '../../components/SubPageHeader';
|
||||||
import RegionPicker from '../../components/RegionPicker';
|
import RegionPicker from '../../components/RegionPicker';
|
||||||
import { buildAddressListUrl, readCheckoutContext } from '../../lib/checkout-nav';
|
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 { normalizePhoneInput, validateMobilePhone } from '../../lib/phone';
|
||||||
import { getStoredUserPhone, resolveDefaultUserPhone } from '../../lib/user-phone';
|
import { getStoredUserPhone, resolveDefaultUserPhone } from '../../lib/user-phone';
|
||||||
|
import { resolveUserCity } from '../../lib/user-location';
|
||||||
import { request, toast, type UserProfile } from '../../lib/api';
|
import { request, toast, type UserProfile } from '../../lib/api';
|
||||||
|
|
||||||
type AddressForm = {
|
type AddressForm = {
|
||||||
@@ -27,6 +33,7 @@ export default function AddressEditPage() {
|
|||||||
const checkoutCtx = readCheckoutContext(router.params);
|
const checkoutCtx = readCheckoutContext(router.params);
|
||||||
const [pickerOpen, setPickerOpen] = useState(false);
|
const [pickerOpen, setPickerOpen] = useState(false);
|
||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
|
const [locating, setLocating] = useState(false);
|
||||||
const [error, setError] = useState('');
|
const [error, setError] = useState('');
|
||||||
const [form, setForm] = useState<AddressForm>(() => ({
|
const [form, setForm] = useState<AddressForm>(() => ({
|
||||||
receiverName: '',
|
receiverName: '',
|
||||||
@@ -49,6 +56,35 @@ export default function AddressEditPage() {
|
|||||||
.catch(() => {});
|
.catch(() => {});
|
||||||
}, [id]);
|
}, [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(() => {
|
useEffect(() => {
|
||||||
if (!id) return;
|
if (!id) return;
|
||||||
request<Array<Record<string, unknown>>>('/user/addresses').then((list) => {
|
request<Array<Record<string, unknown>>>('/user/addresses').then((list) => {
|
||||||
@@ -161,7 +197,11 @@ export default function AddressEditPage() {
|
|||||||
style={{ display: 'flex', alignItems: 'center' }}
|
style={{ display: 'flex', alignItems: 'center' }}
|
||||||
onClick={() => setPickerOpen(true)}
|
onClick={() => setPickerOpen(true)}
|
||||||
>
|
>
|
||||||
<Text>{regionText || '请选择省市区'}</Text>
|
<Text>
|
||||||
|
{locating && !isEdit
|
||||||
|
? '定位中…'
|
||||||
|
: regionText || '请选择省市区'}
|
||||||
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
<View className="address-form-field">
|
<View className="address-form-field">
|
||||||
|
|||||||
@@ -11,10 +11,26 @@ type BenefitSummary = {
|
|||||||
maxRedeemAmount: number;
|
maxRedeemAmount: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const MIN_REDEEM_AMOUNT = 0.01;
|
||||||
|
|
||||||
function formatMoney(amount: number) {
|
function formatMoney(amount: number) {
|
||||||
return amount.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
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() {
|
export default function RedeemPage() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const couponId = router.params.couponId;
|
const couponId = router.params.couponId;
|
||||||
@@ -50,14 +66,14 @@ export default function RedeemPage() {
|
|||||||
}, [couponId]);
|
}, [couponId]);
|
||||||
|
|
||||||
function fillMaxAmount() {
|
function fillMaxAmount() {
|
||||||
if (redeemableMax <= 0) return;
|
if (redeemableMax < MIN_REDEEM_AMOUNT) return;
|
||||||
setAmount(String(redeemableMax));
|
setAmount(String(redeemableMax));
|
||||||
}
|
}
|
||||||
|
|
||||||
async function submit() {
|
async function submit() {
|
||||||
const value = Math.round(Number(amount) * 100) / 100;
|
const value = Math.round(Number(amount) * 100) / 100;
|
||||||
if (!(value > 0)) {
|
if (!Number.isFinite(value) || value < MIN_REDEEM_AMOUNT) {
|
||||||
toast('请输入核销金额');
|
toast(`核销金额不能低于 ${MIN_REDEEM_AMOUNT.toFixed(2)} 元`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (value > redeemableMax) {
|
if (value > redeemableMax) {
|
||||||
@@ -103,7 +119,7 @@ export default function RedeemPage() {
|
|||||||
placeholder="输入核销金额"
|
placeholder="输入核销金额"
|
||||||
placeholderClass="redeem-input-placeholder"
|
placeholderClass="redeem-input-placeholder"
|
||||||
value={amount}
|
value={amount}
|
||||||
onInput={(e) => setAmount(e.detail.value)}
|
onInput={(e) => setAmount(sanitizeRedeemAmountInput(e.detail.value))}
|
||||||
style={{ textAlign: 'center' }}
|
style={{ textAlign: 'center' }}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
@@ -115,12 +131,14 @@ export default function RedeemPage() {
|
|||||||
全部核销
|
全部核销
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
<Text className="redeem-tips">
|
<View className="redeem-tips">
|
||||||
直接核销:金额须大于 0 且不超过全部可用权益余额。核销码有效期 3 分钟,请到店出示。
|
<Text className="redeem-tips-text">
|
||||||
</Text>
|
核销金额最低 0.01 元,且不超过可用权益余额。核销码有效期 3 分钟,请到店出示给收银员扫码。
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
<View
|
<View
|
||||||
className={`redeem-submit${loading || redeemableMax <= 0 ? ' redeem-submit--disabled' : ''}`}
|
className={`redeem-submit${loading || redeemableMax < MIN_REDEEM_AMOUNT ? ' redeem-submit--disabled' : ''}`}
|
||||||
onClick={loading || redeemableMax <= 0 ? undefined : submit}
|
onClick={loading || redeemableMax < MIN_REDEEM_AMOUNT ? undefined : submit}
|
||||||
>
|
>
|
||||||
<Text>{loading ? '生成中...' : '生成核销码'}</Text>
|
<Text>{loading ? '生成中...' : '生成核销码'}</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -77,12 +77,18 @@
|
|||||||
.redeem-tips {
|
.redeem-tips {
|
||||||
display: block;
|
display: block;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
width: 100%;
|
margin: 8px var(--space-page) 0;
|
||||||
margin: 0;
|
padding: 12px 16px;
|
||||||
padding: 0 var(--space-page);
|
border-radius: var(--radius-md);
|
||||||
|
background: rgba(0, 0, 0, 0.03);
|
||||||
|
}
|
||||||
|
|
||||||
|
.redeem-tips-text {
|
||||||
|
display: block;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: var(--color-subtle-gray);
|
color: var(--color-subtle-gray);
|
||||||
line-height: 1.6;
|
line-height: 1.7;
|
||||||
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
.redeem-amount-hint {
|
.redeem-amount-hint {
|
||||||
|
|||||||
Reference in New Issue
Block a user