import { MyBetaModalFormProps } from '@/common'; import { MyModal } from '@/components/MyModal'; import L from 'leaflet'; import 'leaflet/dist/leaflet.css'; import { useEffect, useRef, useState } from 'react'; // 修复 Leaflet 图标问题 delete L.Icon.Default.prototype._getIconUrl; L.Icon.Default.mergeOptions({ iconRetinaUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-icon-2x.png', iconUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-icon.png', shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-shadow.png', }); // 创建自定义图标 const createCustomIcon = (color = 'red') => { return L.divIcon({ className: 'custom-marker', html: `
`, iconSize: [20, 20], iconAnchor: [10, 10], }); }; interface LocationResult { lat: number; lng: number; address: string; name?: string; } export default function ModalsMapLeaflet( props: MyBetaModalFormProps & { onChange?: (location?: LocationResult) => void; }, ) { const modalRef = useRef(); const mapRef = useRef(null); const markersRef = useRef(null); const [searchText, setSearchText] = useState(''); const [searchResults, setSearchResults] = useState([]); const [selectedLocation, setSelectedLocation] = useState(null); const [isLoading, setIsLoading] = useState(false); const [mapReady, setMapReady] = useState(false); const mapInitializedRef = useRef(false); // 天地图配置 const TIANDITU_KEY = '4ce26ecef55ae1ec47910a72a098efc0'; const tiandituUrls = { vector: `https://t0.tianditu.gov.cn/vec_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=vec&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=${TIANDITU_KEY}`, label: `https://t0.tianditu.gov.cn/cva_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cva&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=${TIANDITU_KEY}`, }; // 清理地图 useEffect(() => { return () => { if (mapRef.current) { mapRef.current.remove(); mapRef.current = null; mapInitializedRef.current = false; setMapReady(false); } }; }, []); // 初始化地图 const initializeMap = () => { // 确保DOM已经渲染 setTimeout(() => { const mapContainer = document.getElementById('map'); if (!mapContainer) { console.error('地图容器不存在'); return; } // 如果地图已经存在,先清理 if (mapRef.current) { mapRef.current.remove(); mapRef.current = null; mapInitializedRef.current = false; } // 清理容器 while (mapContainer.firstChild) { mapContainer.removeChild(mapContainer.firstChild); } try { console.log('开始初始化地图...'); const map = L.map('map', { center: [30.258134, 120.19382669582967], zoom: 12, zoomControl: true, attributionControl: false, }); mapRef.current = map; mapInitializedRef.current = true; // 添加天地图图层 const baseLayer = L.tileLayer(tiandituUrls.vector, { attribution: '© 天地图', minZoom: 3, maxZoom: 18, }).addTo(map); const labelLayer = L.tileLayer(tiandituUrls.label, { minZoom: 3, maxZoom: 18, }).addTo(map); // 创建标记图层组 markersRef.current = L.layerGroup().addTo(map); // 添加地图点击事件 map.on('click', (e: L.LeafletMouseEvent) => { handleMapClick(e.latlng.lat, e.latlng.lng); }); // 添加缩放和比例尺控件 L.control.scale({ imperial: false }).addTo(map); // 地图加载完成 map.whenReady(() => { console.log('地图加载完成'); setMapReady(true); }); console.log('地图初始化成功'); } catch (error) { console.error('地图初始化失败:', error); mapInitializedRef.current = false; setMapReady(false); } }, 300); }; // 使用天地图 REST API 进行搜索 const searchWithTiandituAPI = async ( keyword: string, ): Promise => { try { const url = `https://api.tianditu.gov.cn/v2/search?postStr={ "keyWord": "${encodeURIComponent(keyword)}", "level": "12", "mapBound": "115.38333,39.36667,117.68333,41.23333", "queryType": "1", "start": "0", "count": "20" }&type=query&tk=${TIANDITU_KEY}`; console.log('搜索URL:', url); const response = await fetch(url, { method: 'GET', headers: { 'Content-Type': 'application/json', }, }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); console.log('天地图搜索返回数据:', data); // 根据不同的结果类型处理 const resultType = data.resultType; switch (resultType) { case 1: // POI点数据 if (data.pois && Array.isArray(data.pois)) { return data.pois.map((poi: any) => ({ name: poi.name, address: poi.address, lng: parseFloat(poi.lonlat.split(',')[0]), lat: parseFloat(poi.lonlat.split(',')[1]), })); } break; case 2: // 推荐城市 // 这里可以处理推荐城市,暂时返回空数组 console.log('推荐城市结果:', data.prompt); return []; case 3: // 行政区划 if (data.area) { // 将行政区划转换为一个位置结果 const area = data.area; const [lng, lat] = area.lonlat.split(',').map(parseFloat); return [ { name: area.name, address: area.name, lng: lng, lat: lat, }, ]; } break; case 4: // 建议词 console.log('建议词结果:', data.prompt); return []; case 5: // 公交信息 console.log('公交信息结果:', data.lineData); return []; default: console.log('未知结果类型:', resultType); } // 如果没有找到有效结果,尝试使用提示信息 if (data.prompt && data.prompt.length > 0) { const prompt = data.prompt[0]; if (prompt.admins && prompt.admins.length > 0) { const admin = prompt.admins[0]; return [ { name: admin.name, address: admin.name, lng: parseFloat(admin.lonlat.split(',')[0]), lat: parseFloat(admin.lonlat.split(',')[1]), }, ]; } } throw new Error('未找到相关地点'); } catch (error) { console.error('天地图搜索API调用失败:', error); throw error; } }; // 搜索地点 - 使用天地图 REST API const handleSearch = async () => { if (!searchText.trim()) { alert('请输入搜索关键词'); return; } // 检查天地图密钥 if (!TIANDITU_KEY) { alert('请配置有效的天地图密钥'); return; } if (!mapReady) { alert('地图尚未准备好,请稍后重试'); return; } setIsLoading(true); try { console.log('开始天地图搜索:', searchText); // 清空之前的结果 setSearchResults([]); if (markersRef.current) { markersRef.current.clearLayers(); } // 执行搜索 const results = await searchWithTiandituAPI(searchText); console.log('搜索成功,结果数量:', results.length); if (results.length === 0) { alert('未找到相关地点'); return; } setSearchResults(results); // 在地图上显示搜索结果 const bounds = L.latLngBounds([]); results.forEach((result, index) => { bounds.extend([result.lat, result.lng]); addSearchResultMarker(result, index, results.length); }); // 调整地图视野 if (mapRef.current) { mapRef.current.fitBounds(bounds.pad(0.1)); } } catch (error: any) { console.error('搜索失败:', error); alert(`搜索失败: ${error.message || '未知错误'}`); } finally { setIsLoading(false); } }; // 添加搜索结果标记 const addSearchResultMarker = ( location: LocationResult, index: number, total: number, ) => { if (!markersRef.current) return; const marker = L.marker([location.lat, location.lng], { icon: createCustomIcon(index === 0 ? '#1890ff' : '#52c41a'), }).addTo(markersRef.current); marker.bindPopup(`

