/** * 地图事件处理相关的 composable */ import type { Map } from 'ol' import type Overlay from 'ol/Overlay' import type FeatureLike from 'ol/Feature' import { MarkerData, FenceData } from '../types/map.types' import dayjs from 'dayjs' import { fromLonLat } from 'ol/proj' import { TrajectoryService } from '../services/trajectory.service' import { PopupService } from '../services/popup.service' import { DICT_TYPE, getDictLabel } from '@/utils/dict' interface PopupContentGenerator { handleTrajectoryPoint: (feature: FeatureLike) => string handleTrajectoryLine: (feature: FeatureLike) => string handleFence: (feature: FeatureLike) => string handleMarker: (feature: FeatureLike) => string } // 创建弹窗内容生成器 function createPopupContentGenerator( trajectoryService: TrajectoryService, popupService: PopupService ): PopupContentGenerator { return { handleTrajectoryPoint: (feature: FeatureLike): string => { const timeText = feature.get('timeText') || '' const trajectoryId = feature.get('trajectoryId') || '' const timestamp = feature.get('timestamp') const deviceName = trajectoryService?.getTrajectoryData().find((t) => t.deviceId === trajectoryId)?.name || trajectoryId return `
${deviceName}
时间: ${timeText}
${dayjs(timestamp).format('YYYY-MM-DD HH:mm:ss')}
` }, handleTrajectoryLine: (feature: FeatureLike): string => { const deviceId = feature.get('deviceId') || '' const deviceName = trajectoryService?.getTrajectoryData().find((t) => t.deviceId === deviceId)?.name || deviceId return `
${deviceName} - 轨迹路径
` }, handleFence: (feature: FeatureLike): string => { const fenceData: FenceData = feature.get('fenceData') const statusText = getDictLabel(DICT_TYPE.HAND_DETECTOR_FENCE_STATUS, fenceData.status) const typeText = getDictLabel(DICT_TYPE.HAND_DETECTOR_FENCE_TYPE, fenceData.type) return `
${fenceData.name}
状态: ${statusText}
类型: ${typeText}
${fenceData.remark || '无备注'}
` }, handleMarker: (feature: FeatureLike): string => { return popupService?.handlePopupContent(feature) || '' } } } export const useMapEvents = () => { /** * 设置地图事件监听器 */ const setupMapEventListeners = ( map: Map, popupOverlay: Overlay | null, trajectoryService: TrajectoryService | null, popupService: PopupService | null, opts?: { isDrawing?: () => boolean onMarkerClick?: (markerData: MarkerData) => void onClusterClick?: (features: FeatureLike[]) => void onZoomEnd?: (zoom: number) => void } ) => { if (!trajectoryService || !popupService) { return } const popupGenerator = createPopupContentGenerator(trajectoryService, popupService) // 鼠标悬停事件 const handlePointerMove = (event: any) => { // 绘制围栏时屏蔽 hover 弹窗 if (opts?.isDrawing && opts.isDrawing()) { hidePopup(popupOverlay) return } // const feature = map.forEachFeatureAtPixel(event.pixel, (feature: FeatureLike) => feature) if (feature) { const isPopupShown = showPopup(event, feature, popupOverlay, popupGenerator) if (isPopupShown) { map.getTargetElement().style.cursor = 'pointer' } else { hidePopup(popupOverlay) } } else { map.getTargetElement().style.cursor = '' hidePopup(popupOverlay) } } // 点击事件 const handleClick = (event: any) => { // 绘制围栏时屏蔽点击处理 if (opts?.isDrawing && opts.isDrawing()) { return } const feature = map.forEachFeatureAtPixel(event.pixel, (feature: FeatureLike) => feature) if (feature) { handleFeatureClick(feature, map, opts) } } // 地图移动结束事件(包括放缩) const handleMoveEnd = () => { // console.log('handleMoveEnd'); // OpenLayers的Cluster会自动重新计算聚合,只需要刷新样式 // if (opts?.markerLayer) { // opts.markerLayer.changed() // } if (opts?.onZoomEnd) { const zoom = map.getView().getZoom() || 0 opts.onZoomEnd(zoom) } } map.on('pointermove', handlePointerMove) map.on('click', handleClick) map.on('moveend', handleMoveEnd) return { destroy: () => { map.un('pointermove', handlePointerMove) map.un('click', handleClick) map.un('moveend', handleMoveEnd) } } } /** * 显示弹窗 */ const showPopup = ( event: any, feature: FeatureLike, popupOverlay: Overlay | null, popupGenerator: PopupContentGenerator ): Boolean => { if (!popupOverlay) return false const popupElement = popupOverlay.getElement() if (!popupElement) return false const featureType = feature.get('type') let popupContent = '' switch (featureType) { case 'trajectory-point': popupContent = popupGenerator.handleTrajectoryPoint(feature) break case 'trajectory': popupContent = popupGenerator.handleTrajectoryLine(feature) break case 'fence': case 'fence-label': // popupContent = popupGenerator.handleFence(feature) break default: popupContent = popupGenerator.handleMarker(feature) break } if (!popupContent) return false // if (!popupContent) return popupElement.innerHTML = popupContent popupOverlay.setPosition(event.coordinate) return true } /** * 隐藏弹窗 */ const hidePopup = (popupOverlay: any) => { if (popupOverlay) { popupOverlay.setPosition(undefined) } } /** * 处理特征点击事件 */ const handleFeatureClick = ( feature: FeatureLike, map: Map, opts?: { onMarkerClick?: (markerData: any) => void } ) => { const featureType = feature.get('type') // 处理围栏点击 if (featureType === 'fence' || featureType === 'fence-label') { const fenceData = feature.get('fenceData') if (fenceData) { console.log('围栏点击:', fenceData) // 可以在这里添加围栏点击的自定义处理逻辑 } return } // 处理标记点击 handleMarkerClick(feature, map, opts) } /** * 处理标记点击事件 */ const handleMarkerClick = ( feature: FeatureLike, map: Map, opts?: { onMarkerClick?: (markerData: any) => void; onClusterClick?: (features: FeatureLike[]) => void } ) => { const markerData = feature.get('markerData') const features = feature.get('features') if (features && features.length > 1) { // 处理聚合标记点击 // handleClusterClick(features, map) opts?.onClusterClick?.(features) } else if (features && features.length === 1) { // 处理聚合中的单个标记点击 const singleMarkerData = features[0].get('markerData') if (singleMarkerData) { animateToCoordinate(singleMarkerData.coordinates, map, 15) opts?.onMarkerClick?.(singleMarkerData) } } else if (markerData) { // 处理非聚合的单个标记点击 animateToCoordinate(markerData.coordinates, map, 17) opts?.onMarkerClick?.(markerData) } } /** * 处理聚合标记点击 */ // const handleClusterClick = (features: FeatureLike[], map: Map) => { // // 计算聚合标记的中心点 // const coordinates = features.map((f: FeatureLike) => f.get('markerData').coordinates) // const centerLon = // coordinates.reduce((sum: number, coord: any) => sum + coord[0], 0) / coordinates.length // const centerLat = // coordinates.reduce((sum: number, coord: any) => sum + coord[1], 0) / coordinates.length // animateToCoordinate([centerLon, centerLat], map, 12) // } /** * 动画移动到指定坐标 */ const animateToCoordinate = (coordinates: [number, number], map: Map, zoom: number) => { const view = map.getView() view.animate({ center: fromLonLat(coordinates), zoom: Math.max(view.getZoom() || 10, zoom), duration: 1000 }) } return { setupMapEventListeners } }