首页定位问题

This commit is contained in:
2026-07-09 21:12:28 +08:00
parent b506417f5e
commit 5e96008ac4
3 changed files with 90 additions and 19 deletions
+63 -10
View File
@@ -7,6 +7,7 @@ import {
getDistrictsForPicker, getDistrictsForPicker,
getProvincesForPicker, getProvincesForPicker,
normalizeRegionSelection, normalizeRegionSelection,
toCityLevelRegion,
type RegionSelection, type RegionSelection,
} from '../lib/region-data'; } from '../lib/region-data';
@@ -15,26 +16,57 @@ type RegionPickerProps = {
value: RegionSelection; value: RegionSelection;
onClose: () => void; onClose: () => void;
onConfirm: (region: RegionSelection) => void; onConfirm: (region: RegionSelection) => void;
/** 2 = 仅省/市(门店列表);3 = 省/市/区(地址等) */
levels?: 2 | 3;
}; };
type PickerLevel = 'province' | 'city' | 'district'; type PickerLevel = 'province' | 'city' | 'district';
const TABS: Array<{ key: PickerLevel; label: string }> = [ const ALL_TABS: Array<{ key: PickerLevel; label: string }> = [
{ key: 'province', label: '省份' }, { key: 'province', label: '省份' },
{ key: 'city', label: '城市' }, { key: 'city', label: '城市' },
{ key: 'district', 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 [draft, setDraft] = useState<RegionSelection>(value);
const [activeTab, setActiveTab] = useState<PickerLevel>('province'); const [activeTab, setActiveTab] = useState<PickerLevel>('province');
const listRef = useRef<HTMLDivElement>(null); const listRef = useRef<HTMLDivElement>(null);
const tabs = levels === 2 ? ALL_TABS.slice(0, 2) : ALL_TABS;
useEffect(() => { useEffect(() => {
if (!open) return; if (!open) return;
setDraft(normalizeRegionSelection(value)); const normalized = levels === 2 ? toCityLevelRegion(value) : normalizeRegionSelection(value);
setActiveTab('province'); setDraft(normalized);
}, [open, value]); setActiveTab(initialTab(value, levels));
}, [open, value, levels]);
const listItems = useMemo(() => { const listItems = useMemo(() => {
if (activeTab === 'province') return getProvincesForPicker(); if (activeTab === 'province') return getProvincesForPicker();
@@ -45,7 +77,10 @@ export default function RegionPicker({ open, value, onClose, onConfirm }: Region
const selectedValue = const selectedValue =
activeTab === 'province' ? draft.province : activeTab === 'city' ? draft.city : draft.district; 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(() => { useEffect(() => {
if (!open) return; if (!open) return;
@@ -68,6 +103,15 @@ export default function RegionPicker({ open, value, onClose, onConfirm }: Region
} }
const nextCities = getCities(province); const nextCities = getCities(province);
const city = nextCities[0] ?? ''; const city = nextCities[0] ?? '';
if (levels === 2) {
setDraft({
province,
city,
district: REGION_ALL,
});
setActiveTab('city');
return;
}
const nextDistricts = getDistricts(province, city); const nextDistricts = getDistricts(province, city);
setDraft({ setDraft({
province, province,
@@ -80,7 +124,15 @@ export default function RegionPicker({ open, value, onClose, onConfirm }: Region
function selectCity(city: string) { function selectCity(city: string) {
if (city === REGION_ALL) { if (city === REGION_ALL) {
setDraft({ ...draft, city: REGION_ALL, district: 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; return;
} }
const nextDistricts = getDistricts(draft.province, city); const nextDistricts = getDistricts(draft.province, city);
@@ -110,7 +162,8 @@ export default function RegionPicker({ open, value, onClose, onConfirm }: Region
function handleConfirm() { function handleConfirm() {
if (!canConfirm) return; if (!canConfirm) return;
onConfirm(normalizeRegionSelection(draft)); const next = levels === 2 ? toCityLevelRegion(draft) : normalizeRegionSelection(draft);
onConfirm(next);
} }
return ( return (
@@ -123,7 +176,7 @@ export default function RegionPicker({ open, value, onClose, onConfirm }: Region
> >
<div className="region-picker-toolbar"> <div className="region-picker-toolbar">
<div className="region-picker-tabs" role="tablist"> <div className="region-picker-tabs" role="tablist">
{TABS.map((tab) => { {tabs.map((tab) => {
const disabled = const disabled =
(tab.key === 'city' && !draft.province) || (tab.key === 'city' && !draft.province) ||
(tab.key === 'district' && (!draft.province || !draft.city)); (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' : ''}`} className={`region-picker-tab${activeTab === tab.key ? ' active' : ''}`}
onClick={() => onTabClick(tab.key)} onClick={() => onTabClick(tab.key)}
> >
{tab.label} {tabLabel(tab.key, draft, tab.label)}
</button> </button>
); );
})} })}
+19
View File
@@ -58,6 +58,25 @@ export function formatRegion(province: string, city: string, district: string):
return `${province} ${city} ${district}`; 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 = { export type RegionSelection = {
province: string; province: string;
city: string; city: string;
+8 -9
View File
@@ -8,8 +8,9 @@ import RegionPicker from '../components/RegionPicker';
import { import {
DEFAULT_REGION, DEFAULT_REGION,
FALLBACK_CITY_REGION, FALLBACK_CITY_REGION,
formatRegion, formatRegionCity,
REGION_ALL, REGION_ALL,
toCityLevelRegion,
type RegionSelection, type RegionSelection,
} from '../lib/region-data'; } from '../lib/region-data';
import { FALLBACK_CITY_CODE, resolveUserCity } from '../lib/wechat-location'; import { FALLBACK_CITY_CODE, resolveUserCity } from '../lib/wechat-location';
@@ -62,7 +63,7 @@ export default function StoreListPage() {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [categoryTab, setCategoryTab] = useState<string>('全部'); const [categoryTab, setCategoryTab] = useState<string>('全部');
const [keyword, setKeyword] = useState(''); 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 [regionPickerOpen, setRegionPickerOpen] = useState(false);
const [filterMode, setFilterMode] = useState<'auto' | 'manual'>('auto'); const [filterMode, setFilterMode] = useState<'auto' | 'manual'>('auto');
const [usedFallback, setUsedFallback] = useState(false); const [usedFallback, setUsedFallback] = useState(false);
@@ -82,7 +83,7 @@ export default function StoreListPage() {
if (filterMode !== 'auto' || geoReady) return; if (filterMode !== 'auto' || geoReady) return;
resolveUserCity().then((resolved) => { resolveUserCity().then((resolved) => {
if (resolved) { if (resolved) {
setRegion(resolved.region); setRegion(toCityLevelRegion(resolved.region));
} }
setGeoReady(true); setGeoReady(true);
}); });
@@ -103,7 +104,7 @@ export default function StoreListPage() {
code !== FALLBACK_CITY_CODE code !== FALLBACK_CITY_CODE
) { ) {
setUsedFallback(true); setUsedFallback(true);
setRegion(FALLBACK_CITY_REGION); setRegion(toCityLevelRegion(FALLBACK_CITY_REGION));
return null; return null;
} }
return data; return data;
@@ -138,11 +139,8 @@ export default function StoreListPage() {
const city = s.cityName ?? '郑州市'; const city = s.cityName ?? '郑州市';
if (region.province !== REGION_ALL && province !== region.province) return false; if (region.province !== REGION_ALL && province !== region.province) return false;
if (region.city !== REGION_ALL && city !== region.city) 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; return true;
}); });
} else if (region.district !== REGION_ALL) {
list = list.filter((s) => s.district === region.district);
} }
const q = keyword.trim().toLowerCase(); const q = keyword.trim().toLowerCase();
if (q) { if (q) {
@@ -156,7 +154,7 @@ export default function StoreListPage() {
return list; return list;
}, [stores, categoryTab, keyword, region, cityCode]); }, [stores, categoryTab, keyword, region, cityCode]);
const regionLabel = formatRegion(region.province, region.city, region.district); const regionLabel = formatRegionCity(region.province, region.city);
const emptyMessage = const emptyMessage =
filterMode === 'manual' && filtered.length === 0 filterMode === 'manual' && filtered.length === 0
? '未找到匹配门店' ? '未找到匹配门店'
@@ -271,10 +269,11 @@ export default function StoreListPage() {
<RegionPicker <RegionPicker
open={regionPickerOpen} open={regionPickerOpen}
value={region} value={region}
levels={2}
onClose={() => setRegionPickerOpen(false)} onClose={() => setRegionPickerOpen(false)}
onConfirm={(next) => { onConfirm={(next) => {
setFilterMode('manual'); setFilterMode('manual');
setRegion(next); setRegion(toCityLevelRegion(next));
setRegionPickerOpen(false); setRegionPickerOpen(false);
}} }}
/> />