${ location.name }

地址: ${ location.address }

经纬度: ${location.lat.toFixed( 6, )}, ${location.lng.toFixed(6)}

`); // 第一个结果自动打开弹窗 if (index === 0) { setTimeout(() => marker.openPopup(), 500); } // 将选择函数挂载到window对象 (window as any).selectSearchResult = (index: number) => { console.log(location, searchResults, 'selectSearchResult'); if (searchResults && searchResults[index]) { handleSelectResult(searchResults[index]); } else { handleConfirmLocation(location); } }; }; // 地图点击事件处理 const handleMapClick = async (lat: number, lng: number) => { try { setIsLoading(true); // 使用天地图逆地理编码获取真实地址 const address = await reverseGeocodeWithTianditu(lat, lng); const location: LocationResult = { lat, lng, address, name: '点击选择的位置', }; setSelectedLocation(location); addMarkerToMap(location); } catch (error) { console.error('获取地址信息失败:', error); } finally { setIsLoading(false); } }; // 使用天地图逆地理编码服务 const reverseGeocodeWithTianditu = async ( lat: number, lng: number, ): Promise => { try { const url = `https://api.tianditu.gov.cn/geocoder?postStr={'lon':${lng},'lat':${lat},'ver':1}&type=geocode&tk=${TIANDITU_KEY}`; const response = await fetch(url); if (!response.ok) { throw new Error('逆地理编码请求失败'); } const data = await response.json(); if (data.status === '0' && data.result) { return data.result.formatted_address; } return `位置 ${lat.toFixed(6)}, ${lng.toFixed(6)}`; } catch (error) { console.error('天地图逆地理编码失败:', error); return `位置 ${lat.toFixed(6)}, ${lng.toFixed(6)}`; } }; // 添加标记到地图 const addMarkerToMap = (location: LocationResult) => { if (!mapRef.current || !markersRef.current) return; markersRef.current.clearLayers(); const marker = L.marker([location.lat, location.lng], { icon: createCustomIcon('#1890ff'), }).addTo(markersRef.current); marker .bindPopup( `

