This commit is contained in:
2026-06-30 10:33:56 +08:00
commit 6e047dc0a5
607 changed files with 65966 additions and 0 deletions
+273
View File
@@ -0,0 +1,273 @@
import { useEffect, useState } from 'react';
import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
import SubPageHeader from '../components/SubPageHeader';
import { request } from '../lib/api';
import { buildAddressListUrl, readCheckoutContext } from '../lib/navigation';
type AddressForm = {
receiverName: string;
phone: string;
province: string;
city: string;
district: string;
detail: string;
isDefault: boolean;
};
const REGION_OPTIONS = [
{ province: '河南省', city: '郑州市', district: '金水区', label: '河南省 郑州市 金水区' },
{ province: '河南省', city: '郑州市', district: '二七区', label: '河南省 郑州市 二七区' },
{ province: '河南省', city: '洛阳市', district: '涧西区', label: '河南省 洛阳市 涧西区' },
{ province: '河南省', city: '洛阳市', district: '洛龙区', label: '河南省 洛阳市 洛龙区' },
{ province: '北京市', city: '北京市', district: '东城区', label: '北京市 东城区' },
{ province: '上海市', city: '上海市', district: '黄浦区', label: '上海市 黄浦区' },
{ province: '陕西省', city: '西安市', district: '雁塔区', label: '陕西省 西安市 雁塔区' },
] as const;
function regionLabel(form: Pick<AddressForm, 'province' | 'city' | 'district'>) {
if (!form.province || !form.city || !form.district) return '';
return `${form.province} ${form.city} ${form.district}`;
}
export default function AddressEditPage() {
const { id } = useParams();
const [params] = useSearchParams();
const isEdit = Boolean(id);
const navigate = useNavigate();
const [pickerOpen, setPickerOpen] = useState(false);
const [pickerDraft, setPickerDraft] = useState<typeof REGION_OPTIONS[number] | null>(null);
const [saving, setSaving] = useState(false);
const [form, setForm] = useState<AddressForm>({
receiverName: '',
phone: '13800000001',
province: '河南省',
city: params.get('city') || '郑州市',
district: '金水区',
detail: '',
isDefault: true,
});
const checkoutCtx = readCheckoutContext(params);
function goBackToList() {
navigate(buildAddressListUrl(checkoutCtx));
}
useEffect(() => {
if (!id) return;
request<Array<Record<string, unknown>>>('USER_H5', '/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),
city: String(found.city),
district: String(found.district),
detail: String(found.detail),
isDefault: found.isDefault === 1,
});
}
});
}, [id]);
const regionText = regionLabel(form);
function openPicker() {
const current =
REGION_OPTIONS.find(
(r) => r.province === form.province && r.city === form.city && r.district === form.district,
) ?? null;
setPickerDraft(current);
setPickerOpen(true);
}
function confirmRegion() {
if (pickerDraft) {
setForm((f) => ({
...f,
province: pickerDraft.province,
city: pickerDraft.city,
district: pickerDraft.district,
}));
}
setPickerOpen(false);
}
async function save() {
if (!form.receiverName.trim()) return;
if (!form.phone.trim()) return;
if (!regionText) return;
if (!form.detail.trim()) return;
setSaving(true);
try {
if (isEdit && id) {
await request('USER_H5', `/user/addresses/${id}`, {
method: 'PUT',
body: JSON.stringify(form),
});
} else {
await request('USER_H5', '/user/addresses', {
method: 'POST',
body: JSON.stringify(form),
});
}
navigate(buildAddressListUrl(checkoutCtx));
} finally {
setSaving(false);
}
}
return (
<div className="address-edit-page">
<SubPageHeader
title={isEdit ? '编辑收货地址' : '添加收货地址'}
onBack={goBackToList}
/>
<main className="address-edit-main sub-page-body">
<section className="address-edit-card">
<div className="address-edit-field">
<label className="address-edit-label"></label>
<div className="address-edit-line">
<input
type="text"
className="address-edit-input"
placeholder="请输入姓名"
value={form.receiverName}
onChange={(e) => setForm({ ...form, receiverName: e.target.value })}
/>
<span className="material-symbols-outlined address-edit-field-icon">person</span>
</div>
</div>
<div className="address-edit-field">
<label className="address-edit-label"></label>
<div className="address-edit-line">
<span className="address-edit-prefix">+86</span>
<input
type="tel"
className="address-edit-input"
placeholder="请输入手机号"
value={form.phone}
onChange={(e) => setForm({ ...form, phone: e.target.value })}
/>
<span className="material-symbols-outlined address-edit-field-icon">smartphone</span>
</div>
</div>
<button type="button" className="address-edit-field address-edit-region" onClick={openPicker}>
<label className="address-edit-label"></label>
<div className="address-edit-line address-edit-line--picker">
<span className={regionText ? 'address-edit-region-value' : 'address-edit-region-placeholder'}>
{regionText || '请选择省/市/区'}
</span>
<span className="material-symbols-outlined address-edit-field-icon">chevron_right</span>
</div>
</button>
<div className="address-edit-field address-edit-field--last">
<label className="address-edit-label"></label>
<div className="address-edit-textarea-wrap">
<textarea
className="address-edit-textarea"
placeholder="街道、门牌号、小区名称等"
rows={3}
value={form.detail}
onChange={(e) => setForm({ ...form, detail: e.target.value })}
/>
</div>
</div>
</section>
<section className="address-edit-card address-edit-default">
<div className="address-edit-default-info">
<div className="address-edit-default-icon">
<span className="material-symbols-outlined fill-icon">stars</span>
</div>
<div>
<h3 className="address-edit-default-title"></h3>
<p className="address-edit-default-desc">使</p>
</div>
</div>
<label className="address-edit-toggle">
<input
type="checkbox"
checked={form.isDefault}
onChange={(e) => setForm({ ...form, isDefault: e.target.checked })}
/>
<span className="address-edit-toggle-track" />
<span className="address-edit-toggle-thumb" />
</label>
</section>
<div className="address-edit-security">
<span className="material-symbols-outlined">location_on</span>
<span></span>
</div>
</main>
<nav className="address-edit-footer">
<button type="button" className="address-edit-cancel-btn" onClick={goBackToList}>
<span className="material-symbols-outlined">close</span>
<span></span>
</button>
<button
type="button"
className="address-edit-save-btn"
disabled={saving}
onClick={save}
>
<span className="material-symbols-outlined">publish</span>
<span>{saving ? '保存中...' : '保存并发布'}</span>
</button>
</nav>
{pickerOpen && (
<div
className="address-edit-picker-overlay"
role="presentation"
onClick={() => setPickerOpen(false)}
>
<div
className="address-edit-picker-sheet"
role="dialog"
aria-label="选择地区"
onClick={(e) => e.stopPropagation()}
>
<div className="address-edit-picker-head">
<h4></h4>
<button type="button" aria-label="关闭" onClick={() => setPickerOpen(false)}>
<span className="material-symbols-outlined">close</span>
</button>
</div>
<div className="address-edit-picker-list">
{REGION_OPTIONS.map((r) => {
const selected =
pickerDraft?.province === r.province &&
pickerDraft?.city === r.city &&
pickerDraft?.district === r.district;
return (
<button
key={r.label}
type="button"
className={`address-edit-picker-item${selected ? ' selected' : ''}`}
onClick={() => setPickerDraft(r)}
>
<span>{r.label}</span>
{selected && (
<span className="material-symbols-outlined">check_circle</span>
)}
</button>
);
})}
</div>
<button type="button" className="address-edit-picker-confirm" onClick={confirmRegion}>
</button>
</div>
</div>
)}
</div>
);
}