用户端调整
2.首页商品详情增加点击区域 3.浓香型 酱香型 点击增加提示:暂未开放 5.首页餐券2字更换为“好客权益” 6.门店页面地址筛选功能 (三级菜单,省市区) 7.好客权益:去使用点击交互调整为核销页面,单张好客权益点击核销页面带参数和金额 7.核销金额限制为可用余额最大数,不是500 9.收货地址电话号码增加校验,长度限制,地址或者未填提示,地区调整为省市区三级菜单 10.微信授权位置调整在手机验证码下侧
This commit is contained in:
@@ -1,8 +1,11 @@
|
||||
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;
|
||||
@@ -14,35 +17,20 @@ type AddressForm = {
|
||||
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 [error, setError] = useState('');
|
||||
const [form, setForm] = useState<AddressForm>({
|
||||
receiverName: '',
|
||||
phone: '13800000001',
|
||||
province: '河南省',
|
||||
city: params.get('city') || '郑州市',
|
||||
district: '金水区',
|
||||
phone: '',
|
||||
province: DEFAULT_REGION.province,
|
||||
city: params.get('city') || DEFAULT_REGION.city,
|
||||
district: DEFAULT_REGION.district,
|
||||
detail: '',
|
||||
isDefault: true,
|
||||
});
|
||||
@@ -71,35 +59,26 @@ export default function AddressEditPage() {
|
||||
});
|
||||
}, [id]);
|
||||
|
||||
const regionText = regionLabel(form);
|
||||
const regionText = formatRegion(form.province, form.city, form.district);
|
||||
|
||||
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);
|
||||
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() {
|
||||
if (!form.receiverName.trim()) return;
|
||||
if (!form.phone.trim()) return;
|
||||
if (!regionText) return;
|
||||
if (!form.detail.trim()) return;
|
||||
const validationError = validateForm();
|
||||
if (validationError) {
|
||||
setError(validationError);
|
||||
return;
|
||||
}
|
||||
|
||||
setSaving(true);
|
||||
setError('');
|
||||
try {
|
||||
if (isEdit && id) {
|
||||
await request('USER_H5', `/user/addresses/${id}`, {
|
||||
@@ -135,7 +114,10 @@ export default function AddressEditPage() {
|
||||
className="address-edit-input"
|
||||
placeholder="请输入姓名"
|
||||
value={form.receiverName}
|
||||
onChange={(e) => setForm({ ...form, receiverName: e.target.value })}
|
||||
onChange={(e) => {
|
||||
setForm({ ...form, receiverName: e.target.value });
|
||||
setError('');
|
||||
}}
|
||||
/>
|
||||
<span className="material-symbols-outlined address-edit-field-icon">person</span>
|
||||
</div>
|
||||
@@ -149,20 +131,23 @@ export default function AddressEditPage() {
|
||||
type="tel"
|
||||
className="address-edit-input"
|
||||
placeholder="请输入手机号"
|
||||
maxLength={11}
|
||||
inputMode="numeric"
|
||||
value={form.phone}
|
||||
onChange={(e) => setForm({ ...form, phone: e.target.value })}
|
||||
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={openPicker}>
|
||||
<label className="address-edit-label">所在地区</label>
|
||||
<div className="address-edit-line address-edit-line--picker">
|
||||
<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 || '请选择省/市/区'}
|
||||
{regionText || '省份、城市、区县'}
|
||||
</span>
|
||||
<span className="material-symbols-outlined address-edit-field-icon">chevron_right</span>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
@@ -174,7 +159,10 @@ export default function AddressEditPage() {
|
||||
placeholder="街道、门牌号、小区名称等"
|
||||
rows={3}
|
||||
value={form.detail}
|
||||
onChange={(e) => setForm({ ...form, detail: e.target.value })}
|
||||
onChange={(e) => {
|
||||
setForm({ ...form, detail: e.target.value });
|
||||
setError('');
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -201,6 +189,8 @@ export default function AddressEditPage() {
|
||||
</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>
|
||||
@@ -223,51 +213,21 @@ export default function AddressEditPage() {
|
||||
</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>
|
||||
)}
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user