第二篇:Vue-Leaflet地图核心功能实现
1. 地图视窗管理

1.1 视窗状态持久化方案
const saveLocation = async (options = {}) => {try {const {saveToLocal = true, saveToServer = false, notify = true } = options;const mapInstance = await map.value?.leafletObject;const bounds = mapInstance.getBounds();const viewState = {center: mapInstance.getCenter(),zoom: mapInstance.getZoom(),bounds: {southWest: bounds.getSouthWest(),northEast: bounds.getNorthEast()},timestamp: new Date().toISOString()};if (saveToLocal) {localStorage.setItem('mapViewState', JSON.stringify(viewState));}if (saveToServer) {await api.saveMapView({userId: userStore.userId,viewState: JSON.stringify(viewState)});}if (notify) {ElMessage.success({message: '地图视窗已保存',duration: 1500,showClose: true});}return viewState;} catch (error) {console.error('保存视窗失败:', error);ElMessage.error('保存视窗失败');throw error;}
};
1.2 视窗恢复与边界检查
const restoreView = async (options = {}) => {const {fly = true, duration = 1, maxZoom = 18 } = options;const savedData = localStorage.getItem('mapViewState');if (!savedData) return false;try {const { center, zoom, bounds } = JSON.parse(savedData);const mapInstance = await map.value?.leafletObject;const isValidView = (center.lat >= -90 && center.lat <= 90 &¢er.lng >= -180 && center.lng <= 180 &&zoom >= mapOptions.value.minZoom && zoom <= mapOptions.value.maxZoom);if (!isValidView) {throw new Error('无效的视窗数据');}if (fly) {mapInstance.flyTo([center.lat, center.lng], zoom, {duration,maxZoom});} else {mapInstance.setView([center.lat, center.lng], zoom);}return true;} catch (error) {console.error('恢复视窗失败:', error);return false;}
};
2. 高级要素交互功能
2.1 增强版要素高亮系统
const highlightStyles = {default: {color: '#00ffff',weight: 5,opacity: 1,fillColor: '#00ffff',fillOpacity: 0.2},selected: {color: '#3388ff',weight: 6,dashArray: '10, 10',fillOpacity: 0.3},error: {color: '#ff0000',weight: 7,fillColor: '#ff9999'}
};
const highlightFeature = (feature, styleType = 'default') => {if (!feature) return;const style = highlightStyles[styleType] || highlightStyles.default;const geojsonLayer = L.geoJSON(feature, { style });clearHighlight();geojsonLayer.addTo(mapInstance);highlightedLayers.push(geojsonLayer);return geojsonLayer;
};

