首页定位问题
This commit is contained in:
@@ -7,6 +7,7 @@ import {
|
||||
getDistrictsForPicker,
|
||||
getProvincesForPicker,
|
||||
normalizeRegionSelection,
|
||||
toCityLevelRegion,
|
||||
type RegionSelection,
|
||||
} from '../lib/region-data';
|
||||
|
||||
@@ -15,26 +16,57 @@ type RegionPickerProps = {
|
||||
value: RegionSelection;
|
||||
onClose: () => void;
|
||||
onConfirm: (region: RegionSelection) => void;
|
||||
/** 2 = 仅省/市(门店列表);3 = 省/市/区(地址等) */
|
||||
levels?: 2 | 3;
|
||||
};
|
||||
|
||||
type PickerLevel = 'province' | 'city' | 'district';
|
||||
|
||||
const TABS: Array<{ key: PickerLevel; label: string }> = [
|
||||
const ALL_TABS: Array<{ key: PickerLevel; label: string }> = [
|
||||
{ key: 'province', label: '省份' },
|
||||
{ key: 'city', label: '城市' },
|
||||
{ key: 'district', label: '区县' },
|
||||
];
|
||||
|
||||
export default function RegionPicker({ open, value, onClose, onConfirm }: RegionPickerProps) {
|
||||
function initialTab(value: RegionSelection, levels: 2 | 3): PickerLevel {
|
||||
const normalized = levels === 2 ? toCityLevelRegion(value) : normalizeRegionSelection(value);
|
||||
if (levels === 2) {
|
||||
return normalized.province && normalized.province !== REGION_ALL ? 'city' : 'province';
|
||||
}
|
||||
if (normalized.district && normalized.district !== REGION_ALL) return 'district';
|
||||
if (normalized.city && normalized.city !== REGION_ALL) return 'city';
|
||||
return 'province';
|
||||
}
|
||||
|
||||
function tabLabel(tab: PickerLevel, draft: RegionSelection, fallback: string) {
|
||||
if (tab === 'province') {
|
||||
return draft.province && draft.province !== REGION_ALL ? draft.province : fallback;
|
||||
}
|
||||
if (tab === 'city') {
|
||||
return draft.city && draft.city !== REGION_ALL ? draft.city : fallback;
|
||||
}
|
||||
return draft.district && draft.district !== REGION_ALL ? draft.district : fallback;
|
||||
}
|
||||
|
||||
export default function RegionPicker({
|
||||
open,
|
||||
value,
|
||||
onClose,
|
||||
onConfirm,
|
||||
levels = 3,
|
||||
}: RegionPickerProps) {
|
||||
const [draft, setDraft] = useState<RegionSelection>(value);
|
||||
const [activeTab, setActiveTab] = useState<PickerLevel>('province');
|
||||
const listRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const tabs = levels === 2 ? ALL_TABS.slice(0, 2) : ALL_TABS;
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
setDraft(normalizeRegionSelection(value));
|
||||
setActiveTab('province');
|
||||
}, [open, value]);
|
||||
const normalized = levels === 2 ? toCityLevelRegion(value) : normalizeRegionSelection(value);
|
||||
setDraft(normalized);
|
||||
setActiveTab(initialTab(value, levels));
|
||||
}, [open, value, levels]);
|
||||
|
||||
const listItems = useMemo(() => {
|
||||
if (activeTab === 'province') return getProvincesForPicker();
|
||||
@@ -45,7 +77,10 @@ export default function RegionPicker({ open, value, onClose, onConfirm }: Region
|
||||
const selectedValue =
|
||||
activeTab === 'province' ? draft.province : activeTab === 'city' ? draft.city : draft.district;
|
||||
|
||||
const canConfirm = Boolean(draft.province && draft.city && draft.district);
|
||||
const canConfirm =
|
||||
levels === 2
|
||||
? Boolean(draft.province && draft.city)
|
||||
: Boolean(draft.province && draft.city && draft.district);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
@@ -68,6 +103,15 @@ export default function RegionPicker({ open, value, onClose, onConfirm }: Region
|
||||
}
|
||||
const nextCities = getCities(province);
|
||||
const city = nextCities[0] ?? '';
|
||||
if (levels === 2) {
|
||||
setDraft({
|
||||
province,
|
||||
city,
|
||||
district: REGION_ALL,
|
||||
});
|
||||
setActiveTab('city');
|
||||
return;
|
||||
}
|
||||
const nextDistricts = getDistricts(province, city);
|
||||
setDraft({
|
||||
province,
|
||||
@@ -80,7 +124,15 @@ export default function RegionPicker({ open, value, onClose, onConfirm }: Region
|
||||
function selectCity(city: string) {
|
||||
if (city === REGION_ALL) {
|
||||
setDraft({ ...draft, city: REGION_ALL, district: REGION_ALL });
|
||||
setActiveTab('district');
|
||||
if (levels === 3) setActiveTab('district');
|
||||
return;
|
||||
}
|
||||
if (levels === 2) {
|
||||
setDraft({
|
||||
...draft,
|
||||
city,
|
||||
district: REGION_ALL,
|
||||
});
|
||||
return;
|
||||
}
|
||||
const nextDistricts = getDistricts(draft.province, city);
|
||||
@@ -110,7 +162,8 @@ export default function RegionPicker({ open, value, onClose, onConfirm }: Region
|
||||
|
||||
function handleConfirm() {
|
||||
if (!canConfirm) return;
|
||||
onConfirm(normalizeRegionSelection(draft));
|
||||
const next = levels === 2 ? toCityLevelRegion(draft) : normalizeRegionSelection(draft);
|
||||
onConfirm(next);
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -123,7 +176,7 @@ export default function RegionPicker({ open, value, onClose, onConfirm }: Region
|
||||
>
|
||||
<div className="region-picker-toolbar">
|
||||
<div className="region-picker-tabs" role="tablist">
|
||||
{TABS.map((tab) => {
|
||||
{tabs.map((tab) => {
|
||||
const disabled =
|
||||
(tab.key === 'city' && !draft.province) ||
|
||||
(tab.key === 'district' && (!draft.province || !draft.city));
|
||||
@@ -137,7 +190,7 @@ export default function RegionPicker({ open, value, onClose, onConfirm }: Region
|
||||
className={`region-picker-tab${activeTab === tab.key ? ' active' : ''}`}
|
||||
onClick={() => onTabClick(tab.key)}
|
||||
>
|
||||
{tab.label}
|
||||
{tabLabel(tab.key, draft, tab.label)}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -58,6 +58,25 @@ export function formatRegion(province: string, city: string, district: string):
|
||||
return `${province} ${city} ${district}`;
|
||||
}
|
||||
|
||||
/** 仅展示省、市两级(门店列表等场景) */
|
||||
export function formatRegionCity(province: string, city: string): string {
|
||||
if (!province) return '';
|
||||
if (province === REGION_ALL) return REGION_ALL;
|
||||
if (city === REGION_ALL) return `${province} ${REGION_ALL}`;
|
||||
if (!city) return province;
|
||||
return `${province} ${city}`;
|
||||
}
|
||||
|
||||
/** 门店筛选:固定为市级,不按区县过滤 */
|
||||
export function toCityLevelRegion(selection: RegionSelection): RegionSelection {
|
||||
const normalized = normalizeRegionSelection(selection);
|
||||
return {
|
||||
province: normalized.province,
|
||||
city: normalized.city,
|
||||
district: REGION_ALL,
|
||||
};
|
||||
}
|
||||
|
||||
export type RegionSelection = {
|
||||
province: string;
|
||||
city: string;
|
||||
|
||||
@@ -8,8 +8,9 @@ import RegionPicker from '../components/RegionPicker';
|
||||
import {
|
||||
DEFAULT_REGION,
|
||||
FALLBACK_CITY_REGION,
|
||||
formatRegion,
|
||||
formatRegionCity,
|
||||
REGION_ALL,
|
||||
toCityLevelRegion,
|
||||
type RegionSelection,
|
||||
} from '../lib/region-data';
|
||||
import { FALLBACK_CITY_CODE, resolveUserCity } from '../lib/wechat-location';
|
||||
@@ -62,7 +63,7 @@ export default function StoreListPage() {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [categoryTab, setCategoryTab] = useState<string>('全部');
|
||||
const [keyword, setKeyword] = useState('');
|
||||
const [region, setRegion] = useState<RegionSelection>(DEFAULT_REGION);
|
||||
const [region, setRegion] = useState<RegionSelection>(() => toCityLevelRegion(DEFAULT_REGION));
|
||||
const [regionPickerOpen, setRegionPickerOpen] = useState(false);
|
||||
const [filterMode, setFilterMode] = useState<'auto' | 'manual'>('auto');
|
||||
const [usedFallback, setUsedFallback] = useState(false);
|
||||
@@ -82,7 +83,7 @@ export default function StoreListPage() {
|
||||
if (filterMode !== 'auto' || geoReady) return;
|
||||
resolveUserCity().then((resolved) => {
|
||||
if (resolved) {
|
||||
setRegion(resolved.region);
|
||||
setRegion(toCityLevelRegion(resolved.region));
|
||||
}
|
||||
setGeoReady(true);
|
||||
});
|
||||
@@ -103,7 +104,7 @@ export default function StoreListPage() {
|
||||
code !== FALLBACK_CITY_CODE
|
||||
) {
|
||||
setUsedFallback(true);
|
||||
setRegion(FALLBACK_CITY_REGION);
|
||||
setRegion(toCityLevelRegion(FALLBACK_CITY_REGION));
|
||||
return null;
|
||||
}
|
||||
return data;
|
||||
@@ -138,11 +139,8 @@ export default function StoreListPage() {
|
||||
const city = s.cityName ?? '郑州市';
|
||||
if (region.province !== REGION_ALL && province !== region.province) return false;
|
||||
if (region.city !== REGION_ALL && city !== region.city) return false;
|
||||
if (region.district !== REGION_ALL && s.district !== region.district) return false;
|
||||
return true;
|
||||
});
|
||||
} else if (region.district !== REGION_ALL) {
|
||||
list = list.filter((s) => s.district === region.district);
|
||||
}
|
||||
const q = keyword.trim().toLowerCase();
|
||||
if (q) {
|
||||
@@ -156,7 +154,7 @@ export default function StoreListPage() {
|
||||
return list;
|
||||
}, [stores, categoryTab, keyword, region, cityCode]);
|
||||
|
||||
const regionLabel = formatRegion(region.province, region.city, region.district);
|
||||
const regionLabel = formatRegionCity(region.province, region.city);
|
||||
const emptyMessage =
|
||||
filterMode === 'manual' && filtered.length === 0
|
||||
? '未找到匹配门店'
|
||||
@@ -271,10 +269,11 @@ export default function StoreListPage() {
|
||||
<RegionPicker
|
||||
open={regionPickerOpen}
|
||||
value={region}
|
||||
levels={2}
|
||||
onClose={() => setRegionPickerOpen(false)}
|
||||
onConfirm={(next) => {
|
||||
setFilterMode('manual');
|
||||
setRegion(next);
|
||||
setRegion(toCityLevelRegion(next));
|
||||
setRegionPickerOpen(false);
|
||||
}}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user