import { useEffect, useRef, useState } from 'react'; import { Button, Empty, Input, List, Modal, Space, Spin, Typography, message } from 'antd'; import { request } from '../lib/api'; import { placeToPicked, type LbsPlaceItem, type TencentPickedLocation, } from '../lib/tencentLocPicker'; type Props = { open: boolean; onClose: () => void; onPick: (loc: TencentPickedLocation) => void; latitude?: number | null; longitude?: number | null; /** 城市名,提升搜索相关性 */ region?: string | null; }; function hasCoords(lat: unknown, lng: unknown): lat is number { const a = typeof lat === 'number' ? lat : Number(lat); const b = typeof lng === 'number' ? lng : Number(lng); return Number.isFinite(a) && Number.isFinite(b) && !(a === 0 && b === 0); } export default function TencentLocPickerModal({ open, onClose, onPick, latitude, longitude, region, }: Props) { const [keyword, setKeyword] = useState(''); const [items, setItems] = useState([]); const [loading, setLoading] = useState(false); const [pending, setPending] = useState(null); const [hint, setHint] = useState('输入地点名称搜索,或加载附近地点'); const seqRef = useRef(0); useEffect(() => { if (!open) { setKeyword(''); setItems([]); setPending(null); setHint('输入地点名称搜索,或加载附近地点'); return; } const lat = latitude != null ? Number(latitude) : NaN; const lng = longitude != null ? Number(longitude) : NaN; if (hasCoords(lat, lng)) { void loadNearby(lat, lng); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [open]); async function loadNearby(lat: number, lng: number) { const seq = ++seqRef.current; setLoading(true); setHint('正在加载附近地点…'); try { const res = await request<{ items: LbsPlaceItem[] }>( `/common/lbs/nearby?lat=${encodeURIComponent(String(lat))}&lng=${encodeURIComponent(String(lng))}`, ); if (seq !== seqRef.current) return; setItems(res.items ?? []); setHint(res.items?.length ? `附近 ${res.items.length} 个地点,点击选择` : '附近暂无地点,请搜索'); } catch (e) { if (seq !== seqRef.current) return; const msg = e instanceof Error ? e.message : '加载附近地点失败'; setItems([]); setHint(msg); message.error(msg); } finally { if (seq === seqRef.current) setLoading(false); } } async function runSearch(q: string) { const trimmed = q.trim(); if (!trimmed) { message.warning('请输入搜索关键词'); return; } const seq = ++seqRef.current; setLoading(true); setHint('搜索中…'); try { const params = new URLSearchParams({ keyword: trimmed }); if (region?.trim()) params.set('region', region.trim()); const lat = latitude != null ? Number(latitude) : NaN; const lng = longitude != null ? Number(longitude) : NaN; if (Number.isFinite(lat) && Number.isFinite(lng)) { params.set('lat', String(lat)); params.set('lng', String(lng)); } const res = await request<{ items: LbsPlaceItem[] }>(`/common/lbs/suggest?${params.toString()}`); if (seq !== seqRef.current) return; setItems(res.items ?? []); setHint(res.items?.length ? `找到 ${res.items.length} 个结果,点击选择` : '无匹配结果,换个关键词试试'); } catch (e) { if (seq !== seqRef.current) return; const msg = e instanceof Error ? e.message : '搜索失败'; setItems([]); setHint(msg); message.error(msg); } finally { if (seq === seqRef.current) setLoading(false); } } function useBrowserLocation() { if (!navigator.geolocation) { message.error('当前浏览器不支持定位'); return; } setLoading(true); navigator.geolocation.getCurrentPosition( (pos) => { const lat = pos.coords.latitude; const lng = pos.coords.longitude; setPending({ latitude: lat, longitude: lng, name: '当前位置' }); void loadNearby(lat, lng); }, () => { setLoading(false); message.error('定位失败,请检查浏览器定位权限'); }, { enableHighAccuracy: true, timeout: 12000 }, ); } function confirmPick() { if (!pending) { message.warning('请先从列表中选择一个地点'); return; } onPick(pending); onClose(); } return ( {pending ? `${pending.latitude.toFixed(6)}, ${pending.longitude.toFixed(6)}${ pending.name ? ` · ${pending.name}` : '' }` : '搜索或选择附近地点后确认'} } > setKeyword(e.target.value)} onPressEnter={() => void runSearch(keyword)} /> {hint}
{loading && !items.length ? (
) : items.length ? ( { const active = pending?.latitude === item.latitude && pending?.longitude === item.longitude; return ( setPending(placeToPicked(item))} > {item.address}
{item.latitude.toFixed(6)}, {item.longitude.toFixed(6)} } />
); }} /> ) : ( )}
); }