2.2 要素查询与空间分析
const queryFeatures = async (geometry, layerNames, options = {}) => {const {buffer = 0, limit = 10, sortByDistance = true } = options;let searchArea = geometry;if (buffer > 0) {const buffered = turf.buffer(geometry, buffer, { units: 'meters' });searchArea = buffered.geometry;}const wfsParams = {service: 'WFS',version: '2.0.0',request: 'GetFeature',typeNames: layerNames.join(','),outputFormat: 'application/json',srsName: 'EPSG:4490',bbox: getBboxString(searchArea),propertyName: options.fields || '*',count: limit};const results = await axios.get(`${GEOSERVER_URL}/wfs`, { params: wfsParams });let features = results.data.features;if (sortByDistance && geometry.type === 'Point') {const center = turf.point([geometry.coordinates[0], geometry.coordinates[1]]);features = features.map(f => ({...f,_distance: turf.distance(center, turf.centerOfMass(f))})).sort((a, b) => a._distance - b._distance);}return features.slice(0, limit);
};
3. 地图事件高级处理
3.1 防抖点击处理
import { debounce } from 'lodash-es';const handleMapClick = debounce(async (event) => {const { latlng } = event;const loading = ElLoading.service({target: '.base-map-container',text: '正在查询地图数据...'});try {const features = await queryFeatures({ type: 'Point', coordinates: [latlng.lng, latlng.lat] },['sde:租赁采集_小区', 'sde:wybld'],{ buffer: 50, limit: 5 });if (features.length > 0) {const primaryFeature = features[0];highlightFeature(primaryFeature, 'selected');showFeaturePopup(primaryFeature, latlng);emit('feature-selected', primaryFeature);}} catch (error) {ElMessage.error(`查询失败: ${error.message}`);} finally {loading.close();}
}, 300, { leading: true, trailing: false });
3.2 自定义地图上下文菜单
const initContextMenu = () => {mapInstance.on('contextmenu', (e) => {L.DomEvent.stop(e);removeContextMenu();const menu = L.popup({ className: 'map-context-menu' }).setLatLng(e.latlng).setContent(`<div class="context-menu"><div class="item" data-action="add-marker"><i class="el-icon-location"></i> 添加标记</div><div class="item" data-action="measure-distance"><i class="el-icon-odometer"></i> 测量距离</div><div class="item" data-action="query-location"><i class="el-icon-search"></i> 查询位置</div></div>`).openOn(mapInstance);contextMenu.value = menu;});
};
const onMenuClick = (action, latlng) => {switch (action) {case 'add-marker':addCustomMarker(latlng);break;case 'measure-distance':startMeasuring(latlng);break;case 'query-location':handleMapClick({ latlng });break;}
};
4. 地图工具集成
4.1 测量工具实现
const measureControl = {startMeasuring: (type = 'line') => {measuring.value = true;measureType.value = type;measureLayer = L.featureGroup().addTo(mapInstance);mapInstance.dragging.disable();mapInstance.getContainer().style.cursor = 'crosshair';mapInstance.on('click', handleMeasureClick);mapInstance.on('mousemove', handleMeasureMove);},endMeasuring: () => {measuring.value = false;mapInstance.dragging.enable();mapInstance.getContainer().style.cursor = '';mapInstance.off('click', handleMeasureClick);mapInstance.off('mousemove', handleMeasureMove);const result = {type: measureType.value,coordinates: currentMeasureCoords.value,length: calculateMeasureLength(),area: calculateMeasureArea()};if (measureLayer) {mapInstance.removeLayer(measureLayer);}return result;}
};
4.2 绘图工具集成
import 'leaflet-draw/dist/leaflet.draw.css';
import { FeatureGroup, Draw } from 'leaflet-draw';const initDrawingTools = () => {const drawnItems = new FeatureGroup();mapInstance.addLayer(drawnItems);const drawControl = new L.Control.Draw({position: 'topright',draw: {polygon: {allowIntersection: false,showArea: true,metric: true},circle: false,rectangle: false,marker: {icon: new L.Icon.Default({iconUrl: '/custom-marker.png'})}},edit: {featureGroup: drawnItems}});mapInstance.addControl(drawControl);mapInstance.on(L.Draw.Event.CREATED, (e) => {const layer = e.layer;drawnItems.addLayer(layer);emit('feature-created', {type: e.layerType,geojson: layer.toGeoJSON(),layer: layer});});
};
5. 性能优化技巧
5.1 图层加载优化
const lazyLoadLayer = (layerName) => {if (!loadedLayers.value.includes(layerName)) {const loading = ElLoading.service({ text: `加载 ${layerName} 图层...` });getWMSLayer(layerName).addTo(mapInstance).once('load', () => {loadedLayers.value.push(layerName);loading.close();}).on('error', () => {loading.close();ElMessage.error(`${layerName} 图层加载失败`);});}
};
5.2 视窗变化时的优化策略
const onViewChange = debounce(async (e) => {const zoomLevel = mapInstance.getZoom();const bounds = mapInstance.getBounds();if (zoomLevel < 10) {switchToLowDetailLayers();} else {switchToHighDetailLayers();}preloadDataForBounds(bounds);
}, 500);
6. 完整功能集成示例
<template><div class="advanced-map-container"><l-map ref="map" :options="mapOptions" @ready="initAdvancedMap"><!-- 基础图层 --><l-tile-layer :url="baseLayerUrl" /><!-- 绘图工具栏 --><l-control position="topright" v-if="drawingEnabled"><button @click="toggleDrawing('polygon')">绘制多边形</button><button @click="toggleDrawing('marker')">添加标记</button></l-control><!-- 测量结果展示 --><l-control position="bottomleft" class="measure-result">距离: {{ measureResult.distance }} m | 面积: {{ measureResult.area }} m²</l-control></l-map><!-- 地图控制面板 --><div class="map-control-panel"><el-button-group><el-button @click="saveCurrentView">保存视窗</el-button><el-button @click="restoreView">恢复视窗</el-button><el-button @click="startMeasurement('line')">测量距离</el-button><el-button @click="startMeasurement('polygon')">测量面积</el-button></el-button-group></div></div>
</template><script setup>
// 这里集成前面介绍的所有高级功能
</script><style>
.advanced-map-container {position: relative;height: 100%;
}.map-control-panel {position: absolute;top: 10px;left: 10px;z-index: 1000;background: white;padding: 10px;border-radius: 4px;box-shadow: 0 2px 12px rgba(0,0,0,0.1);
}.measure-result {background: white;padding: 5px 10px;border-radius: 4px;font-size: 14px;
}
</style>