实现说明
-
自定义图标配置:
-
在
customIcons
对象中定义所有类型的图标URL -
支持不同类型的机房、基站和断点使用不同图标
-
-
标记创建函数:
-
addCustomMarker
是一个通用函数,用于创建带有自定义图标的标记 -
可以设置图标URL、标题和自定义CSS类
-
-
标记类型专用函数:
-
addComputerRoomMarkers
- 添加机房标记 -
addBaseStationMarkers
- 添加基站标记 -
addBreakPointMarkers
- 添加断点标记
-
-
图标样式控制:
-
通过CSS控制图标大小(32x32像素)
-
使用
background-image
显示自定义图标 -
添加了悬停提示(title属性)
-
javascript
import React, { useState, useEffect, useRef } from 'react';
const MapComponent = () => {
const mapContainer = useRef(null);
const map = useRef(null);
const [mapLoaded, setMapLoaded] = useState(false);
const [markers, setMarkers] = useState([]);
const [legendVisible, setLegendVisible] = useState(true);
const [visibleLayers, setVisibleLayers] = useState({
computerRooms: true,
baseStations: true,
breakPoints: true
});
// 模拟图标URL - 实际使用时替换为您的真实图标URL
const customIcons = {
computerRoom: {
core: 'https://cdn-icons-png.flaticon.com/512/149/149060.png', // 替换为您的核心机房图标
edge: 'https://cdn-icons-png.flaticon.com/512/149/149025.png', // 替换为您的边缘机房图标
access: 'https://cdn-icons-png.flaticon.com/512/149/149014.png' // 替换为您的接入机房图标
},
baseStation: {
'5g': 'https://cdn-icons-png.flaticon.com/512/4618/4618295.png', // 5G基站图标
'4g': 'https://cdn-icons-png.flaticon.com/512/4618/4618286.png', // 4G基站图标
'3g': 'https://cdn-icons-png.flaticon.com/512/4618/4618275.png' // 3G基站图标
},
breakPoint: 'https://cdn-icons-png.flaticon.com/512/1828/1828665.png' // 断点图标
};
// 动态加载 Mapbox GL JS
useEffect(() => {
if (window.mapboxgl) {
setMapLoaded(true);
return;
}
const script = document.createElement('script');
script.src = 'https://api.mapbox.com/mapbox-gl-js/v2.2.0/mapbox-gl.js';
script.onload = () => {
const link = document.createElement('link');
link.href = 'https://api.mapbox.com/mapbox-gl-js/v2.2.0/mapbox-gl.css';
link.rel = 'stylesheet';
document.head.appendChild(link);
setMapLoaded(true);
};
script.onerror = () => console.error('Failed to load Mapbox GL JS');
document.head.appendChild(script);
return () => {
document.head.removeChild(script);
};
}, []);
// 添加带有自定义图标的标记
const addCustomMarker = (lnglat, iconUrl, title, className = '') => {
if (!map.current) return null;
const el = document.createElement('div');
el.className = `custom-marker ${className}`;
el.style.width = '32px';
el.style.height = '32px';
el.style.backgroundImage = `url(${iconUrl})`;
el.style.backgroundSize = 'contain';
el.style.backgroundRepeat = 'no-repeat';
el.style.cursor = 'pointer';
if (title) el.title = title;
const marker = new window.mapboxgl.Marker({
element: el,
anchor: 'bottom' // 图标底部对准坐标点
})
.setLngLat(lnglat)
.addTo(map.current);
return marker;
};
// 添加机房标记
const addComputerRoomMarkers = (rooms) => {
if (!map.current) return;
const newMarkers = rooms.map(room => {
const marker = addCustomMarker(
[room.lng, room.lat],
customIcons.computerRoom[room.type],
room.name,
`computer-room ${room.type}`
);
return {
id: room.id,
marker,
type: 'computerRoom',
originalType: room.type
};
});
setMarkers(prev => [...prev, ...newMarkers]);
};
// 添加基站标记
const addBaseStationMarkers = (stations) => {
if (!map.current) return;
const newStations = stations.map(station => {
const marker = addCustomMarker(
[station.lng, station.lat],
customIcons.baseStation[station.type],
`${station.name} (${station.type.toUpperCase()})`,
`base-station ${station.type}`
);
return {
id: station.id,
marker,
type: 'baseStation',
originalType: station.type
};
});
setMarkers(prev => [...prev, ...newStations]);
};
// 添加断点标记
const addBreakPointMarkers = (points) => {
if (!map.current) return;
const newMarkers = points.map(point => {
const marker = addCustomMarker(
[point.lng, point.lat],
customIcons.breakPoint,
point.title,
'break-point'
);
return { id: point.id, marker, type: 'breakPoint' };
});
setMarkers(prev => [...prev, ...newMarkers]);
};
// 切换图层可见性
const toggleLayerVisibility = (layerType) => {
const newVisibility = !visibleLayers[layerType];
setVisibleLayers(prev => ({ ...prev, [layerType]: newVisibility }));
markers.forEach(marker => {
if (marker.type === layerType ||
(layerType === 'computerRooms' && marker.type === 'computerRoom') ||
(layerType === 'baseStations' && marker.type === 'baseStation') ||
(layerType === 'breakPoints' && marker.type === 'breakPoint')) {
const element = marker.marker.getElement();
if (element) {
element.style.display = newVisibility ? 'block' : 'none';
}
}
});
};
// 初始化地图
useEffect(() => {
if (!mapLoaded || !mapContainer.current) return;
map.current = new window.mapboxgl.Map({
container: mapContainer.current,
style: 'mapbox://styles/mapbox/streets-v11', // 使用默认地图样式
center: [111.62299, 40.80772],
zoom: 10,
minZoom: 7,
maxZoom: 19
});
map.current.on('load', () => {
// 添加机房标记
const computerRooms = [
{ id: 1, name: '核心机房', lng: 111.62, lat: 40.81, type: 'core' },
{ id: 2, name: '边缘机房', lng: 111.63, lat: 40.80, type: 'edge' },
{ id: 3, name: '接入机房', lng: 111.61, lat: 40.805, type: 'access' }
];
addComputerRoomMarkers(computerRooms);
// 添加基站标记
const baseStations = [
{ id: 101, name: '5G基站A', lng: 111.615, lat: 40.808, type: '5g' },
{ id: 102, name: '4G基站B', lng: 111.625, lat: 40.806, type: '4g' },
{ id: 103, name: '3G基站C', lng: 111.635, lat: 40.804, type: '3g' }
];
addBaseStationMarkers(baseStations);
// 添加断点标记
const breakPoints = [
{ id: 'bp1', lng: 111.625, lat: 40.806, title: '光缆断点1' },
{ id: 'bp2', lng: 111.635, lat: 40.8045, title: '光缆断点2' }
];
addBreakPointMarkers(breakPoints);
});
return () => {
if (map.current) {
map.current.remove();
}
};
}, [mapLoaded]);
if (!mapLoaded) {
return <div style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
height: '100vh',
fontSize: '18px',
color: '#666'
}}>正在加载地图资源...</div>;
}
return (
<div style={{ position: 'relative', width: '100%', height: '100vh' }}>
<div ref={mapContainer} style={{ width: '100%', height: '100%' }} />
{/* 图例悬浮框 */}
{legendVisible && (
<div style={{
position: 'absolute',
bottom: '20px',
right: '20px',
backgroundColor: 'white',
padding: '15px',
borderRadius: '5px',
boxShadow: '0 0 10px rgba(0,0,0,0.2)',
zIndex: 1,
maxWidth: '250px'
}}>
<h3 style={{ marginTop: 0, marginBottom: '15px' }}>地图图例</h3>
{/* 机房图例 */}
<div style={{ marginBottom: '15px' }}>
<label style={{ display: 'flex', alignItems: 'center', cursor: 'pointer', marginBottom: '5px' }}>
<input
type="checkbox"
checked={visibleLayers.computerRooms}
onChange={() => toggleLayerVisibility('computerRooms')}
style={{ marginRight: '8px' }}
/>
<span>显示机房</span>
</label>
<div style={{ marginLeft: '25px' }}>
<div style={{ display: 'flex', alignItems: 'center', marginBottom: '3px' }}>
<img src={customIcons.computerRoom.core} style={{ width: '20px', height: '20px', marginRight: '8px' }} alt="核心机房" />
<span>核心机房</span>
</div>
<div style={{ display: 'flex', alignItems: 'center', marginBottom: '3px' }}>
<img src={customIcons.computerRoom.edge} style={{ width: '20px', height: '20px', marginRight: '8px' }} alt="边缘机房" />
<span>边缘机房</span>
</div>
<div style={{ display: 'flex', alignItems: 'center' }}>
<img src={customIcons.computerRoom.access} style={{ width: '20px', height: '20px', marginRight: '8px' }} alt="接入机房" />
<span>接入机房</span>
</div>
</div>
</div>
{/* 基站图例 */}
<div style={{ marginBottom: '15px' }}>
<label style={{ display: 'flex', alignItems: 'center', cursor: 'pointer', marginBottom: '5px' }}>
<input
type="checkbox"
checked={visibleLayers.baseStations}
onChange={() => toggleLayerVisibility('baseStations')}
style={{ marginRight: '8px' }}
/>
<span>显示基站</span>
</label>
<div style={{ marginLeft: '25px' }}>
<div style={{ display: 'flex', alignItems: 'center', marginBottom: '3px' }}>
<img src={customIcons.baseStation['5g']} style={{ width: '20px', height: '20px', marginRight: '8px' }} alt="5G基站" />
<span>5G基站</span>
</div>
<div style={{ display: 'flex', alignItems: 'center', marginBottom: '3px' }}>
<img src={customIcons.baseStation['4g']} style={{ width: '20px', height: '20px', marginRight: '8px' }} alt="4G基站" />
<span>4G基站</span>
</div>
<div style={{ display: 'flex', alignItems: 'center' }}>
<img src={customIcons.baseStation['3g']} style={{ width: '20px', height: '20px', marginRight: '8px' }} alt="3G基站" />
<span>3G基站</span>
</div>
</div>
</div>
{/* 断点图例 */}
<div>
<label style={{ display: 'flex', alignItems: 'center', cursor: 'pointer', marginBottom: '5px' }}>
<input
type="checkbox"
checked={visibleLayers.breakPoints}
onChange={() => toggleLayerVisibility('breakPoints')}
style={{ marginRight: '8px' }}
/>
<span>显示断点</span>
</label>
<div style={{ marginLeft: '25px' }}>
<div style={{ display: 'flex', alignItems: 'center' }}>
<img src={customIcons.breakPoint} style={{ width: '20px', height: '20px', marginRight: '8px' }} alt="断点" />
<span>光缆断点</span>
</div>
</div>
</div>
</div>
)}
{/* 显示/隐藏图例按钮 */}
<button
onClick={() => setLegendVisible(!legendVisible)}
style={{
position: 'absolute',
bottom: '20px',
right: legendVisible ? '280px' : '20px',
zIndex: 1,
padding: '6px 12px',
backgroundColor: 'white',
border: '1px solid #ccc',
borderRadius: '4px',
cursor: 'pointer',
fontSize: '14px',
boxShadow: '0 0 5px rgba(0,0,0,0.1)'
}}
>
{legendVisible ? '隐藏图例' : '显示图例'}
</button>
</div>
);
};
export default MapComponent;