Compare commits

...

2 Commits

  1. 27
      web/src/components/VirtualCollapsePanel/index.vue
  2. 22
      web/src/views/HandDevice/Home/components/OpenLayerMap.vue
  3. 36
      web/src/views/HandDevice/Home/components/composables/useMapServices.ts
  4. 15
      web/src/views/HandDevice/Home/components/composables/useMapWatchers.ts
  5. 6
      web/src/views/HandDevice/Home/components/services/map.service.ts
  6. 85
      web/src/views/HandDevice/Home/components/services/marker.service.ts
  7. 2
      web/src/views/HandDevice/Home/components/utils/map.utils.ts
  8. 42
      web/src/views/HandDevice/Home/index.vue

27
web/src/components/VirtualCollapsePanel/index.vue

@ -37,7 +37,7 @@
</DynamicScroller> </DynamicScroller>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, onBeforeUnmount, onDeactivated, useTemplateRef } from 'vue'
import { ref, computed, onBeforeUnmount, onDeactivated, useTemplateRef, nextTick } from 'vue'
import type { PropType } from 'vue' import type { PropType } from 'vue'
import { ArrowRight } from '@element-plus/icons-vue' import { ArrowRight } from '@element-plus/icons-vue'
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css' import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
@ -86,8 +86,10 @@ const list = computed(() => {
const scrollbarRef = useTemplateRef('scrollbarRef') const scrollbarRef = useTemplateRef('scrollbarRef')
const scrollbarScrollTop = ref(0) const scrollbarScrollTop = ref(0)
function onScroll(e) { function onScroll(e) {
// console.log('scroll-end',e);
scrollbarScrollTop.value = e.target.scrollTop scrollbarScrollTop.value = e.target.scrollTop
emit('scroll', e)
// emit('scroll', e)
} }
/** /**
* 切换折叠面板展开状态 * 切换折叠面板展开状态
@ -120,11 +122,20 @@ function scrollToIndex(index) {
if (index < 0 || index >= list.value.length) return if (index < 0 || index >= list.value.length) return
activeItem.value = list.value[index] activeItem.value = list.value[index]
const top = props.minItemSize * index const top = props.minItemSize * index
//
cancelAnimationFrame(AnimationId.value as number)
scrollTo(scrollbarScrollTop.value, top, scrollbarScrollTop.value)
// const top = props.minItemSize * index
// console.log('top', top)
// //
// cancelAnimationFrame(AnimationId.value as number)
// scrollTo(scrollbarScrollTop.value, top, scrollbarScrollTop.value)
// nextTick(() => {
// // scrollbarRef.value?.scrollToItem(index)
// scrollbarRef.value?.$refs?.scroller?.scrollToPosition(top)
// })
setTimeout(() => {
scrollbarRef.value?.$refs?.scroller?.scrollToPosition(top)
}, 200)
} }
/** /**
* 滚动到指定位置 * 滚动到指定位置
@ -143,7 +154,7 @@ function scrollTo(from: number, to: number, current: number) {
if (scrollbarScrollTop.value === to) { if (scrollbarScrollTop.value === to) {
return return
} }
const speed = (to - from) / 30
const speed = (to - from) / props.minItemSize
if (speed < 0) { if (speed < 0) {
if (current <= to) { if (current <= to) {
@ -203,7 +214,7 @@ defineExpose({
// collapse-content // collapse-content
.fade-enter-active, .fade-enter-active,
.fade-leave-active { .fade-leave-active {
transition: all 0.2s ease;
transition: all 0.1s ease;
} }
.fade-enter-from, .fade-enter-from,

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

@ -93,8 +93,7 @@ const {
setTrajectoriesVisible, setTrajectoriesVisible,
setFencesVisible, setFencesVisible,
toggleFenceDrawing, toggleFenceDrawing,
clearFenceDrawLayer,
updateMarkers
clearFenceDrawLayer
} = useMapServices() } = useMapServices()
const { const {
@ -175,22 +174,21 @@ const init = () => {
{ {
isDrawing: () => !!services.fenceDrawService?.isCurrentlyDrawing?.(), isDrawing: () => !!services.fenceDrawService?.isCurrentlyDrawing?.(),
onMarkerClick: (marker: MarkerData) => { onMarkerClick: (marker: MarkerData) => {
console.log('marker clicked', marker)
// console.log('marker clicked', marker)
emit('on-click-marker', marker) emit('on-click-marker', marker)
}, },
onClusterClick: (features: FeatureLike[]) => { onClusterClick: (features: FeatureLike[]) => {
console.log('onClusterClick', features)
// console.log('onClusterClick', features)
const markerCoords = features.map((feature) => feature.get('markerData').coordinates) const markerCoords = features.map((feature) => feature.get('markerData').coordinates)
services.mapService?.fitToMarkers(markerCoords) services.mapService?.fitToMarkers(markerCoords)
}, },
onZoomEnd: (zoom: number) => { onZoomEnd: (zoom: number) => {
console.log('onZoomEnd', zoom) console.log('onZoomEnd', zoom)
// //
// services.markerService?.createMarkerLayerFromProps(props)
services.markerService?.setClusterDistance() services.markerService?.setClusterDistance()
services.markerService?.getSinglePointsInView() services.markerService?.getSinglePointsInView()
// console.log('marker', marker)
} }
} }
) )
@ -252,6 +250,7 @@ const setCenter = (coords: [number, number]) => {
if (isMapInitialized) { if (isMapInitialized) {
services.mapService?.setCenter(coords) services.mapService?.setCenter(coords)
services.mapService?.setZoom(17)
} }
} }
/** /**
@ -290,25 +289,24 @@ const refreshFences = () => {
watch( watch(
() => props.markers, () => props.markers,
(newMarkers, oldMarkers) => { (newMarkers, oldMarkers) => {
updateMarkers(newMarkers, props)
if (newMarkers.length !== oldMarkers.length) {
console.log('markers changed', newMarkers, oldMarkers)
services.markerService?.updateData(newMarkers)
if (oldMarkers == undefined || newMarkers.length !== oldMarkers.length) {
fitToMarkers() fitToMarkers()
} }
}, },
{ deep: true, immediate: false }
{ deep: false, immediate: true }
) )
watch( watch(
() => props.fences, () => props.fences,
() => { () => {
refreshFences() refreshFences()
}, },
{ deep: true, immediate: false }
{ deep: false, immediate: false }
) )
onMounted(() => { onMounted(() => {
setTimeout(() => {
init() init()
}, 100)
}) })
defineExpose({ refreshFences, fitToMarkers, setCenter, showTrajectory }) defineExpose({ refreshFences, fitToMarkers, setCenter, showTrajectory })

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

@ -54,8 +54,6 @@ export const useMapServices = () => {
services.fenceService = new FenceService(mapService.map) services.fenceService = new FenceService(mapService.map)
services.fenceDrawService = new FenceDrawService(mapService.map) services.fenceDrawService = new FenceDrawService(mapService.map)
// 创建marker图层
services.markerService.createMarkerLayer(props)
// 创建轨迹图层 // 创建轨迹图层
services.trajectoryService.createTrajectoryLayer() services.trajectoryService.createTrajectoryLayer()
// 创建围栏图层 // 创建围栏图层
@ -70,10 +68,8 @@ export const useMapServices = () => {
const setMarkersVisible = (visible: boolean) => { const setMarkersVisible = (visible: boolean) => {
if (visible) { if (visible) {
services.markerService?.show() services.markerService?.show()
} else { } else {
services.markerService?.hide() services.markerService?.hide()
} }
} }
@ -129,34 +125,6 @@ export const useMapServices = () => {
} }
} }
/**
*
*/
const updateMarkers = (markers: any[], currentProps?: MapProps) => {
if (services.markerService) {
const map = services.mapService?.getMap()
if (map) {
// console.log('updateMarkers', markers)
// 更新marker service(这可能会创建新的layer)
console.time('createMarkerLayer');
services.markerService.createMarkerLayer({
...currentProps,
markers
})
console.timeEnd('createMarkerLayer');
}
}
}
/**
*
*/
const refreshMarkerStyles = () => {
if (services.markerService) {
services.markerService.refreshStyles()
}
}
/** /**
* *
@ -204,8 +172,8 @@ export const useMapServices = () => {
setFencesVisible, setFencesVisible,
toggleFenceDrawing, toggleFenceDrawing,
clearFenceDrawLayer, clearFenceDrawLayer,
updateMarkers,
refreshMarkerStyles,
destroyServices destroyServices
} }
} }

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

@ -86,21 +86,6 @@ 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 }
// )
// }
/** /**
* *

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

@ -108,7 +108,11 @@ export class MapService {
center = fromLonLat(center) center = fromLonLat(center)
this.map.getView().setCenter(center) this.map.getView().setCenter(center)
} }
// 设置缩放级别
setZoom(zoom: number): void {
if (!this.map) return
this.map.getView().setZoom(zoom)
}
/** /**
* *
*/ */

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

@ -13,14 +13,13 @@ import { AnimationService } from './animation.service'
import type { MarkerData, MapProps } from '../types/map.types' import type { MarkerData, MapProps } from '../types/map.types'
import { createMarkerStyle, getClusterMarkerData, getStatusColor } from '../utils/map.utils' import { createMarkerStyle, getClusterMarkerData, getStatusColor } from '../utils/map.utils'
import { ANIMATION_CONFIG } from '../constants/map.constants'
// 防抖 // 防抖
import { debounce } from 'lodash-es' import { debounce } from 'lodash-es'
export class MarkerService { export class MarkerService {
private map: Map | null = null private map: Map | null = null
markerLayer: VectorLayer<VectorSource | Cluster> | null = null
// 当前图层模式(single或cluster聚合):避免重复创建图层
private currentLayerMode: 'single' | 'cluster' | '' = ''
markerLayer: VectorLayer<Cluster> | null = null
private vectorSource: VectorSource private vectorSource: VectorSource
private animationService: AnimationService | null = null private animationService: AnimationService | null = null
@ -28,6 +27,7 @@ export class MarkerService {
this.map = map this.map = map
this.vectorSource = new VectorSource() this.vectorSource = new VectorSource()
this.animationService = new AnimationService(map) this.animationService = new AnimationService(map)
this.createMarkerLayer()
} }
show() { show() {
this.markerLayer?.setVisible(true) this.markerLayer?.setVisible(true)
@ -37,27 +37,14 @@ export class MarkerService {
this.markerLayer?.setVisible(false) this.markerLayer?.setVisible(false)
this.animationService?.hide() this.animationService?.hide()
} }
/**
*
*/
createMarkerLayer = debounce((props: MapProps) => {
console.time('updateData')
this.updateData(props)
console.timeEnd('updateData')
console.time('createMarkerLayerFromProps')
this.createMarkerLayerFromProps(props)
console.timeEnd('createMarkerLayerFromProps')
}, 1000)
/** /**
* *
*/ */
updateData(props: MapProps): void {
updateData(markers: MarkerData[]): void {
this.animationService?.clear() this.animationService?.clear()
this.vectorSource.clear() this.vectorSource.clear()
// 添加标记
const markers = props.markers || []
// debugger
const features: Feature<Point>[] = [] const features: Feature<Point>[] = []
markers.forEach((marker) => { markers.forEach((marker) => {
const feature = new Feature({ const feature = new Feature({
@ -72,7 +59,7 @@ export class MarkerService {
this.vectorSource.addFeatures(features) this.vectorSource.addFeatures(features)
this.getSinglePointsInView() this.getSinglePointsInView()
} }
setClusterDistance= debounce(()=> {
setClusterDistance = debounce(() => {
if (!this.map) return if (!this.map) return
const clusterLayer = this.markerLayer const clusterLayer = this.markerLayer
if (!clusterLayer) return if (!clusterLayer) return
@ -82,18 +69,18 @@ export class MarkerService {
let distance = 2 let distance = 2
if (zoom <= 4) { if (zoom <= 4) {
distance = 100 distance = 100
}else if (zoom <= 6) {
} else if (zoom <= 6) {
distance = 80 distance = 80
} else if (zoom <= 10) { } else if (zoom <= 10) {
distance = 30 distance = 30
} else if (zoom <= 16) { } else if (zoom <= 16) {
distance = 30 distance = 30
}else if (zoom <= 17) {
} else if (zoom <= 17) {
distance = 10 distance = 10
} }
console.log('zoom',zoom,'distance',distance)
console.log('zoom', zoom, 'distance', distance)
clusterSource?.setDistance(distance) clusterSource?.setDistance(distance)
},200)
}, 200)
/** /**
* *
* @returns {Array} * @returns {Array}
@ -107,6 +94,7 @@ export class MarkerService {
// 获取聚合图层的源 // 获取聚合图层的源
const clusterSource = clusterLayer.getSource() const clusterSource = clusterLayer.getSource()
if (!clusterSource) return
// 获取当前视图范围 // 获取当前视图范围
const view = map.getView() const view = map.getView()
@ -127,33 +115,17 @@ export class MarkerService {
} }
}) })
this.animationService?.addAll(singlePoints || []) this.animationService?.addAll(singlePoints || [])
return singlePoints
},300)
}, 300)
/** /**
* props创建markerLayer * props创建markerLayer
* *
*/ */
// : VectorLayer<VectorSource | Cluster> // : VectorLayer<VectorSource | Cluster>
createMarkerLayerFromProps(props: MapProps) {
// console.log('createMarkerLayerFromProps')
createMarkerLayer() {
let newLayer: VectorLayer<Cluster> | null = null
// this.updateData(props)
// 检查是否应该强制使用单个marker模式
const shouldForceSingleMark = () => {
if (!props.forceSingleMark || !this.map) return false
const currentZoom = this.map.getView().getZoom()
return currentZoom && currentZoom >= ANIMATION_CONFIG.clusterThreshold
}
let newLayer: VectorLayer<VectorSource | Cluster> | null = null
// 如果启用聚合且不强制使用单个marker模式
if (props.enableCluster && !shouldForceSingleMark()) {
if (this.currentLayerMode === 'cluster') return
this.currentLayerMode = 'cluster'
// this.animationService?.clear()
console.log('聚合图层') console.log('聚合图层')
const clusterSource = new Cluster({ const clusterSource = new Cluster({
@ -161,7 +133,6 @@ export class MarkerService {
distance: 20 // 单位是像素 distance: 20 // 单位是像素
}) })
newLayer = new VectorLayer({ newLayer = new VectorLayer({
source: clusterSource, source: clusterSource,
zIndex: 1, zIndex: 1,
@ -189,27 +160,6 @@ export class MarkerService {
} }
} }
}) })
} else {
if (this.currentLayerMode === 'single') return
this.currentLayerMode = 'single'
console.log('基础marker图层')
newLayer = new VectorLayer({
source: this.vectorSource,
zIndex: 1,
renderOrder: (a, b) => {
// 按xxx属性排列
return b.get('markerData').statusPriority - a.get('markerData').statusPriority
}
})
}
if (this.markerLayer) {
const isVisible = this.markerLayer?.getVisible() || false
newLayer.setVisible(isVisible) // 新图层保持当前可见状态
this.map?.removeLayer(this.markerLayer)
}
this.markerLayer = newLayer this.markerLayer = newLayer
this.map?.addLayer(this.markerLayer) this.map?.addLayer(this.markerLayer)
@ -230,8 +180,7 @@ export class MarkerService {
destroy(): void { destroy(): void {
this.markerLayer = null this.markerLayer = null
this.animationService?.destroy() this.animationService?.destroy()
// this.currentProps = null
this.createMarkerLayer.cancel()
this.map = null this.map = null
} }
} }

2
web/src/views/HandDevice/Home/components/utils/map.utils.ts

@ -158,7 +158,7 @@ export const createMarkerStyle = (
styleCache[key]= new Style({ styleCache[key]= new Style({
image: new Circle({ image: new Circle({
radius: Math.min(20 + clusterSize, 40),
radius: Math.min(20 + clusterSize/4, 40),
fill: new Fill({ fill: new Fill({
color: color + '80' // 添加透明度 color: color + '80' // 添加透明度
}), }),

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

@ -30,10 +30,7 @@
<template #header="{ item }"> <template #header="{ item }">
<div class="marker-item"> <div class="marker-item">
<div class="flex-1 text-13px"> {{ item.name }}</div> <div class="flex-1 text-13px"> {{ item.name }}</div>
<div
class="text-12px pr-1"
:style="{ color: item.statusColor }"
>
<div class="text-12px pr-1" :style="{ color: item.statusColor }">
{{ item.statusLabel }} {{ item.statusLabel }}
</div> </div>
</div> </div>
@ -113,7 +110,7 @@ import { MarkerData, FenceData } from './components/types/map.types'
import { useHandDetectorStore } from '@/store/modules/handDetector' import { useHandDetectorStore } from '@/store/modules/handDetector'
import { ElMessage, ElScrollbar } from 'element-plus' import { ElMessage, ElScrollbar } from 'element-plus'
import dayjs, { Dayjs } from 'dayjs'
import dayjs from 'dayjs'
import { getDistance } from 'ol/sphere' import { getDistance } from 'ol/sphere'
import { shallowRef } from 'vue' import { shallowRef } from 'vue'
const componentsIsActive = ref(false) const componentsIsActive = ref(false)
@ -126,7 +123,7 @@ const fences = ref<FenceData[]>([])
const mapRef = ref<InstanceType<typeof OpenLayerMap>>() const mapRef = ref<InstanceType<typeof OpenLayerMap>>()
const search = ref('') const search = ref('')
const selectStatus = ref(['normal', 'offline', 'fenceStatus_1', 'alarm','batteryStatus_1'])
const selectStatus = ref(['normal', 'offline', 'fenceStatus_1', 'alarm', 'batteryStatus_1'])
watch( watch(
() => search.value, () => search.value,
(newSearch, oldSearch) => { (newSearch, oldSearch) => {
@ -152,7 +149,6 @@ const filterMarkers = computed(() => {
}) })
} }
if (selectStatus.value.length !== 0) { if (selectStatus.value.length !== 0) {
arr = arr.filter((item) => { arr = arr.filter((item) => {
// console.log('selectStatus', selectStatus.value,item.statusStr); // console.log('selectStatus', selectStatus.value,item.statusStr);
@ -162,23 +158,21 @@ const filterMarkers = computed(() => {
if (item.statusStr == 'gasStatus_2' || item.statusStr == 'gasStatus_1') { if (item.statusStr == 'gasStatus_2' || item.statusStr == 'gasStatus_1') {
return selectStatus.value.includes('alarm') return selectStatus.value.includes('alarm')
} }
// if (item.statusStr == 'fenceStatus_1') {
// return selectStatus.value.includes('fenceStatus_1')
// }
return selectStatus.value.includes(item.statusStr) return selectStatus.value.includes(item.statusStr)
}) })
} }
// console.log('markers.value', markers.value)
return arr return arr
}) })
const filterMarkers2 = function getFilterMarkers2() { const filterMarkers2 = function getFilterMarkers2() {
var arr: MarkerData[] = [] var arr: MarkerData[] = []
var nowTime=dayjs().format('YYYY-MM-DD HH:mm:ss')
for (let i = 0; i < 10000; i++) {
const lon= 80+Math.random()*20
const lat= 30+Math.random()*20
var nowTime = dayjs().format('YYYY-MM-DD HH:mm:ss')
for (let i = 0; i < 50000; i++) {
const lon = 100 + Math.random() * 2
const lat = 30 + Math.random() * 2
arr.push({ arr.push({
id: i + 1, id: i + 1,
sn: '867989072728120', sn: '867989072728120',
@ -222,17 +216,14 @@ const filterMarkers2 = function getFilterMarkers2() {
expanded: false expanded: false
}) })
} }
markers.value=arr
markers.value = arr
} }
const getMarkers = async () => { const getMarkers = async () => {
console.log('getMarkers') console.log('getMarkers')
return await getLastDetectorData().then((res: HandDetectorData[]) => { return await getLastDetectorData().then((res: HandDetectorData[]) => {
console.time('getLastDetectorData');
res = res.filter((i) => i.enableStatus === 1) res = res.filter((i) => i.enableStatus === 1)
var res2 = res var res2 = res
.map((i) => { .map((i) => {
// console.log([i.longitude, i.latitude])
let statusStr = getHighestPriorityStatus({ let statusStr = getHighestPriorityStatus({
gasStatus: i.gasStatus, // gasStatus: i.gasStatus, //
batteryStatus: i.batteryStatus, // batteryStatus: i.batteryStatus, //
@ -252,13 +243,13 @@ const getMarkers = async () => {
} }
}) })
.sort((a, b) => a.statusPriority - b.statusPriority) .sort((a, b) => a.statusPriority - b.statusPriority)
console.timeEnd('getLastDetectorData');
markers.value = res2 markers.value = res2
}) })
} }
const getFences = async () => { const getFences = async () => {
return await handDetectorStore.getAllFences().then((res) => { return await handDetectorStore.getAllFences().then((res) => {
// console.log('getFences', res)
let fencesData = res let fencesData = res
.map((i) => { .map((i) => {
return { return {
@ -272,7 +263,6 @@ const getFences = async () => {
} }
// //
function setCenter(item: MarkerData) { function setCenter(item: MarkerData) {
console.log('setCenter', item)
if (item.longitude && item.latitude) { if (item.longitude && item.latitude) {
mapRef.value?.setCenter([item.longitude || 0, item.latitude || 0]) mapRef.value?.setCenter([item.longitude || 0, item.latitude || 0])
} }
@ -396,13 +386,11 @@ async function showTrajectory(item: MarkerData) {
const historicalCurveRef = ref<InstanceType<typeof HistoricalCurve>>() const historicalCurveRef = ref<InstanceType<typeof HistoricalCurve>>()
// 线 // 线
function onClickHistoricalCurve(item: MarkerData) { function onClickHistoricalCurve(item: MarkerData) {
// console.log('onClickHistoricalCurve', item)
historicalCurveRef.value?.openDrawer(toRaw(item)) historicalCurveRef.value?.openDrawer(toRaw(item))
} }
// //
function onClickTrajectory(item: MarkerData) { function onClickTrajectory(item: MarkerData) {
console.log('onClickTrajectory', item)
trajectoryTimeRange.value = [dayjs().subtract(1, 'hour').valueOf(), dayjs().valueOf()] trajectoryTimeRange.value = [dayjs().subtract(1, 'hour').valueOf(), dayjs().valueOf()]
showTrajectory(item) showTrajectory(item)
} }
@ -410,15 +398,13 @@ function onClickTrajectory(item: MarkerData) {
const scrollbarRef = useTemplateRef<InstanceType<typeof VirtualCollapsePanel>>('scrollbarRef') const scrollbarRef = useTemplateRef<InstanceType<typeof VirtualCollapsePanel>>('scrollbarRef')
// //
function onClickMarker(markerItem: MarkerData) { function onClickMarker(markerItem: MarkerData) {
console.log('onClickMarker', markerItem)
var findIndex = filterMarkers.value.findIndex((item) => item.id === markerItem.id) var findIndex = filterMarkers.value.findIndex((item) => item.id === markerItem.id)
if (findIndex === -1) { if (findIndex === -1) {
return return
} }
setTimeout(() => {
// console.log('findIndex', findIndex, filterMarkers.value[findIndex])
scrollbarRef.value?.scrollToIndex(findIndex) scrollbarRef.value?.scrollToIndex(findIndex)
}, 300)
} }
onMounted(() => { onMounted(() => {

Loading…
Cancel
Save