首页定位问题
This commit is contained in:
@@ -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>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -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,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);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in New Issue
Block a user