首页定位问题

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,
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>
);
})}
+19
View File
@@ -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 -9
View File
@@ -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);
}}
/>