用户端调整

2.首页商品详情增加点击区域
3.浓香型 酱香型 点击增加提示:暂未开放
5.首页餐券2字更换为“好客权益”
6.门店页面地址筛选功能 (三级菜单,省市区)
7.好客权益:去使用点击交互调整为核销页面,单张好客权益点击核销页面带参数和金额
7.核销金额限制为可用余额最大数,不是500
9.收货地址电话号码增加校验,长度限制,地址或者未填提示,地区调整为省市区三级菜单
10.微信授权位置调整在手机验证码下侧
This commit is contained in:
2026-07-01 00:19:08 +08:00
parent 6e047dc0a5
commit c43defca3f
18 changed files with 772 additions and 190 deletions
@@ -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>
);
}