716 lines
21 KiB
TypeScript
716 lines
21 KiB
TypeScript
|
|
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: `
|
|||
|
|
<div style="
|
|||
|
|
background-color: ${color};
|
|||
|
|
width: 20px;
|
|||
|
|
height: 20px;
|
|||
|
|
border-radius: 50%;
|
|||
|
|
border: 3px solid white;
|
|||
|
|
box-shadow: 0 2px 6px rgba(0,0,0,0.3);
|
|||
|
|
"></div>
|
|||
|
|
`,
|
|||
|
|
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<any>();
|
|||
|
|
const mapRef = useRef<L.Map | null>(null);
|
|||
|
|
const markersRef = useRef<L.LayerGroup | null>(null);
|
|||
|
|
const [searchText, setSearchText] = useState('');
|
|||
|
|
const [searchResults, setSearchResults] = useState<LocationResult[]>([]);
|
|||
|
|
const [selectedLocation, setSelectedLocation] =
|
|||
|
|
useState<LocationResult | null>(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<LocationResult[]> => {
|
|||
|
|
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(`
|
|||
|
|
<div style="min-width: 220px;">
|
|||
|
|
<h4 style="margin: 0 0 8px 0; color: #1890ff; font-size: 14px;">${
|
|||
|
|
location.name
|
|||
|
|
}</h4>
|
|||
|
|
<p style="margin: 4px 0; font-size: 12px;"><strong>地址:</strong> ${
|
|||
|
|
location.address
|
|||
|
|
}</p>
|
|||
|
|
<p style="margin: 4px 0; font-size: 12px;"><strong>经纬度:</strong> ${location.lat.toFixed(
|
|||
|
|
6,
|
|||
|
|
)}, ${location.lng.toFixed(6)}</p>
|
|||
|
|
<div style="margin-top: 10px; display: flex; gap: 8px;">
|
|||
|
|
<button onclick="window.selectSearchResult(${index})" style="
|
|||
|
|
padding: 6px 12px;
|
|||
|
|
background: #1890ff;
|
|||
|
|
color: white;
|
|||
|
|
border: none;
|
|||
|
|
border-radius: 4px;
|
|||
|
|
cursor: pointer;
|
|||
|
|
font-size: 12px;
|
|||
|
|
flex: 1;
|
|||
|
|
">选择此位置</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
`);
|
|||
|
|
|
|||
|
|
// 第一个结果自动打开弹窗
|
|||
|
|
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<string> => {
|
|||
|
|
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(
|
|||
|
|
`
|
|||
|
|
<div style="min-width: 200px;">
|
|||
|
|
<h4 style="margin: 0 0 8px 0; color: #1890ff;">位置信息</h4>
|
|||
|
|
<p style="margin: 4px 0;"><strong>地址:</strong> ${location.address}</p>
|
|||
|
|
<p style="margin: 4px 0;"><strong>经纬度:</strong> ${location.lat.toFixed(
|
|||
|
|
6,
|
|||
|
|
)}, ${location.lng.toFixed(6)}</p>
|
|||
|
|
<button onclick="window.confirmSelection()" style="
|
|||
|
|
margin-top: 8px;
|
|||
|
|
padding: 6px 12px;
|
|||
|
|
background: #1890ff;
|
|||
|
|
color: white;
|
|||
|
|
border: none;
|
|||
|
|
border-radius: 4px;
|
|||
|
|
cursor: pointer;
|
|||
|
|
">选择此位置</button>
|
|||
|
|
</div>
|
|||
|
|
`,
|
|||
|
|
)
|
|||
|
|
.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 (
|
|||
|
|
<MyModal
|
|||
|
|
title={'获取经纬度'}
|
|||
|
|
width="1000px"
|
|||
|
|
myRef={modalRef}
|
|||
|
|
size="middle"
|
|||
|
|
onOpen={() => {
|
|||
|
|
console.log('模态框打开,初始化地图...');
|
|||
|
|
setTimeout(initializeMap, 500);
|
|||
|
|
}}
|
|||
|
|
node={
|
|||
|
|
<div style={{ padding: '20px' }}>
|
|||
|
|
{/* 状态提示 */}
|
|||
|
|
{!mapReady && (
|
|||
|
|
<div
|
|||
|
|
style={{
|
|||
|
|
padding: '10px',
|
|||
|
|
backgroundColor: '#fffbe6',
|
|||
|
|
border: '1px solid #ffe58f',
|
|||
|
|
borderRadius: '4px',
|
|||
|
|
marginBottom: '10px',
|
|||
|
|
textAlign: 'center',
|
|||
|
|
}}
|
|||
|
|
>
|
|||
|
|
地图初始化中...
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
|
|||
|
|
{/* 搜索区域 */}
|
|||
|
|
<div style={{ marginBottom: '20px' }}>
|
|||
|
|
<div style={{ display: 'flex', gap: '10px', marginBottom: '10px' }}>
|
|||
|
|
<input
|
|||
|
|
type="text"
|
|||
|
|
value={searchText}
|
|||
|
|
onChange={(e) => 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()}
|
|||
|
|
/>
|
|||
|
|
<button
|
|||
|
|
onClick={handleSearch}
|
|||
|
|
disabled={isLoading || !mapReady}
|
|||
|
|
style={{
|
|||
|
|
padding: '8px 16px',
|
|||
|
|
background: !isLoading && mapReady ? '#1890ff' : '#ccc',
|
|||
|
|
color: 'white',
|
|||
|
|
border: 'none',
|
|||
|
|
borderRadius: '4px',
|
|||
|
|
cursor: isLoading || !mapReady ? 'not-allowed' : 'pointer',
|
|||
|
|
}}
|
|||
|
|
>
|
|||
|
|
{isLoading ? '搜索中...' : !mapReady ? '地图加载中' : '搜索'}
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* 搜索结果 */}
|
|||
|
|
{searchResults.length > 0 && (
|
|||
|
|
<div
|
|||
|
|
style={{
|
|||
|
|
border: '1px solid #e8e8e8',
|
|||
|
|
borderRadius: '4px',
|
|||
|
|
maxHeight: '200px',
|
|||
|
|
overflowY: 'auto',
|
|||
|
|
backgroundColor: '#fafafa',
|
|||
|
|
}}
|
|||
|
|
>
|
|||
|
|
<div
|
|||
|
|
style={{
|
|||
|
|
padding: '8px',
|
|||
|
|
fontSize: '12px',
|
|||
|
|
color: '#666',
|
|||
|
|
borderBottom: '1px solid #e8e8e8',
|
|||
|
|
}}
|
|||
|
|
>
|
|||
|
|
找到 {searchResults.length} 个结果
|
|||
|
|
</div>
|
|||
|
|
{searchResults.map((result, index) => (
|
|||
|
|
<div
|
|||
|
|
key={index}
|
|||
|
|
onClick={() => 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')
|
|||
|
|
}
|
|||
|
|
>
|
|||
|
|
<div
|
|||
|
|
style={{
|
|||
|
|
fontWeight: 'bold',
|
|||
|
|
marginBottom: '4px',
|
|||
|
|
color: '#1890ff',
|
|||
|
|
}}
|
|||
|
|
>
|
|||
|
|
{result.name || '未知地点'}
|
|||
|
|
</div>
|
|||
|
|
<div style={{ fontSize: '12px', color: '#666' }}>
|
|||
|
|
{result.address}
|
|||
|
|
</div>
|
|||
|
|
<div
|
|||
|
|
style={{
|
|||
|
|
fontSize: '11px',
|
|||
|
|
color: '#999',
|
|||
|
|
marginTop: '2px',
|
|||
|
|
}}
|
|||
|
|
>
|
|||
|
|
经纬度: {result.lat.toFixed(6)}, {result.lng.toFixed(6)}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
))}
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* 地图容器 */}
|
|||
|
|
<div
|
|||
|
|
id="map"
|
|||
|
|
style={{
|
|||
|
|
height: '400px',
|
|||
|
|
border: '1px solid #e8e8e8',
|
|||
|
|
borderRadius: '4px',
|
|||
|
|
}}
|
|||
|
|
/>
|
|||
|
|
|
|||
|
|
{/* 选择信息显示 */}
|
|||
|
|
{selectedLocation && (
|
|||
|
|
<div
|
|||
|
|
style={{
|
|||
|
|
marginTop: '20px',
|
|||
|
|
padding: '15px',
|
|||
|
|
backgroundColor: '#f6ffed',
|
|||
|
|
border: '1px solid #b7eb8f',
|
|||
|
|
borderRadius: '4px',
|
|||
|
|
}}
|
|||
|
|
>
|
|||
|
|
<h4 style={{ margin: '0 0 10px 0', color: '#52c41a' }}>
|
|||
|
|
已选择位置
|
|||
|
|
</h4>
|
|||
|
|
<p style={{ margin: '4px 0' }}>
|
|||
|
|
<strong>地址:</strong> {selectedLocation.address}
|
|||
|
|
</p>
|
|||
|
|
<p style={{ margin: '4px 0' }}>
|
|||
|
|
<strong>经纬度:</strong> {selectedLocation.lat.toFixed(6)},{' '}
|
|||
|
|
{selectedLocation.lng.toFixed(6)}
|
|||
|
|
</p>
|
|||
|
|
<div style={{ marginTop: '10px', display: 'flex', gap: '10px' }}>
|
|||
|
|
<button
|
|||
|
|
onClick={() => handleConfirmLocation(selectedLocation)}
|
|||
|
|
style={{
|
|||
|
|
padding: '8px 16px',
|
|||
|
|
background: '#52c41a',
|
|||
|
|
color: 'white',
|
|||
|
|
border: 'none',
|
|||
|
|
borderRadius: '4px',
|
|||
|
|
cursor: 'pointer',
|
|||
|
|
fontWeight: 'bold',
|
|||
|
|
}}
|
|||
|
|
>
|
|||
|
|
确认选择
|
|||
|
|
</button>
|
|||
|
|
<button
|
|||
|
|
onClick={handleClearSelection}
|
|||
|
|
style={{
|
|||
|
|
padding: '8px 16px',
|
|||
|
|
background: '#ff4d4f',
|
|||
|
|
color: 'white',
|
|||
|
|
border: 'none',
|
|||
|
|
borderRadius: '4px',
|
|||
|
|
cursor: 'pointer',
|
|||
|
|
}}
|
|||
|
|
>
|
|||
|
|
清除选择
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
|
|||
|
|
{/* 使用说明 */}
|
|||
|
|
<div
|
|||
|
|
style={{
|
|||
|
|
marginTop: '15px',
|
|||
|
|
padding: '10px',
|
|||
|
|
backgroundColor: '#f0f8ff',
|
|||
|
|
border: '1px solid #91d5ff',
|
|||
|
|
borderRadius: '4px',
|
|||
|
|
fontSize: '12px',
|
|||
|
|
color: '#666',
|
|||
|
|
}}
|
|||
|
|
>
|
|||
|
|
<strong>使用说明:</strong>
|
|||
|
|
<ul style={{ margin: '8px 0', paddingLeft: '20px' }}>
|
|||
|
|
<li>等待地图加载完成后再进行搜索</li>
|
|||
|
|
<li>在搜索框输入地点名称进行真实搜索(使用天地图搜索API)</li>
|
|||
|
|
<li>直接点击地图选择位置(使用天地图逆地理编码)</li>
|
|||
|
|
<li>搜索结果会自动在地图上标记并定位</li>
|
|||
|
|
<li>点击标记弹出窗口可确认选择</li>
|
|||
|
|
<li>经纬度精度可达小数点后14位</li>
|
|||
|
|
</ul>
|
|||
|
|
{!TIANDITU_KEY && (
|
|||
|
|
<div style={{ color: '#ff4d4f', marginTop: '8px' }}>
|
|||
|
|
⚠️ 请配置有效的天地图密钥
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
}
|
|||
|
|
></MyModal>
|
|||
|
|
);
|
|||
|
|
}
|