小程序修改
This commit is contained in:
@@ -1,25 +1,129 @@
|
||||
import { useState } from 'react';
|
||||
import { View, Text, Input, Textarea } from '@tarojs/components';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { View, Text, Input, Textarea, Switch } from '@tarojs/components';
|
||||
import Taro, { useRouter } from '@tarojs/taro';
|
||||
import PageShell from '../../components/PageShell';
|
||||
import SubPageHeader from '../../components/SubPageHeader';
|
||||
import { toast } from '../../lib/api';
|
||||
import RegionPicker from '../../components/RegionPicker';
|
||||
import { buildAddressListUrl, readCheckoutContext } from '../../lib/checkout-nav';
|
||||
import { DEFAULT_REGION, formatRegion, type RegionSelection } from '../../lib/region-data';
|
||||
import { normalizePhoneInput, validateMobilePhone } from '../../lib/phone';
|
||||
import { getStoredUserPhone } from '../../lib/user-phone';
|
||||
import { request, toast, type UserProfile } from '../../lib/api';
|
||||
|
||||
type AddressForm = {
|
||||
receiverName: string;
|
||||
phone: string;
|
||||
province: string;
|
||||
city: string;
|
||||
district: string;
|
||||
detail: string;
|
||||
isDefault: boolean;
|
||||
};
|
||||
|
||||
export default function AddressEditPage() {
|
||||
const router = useRouter();
|
||||
const isEdit = !!router.params.id;
|
||||
const [name, setName] = useState('');
|
||||
const [phone, setPhone] = useState('');
|
||||
const [region, setRegion] = useState('河南省 郑州市');
|
||||
const [detail, setDetail] = useState('');
|
||||
const id = router.params.id;
|
||||
const isEdit = !!id;
|
||||
const checkoutCtx = readCheckoutContext(router.params);
|
||||
const [pickerOpen, setPickerOpen] = useState(false);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
const [form, setForm] = useState<AddressForm>({
|
||||
receiverName: '',
|
||||
phone: '',
|
||||
province: DEFAULT_REGION.province,
|
||||
city: DEFAULT_REGION.city,
|
||||
district: DEFAULT_REGION.district,
|
||||
detail: '',
|
||||
isDefault: true,
|
||||
});
|
||||
|
||||
function save() {
|
||||
if (!name.trim() || !phone.trim() || !detail.trim()) {
|
||||
toast('请完善地址信息');
|
||||
useEffect(() => {
|
||||
if (id) return;
|
||||
request<UserProfile>('/auth/me')
|
||||
.then((me) => {
|
||||
if (!me.phoneVerified) return;
|
||||
const stored = getStoredUserPhone();
|
||||
if (!stored) return;
|
||||
setForm((prev) => (prev.phone ? prev : { ...prev, phone: stored }));
|
||||
})
|
||||
.catch(() => {});
|
||||
}, [id]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!id) return;
|
||||
request<Array<Record<string, unknown>>>('/user/addresses').then((list) => {
|
||||
const found = list.find((a) => String(a.id) === id);
|
||||
if (found) {
|
||||
setForm({
|
||||
receiverName: String(found.receiverName ?? ''),
|
||||
phone: String(found.phone ?? ''),
|
||||
province: String(found.province ?? DEFAULT_REGION.province),
|
||||
city: String(found.city ?? DEFAULT_REGION.city),
|
||||
district: String(found.district ?? DEFAULT_REGION.district),
|
||||
detail: String(found.detail ?? ''),
|
||||
isDefault: found.isDefault === 1 || found.isDefault === true,
|
||||
});
|
||||
}
|
||||
}).catch(() => {});
|
||||
}, [id]);
|
||||
|
||||
const regionText = formatRegion(form.province, form.city, form.district);
|
||||
|
||||
function validateForm(): string | null {
|
||||
if (!form.receiverName.trim()) return '请输入收货人姓名';
|
||||
const phoneCheck = validateMobilePhone(form.phone);
|
||||
if (!phoneCheck.ok) return phoneCheck.message ?? '请输入正确的手机号码';
|
||||
if (!form.province || !form.city || !form.district) return '请选择所在地区';
|
||||
if (!form.detail.trim()) return '请输入详细地址';
|
||||
return null;
|
||||
}
|
||||
|
||||
async function save() {
|
||||
const validationError = validateForm();
|
||||
if (validationError) {
|
||||
setError(validationError);
|
||||
return;
|
||||
}
|
||||
toast(isEdit ? '地址已更新(UI 壳)' : '地址已新增(UI 壳)', 'success');
|
||||
setTimeout(() => Taro.navigateBack(), 600);
|
||||
|
||||
setSaving(true);
|
||||
setError('');
|
||||
try {
|
||||
const payload = {
|
||||
receiverName: form.receiverName.trim(),
|
||||
phone: form.phone.trim(),
|
||||
province: form.province,
|
||||
city: form.city,
|
||||
district: form.district,
|
||||
detail: form.detail.trim(),
|
||||
isDefault: form.isDefault,
|
||||
};
|
||||
if (isEdit && id) {
|
||||
await request(`/user/addresses/${id}`, { method: 'PUT', data: payload });
|
||||
toast('地址已更新', 'success');
|
||||
} else {
|
||||
await request('/user/addresses', { method: 'POST', data: payload });
|
||||
toast('地址已新增', 'success');
|
||||
}
|
||||
setTimeout(() => {
|
||||
Taro.redirectTo({ url: buildAddressListUrl(checkoutCtx) }).catch(() => {
|
||||
Taro.navigateBack();
|
||||
});
|
||||
}, 400);
|
||||
} catch (e) {
|
||||
setError(e instanceof Error ? e.message : '保存失败');
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
}
|
||||
|
||||
function onRegionConfirm(region: RegionSelection) {
|
||||
setForm((prev) => ({
|
||||
...prev,
|
||||
province: region.province,
|
||||
city: region.city,
|
||||
district: region.district,
|
||||
}));
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -31,8 +135,8 @@ export default function AddressEditPage() {
|
||||
<Input
|
||||
className="address-form-input"
|
||||
placeholder="请输入姓名"
|
||||
value={name}
|
||||
onInput={(e) => setName(e.detail.value)}
|
||||
value={form.receiverName}
|
||||
onInput={(e) => setForm((prev) => ({ ...prev, receiverName: e.detail.value }))}
|
||||
/>
|
||||
</View>
|
||||
<View className="address-form-field">
|
||||
@@ -42,8 +146,10 @@ export default function AddressEditPage() {
|
||||
type="number"
|
||||
maxlength={11}
|
||||
placeholder="请输入手机号"
|
||||
value={phone}
|
||||
onInput={(e) => setPhone(e.detail.value)}
|
||||
value={form.phone}
|
||||
onInput={(e) =>
|
||||
setForm((prev) => ({ ...prev, phone: normalizePhoneInput(e.detail.value) }))
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
<View className="address-form-field">
|
||||
@@ -51,9 +157,9 @@ export default function AddressEditPage() {
|
||||
<View
|
||||
className="address-form-input"
|
||||
style={{ display: 'flex', alignItems: 'center' }}
|
||||
onClick={() => toast('区域选择器后续接入')}
|
||||
onClick={() => setPickerOpen(true)}
|
||||
>
|
||||
<Text>{region || '请选择省市区'}</Text>
|
||||
<Text>{regionText || '请选择省市区'}</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View className="address-form-field">
|
||||
@@ -61,14 +167,31 @@ export default function AddressEditPage() {
|
||||
<Textarea
|
||||
className="address-form-textarea"
|
||||
placeholder="街道门牌号等"
|
||||
value={detail}
|
||||
onInput={(e) => setDetail(e.detail.value)}
|
||||
value={form.detail}
|
||||
onInput={(e) => setForm((prev) => ({ ...prev, detail: e.detail.value }))}
|
||||
/>
|
||||
</View>
|
||||
<View className="address-form-row">
|
||||
<Text>设为默认地址</Text>
|
||||
<Switch
|
||||
checked={form.isDefault}
|
||||
color="#A61D24"
|
||||
onChange={(e) => setForm((prev) => ({ ...prev, isDefault: e.detail.value }))}
|
||||
/>
|
||||
</View>
|
||||
{error ? <Text className="address-form-error">{error}</Text> : null}
|
||||
</View>
|
||||
<View className="address-fab" onClick={save}>
|
||||
<Text>保存</Text>
|
||||
<View className="address-fab" onClick={() => !saving && void save()}>
|
||||
<Text>{saving ? '保存中…' : '保存'}</Text>
|
||||
</View>
|
||||
|
||||
<RegionPicker
|
||||
open={pickerOpen}
|
||||
value={{ province: form.province, city: form.city, district: form.district }}
|
||||
onClose={() => setPickerOpen(false)}
|
||||
onConfirm={onRegionConfirm}
|
||||
levels={3}
|
||||
/>
|
||||
</PageShell>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user