Browse Source

搜索缩放等

master
xh 6 days ago
parent
commit
1ff99ae4be
  1. 57
      web/src/views/HandDevice/Home/components/OpenLayerMap.vue
  2. 149
      web/src/views/HandDevice/Home/components/TopPanel.vue
  3. 54
      web/src/views/HandDevice/Home/components/composables/useMapEvents.ts
  4. 32
      web/src/views/HandDevice/Home/components/composables/useMapServices.ts
  5. 32
      web/src/views/HandDevice/Home/components/composables/useMapWatchers.ts
  6. 29
      web/src/views/HandDevice/Home/components/services/map.service.ts
  7. 9
      web/src/views/HandDevice/Home/components/services/marker.service.ts
  8. 10
      web/src/views/HandDevice/Home/components/types/map.types.ts
  9. 199
      web/src/views/HandDevice/Home/index.vue

57
web/src/views/HandDevice/Home/components/OpenLayerMap.vue

@ -44,7 +44,6 @@
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, onMounted, watch } from 'vue' import { ref, onMounted, watch } from 'vue'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
// //
@ -59,7 +58,6 @@ import { useMapEvents } from './composables/useMapEvents'
import { useTrajectoryControls } from './composables/useTrajectoryControls' import { useTrajectoryControls } from './composables/useTrajectoryControls'
import { useMapWatchers } from './composables/useMapWatchers' import { useMapWatchers } from './composables/useMapWatchers'
import { MapService } from './services/map.service'
// //
import TrajectoryControls from './TrajectoryControls.vue' import TrajectoryControls from './TrajectoryControls.vue'
import MapControls from './MapControls.vue' import MapControls from './MapControls.vue'
@ -108,7 +106,7 @@ enableStatus 1启用 0未启用
const { const {
services, services,
initializeMapAndLayers,
initMap,
setMarkersVisible, setMarkersVisible,
setTrajectoriesVisible, setTrajectoriesVisible,
setFencesVisible, setFencesVisible,
@ -160,26 +158,19 @@ const toggleDrawFences = () => {
} }
} }
let mapService: MapService | null = null
let isMapInitialized = false let isMapInitialized = false
/** /**
* 初始化地图 * 初始化地图
*/ */
const initMap = () => {
const init = () => {
if (!mapContainerRef.value) return if (!mapContainerRef.value) return
//
mapService = new MapService(mapContainerRef.value, props)
try { try {
// const mapInstance = mapService.initMap(mapContainerRef.value, props)
// //
const { map, popupOverlay } = initializeMapAndLayers(mapService, props)
initMap(mapContainerRef.value, props)
if (!services.mapService?.map) {
throw new Error('Map instance is null')
}
// //
setMarkersVisible(showMarkers.value) setMarkersVisible(showMarkers.value)
@ -188,14 +179,14 @@ const initMap = () => {
// ,, marker // ,, marker
setupMapEventListeners( setupMapEventListeners(
map,
popupOverlay,
services.trajectoryService as any,
services.mapService.map,
services.mapService.popupOverlay,
services.trajectoryService,
services.popupService, services.popupService,
{ {
isDrawing: () => !!services.fenceDrawService?.isCurrentlyDrawing?.(), isDrawing: () => !!services.fenceDrawService?.isCurrentlyDrawing?.(),
onMarkerClick: (marker: MarkerData) => { onMarkerClick: (marker: MarkerData) => {
console.log('marker clicked', marker);
console.log('marker clicked', marker)
selectedMarker.value = marker selectedMarker.value = marker
panelVisible.value = true panelVisible.value = true
@ -204,7 +195,7 @@ const initMap = () => {
// console.log('onZoomEnd', zoom) // console.log('onZoomEnd', zoom)
services.markerService?.createMarkerLayer(props) services.markerService?.createMarkerLayer(props)
// updateMarkers(props.markers || [],props) // updateMarkers(props.markers || [],props)
},
}
// refreshMarkerStyles // refreshMarkerStyles
} }
) )
@ -221,7 +212,7 @@ const initMap = () => {
setTrajectoriesVisible, setTrajectoriesVisible,
setFencesVisible, setFencesVisible,
toggleFenceDrawing, toggleFenceDrawing,
updateMarkers,
markers: props.markers || [] markers: props.markers || []
}) })
@ -230,7 +221,7 @@ const initMap = () => {
// //
isMapInitialized = true isMapInitialized = true
console.log('地图初始化成功', { map, services: services })
console.log('地图初始化成功')
} catch (error) { } catch (error) {
console.error('地图初始化失败:', error) console.error('地图初始化失败:', error)
} }
@ -251,6 +242,16 @@ const handleFenceDrawComplete = (coordinates: [number, number][]) => {
// //
showDrawFences.value = false showDrawFences.value = false
} }
//
const fitToMarkers = () => {
if (isMapInitialized && props.markers && props.markers.length > 0) {
const markerCoords = props.markers.map((marker) => [
marker.longitude || 0,
marker.latitude || 0
])
services.mapService?.fitToMarkers(markerCoords)
}
}
const refreshFences = () => { const refreshFences = () => {
if (isMapInitialized) { if (isMapInitialized) {
@ -260,9 +261,10 @@ const refreshFences = () => {
// markers props // markers props
watch( watch(
() => props.markers, () => props.markers,
(newMarkers) => {
if (newMarkers && newMarkers.length > 0 && isMapInitialized) {
(newMarkers, oldMarkers) => {
updateMarkers(newMarkers, props) updateMarkers(newMarkers, props)
if (newMarkers.length !== oldMarkers.length) {
fitToMarkers()
} }
}, },
{ deep: true, immediate: false } { deep: true, immediate: false }
@ -270,21 +272,18 @@ watch(
watch( watch(
() => props.fences, () => props.fences,
(newFences) => { (newFences) => {
if (newFences && newFences.length > 0 && isMapInitialized) {
refreshFences() refreshFences()
}
}, },
{ deep: true, immediate: false } { deep: true, immediate: false }
) )
onMounted(() => { onMounted(() => {
setTimeout(() => { setTimeout(() => {
initMap()
init()
}, 100) }, 100)
}) })
defineExpose({ refreshFences })
defineExpose({ refreshFences,fitToMarkers })
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">

149
web/src/views/HandDevice/Home/components/TopPanel.vue

@ -0,0 +1,149 @@
<template>
<div class="top-panel" v-show="!appStore.mobile">
<div class="top-panel__left">
<div class="search-group">
<el-input v-model="search" class="search-input" placeholder="请输入关键词" />
</div>
</div>
<div class="top-panel__center">
<div class="data_item">
<span class="data_item__title">手持设备</span>
<span class="data_item__value">{{ handDetectorCount }}</span>
<span class="data_item__unit"></span>
</div>
<div class="data_item">
<span class="data_item__title">在线数量</span>
<span class="data_item__value">{{ onlineCount }}</span>
<span class="data_item__unit"></span>
</div>
</div>
<div class="top-panel__right">
<span class="legend-title">报警图例</span>
<div class="normal-legend">正常状态</div>
<div class="alarm1-legend">围栏报警</div>
<div class="alarm2-legend">气体报警</div>
</div>
</div>
</template>
<script lang="ts" setup>
// import { ref, computed } from 'vue'
import { useAppStore } from '@/store/modules/app'
const appStore = useAppStore()
var props = defineProps({
handDetectorCount: {
type: Number,
default: 0
},
onlineCount: {
type: Number,
default: 0
}
})
var search = defineModel({
type: String,
default: ''
})
</script>
<style scoped lang="scss">
/* 顶部面板样式 */
.top-panel {
position: absolute;
top: 0px;
left: 50px;
right: 10px;
z-index: 999;
display: flex;
align-items: center;
justify-content: space-between;
// gap: 12px;
padding: 6px 0px;
flex-wrap: wrap;
box-sizing: border-box;
.top-panel__left {
background: rgba(255, 255, 255, 0.7);
border: 1px solid rgba(0, 0, 0, 0.06);
border-radius: 10px;
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.12);
height: 100%;
margin-right: 12px;
padding: 10px;
}
.top-panel__center {
flex: 1 1 auto;
display: flex;
align-items: center;
.data_item {
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.12);
width: 200px;
background: rgba(255, 255, 255, 0.9);
border: 1px solid rgba(0, 0, 0, 0.06);
border-radius: 8px;
padding: 12px 12px;
margin-right: 12px;
.data_item__title {
font-size: 14px;
color: #3f3f3f;
vertical-align: middle;
}
.data_item__value {
display: inline-block;
padding: 0 10px;
font-size: 18px;
font-weight: 600;
color: #3399ff;
vertical-align: middle;
}
.data_item__unit {
font-size: 14px;
color: #3f3f3f;
vertical-align: middle;
}
}
}
.top-panel__right {
display: flex;
align-items: center;
gap: 8px;
background: rgba(255, 255, 255, 0.85);
border: 1px solid rgba(0, 0, 0, 0.06);
padding: 12px 12px;
border-radius: 8px;
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.12);
white-space: nowrap;
height: 100%;
.legend-title {
color: #606266;
font-size: 14px;
}
.normal-legend,
.alarm1-legend,
.alarm2-legend {
padding: 4px 8px;
border-radius: 999px;
color: #fff;
font-size: 12px;
line-height: 1;
}
.normal-legend {
background: #67c23a;
}
.alarm1-legend {
background: #e6a23c;
}
.alarm2-legend {
background: #f56c6c;
}
}
}
</style>

54
web/src/views/HandDevice/Home/components/composables/useMapEvents.ts

@ -3,25 +3,28 @@
*/ */
import type { Map } from 'ol' import type { Map } from 'ol'
import type Overlay from 'ol/Overlay' import type Overlay from 'ol/Overlay'
import type FeatureLike from 'ol/Feature'
import { MarkerData, FenceData } from '../types/map.types'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { fromLonLat } from 'ol/proj' import { fromLonLat } from 'ol/proj'
import { TrajectoryService } from '../services/trajectory.service' import { TrajectoryService } from '../services/trajectory.service'
import { PopupService } from '../services/popup.service' import { PopupService } from '../services/popup.service'
import { DICT_TYPE, getDictLabel } from '@/utils/dict' import { DICT_TYPE, getDictLabel } from '@/utils/dict'
interface PopupContentGenerator { interface PopupContentGenerator {
handleTrajectoryPoint: (feature: any) => string
handleTrajectoryLine: (feature: any) => string
handleFence: (feature: any) => string
handleMarker: (feature: any) => string
handleTrajectoryPoint: (feature: FeatureLike) => string
handleTrajectoryLine: (feature: FeatureLike) => string
handleFence: (feature: FeatureLike) => string
handleMarker: (feature: FeatureLike) => string
} }
export const useMapEvents = () => { export const useMapEvents = () => {
// 创建弹窗内容生成器 // 创建弹窗内容生成器
const createPopupContentGenerator = ( const createPopupContentGenerator = (
trajectoryService: any,
popupService: any
trajectoryService: TrajectoryService,
popupService: PopupService
): PopupContentGenerator => ({ ): PopupContentGenerator => ({
handleTrajectoryPoint: (feature: any): string => {
handleTrajectoryPoint: (feature: FeatureLike): string => {
const timeText = feature.get('timeText') || '' const timeText = feature.get('timeText') || ''
const trajectoryId = feature.get('trajectoryId') || '' const trajectoryId = feature.get('trajectoryId') || ''
const timestamp = feature.get('timestamp') const timestamp = feature.get('timestamp')
@ -40,7 +43,7 @@ export const useMapEvents = () => {
` `
}, },
handleTrajectoryLine: (feature: any): string => {
handleTrajectoryLine: (feature: FeatureLike): string => {
const deviceId = feature.get('deviceId') || '' const deviceId = feature.get('deviceId') || ''
const deviceName = const deviceName =
trajectoryService?.getTrajectoryData().find((t) => t.deviceId === deviceId)?.name || trajectoryService?.getTrajectoryData().find((t) => t.deviceId === deviceId)?.name ||
@ -53,8 +56,8 @@ export const useMapEvents = () => {
` `
}, },
handleFence: (feature: any): string => {
const fenceData = feature.get('fenceData')
handleFence: (feature: FeatureLike): string => {
const fenceData: FenceData = feature.get('fenceData')
const statusText = getDictLabel(DICT_TYPE.HAND_DETECTOR_FENCE_STATUS, fenceData.status) const statusText = getDictLabel(DICT_TYPE.HAND_DETECTOR_FENCE_STATUS, fenceData.status)
const typeText = getDictLabel(DICT_TYPE.HAND_DETECTOR_FENCE_TYPE, fenceData.type) const typeText = getDictLabel(DICT_TYPE.HAND_DETECTOR_FENCE_TYPE, fenceData.type)
@ -70,7 +73,7 @@ export const useMapEvents = () => {
` `
}, },
handleMarker: (feature: any): string => {
handleMarker: (feature: FeatureLike): string => {
return popupService?.handlePopupContent(feature) || '' return popupService?.handlePopupContent(feature) || ''
} }
}) })
@ -85,12 +88,16 @@ export const useMapEvents = () => {
popupService: PopupService | null, popupService: PopupService | null,
opts?: { opts?: {
isDrawing?: () => boolean isDrawing?: () => boolean
onMarkerClick?: (markerData: any) => void
onMarkerClick?: (markerData: MarkerData) => void
onZoomEnd?: (zoom: number) => void onZoomEnd?: (zoom: number) => void
// markerLayer?: any // markerLayer?: any
// refreshMarkerStyles?: () => void // refreshMarkerStyles?: () => void
} }
) => { ) => {
if (!trajectoryService || !popupService) {
return
}
const popupGenerator = createPopupContentGenerator(trajectoryService, popupService) const popupGenerator = createPopupContentGenerator(trajectoryService, popupService)
// 鼠标悬停事件 // 鼠标悬停事件
@ -101,7 +108,7 @@ export const useMapEvents = () => {
return return
} }
// //
const feature = map.forEachFeatureAtPixel(event.pixel, (feature: any) => feature)
const feature = map.forEachFeatureAtPixel(event.pixel, (feature: FeatureLike) => feature)
if (feature) { if (feature) {
map.getTargetElement().style.cursor = 'pointer' map.getTargetElement().style.cursor = 'pointer'
@ -118,7 +125,7 @@ export const useMapEvents = () => {
if (opts?.isDrawing && opts.isDrawing()) { if (opts?.isDrawing && opts.isDrawing()) {
return return
} }
const feature = map.forEachFeatureAtPixel(event.pixel, (feature: any) => feature)
const feature = map.forEachFeatureAtPixel(event.pixel, (feature: FeatureLike) => feature)
if (feature) { if (feature) {
handleFeatureClick(feature, map, opts) handleFeatureClick(feature, map, opts)
} }
@ -142,7 +149,6 @@ export const useMapEvents = () => {
map.on('click', handleClick) map.on('click', handleClick)
map.on('moveend', handleMoveEnd) map.on('moveend', handleMoveEnd)
return { return {
destroy: () => { destroy: () => {
map.un('pointermove', handlePointerMove) map.un('pointermove', handlePointerMove)
@ -157,7 +163,7 @@ export const useMapEvents = () => {
*/ */
const showPopup = ( const showPopup = (
event: any, event: any,
feature: any,
feature: FeatureLike,
popupOverlay: Overlay | null, popupOverlay: Overlay | null,
popupGenerator: PopupContentGenerator popupGenerator: PopupContentGenerator
) => { ) => {
@ -202,8 +208,8 @@ export const useMapEvents = () => {
* *
*/ */
const handleFeatureClick = ( const handleFeatureClick = (
feature: any,
map: any,
feature: FeatureLike,
map: Map,
opts?: { onMarkerClick?: (markerData: any) => void } opts?: { onMarkerClick?: (markerData: any) => void }
) => { ) => {
const featureType = feature.get('type') const featureType = feature.get('type')
@ -226,8 +232,8 @@ export const useMapEvents = () => {
* *
*/ */
const handleMarkerClick = ( const handleMarkerClick = (
feature: any,
map: any,
feature: FeatureLike,
map: Map,
opts?: { onMarkerClick?: (markerData: any) => void } opts?: { onMarkerClick?: (markerData: any) => void }
) => { ) => {
const markerData = feature.get('markerData') const markerData = feature.get('markerData')
@ -245,7 +251,7 @@ export const useMapEvents = () => {
} }
} else if (markerData) { } else if (markerData) {
// 处理非聚合的单个标记点击 // 处理非聚合的单个标记点击
animateToCoordinate(markerData.coordinates, map, 15)
animateToCoordinate(markerData.coordinates, map, 17)
opts?.onMarkerClick?.(markerData) opts?.onMarkerClick?.(markerData)
} }
} }
@ -253,9 +259,9 @@ export const useMapEvents = () => {
/** /**
* *
*/ */
const handleClusterClick = (features: any[], map: any) => {
const handleClusterClick = (features: FeatureLike[], map: Map) => {
// 计算聚合标记的中心点 // 计算聚合标记的中心点
const coordinates = features.map((f: any) => f.get('markerData').coordinates)
const coordinates = features.map((f: FeatureLike) => f.get('markerData').coordinates)
const centerLon = const centerLon =
coordinates.reduce((sum: number, coord: any) => sum + coord[0], 0) / coordinates.length coordinates.reduce((sum: number, coord: any) => sum + coord[0], 0) / coordinates.length
const centerLat = const centerLat =
@ -267,7 +273,7 @@ export const useMapEvents = () => {
/** /**
* *
*/ */
const animateToCoordinate = (coordinates: [number, number], map: any, zoom: number) => {
const animateToCoordinate = (coordinates: [number, number], map: Map, zoom: number) => {
const view = map.getView() const view = map.getView()
view.animate({ view.animate({

32
web/src/views/HandDevice/Home/components/composables/useMapServices.ts

@ -4,7 +4,7 @@
import type { Map } from 'ol' import type { Map } from 'ol'
import { ref, onUnmounted, reactive } from 'vue' import { ref, onUnmounted, reactive } from 'vue'
import type { MapProps, MapInstance } from '../types/map.types'
import type { MapProps } from '../types/map.types'
import { MapService } from '../services/map.service' import { MapService } from '../services/map.service'
import { MarkerService } from '../services/marker.service' import { MarkerService } from '../services/marker.service'
import { AnimationService } from '../services/animation.service' import { AnimationService } from '../services/animation.service'
@ -25,7 +25,7 @@ interface ServiceInstances {
export const useMapServices = () => { export const useMapServices = () => {
// 服务实例状态 // 服务实例状态
const services = reactive<ServiceInstances>({
const services: ServiceInstances = {
mapService: null, mapService: null,
markerService: null, markerService: null,
animationService: null, animationService: null,
@ -33,27 +33,26 @@ export const useMapServices = () => {
trajectoryService: null, trajectoryService: null,
fenceService: null, fenceService: null,
fenceDrawService: null fenceDrawService: null
})
}
/** /**
* *
*/ */
const initializeMapAndLayers = (mapInstance: MapService, props: MapProps) => {
const initMap = (mapContainerRef: HTMLElement, props: MapProps) => {
// 初始化地图 // 初始化地图
const map = mapInstance.map
const popupOverlay = mapInstance.popupOverlay
if (!map) {
const mapService = new MapService(mapContainerRef, props)
if (!mapService.map) {
throw new Error('Map instance is null') throw new Error('Map instance is null')
} }
// 重新初始化服务,确保markerService有地图实例 // 重新初始化服务,确保markerService有地图实例
services.mapService = mapInstance
services.markerService = new MarkerService(map)
services.animationService = new AnimationService(map)
services.mapService = mapService
services.markerService = new MarkerService(mapService.map)
services.animationService = new AnimationService(mapService.map)
services.popupService = new PopupService() services.popupService = new PopupService()
services.trajectoryService = new TrajectoryService(map)
services.fenceService = new FenceService(map)
services.fenceDrawService = new FenceDrawService(map)
services.trajectoryService = new TrajectoryService(mapService.map)
services.fenceService = new FenceService(mapService.map)
services.fenceDrawService = new FenceDrawService(mapService.map)
// 创建marker图层 // 创建marker图层
services.markerService.createMarkerLayer(props) services.markerService.createMarkerLayer(props)
@ -65,11 +64,6 @@ export const useMapServices = () => {
services.fenceService.createFenceLayer(props.fences || []) services.fenceService.createFenceLayer(props.fences || [])
// 初始化围栏绘制服务 // 初始化围栏绘制服务
services.fenceDrawService.createDrawLayer() services.fenceDrawService.createDrawLayer()
return {
map,
popupOverlay
}
} }
/** /**
@ -208,7 +202,7 @@ export const useMapServices = () => {
// 方法 // 方法
initializeMapAndLayers,
initMap,
setMarkersVisible, setMarkersVisible,
setTrajectoriesVisible, setTrajectoriesVisible,
setFencesVisible, setFencesVisible,

32
web/src/views/HandDevice/Home/components/composables/useMapWatchers.ts

@ -15,7 +15,6 @@ interface WatchOptions {
isDrawing: boolean, isDrawing: boolean,
onComplete?: (coordinates: [number, number][]) => void onComplete?: (coordinates: [number, number][]) => void
) => void ) => void
updateMarkers: (markers: any[]) => void
markers: any[] markers: any[]
} }
@ -29,7 +28,7 @@ export const useMapWatchers = (options: WatchOptions) => {
setTrajectoriesVisible, setTrajectoriesVisible,
setFencesVisible, setFencesVisible,
toggleFenceDrawing, toggleFenceDrawing,
updateMarkers,
markers markers
} = options } = options
@ -93,18 +92,18 @@ export const useMapWatchers = (options: WatchOptions) => {
/** /**
* *
*/ */
const setupMarkersDataWatcher = () => {
return watch(
markers,
(newMarkers) => {
if (newMarkers && newMarkers.length > 0) {
console.log('Markers data changed, updating markers:', newMarkers.length)
updateMarkers(newMarkers)
}
},
{ deep: true, immediate: false }
)
}
// const setupMarkersDataWatcher = () => {
// return watch(
// markers,
// (newMarkers = []) => {
// // if (newMarkers && newMarkers.length > 0) {
// console.log('Markers data changed, updating markers:', newMarkers.length)
// updateMarkers(newMarkers)
// // }
// },
// { deep: true, immediate: false }
// )
// }
/** /**
* *
@ -114,8 +113,7 @@ export const useMapWatchers = (options: WatchOptions) => {
setupMarkersWatcher(), setupMarkersWatcher(),
setupTrajectoriesWatcher(), setupTrajectoriesWatcher(),
setupFencesWatcher(), setupFencesWatcher(),
setupDrawFencesWatcher(),
setupMarkersDataWatcher()
setupDrawFencesWatcher()
] ]
// 返回清理函数 // 返回清理函数
@ -133,7 +131,7 @@ export const useMapWatchers = (options: WatchOptions) => {
setupTrajectoriesWatcher, setupTrajectoriesWatcher,
setupFencesWatcher, setupFencesWatcher,
setupDrawFencesWatcher, setupDrawFencesWatcher,
setupMarkersDataWatcher,
setupAllWatchers setupAllWatchers
} }
} }

29
web/src/views/HandDevice/Home/components/services/map.service.ts

@ -2,11 +2,14 @@
* *
*/ */
import { Map, View } from 'ol' import { Map, View } from 'ol'
import { Tile as TileLayer } from 'ol/layer' import { Tile as TileLayer } from 'ol/layer'
import { OSM, XYZ } from 'ol/source' import { OSM, XYZ } from 'ol/source'
import { fromLonLat } from 'ol/proj'
import { fromLonLat,transformExtent } from 'ol/proj'
import { boundingExtent } from 'ol/extent'
import Overlay from 'ol/Overlay' import Overlay from 'ol/Overlay'
import type { MapProps, MapInstance } from '../types/map.types'
import type { MapProps } from '../types/map.types'
export class MapService { export class MapService {
map: Map | null = null map: Map | null = null
@ -35,14 +38,6 @@ export class MapService {
minZoom: props.minZoom minZoom: props.minZoom
}) })
}) })
// return {
// map: this.map,
// tileLayer: this.tileLayer,
// markerLayer: null,
// rippleLayer: null,
// popupOverlay: this.popupOverlay
// }
} }
/** /**
@ -89,6 +84,20 @@ export class MapService {
} }
/** /**
*
*/
fitToMarkers(lonLats: number[][]): void {
if (!this.map || lonLats.length === 0) return
const extent = boundingExtent(lonLats)
const webMercatorExtent = transformExtent(extent, 'EPSG:4326', 'EPSG:3857')
this.map.getView().fit(webMercatorExtent, {
padding: [80, 50, 50, 50],
maxZoom: 15
})
}
/**
* *
*/ */
getMap(): Map | null { getMap(): Map | null {

9
web/src/views/HandDevice/Home/components/services/marker.service.ts

@ -8,6 +8,7 @@ import { Feature } from 'ol'
import { Point } from 'ol/geom' import { Point } from 'ol/geom'
import { Style } from 'ol/style' import { Style } from 'ol/style'
import { fromLonLat } from 'ol/proj' import { fromLonLat } from 'ol/proj'
import type { MarkerData, MapProps } from '../types/map.types' import type { MarkerData, MapProps } from '../types/map.types'
import { createMarkerStyle, getClusterMarkerData } from '../utils/map.utils' import { createMarkerStyle, getClusterMarkerData } from '../utils/map.utils'
import { ANIMATION_CONFIG } from '../constants/map.constants' import { ANIMATION_CONFIG } from '../constants/map.constants'
@ -30,7 +31,7 @@ export class MarkerService {
* *
*/ */
createMarkerLayer(props: MapProps): VectorLayer<VectorSource | Cluster> { createMarkerLayer(props: MapProps): VectorLayer<VectorSource | Cluster> {
console.log('createMarkerLayer')
// console.log('createMarkerLayer')
if (this.markerLayer) { if (this.markerLayer) {
this.map?.removeLayer(this.markerLayer) this.map?.removeLayer(this.markerLayer)
} }
@ -77,13 +78,13 @@ export class MarkerService {
const shouldForceSingleMark = () => { const shouldForceSingleMark = () => {
if (!props.forceSingleMark || !this.map) return false if (!props.forceSingleMark || !this.map) return false
const currentZoom = this.map.getView().getZoom() const currentZoom = this.map.getView().getZoom()
console.log('currentZoom', currentZoom)
// console.log('currentZoom', currentZoom)
// return currentZoom && currentZoom >= props.forceSingleMark // return currentZoom && currentZoom >= props.forceSingleMark
return currentZoom && currentZoom >= ANIMATION_CONFIG.clusterThreshold return currentZoom && currentZoom >= ANIMATION_CONFIG.clusterThreshold
} }
// console.log('createMarkerLayerFromProps shouldForceSingleMark', shouldForceSingleMark()) // console.log('createMarkerLayerFromProps shouldForceSingleMark', shouldForceSingleMark())
// 如果启用聚合且不强制使用单个marker模式 // 如果启用聚合且不强制使用单个marker模式
console.log('shouldForceSingleMark()', shouldForceSingleMark())
// console.log('shouldForceSingleMark()', shouldForceSingleMark())
if (props.enableCluster && !shouldForceSingleMark()) { if (props.enableCluster && !shouldForceSingleMark()) {
const clusterSource = new Cluster({ const clusterSource = new Cluster({
@ -122,7 +123,7 @@ export class MarkerService {
renderOrder: (a, b) => { renderOrder: (a, b) => {
// console.log('renderOrder',a,b.get('markerData').time); // console.log('renderOrder',a,b.get('markerData').time);
// 按priority属性降序排列
// 按xxx属性降序排列
return b.get('markerData').time - a.get('markerData').time return b.get('markerData').time - a.get('markerData').time
} }
}) })

10
web/src/views/HandDevice/Home/components/types/map.types.ts

@ -168,13 +168,3 @@ export interface TrajectoryPlayState {
/** 播放结束时间 */ /** 播放结束时间 */
endTime?: number endTime?: number
} }
// 地图实例接口
export interface MapInstance {
map: Map
tileLayer: TileLayer<XYZ | OSM>
markerLayer: any
rippleLayer: any
popupOverlay: Overlay|null
trajectoryLayer?: any
}

199
web/src/views/HandDevice/Home/index.vue

@ -1,67 +1,72 @@
<template> <template>
<div class="flex flex-row overflow-auto" style="height: calc(100vh - 160px)">
<div class="flex-1 h-full position-relative">
<OpenLayerMap <OpenLayerMap
class="w-full map-container"
v-if="inited"
ref="mapRef"
class="map-container"
:showDrawFences="true" :showDrawFences="true"
:showTrajectories="true" :showTrajectories="true"
:markers="markers"
:markers="filterMarkers"
:fences="fences" :fences="fences"
/> />
<div class="top-panel" v-show="!appStore.mobile">
<div class="top-panel__left">
<div class="search-group">
<el-input v-model="search" class="search-input" placeholder="请输入关键词" />
</div>
<!-- <div>
<el-checkbox-group v-model="openFences">
<el-checkbox label="显示电子围栏" value="1" />
</el-checkbox-group>
</div> -->
</div>
<div class="top-panel__center">
<div class="data_item">
<span class="data_item__title">手持设备</span>
<span class="data_item__value">{{ handDetectorCount }}</span>
<span class="data_item__unit"></span>
</div>
<div class="data_item">
<span class="data_item__title">在线数量</span>
<span class="data_item__value">{{ onlineCount }}</span>
<span class="data_item__unit"></span>
<TopPanel
v-model="search"
:handDetectorCount="handDetectorCount"
:onlineCount="onlineCount"
/>
</div> </div>
<div class="markerList">
<!--marker列表 -->
<div v-for="item in filterMarkers" :key="item.id" class="marker-item">
<div>{{ item.name }}{{ item.onlineStatus === 1 ? '在线' : '离线' }}</div>
</div> </div>
<div class="top-panel__right">
<span class="legend-title">报警图例</span>
<div class="normal-legend">正常状态</div>
<div class="alarm1-legend">围栏报警</div>
<div class="alarm2-legend">气体报警</div>
</div> </div>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import OpenLayerMap from './components/OpenLayerMap.vue' import OpenLayerMap from './components/OpenLayerMap.vue'
import TopPanel from './components/TopPanel.vue'
import { getLastDetectorData } from '@/api/gas' import { getLastDetectorData } from '@/api/gas'
import { HandDetectorData } from '@/api/gas/handdetector' import { HandDetectorData } from '@/api/gas/handdetector'
import { MarkerData, FenceData } from './components/types/map.types' import { MarkerData, FenceData } from './components/types/map.types'
import { useAppStore } from '@/store/modules/app'
import { useHandDetectorStore } from '@/store/modules/handDetector' import { useHandDetectorStore } from '@/store/modules/handDetector'
import dayjs from 'dayjs' import dayjs from 'dayjs'
const appStore = useAppStore()
const handDetectorStore = useHandDetectorStore() // store const handDetectorStore = useHandDetectorStore() // store
const getDataTimer = ref<NodeJS.Timeout | null>(null) const getDataTimer = ref<NodeJS.Timeout | null>(null)
const markers = ref<MarkerData[]>([]) const markers = ref<MarkerData[]>([])
const fences = ref<FenceData[]>([]) const fences = ref<FenceData[]>([])
const inited = ref(false)
const mapRef = ref<typeof OpenLayerMap>()
const search = ref('') const search = ref('')
watch(
() => search.value,
(newSearch, oldSearch) => {
if (newSearch !== oldSearch) {
mapRef.value?.fitToMarkers()
}
},
{ immediate: false }
)
// //
const handDetectorCount = computed(() => markers.value.length) const handDetectorCount = computed(() => markers.value.length)
const onlineCount = computed(() => markers.value.filter((item) => item.onlineStatus === 1).length) const onlineCount = computed(() => markers.value.filter((item) => item.onlineStatus === 1).length)
const filterMarkers = computed(() => {
if (search.value) {
return markers.value.filter((item) => {
var isName = item.name.includes(search.value)
var isSn = item.sn?.includes(search.value)
var isGasChemical = item.gasChemical?.includes(search.value)
return isName || isSn || isGasChemical
})
}
return markers.value
})
const getMarkers = async () => { const getMarkers = async () => {
console.log('getMarkers') console.log('getMarkers')
@ -85,7 +90,6 @@ const getMarkers = async () => {
} }
}) })
markers.value = res2 as unknown as any[] markers.value = res2 as unknown as any[]
inited.value = true
}) })
} }
const getFences = async () => { const getFences = async () => {
@ -102,16 +106,16 @@ const getFences = async () => {
} }
onMounted(() => { onMounted(() => {
getMarkers() getMarkers()
setTimeout(() => {
getFences() getFences()
}, 2000)
// getFences() // getFences()
console.log('定时器,暂时关掉,太烦了') console.log('定时器,暂时关掉,太烦了')
// getDataTimer.value = setInterval(() => {
// getMarkers()
// getFences()
// }, 5000)
getDataTimer.value = setInterval(() => {
getMarkers()
getFences()
}, 5000)
}) })
onUnmounted(() => { onUnmounted(() => {
clearInterval(getDataTimer.value as NodeJS.Timeout) clearInterval(getDataTimer.value as NodeJS.Timeout)
@ -120,116 +124,13 @@ onUnmounted(() => {
<style scoped lang="scss"> <style scoped lang="scss">
.map-container { .map-container {
width: 100%; width: 100%;
height: calc(100vh - 140px);
}
/* 顶部面板样式 */
.top-panel {
position: absolute;
top: 12px;
left: 50px;
right: 10px;
z-index: 999;
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 10px 12px;
flex-wrap: wrap;
box-sizing: border-box;
.top-panel__left {
// flex: 0 0 260px;
width: 260px;
background: rgba(255, 255, 255, 0.7);
border: 1px solid rgba(0, 0, 0, 0.06);
border-radius: 10px;
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.12);
height: 100%; height: 100%;
display: flex;
align-items: center;
padding: 10px;
.search-group {
.search-input {
width: 100%;
}
}
} }
.top-panel__center {
flex: 1 1 auto;
display: flex;
align-items: center;
.data_item {
width: 200px;
background: rgba(255, 255, 255, 0.9);
border: 1px solid rgba(0, 0, 0, 0.06);
border-radius: 8px;
padding: 12px 12px;
margin-right: 12px;
.data_item__title {
font-size: 14px;
color: #3f3f3f;
vertical-align: middle;
}
.data_item__value {
display: inline-block;
padding: 0 10px;
font-size: 18px;
font-weight: 600;
color: #3399ff;
vertical-align: middle;
}
.data_item__unit {
font-size: 14px;
color: #3f3f3f;
vertical-align: middle;
}
}
}
.top-panel__right {
display: flex;
align-items: center;
gap: 8px;
background: rgba(255, 255, 255, 0.85);
border: 1px solid rgba(0, 0, 0, 0.06);
padding: 12px 12px;
border-radius: 8px;
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.12);
white-space: nowrap;
.markerList {
width: 220px;
height: 100%; height: 100%;
.legend-title {
color: #606266;
font-size: 14px;
}
.normal-legend,
.alarm1-legend,
.alarm2-legend {
padding: 4px 8px;
border-radius: 999px;
color: #fff;
font-size: 12px;
line-height: 1;
}
.normal-legend {
background: #67c23a;
}
.alarm1-legend {
background: #e6a23c;
}
.alarm2-legend {
background: #f56c6c;
}
}
background-color: white;
padding:0 10px;
margin-left: 10px;
} }
</style> </style>

Loading…
Cancel
Save