You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

297 lines
8.7 KiB

/**
* 地图事件处理相关的 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 `
<div style="font-size: 12px; color: #333;">
<div style="font-weight: bold; margin-bottom: 4px;">${deviceName}</div>
<div>时间: ${timeText}</div>
<div style="font-size: 10px; color: #666;">
${dayjs(timestamp).format('YYYY-MM-DD HH:mm:ss')}
</div>
</div>
`
},
handleTrajectoryLine: (feature: FeatureLike): string => {
const deviceId = feature.get('deviceId') || ''
const deviceName =
trajectoryService?.getTrajectoryData().find((t) => t.deviceId === deviceId)?.name ||
deviceId
return `
<div style="font-size: 12px; color: #333;">
<div style="font-weight: bold;">${deviceName} - 轨迹路径</div>
</div>
`
},
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 `
<div style="font-size: 12px; color: #333;">
<div style="font-weight: bold; margin-bottom: 4px;">${fenceData.name}</div>
<div>状态: ${statusText}</div>
<div>类型: ${typeText}</div>
<div style="font-size: 10px; color: #666; margin-top: 2px;">
${fenceData.remark || '无备注'}
</div>
</div>
`
},
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
}
}