用户端调整
2.首页商品详情增加点击区域 3.浓香型 酱香型 点击增加提示:暂未开放 5.首页餐券2字更换为“好客权益” 6.门店页面地址筛选功能 (三级菜单,省市区) 7.好客权益:去使用点击交互调整为核销页面,单张好客权益点击核销页面带参数和金额 7.核销金额限制为可用余额最大数,不是500 9.收货地址电话号码增加校验,长度限制,地址或者未填提示,地区调整为省市区三级菜单 10.微信授权位置调整在手机验证码下侧
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import {
|
||||
REGION_ALL,
|
||||
getCities,
|
||||
getCitiesForPicker,
|
||||
getDistricts,
|
||||
getDistrictsForPicker,
|
||||
getProvincesForPicker,
|
||||
normalizeRegionSelection,
|
||||
type RegionSelection,
|
||||
} from '../lib/region-data';
|
||||
|
||||
type RegionPickerProps = {
|
||||
open: boolean;
|
||||
value: RegionSelection;
|
||||
onClose: () => void;
|
||||
onConfirm: (region: RegionSelection) => void;
|
||||
};
|
||||
|
||||
type PickerLevel = 'province' | 'city' | 'district';
|
||||
|
||||
const TABS: Array<{ key: PickerLevel; label: string }> = [
|
||||
{ key: 'province', label: '省份' },
|
||||
{ key: 'city', label: '城市' },
|
||||
{ key: 'district', label: '区县' },
|
||||
];
|
||||
|
||||
export default function RegionPicker({ open, value, onClose, onConfirm }: RegionPickerProps) {
|
||||
const [draft, setDraft] = useState<RegionSelection>(value);
|
||||
const [activeTab, setActiveTab] = useState<PickerLevel>('province');
|
||||
const listRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
setDraft(normalizeRegionSelection(value));
|
||||
setActiveTab('province');
|
||||
}, [open, value]);
|
||||
|
||||
const listItems = useMemo(() => {
|
||||
if (activeTab === 'province') return getProvincesForPicker();
|
||||
if (activeTab === 'city') return getCitiesForPicker(draft.province);
|
||||
return getDistrictsForPicker(draft.province, draft.city);
|
||||
}, [activeTab, draft.province, draft.city]);
|
||||
|
||||
const selectedValue =
|
||||
activeTab === 'province' ? draft.province : activeTab === 'city' ? draft.city : draft.district;
|
||||
|
||||
const canConfirm = Boolean(draft.province && draft.city && draft.district);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
scrollActiveIntoView(listRef.current, selectedValue);
|
||||
}, [open, activeTab, selectedValue, listItems.length]);
|
||||
|
||||
if (!open) return null;
|
||||
|
||||
function scrollActiveIntoView(container: HTMLDivElement | null, label: string) {
|
||||
if (!container || !label) return;
|
||||
const active = container.querySelector<HTMLElement>(`[data-label="${CSS.escape(label)}"]`);
|
||||
active?.scrollIntoView({ block: 'nearest' });
|
||||
}
|
||||
|
||||
function selectProvince(province: string) {
|
||||
if (province === REGION_ALL) {
|
||||
setDraft({ province: REGION_ALL, city: REGION_ALL, district: REGION_ALL });
|
||||
setActiveTab('city');
|
||||
return;
|
||||
}
|
||||
const nextCities = getCities(province);
|
||||
const city = nextCities[0] ?? '';
|
||||
const nextDistricts = getDistricts(province, city);
|
||||
setDraft({
|
||||
province,
|
||||
city,
|
||||
district: nextDistricts[0] ?? '',
|
||||
});
|
||||
setActiveTab('city');
|
||||
}
|
||||
|
||||
function selectCity(city: string) {
|
||||
if (city === REGION_ALL) {
|
||||
setDraft({ ...draft, city: REGION_ALL, district: REGION_ALL });
|
||||
setActiveTab('district');
|
||||
return;
|
||||
}
|
||||
const nextDistricts = getDistricts(draft.province, city);
|
||||
setDraft({
|
||||
...draft,
|
||||
city,
|
||||
district: nextDistricts[0] ?? '',
|
||||
});
|
||||
setActiveTab('district');
|
||||
}
|
||||
|
||||
function selectDistrict(district: string) {
|
||||
setDraft({ ...draft, district });
|
||||
}
|
||||
|
||||
function onSelectItem(item: string) {
|
||||
if (activeTab === 'province') selectProvince(item);
|
||||
else if (activeTab === 'city') selectCity(item);
|
||||
else selectDistrict(item);
|
||||
}
|
||||
|
||||
function onTabClick(tab: PickerLevel) {
|
||||
if (tab === 'city' && !draft.province) return;
|
||||
if (tab === 'district' && (!draft.province || !draft.city)) return;
|
||||
setActiveTab(tab);
|
||||
}
|
||||
|
||||
function handleConfirm() {
|
||||
if (!canConfirm) return;
|
||||
onConfirm(normalizeRegionSelection(draft));
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="region-picker-overlay" role="presentation" onClick={onClose}>
|
||||
<div
|
||||
className="region-picker-sheet"
|
||||
role="dialog"
|
||||
aria-label="选择地区"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<div className="region-picker-toolbar">
|
||||
<div className="region-picker-tabs" role="tablist">
|
||||
{TABS.map((tab) => {
|
||||
const disabled =
|
||||
(tab.key === 'city' && !draft.province) ||
|
||||
(tab.key === 'district' && (!draft.province || !draft.city));
|
||||
return (
|
||||
<button
|
||||
key={tab.key}
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-selected={activeTab === tab.key}
|
||||
disabled={disabled}
|
||||
className={`region-picker-tab${activeTab === tab.key ? ' active' : ''}`}
|
||||
onClick={() => onTabClick(tab.key)}
|
||||
>
|
||||
{tab.label}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className={`region-picker-confirm${canConfirm ? ' ready' : ''}`}
|
||||
disabled={!canConfirm}
|
||||
onClick={handleConfirm}
|
||||
>
|
||||
确定
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="region-picker-list" ref={listRef}>
|
||||
{listItems.map((item) => (
|
||||
<button
|
||||
key={item}
|
||||
type="button"
|
||||
data-label={item}
|
||||
className={`region-picker-option${selectedValue === item ? ' selected' : ''}${
|
||||
item === REGION_ALL ? ' region-picker-option--all' : ''
|
||||
}`}
|
||||
onClick={() => onSelectItem(item)}
|
||||
>
|
||||
{item}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user