位置信息

地址: ${location.address}

经纬度: ${location.lat.toFixed( 6, )}, ${location.lng.toFixed(6)}

`, ) .openPopup(); (window as any).confirmSelection = () => { console.log(location, 'confirmSelection'); handleConfirmLocation(location); }; mapRef.current.setView([location.lat, location.lng], 15); }; // 选择搜索结果 const handleSelectResult = (result: LocationResult) => { setSelectedLocation(result); addMarkerToMap(result); setSearchResults([]); setSearchText(result.name || result.address); }; // 确认选择位置 const handleConfirmLocation = (location?: LocationResult) => { const finalLocation = location || selectedLocation; if (!finalLocation) return; props?.onChange?.(location); // if (props.onConfirm) { // props.onConfirm(finalLocation); // } if (modalRef.current) { modalRef.current.close(); } }; // 清除选择 const handleClearSelection = () => { setSelectedLocation(null); setSearchText(''); setSearchResults([]); if (markersRef.current) { markersRef.current.clearLayers(); } if (mapRef.current) { mapRef.current.setView([30.258134, 120.19382669582967], 12); } }; return ( { console.log('模态框打开,初始化地图...'); setTimeout(initializeMap, 500); }} node={
{/* 状态提示 */} {!mapReady && (
地图初始化中...
)} {/* 搜索区域 */}
setSearchText(e.target.value)} placeholder="请输入地点名称进行搜索(如:杭州中大广场...)" style={{ flex: 1, padding: '8px 12px', border: '1px solid #d9d9d9', borderRadius: '4px', fontSize: '14px', }} onKeyPress={(e) => e.key === 'Enter' && handleSearch()} />
{/* 搜索结果 */} {searchResults.length > 0 && (
找到 {searchResults.length} 个结果
{searchResults.map((result, index) => (
handleSelectResult(result)} style={{ padding: '10px', borderBottom: '1px solid #f0f0f0', cursor: 'pointer', backgroundColor: index === 0 ? '#e6f7ff' : '#fafafa', }} onMouseEnter={(e) => (e.currentTarget.style.backgroundColor = '#e6f7ff') } onMouseLeave={(e) => (e.currentTarget.style.backgroundColor = index === 0 ? '#e6f7ff' : '#fafafa') } >
{result.name || '未知地点'}
{result.address}
经纬度: {result.lat.toFixed(6)}, {result.lng.toFixed(6)}
))}
)}
{/* 地图容器 */}
{/* 选择信息显示 */} {selectedLocation && (

已选择位置

地址: {selectedLocation.address}

经纬度: {selectedLocation.lat.toFixed(6)},{' '} {selectedLocation.lng.toFixed(6)}

)} {/* 使用说明 */}
使用说明:
  • 等待地图加载完成后再进行搜索
  • 在搜索框输入地点名称进行真实搜索(使用天地图搜索API)
  • 直接点击地图选择位置(使用天地图逆地理编码)
  • 搜索结果会自动在地图上标记并定位
  • 点击标记弹出窗口可确认选择
  • 经纬度精度可达小数点后14位
{!TIANDITU_KEY && (
⚠️ 请配置有效的天地图密钥
)}
} > ); }