定位获取城市功能,需要微信地图的key?

This commit is contained in:
2026-07-06 14:02:07 +08:00
parent 67af6d7d53
commit c87d79109a
18 changed files with 792 additions and 70 deletions
+57 -11
View File
@@ -1,4 +1,4 @@
import { useEffect, useMemo, useState } from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { Link, useNavigate } from 'react-router-dom';
import AppImage from '@dukang/shared-ui/AppImage';
import { request } from '../lib/api';
@@ -7,10 +7,12 @@ import TabMainHeader from '../components/TabMainHeader';
import RegionPicker from '../components/RegionPicker';
import {
DEFAULT_REGION,
FALLBACK_CITY_REGION,
formatRegion,
REGION_ALL,
type RegionSelection,
} from '../lib/region-data';
import { FALLBACK_CITY_CODE, resolveUserCity } from '../lib/wechat-location';
type OpenCity = {
code: string;
@@ -62,6 +64,9 @@ export default function StoreListPage() {
const [keyword, setKeyword] = useState('');
const [region, setRegion] = useState<RegionSelection>(DEFAULT_REGION);
const [regionPickerOpen, setRegionPickerOpen] = useState(false);
const [filterMode, setFilterMode] = useState<'auto' | 'manual'>('auto');
const [usedFallback, setUsedFallback] = useState(false);
const [geoReady, setGeoReady] = useState(false);
const cityCode = useMemo(() => resolveCityCode(region, cities), [region, cities]);
@@ -74,15 +79,45 @@ export default function StoreListPage() {
}, []);
useEffect(() => {
if (filterMode !== 'auto' || geoReady) return;
resolveUserCity().then((resolved) => {
if (resolved) {
setRegion(resolved.region);
}
setGeoReady(true);
});
}, [filterMode, geoReady]);
const fetchStores = useCallback(
async (targetRegion: RegionSelection, allowFallback: boolean) => {
const code = resolveCityCode(targetRegion, cities);
const qs = code ? `?cityCode=${encodeURIComponent(code)}` : '';
const data = await request<StoreItem[]>('USER_H5', `/stores${qs}`).catch(() => [] as StoreItem[]);
if (
allowFallback &&
filterMode === 'auto' &&
data.length === 0 &&
!usedFallback &&
code &&
code !== FALLBACK_CITY_CODE
) {
setUsedFallback(true);
setRegion(FALLBACK_CITY_REGION);
return null;
}
return data;
},
[cities, filterMode, usedFallback],
);
useEffect(() => {
if (filterMode === 'auto' && !geoReady) return;
let cancelled = false;
setLoading(true);
const qs = cityCode ? `?cityCode=${encodeURIComponent(cityCode)}` : '';
request<StoreItem[]>('USER_H5', `/stores${qs}`)
fetchStores(region, true)
.then((data) => {
if (!cancelled) setStores(data);
})
.catch(() => {
if (!cancelled) setStores([]);
if (!cancelled && data) setStores(data);
})
.finally(() => {
if (!cancelled) setLoading(false);
@@ -90,7 +125,7 @@ export default function StoreListPage() {
return () => {
cancelled = true;
};
}, [cityCode]);
}, [region, cities, filterMode, geoReady, fetchStores, usedFallback]);
const filtered = useMemo(() => {
let list = stores;
@@ -122,6 +157,10 @@ export default function StoreListPage() {
}, [stores, categoryTab, keyword, region, cityCode]);
const regionLabel = formatRegion(region.province, region.city, region.district);
const emptyMessage =
filterMode === 'manual' && filtered.length === 0
? '未找到匹配门店'
: '暂无门店';
return (
<div className="page store-page">
@@ -144,7 +183,10 @@ export default function StoreListPage() {
type="search"
placeholder="搜索门店名称或地址"
value={keyword}
onChange={(e) => setKeyword(e.target.value)}
onChange={(e) => {
setFilterMode('manual');
setKeyword(e.target.value);
}}
/>
</div>
</div>
@@ -156,7 +198,10 @@ export default function StoreListPage() {
key={tab}
type="button"
className={`store-category-tab${categoryTab === tab ? ' active' : ''}`}
onClick={() => setCategoryTab(tab)}
onClick={() => {
setFilterMode('manual');
setCategoryTab(tab);
}}
>
{tab}
</button>
@@ -215,7 +260,7 @@ export default function StoreListPage() {
))}
{!loading && filtered.length === 0 && (
<div className="store-empty">{stores.length === 0 ? '暂无门店' : '未找到匹配门店'}</div>
<div className="store-empty">{emptyMessage}</div>
)}
{!loading && filtered.length > 0 && (
@@ -228,6 +273,7 @@ export default function StoreListPage() {
value={region}
onClose={() => setRegionPickerOpen(false)}
onConfirm={(next) => {
setFilterMode('manual');
setRegion(next);
setRegionPickerOpen(false);
}}