c43defca3f
2.首页商品详情增加点击区域 3.浓香型 酱香型 点击增加提示:暂未开放 5.首页餐券2字更换为“好客权益” 6.门店页面地址筛选功能 (三级菜单,省市区) 7.好客权益:去使用点击交互调整为核销页面,单张好客权益点击核销页面带参数和金额 7.核销金额限制为可用余额最大数,不是500 9.收货地址电话号码增加校验,长度限制,地址或者未填提示,地区调整为省市区三级菜单 10.微信授权位置调整在手机验证码下侧
234 lines
7.9 KiB
TypeScript
234 lines
7.9 KiB
TypeScript
import { useEffect, useState } from 'react';
|
|
import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
|
|
import SubPageHeader from '../components/SubPageHeader';
|
|
import RegionPicker from '../components/RegionPicker';
|
|
import { request } from '../lib/api';
|
|
import { buildAddressListUrl, readCheckoutContext } from '../lib/navigation';
|
|
import { DEFAULT_REGION, formatRegion } from '../lib/region-data';
|
|
import { normalizePhoneInput, validateMobilePhone } from '../lib/phone';
|
|
|
|
type AddressForm = {
|
|
receiverName: string;
|
|
phone: string;
|
|
province: string;
|
|
city: string;
|
|
district: string;
|
|
detail: string;
|
|
isDefault: boolean;
|
|
};
|
|
|
|
export default function AddressEditPage() {
|
|
const { id } = useParams();
|
|
const [params] = useSearchParams();
|
|
const isEdit = Boolean(id);
|
|
const navigate = useNavigate();
|
|
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: params.get('city') || DEFAULT_REGION.city,
|
|
district: DEFAULT_REGION.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 = 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;
|
|
}
|
|
|
|
setSaving(true);
|
|
setError('');
|
|
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 });
|
|
setError('');
|
|
}}
|
|
/>
|
|
<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="请输入手机号"
|
|
maxLength={11}
|
|
inputMode="numeric"
|
|
value={form.phone}
|
|
onChange={(e) => {
|
|
setForm({ ...form, phone: normalizePhoneInput(e.target.value) });
|
|
setError('');
|
|
}}
|
|
/>
|
|
<span className="material-symbols-outlined address-edit-field-icon">smartphone</span>
|
|
</div>
|
|
</div>
|
|
|
|
<button type="button" className="address-edit-field address-edit-region" onClick={() => setPickerOpen(true)}>
|
|
<div className="address-edit-line address-edit-line--picker address-edit-line--region">
|
|
<span className={regionText ? 'address-edit-region-value' : 'address-edit-region-placeholder'}>
|
|
{regionText || '省份、城市、区县'}
|
|
</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 });
|
|
setError('');
|
|
}}
|
|
/>
|
|
</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>
|
|
|
|
{error && <p className="address-edit-error">{error}</p>}
|
|
|
|
<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>
|
|
|
|
<RegionPicker
|
|
open={pickerOpen}
|
|
value={{ province: form.province, city: form.city, district: form.district }}
|
|
onClose={() => setPickerOpen(false)}
|
|
onConfirm={(region) => {
|
|
setForm((f) => ({
|
|
...f,
|
|
province: region.province,
|
|
city: region.city,
|
|
district: region.district,
|
|
}));
|
|
setPickerOpen(false);
|
|
setError('');
|
|
}}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|