From 587d3c9a2dbe62fa765e9d349768931d4e7ce008 Mon Sep 17 00:00:00 2001 From: xh <11675084@qq.com> Date: Tue, 21 Oct 2025 21:24:56 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/api/gas/index.ts | 4 +- .../HandDevice/Home/components/OpenLayerMap.vue | 27 ++-- .../Home/components/composables/useMapServices.ts | 104 ++++++--------- .../Home/components/services/animation.service.ts | 17 ++- .../Home/components/services/fence-draw.service.ts | 12 +- .../Home/components/services/fence.service.ts | 11 +- .../Home/components/services/map.service.ts | 74 +++++------ .../Home/components/services/marker.service.ts | 148 ++++++--------------- .../Home/components/services/trajectory.service.ts | 9 +- .../HandDevice/Home/components/types/map.types.ts | 2 +- .../HandDevice/Home/components/utils/map.utils.ts | 2 +- web/src/views/HandDevice/Home/index.vue | 14 +- 12 files changed, 185 insertions(+), 239 deletions(-) diff --git a/web/src/api/gas/index.ts b/web/src/api/gas/index.ts index d830fdb..e730d0c 100644 --- a/web/src/api/gas/index.ts +++ b/web/src/api/gas/index.ts @@ -1,7 +1,7 @@ import request from '@/config/axios' -const getLastestDetectorData = async () => { +const getLastDetectorData = async () => { const data = await request.get({ url: `/gas/hand-detector/getByHandData` }) return Object.values(data) } -export { getLastestDetectorData } +export { getLastDetectorData } diff --git a/web/src/views/HandDevice/Home/components/OpenLayerMap.vue b/web/src/views/HandDevice/Home/components/OpenLayerMap.vue index 0772efa..eaf47d2 100644 --- a/web/src/views/HandDevice/Home/components/OpenLayerMap.vue +++ b/web/src/views/HandDevice/Home/components/OpenLayerMap.vue @@ -59,6 +59,7 @@ import { useMapEvents } from './composables/useMapEvents' import { useTrajectoryControls } from './composables/useTrajectoryControls' import { useMapWatchers } from './composables/useMapWatchers' +import { MapService } from './services/map.service' // 导入组件 import TrajectoryControls from './TrajectoryControls.vue' import MapControls from './MapControls.vue' @@ -107,7 +108,7 @@ enableStatus 1启用 0未启用 const { services, layerRefs, - initializeServices, + initializeMapAndLayers, setMarkersVisible, setTrajectoriesVisible, @@ -160,7 +161,7 @@ const toggleDrawFences = () => { } } -import { MapService } from './services/map.service' + let mapService: MapService | null = null let isMapInitialized = false @@ -170,17 +171,16 @@ let isMapInitialized = false const initMap = () => { if (!mapContainerRef.value) return - // 初始化服务 - mapService = new MapService() + // 初始化地图服务 + mapService = new MapService(mapContainerRef.value, props) + - // 初始化地图 try { - // 初始化服务 - initializeServices() - const mapInstance = mapService.initMap(mapContainerRef.value, props) + + // const mapInstance = mapService.initMap(mapContainerRef.value, props) // 初始化地图和图层 - const { map, popupOverlay } = initializeMapAndLayers(mapInstance, props) + const { map, popupOverlay } = initializeMapAndLayers(mapService, props) // 设置初始显示状态 setMarkersVisible(showMarkers.value) @@ -196,12 +196,15 @@ const initMap = () => { { isDrawing: () => !!services.fenceDrawService?.isCurrentlyDrawing?.(), onMarkerClick: (marker: MarkerData) => { + console.log('marker clicked', marker); + selectedMarker.value = marker panelVisible.value = true }, onZoomEnd: (zoom: number) => { // console.log('onZoomEnd', zoom) - // services.markerService?.updateMarkers(props.markers) + services.markerService?.createMarkerLayer(props) + // updateMarkers(props.markers || [],props) }, markerLayer: layerRefs.value?.markerLayer, @@ -274,6 +277,7 @@ onMounted(() => { }, 100) }) + defineExpose({ refreshFences }) @@ -297,11 +301,12 @@ defineExpose({ refreshFences }) position: absolute; top: 100px; right: 20px; + background-color: rgba(255, 255, 255, 0.9); border-radius: 8px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); z-index: 1000; - width: 300px; + width: 313px; max-height: 80vh; overflow-y: auto; display: flex; diff --git a/web/src/views/HandDevice/Home/components/composables/useMapServices.ts b/web/src/views/HandDevice/Home/components/composables/useMapServices.ts index f7a444f..01181ac 100644 --- a/web/src/views/HandDevice/Home/components/composables/useMapServices.ts +++ b/web/src/views/HandDevice/Home/components/composables/useMapServices.ts @@ -1,8 +1,10 @@ /** * 地图服务管理相关的 composable */ +import type { Map } from 'ol' + import { ref, onUnmounted, reactive } from 'vue' -import type { MapProps,MapInstance } from '../types/map.types' +import type { MapProps, MapInstance } from '../types/map.types' import { MapService } from '../services/map.service' import { MarkerService } from '../services/marker.service' import { AnimationService } from '../services/animation.service' @@ -44,66 +46,45 @@ export const useMapServices = () => { const layerRefs = ref(null) /** - * 初始化所有服务 - */ - const initializeServices = (map?: any) => { - // 就地更新,保持对 services 的引用不变,避免外部拿到旧引用 - services.mapService = new MapService() - services.markerService = new MarkerService() - services.animationService = new AnimationService() - services.popupService = new PopupService() - services.trajectoryService = new TrajectoryService() - services.fenceService = new FenceService() - services.fenceDrawService = new FenceDrawService() - - // 如果提供了地图实例,设置给相关服务 - if (map) { - services.markerService.setMap(map) - } - } - - /** * 初始化地图和图层 */ - const initializeMapAndLayers = ( - mapInstance: MapInstance, - props: MapProps - ) => { + const initializeMapAndLayers = (mapInstance: MapService, props: MapProps) => { // 初始化地图 const map = mapInstance.map const popupOverlay = mapInstance.popupOverlay + if (!map) { + throw new Error('Map instance is null') + } // 重新初始化服务,确保markerService有地图实例 - initializeServices(map) - - if ( - !services.markerService || - !services.animationService || - !services.trajectoryService || - !services.fenceService || - !services.fenceDrawService - ) { - throw new Error('Services not initialized') - } + services.mapService = mapInstance + services.markerService = new MarkerService(map) + services.animationService = new AnimationService(map) + services.popupService = new PopupService() + services.trajectoryService = new TrajectoryService(map) + services.fenceService = new FenceService(map) + services.fenceDrawService = new FenceDrawService(map) - // 创建各种图层 - const markerLayer = services.markerService.createMarkerLayer(props, map) + // 创建marker图层 + const markerLayer = services.markerService.createMarkerLayer(props) + // 创建波纹图层 const rippleLayer = services.animationService.createRippleLayer( props.markers || [], - map, props.enableCluster ) - const trajectoryLayer = services.trajectoryService.createTrajectoryLayer(map) - const fenceLayer = services.fenceService.createFenceLayer(props.fences || [], map) + // 创建轨迹图层 + const trajectoryLayer = services.trajectoryService.createTrajectoryLayer() + // 创建围栏图层 + const fenceLayer = services.fenceService.createFenceLayer(props.fences || []) // // 添加图层到地图 - map.addLayer(markerLayer) + // map.addLayer(markerLayer) map.addLayer(rippleLayer) map.addLayer(trajectoryLayer) map.addLayer(fenceLayer) // 初始化围栏绘制服务 - services.fenceDrawService.init(map) + services.fenceDrawService.createDrawLayer() // 存储图层引用 layerRefs.value = { @@ -192,7 +173,7 @@ export const useMapServices = () => { /** * 更新标记数据 */ - const updateMarkers = (markers: any[], currentProps?: any) => { + const updateMarkers = (markers: any[], currentProps?: MapProps) => { if ( services.markerService && services.animationService && @@ -203,30 +184,29 @@ export const useMapServices = () => { const enableCluster = currentProps?.enableCluster ?? true if (map) { // 从地图中移除旧的marker layer - map.removeLayer(layerRefs.value.markerLayer) - map.removeLayer(layerRefs.value.rippleLayer) -console.log('updateMarkers', markers); + // map.removeLayer(layerRefs.value.markerLayer) + // map.removeLayer(layerRefs.value.rippleLayer) + console.log('updateMarkers', markers); // 更新marker service(这会创建新的layer) - services.markerService.updateMarkers(markers) + services.markerService.createMarkerLayer({ + ...currentProps, + markers + }) // 重新创建波纹图层 - const newRippleLayer = services.animationService.createRippleLayer( - markers, - map, - enableCluster - ) + services.animationService.createRippleLayer(markers, enableCluster) // 获取新的layer并添加到地图 - const newMarkerLayer = services.markerService.getMarkerLayer() - if (newMarkerLayer && newRippleLayer) { - // 确保markerService有最新的地图实例 - services.markerService.setMap(map) - map.addLayer(newMarkerLayer) - map.addLayer(newRippleLayer) - layerRefs.value.markerLayer = newMarkerLayer - layerRefs.value.rippleLayer = newRippleLayer - } + // const newMarkerLayer = services.markerService.getMarkerLayer() + // if (newMarkerLayer && newRippleLayer) { + // // // 确保markerService有最新的地图实例 + // // services.markerService.setMap(map) + // map.addLayer(newMarkerLayer) + // map.addLayer(newRippleLayer) + // layerRefs.value.markerLayer = newMarkerLayer + // layerRefs.value.rippleLayer = newRippleLayer + // } } } } @@ -284,7 +264,7 @@ console.log('updateMarkers', markers); layerRefs, // 方法 - initializeServices, + initializeMapAndLayers, setMarkersVisible, setTrajectoriesVisible, diff --git a/web/src/views/HandDevice/Home/components/services/animation.service.ts b/web/src/views/HandDevice/Home/components/services/animation.service.ts index 442f7bc..93e000e 100644 --- a/web/src/views/HandDevice/Home/components/services/animation.service.ts +++ b/web/src/views/HandDevice/Home/components/services/animation.service.ts @@ -18,16 +18,20 @@ export class AnimationService { private animationTimer: number | null = null private map: Map | null = null private enableCluster: boolean = true - + constructor(map: Map) { + this.map = map + } /** * 创建波纹图层 */ createRippleLayer( markers: MarkerData[], - map: Map, + enableCluster: boolean = true ): VectorLayer { - this.map = map + if (this.rippleLayer && this.map) { + this.map?.removeLayer(this.rippleLayer) + } this.enableCluster = enableCluster const source = new VectorSource() @@ -107,6 +111,10 @@ export class AnimationService { } }) + if (this.rippleLayer && this.map) { + this.map?.addLayer(this.rippleLayer) + } + return this.rippleLayer } @@ -157,6 +165,9 @@ export class AnimationService { * 销毁动画服务 */ destroy(): void { + if (this.rippleLayer && this.map) { + this.map?.removeLayer(this.rippleLayer) + } this.stopAnimation() this.rippleLayer = null this.map = null diff --git a/web/src/views/HandDevice/Home/components/services/fence-draw.service.ts b/web/src/views/HandDevice/Home/components/services/fence-draw.service.ts index df71cc4..37e382d 100644 --- a/web/src/views/HandDevice/Home/components/services/fence-draw.service.ts +++ b/web/src/views/HandDevice/Home/components/services/fence-draw.service.ts @@ -21,19 +21,21 @@ export class FenceDrawService { // 绘制完成回调 private onDrawComplete: ((coordinates: [number, number][]) => void) | null = null + constructor(map: Map) { + this.map = map + } /** * 初始化绘制服务 */ - init(map: Map): void { - this.map = map - this.createDrawLayer() - } + // init(): void { + // this.createDrawLayer() + // } /** * 创建绘制图层 */ - private createDrawLayer(): void { + createDrawLayer(): void { const source = new VectorSource() this.drawLayer = new VectorLayer({ diff --git a/web/src/views/HandDevice/Home/components/services/fence.service.ts b/web/src/views/HandDevice/Home/components/services/fence.service.ts index cfdf60e..1368755 100644 --- a/web/src/views/HandDevice/Home/components/services/fence.service.ts +++ b/web/src/views/HandDevice/Home/components/services/fence.service.ts @@ -18,11 +18,15 @@ export class FenceService { private map: Map | null = null private isVisible: boolean = true + constructor(map: Map) { + this.map = map + } + /** * 创建围栏图层 */ - createFenceLayer(fences: FenceData[], map: Map): VectorLayer { - this.map = map + createFenceLayer(fences: FenceData[]): VectorLayer { + this.fenceData = fences const source = new VectorSource() @@ -306,7 +310,8 @@ export class FenceService { if (source) { const features = source.getFeatures() const fenceFeature = features.find( - (feature) => feature.get('type') === 'fence' && feature.get('fenceId') === fenceId && fenceId + (feature) => + feature.get('type') === 'fence' && feature.get('fenceId') === fenceId && fenceId ) if (fenceFeature) { fenceFeature.setStyle(this.createFenceStyle(fence)) diff --git a/web/src/views/HandDevice/Home/components/services/map.service.ts b/web/src/views/HandDevice/Home/components/services/map.service.ts index 549cc7a..a34467e 100644 --- a/web/src/views/HandDevice/Home/components/services/map.service.ts +++ b/web/src/views/HandDevice/Home/components/services/map.service.ts @@ -9,9 +9,41 @@ import Overlay from 'ol/Overlay' import type { MapProps, MapInstance } from '../types/map.types' export class MapService { - private map: Map | null = null - private tileLayer: TileLayer | null = null - private popupOverlay: Overlay | null = null + map: Map | null = null + tileLayer: TileLayer | null = null + popupOverlay: Overlay | null = null + + /** + * 初始化地图 + */ + constructor(container: HTMLElement, props: MapProps) { + this.tileLayer = this.createTileLayer(props) + const popup = this.createPopup() + + const center = fromLonLat(props.center!) + + this.map = new Map({ + target: container, + layers: [this.tileLayer], //图层数组 + overlays: [popup], //弹窗数组 + + // 定义地图的显示范围、投影、缩放级别和旋转角度 + view: new View({ + center: center, + zoom: props.zoom, + maxZoom: props.maxZoom, + minZoom: props.minZoom + }) + }) + + // return { + // map: this.map, + // tileLayer: this.tileLayer, + // markerLayer: null, + // rippleLayer: null, + // popupOverlay: this.popupOverlay + // } + } /** * 创建瓦片图层 @@ -48,8 +80,8 @@ export class MapService { this.popupOverlay = new Overlay({ element: popupElement, - positioning: 'bottom-center',// 弹窗位置相对于标记的位置 - stopEvent: false,// 是否阻止事件冒泡 + positioning: 'bottom-center', // 弹窗位置相对于标记的位置 + stopEvent: false, // 是否阻止事件冒泡 offset: [0, -10] }) @@ -57,38 +89,6 @@ export class MapService { } /** - * 初始化地图 - */ - initMap(container: HTMLElement, props: MapProps): MapInstance { - this.tileLayer = this.createTileLayer(props) - const popup = this.createPopup() - - const center = fromLonLat(props.center!) - - this.map = new Map({ - target: container, - layers: [this.tileLayer],//图层数组 - overlays: [popup],//弹窗数组 - - // 定义地图的显示范围、投影、缩放级别和旋转角度 - view: new View({ - center: center, - zoom: props.zoom, - maxZoom: props.maxZoom, - minZoom: props.minZoom - }) - }) - - return { - map: this.map, - tileLayer: this.tileLayer, - markerLayer: null, - rippleLayer: null, - popupOverlay: this.popupOverlay - } - } - - /** * 获取地图实例 */ getMap(): Map | null { diff --git a/web/src/views/HandDevice/Home/components/services/marker.service.ts b/web/src/views/HandDevice/Home/components/services/marker.service.ts index 206dde8..6cecb0e 100644 --- a/web/src/views/HandDevice/Home/components/services/marker.service.ts +++ b/web/src/views/HandDevice/Home/components/services/marker.service.ts @@ -10,32 +10,33 @@ import { Style } from 'ol/style' import { fromLonLat } from 'ol/proj' import type { MarkerData, MapProps } from '../types/map.types' import { createMarkerStyle, getClusterMarkerData } from '../utils/map.utils' - +import { ANIMATION_CONFIG } from '../constants/map.constants' export class MarkerService { - private markerLayer: VectorLayer | null = null - private currentProps: MapProps | null = null private map: Map | null = null + private markerLayer: VectorLayer | null = null + private vectorSource: VectorSource - /** - * 设置地图实例 - */ - setMap(map: Map): void { + constructor(map: Map) { this.map = map + this.vectorSource = new VectorSource() + } /** * 创建标记图层 */ - createMarkerLayer(props: MapProps, map?: Map): VectorLayer { - // 保存地图实例 - if (map) { - this.map = map + createMarkerLayer(props: MapProps): VectorLayer { + console.log('createMarkerLayer') + if (this.markerLayer) { + this.map?.removeLayer(this.markerLayer) } - - // 保存当前props - this.currentProps = { ...props } - - const source = new VectorSource() + return this.createMarkerLayerFromProps(props) + } + /** + * 更新标记数据 + */ + updateData(props: MapProps): void { + this.vectorSource.clear() // 添加标记 const markers = props.markers || [] markers.forEach((marker) => { @@ -44,56 +45,17 @@ export class MarkerService { markerData: marker }) feature.setStyle(createMarkerStyle(marker)) - source.addFeature(feature) + this.vectorSource.addFeature(feature) }) - // 检查是否应该强制使用单个marker模式 - const shouldForceSingleMark = () => { - if (!props.forceSingleMark || !this.map) return false - const currentZoom = this.map.getView().getZoom() - return currentZoom && currentZoom >= props.forceSingleMark - } - console.log('shouldForceSingleMark', shouldForceSingleMark()) - - // 如果启用聚合且不强制使用单个marker模式 - if (props.enableCluster && !shouldForceSingleMark()) { - const clusterSource = new Cluster({ - source: source, - distance: Math.max(props.clusterDistance || 40, 10) // 确保最小距离为10像素 - }) - - this.markerLayer = new VectorLayer({ - source: clusterSource, - style: (feature) => { - const features = feature.get('features') - - // 确保features存在且不为空 - if (!features || features.length === 0) { - return new Style() // 返回空样式,隐藏无效的feature - } - - if (features.length === 1) { - // 单个marker - const markerData = features[0].get('markerData') - return markerData ? createMarkerStyle(markerData) : new Style() - } else { - // 聚合marker - const highestStatus = getClusterMarkerData(features) - return createMarkerStyle(highestStatus, true, features.length) - } - } - }) - } else { - this.markerLayer = new VectorLayer({ - source: source - }) + // this.createMarkerLayerFromProps(props) + } + updateLayer(props: MapProps): void { + if (this.markerLayer) { + this.map?.removeLayer(this.markerLayer) } -// this.markerLayer = new VectorLayer({ -// source: source -// }) - return this.markerLayer + this.createMarkerLayerFromProps(props) } - /** * 获取标记图层 */ @@ -102,60 +64,30 @@ export class MarkerService { } /** - * 更新标记数据 - */ - updateMarkers(markers: MarkerData[]): void { - if (!this.currentProps) return - console.log('updateMarkers', markers) - - // 更新props中的markers - this.currentProps.markers = markers - - console.log('updateMarkers', markers) - - // 完全重新创建markerLayer - const newLayer = this.createMarkerLayerFromProps(this.currentProps) - - // 如果有旧的layer,将其从地图中移除 - if (this.markerLayer) { - // 这里需要外部调用来移除旧layer并添加新layer - // 我们只负责创建新的layer - } - - this.markerLayer = newLayer - } - - /** * 从props创建markerLayer(内部方法) */ private createMarkerLayerFromProps(props: MapProps): VectorLayer { - const source = new VectorSource() - // 添加标记 - const markers = props.markers || [] - markers.forEach((marker) => { - const feature = new Feature({ - geometry: new Point(fromLonLat(marker.coordinates)), - markerData: marker - }) - feature.setStyle(createMarkerStyle(marker)) - source.addFeature(feature) - }) + this.updateData(props) // 检查是否应该强制使用单个marker模式 const shouldForceSingleMark = () => { if (!props.forceSingleMark || !this.map) return false const currentZoom = this.map.getView().getZoom() - return currentZoom && currentZoom >= props.forceSingleMark + console.log('currentZoom',currentZoom) + // return currentZoom && currentZoom >= props.forceSingleMark + return currentZoom && currentZoom >= ANIMATION_CONFIG.clusterThreshold } - console.log('createMarkerLayerFromProps shouldForceSingleMark', shouldForceSingleMark()) + // console.log('createMarkerLayerFromProps shouldForceSingleMark', shouldForceSingleMark()) // 如果启用聚合且不强制使用单个marker模式 + console.log('shouldForceSingleMark()',shouldForceSingleMark()); + if (props.enableCluster && !shouldForceSingleMark()) { const clusterSource = new Cluster({ - source: source, - distance: Math.max(props.clusterDistance || 40, 10) + source: this.vectorSource, + distance: Math.max(props.clusterDistance || 10, 10) }) - return new VectorLayer({ + this.markerLayer = new VectorLayer({ source: clusterSource, style: (feature) => { const features = feature.get('features') @@ -177,10 +109,16 @@ export class MarkerService { } }) } else { - return new VectorLayer({ - source: source + // console.log('基础marker') + + this.markerLayer = new VectorLayer({ + source: this.vectorSource }) } + this.map?.addLayer(this.markerLayer) + + + return this.markerLayer } /** @@ -197,7 +135,7 @@ export class MarkerService { */ destroy(): void { this.markerLayer = null - this.currentProps = null + // this.currentProps = null this.map = null } } diff --git a/web/src/views/HandDevice/Home/components/services/trajectory.service.ts b/web/src/views/HandDevice/Home/components/services/trajectory.service.ts index 17bf92c..1dc94b5 100644 --- a/web/src/views/HandDevice/Home/components/services/trajectory.service.ts +++ b/web/src/views/HandDevice/Home/components/services/trajectory.service.ts @@ -29,11 +29,14 @@ export class TrajectoryService { // 按时间排序的所有轨迹点 private sortedTrajectoryPoints: Array = [] + constructor(map: Map) { + this.map = map + } /** * 创建轨迹图层 */ - createTrajectoryLayer(map: Map): VectorLayer { - this.map = map + createTrajectoryLayer(): VectorLayer { + const source = new VectorSource() this.trajectoryLayer = new VectorLayer({ @@ -138,7 +141,7 @@ export class TrajectoryService { } }) - map.addLayer(this.movingMarkerLayer) + this.map?.addLayer(this.movingMarkerLayer) return this.trajectoryLayer } diff --git a/web/src/views/HandDevice/Home/components/types/map.types.ts b/web/src/views/HandDevice/Home/components/types/map.types.ts index f8a3f8f..53520eb 100644 --- a/web/src/views/HandDevice/Home/components/types/map.types.ts +++ b/web/src/views/HandDevice/Home/components/types/map.types.ts @@ -138,7 +138,7 @@ export interface TrajectoryPoint { // 轨迹数据接口 export interface TrajectoryData { /** 设备ID */ - deviceId: number + deviceId?: number /** 轨迹点列表 */ points: TrajectoryPoint[] /** 轨迹颜色 */ diff --git a/web/src/views/HandDevice/Home/components/utils/map.utils.ts b/web/src/views/HandDevice/Home/components/utils/map.utils.ts index 18fc929..0ca31cf 100644 --- a/web/src/views/HandDevice/Home/components/utils/map.utils.ts +++ b/web/src/views/HandDevice/Home/components/utils/map.utils.ts @@ -141,7 +141,7 @@ export const createMarkerStyle = ( image: new Icon({ src: createLocationIconSVG(color, 24), scale: 1, - anchor: [0.5, 1], // 锚点设置在底部中心 + anchor: [0.5, 0.8], // 锚点设置在底部中心 anchorXUnits: 'fraction', anchorYUnits: 'fraction' }) diff --git a/web/src/views/HandDevice/Home/index.vue b/web/src/views/HandDevice/Home/index.vue index feafbfc..f3bd2a9 100644 --- a/web/src/views/HandDevice/Home/index.vue +++ b/web/src/views/HandDevice/Home/index.vue @@ -3,7 +3,7 @@ class="w-full map-container" v-if="inited" :showDrawFences="false" - :showTrajectories="false" + :showTrajectories="true" :markers="markers" :fences="fences" /> @@ -41,7 +41,7 @@