Browse Source

性能优化

master
xh 1 week ago
parent
commit
038ad0a29f
  1. 32
      web/src/views/HandDevice/Home/components/OpenLayerMap.vue
  2. 28
      web/src/views/HandDevice/Home/components/composables/useMapEvents.ts
  3. 14
      web/src/views/HandDevice/Home/components/composables/useMapServices.ts
  4. 10
      web/src/views/HandDevice/Home/components/constants/map.constants.ts
  5. 19
      web/src/views/HandDevice/Home/components/services/animation.service.ts
  6. 2
      web/src/views/HandDevice/Home/components/services/map.service.ts
  7. 101
      web/src/views/HandDevice/Home/components/services/marker.service.ts
  8. 3
      web/src/views/HandDevice/Home/components/types/map.types.ts
  9. 15
      web/src/views/HandDevice/Home/components/utils/map.utils.ts
  10. 118
      web/src/views/HandDevice/Home/index.vue

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

@ -24,13 +24,13 @@
@time-change="setTrajectoryTime"
@time-range-change="setTrajectoryTimeRangeCustom"
/>
</div>
</template>
<script lang="ts" setup>
import { ref, onMounted, watch } from 'vue'
import { ElMessage } from 'element-plus'
import type FeatureLike from 'ol/Feature'
//
import type { MapProps, MarkerData } from './types/map.types'
@ -56,7 +56,7 @@ const props = withDefaults(defineProps<MapProps>(), {
fences: () => DEFAULT_FENCES,
forceSingleMark: 13,
enableCluster: MAP_DEFAULTS.enableCluster,
clusterDistance: MAP_DEFAULTS.clusterDistance,
showTrajectories: true,
showMarkers: true,
showFences: true,
@ -76,7 +76,6 @@ const showFences = ref(props.showFences)
const showDrawFences = ref(false)
const mapContainerRef = ref<HTMLElement | null>(null)
/**
gasStatus 气体报警状态
batteryStatus 电池报警状态
@ -95,8 +94,7 @@ const {
setFencesVisible,
toggleFenceDrawing,
clearFenceDrawLayer,
updateMarkers,
updateMarkers
} = useMapServices()
const {
@ -126,7 +124,6 @@ const toggleTrajectories = () => {
// console.log(showTrajectoriesStatus.value, props.markers);
if (showTrajectoriesStatus.value) {
setTrajectoriesVisible(showTrajectoriesStatus.value)
}
}
@ -182,11 +179,19 @@ const init = () => {
emit('on-click-marker', marker)
},
onClusterClick: (features: FeatureLike[]) => {
console.log('onClusterClick', features)
const markerCoords = features.map((feature) => feature.get('markerData').coordinates)
services.mapService?.fitToMarkers(markerCoords)
},
onZoomEnd: (zoom: number) => {
console.log('onZoomEnd', zoom)
services.markerService?.createMarkerLayer(props)
//
// services.markerService?.createMarkerLayerFromProps(props)
services.markerService?.setClusterDistance()
services.markerService?.getSinglePointsInView()
// console.log('marker', marker)
}
}
)
//
@ -201,7 +206,7 @@ const init = () => {
setMarkersVisible,
setTrajectoriesVisible,
setFencesVisible,
toggleFenceDrawing,
toggleFenceDrawing
})
setupAllWatchers()
@ -237,7 +242,7 @@ const fitToMarkers = () => {
.filter((marker) => {
return marker.longitude && marker.latitude
})
.map((marker) => [marker.longitude||0, marker.latitude||0])
.map((marker) => [marker.longitude || 0, marker.latitude || 0])
services.mapService?.fitToMarkers(markerCoords)
}
}
@ -271,10 +276,8 @@ const showTrajectory = (startTime: number, endTime: number, markers: MarkerData[
services.trajectoryService?.setTrajectoryData(markers)
showTrajectoriesStatus.value = true
setTrajectoriesVisible(true)
console.log('showTrajectory', latLng)
console.log('showTrajectory', latLng)
}
}
@ -326,5 +329,4 @@ defineExpose({ refreshFences, fitToMarkers, setCenter, showTrajectory })
height: 100% !important;
}
}
</style>

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

@ -90,8 +90,8 @@ export const useMapEvents = () => {
opts?: {
isDrawing?: () => boolean
onMarkerClick?: (markerData: MarkerData) => void
onClusterClick?: (features: FeatureLike[]) => void
onZoomEnd?: (zoom: number) => void
}
) => {
if (!trajectoryService || !popupService) {
@ -240,14 +240,16 @@ export const useMapEvents = () => {
const handleMarkerClick = (
feature: FeatureLike,
map: Map,
opts?: { onMarkerClick?: (markerData: any) => void }
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)
// handleClusterClick(features, map)
opts?.onClusterClick?.(features)
} else if (features && features.length === 1) {
// 处理聚合中的单个标记点击
const singleMarkerData = features[0].get('markerData')
@ -265,16 +267,16 @@ export const useMapEvents = () => {
/**
*
*/
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 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)
// }
/**
*

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

@ -7,7 +7,7 @@ import { onUnmounted } from 'vue'
import type { MapProps } from '../types/map.types'
import { MapService } from '../services/map.service'
import { MarkerService } from '../services/marker.service'
// import { AnimationService } from '../services/animation.service'
import { PopupService } from '../services/popup.service'
import { TrajectoryService } from '../services/trajectory.service'
import { FenceService } from '../services/fence.service'
@ -16,7 +16,7 @@ import { FenceDrawService } from '../services/fence-draw.service'
interface ServiceInstances {
mapService: MapService | null
markerService: MarkerService | null
// animationService: AnimationService | null
popupService: PopupService | null
trajectoryService: TrajectoryService | null
fenceService: FenceService | null
@ -28,7 +28,7 @@ export const useMapServices = () => {
const services: ServiceInstances = {
mapService: null,
markerService: null,
// animationService: null,
popupService: null,
trajectoryService: null,
fenceService: null,
@ -48,7 +48,7 @@ export const useMapServices = () => {
// 重新初始化服务,确保markerService有地图实例
services.mapService = mapService
services.markerService = new MarkerService(mapService.map)
// services.animationService = new AnimationService(mapService.map)
services.popupService = new PopupService()
services.trajectoryService = new TrajectoryService(mapService.map)
services.fenceService = new FenceService(mapService.map)
@ -70,10 +70,10 @@ export const useMapServices = () => {
const setMarkersVisible = (visible: boolean) => {
if (visible) {
services.markerService?.show()
// services.animationService?.show()
} else {
services.markerService?.hide()
// services.animationService?.hide()
}
}
@ -139,10 +139,12 @@ export const useMapServices = () => {
if (map) {
// console.log('updateMarkers', markers)
// 更新marker service(这可能会创建新的layer)
console.time('createMarkerLayer');
services.markerService.createMarkerLayer({
...currentProps,
markers
})
console.timeEnd('createMarkerLayer');
}
}
}

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

@ -49,11 +49,11 @@ export const MAP_DEFAULTS = {
// tileUrl: 'http://qtbj.icpcdev.site/roadmap/{z}/{x}/{y}.png',
// tileUrl: 'http://192.168.43.94:8080/{z}/{x}/{y}.png',
center: [116.3912757, 39.906217] as [number, number],
zoom: 10,
zoom: 16,
maxZoom: 18,
minZoom: 3,
minZoom: 2,
enableCluster: true,
clusterDistance: 1
}
// 动画配置
@ -75,9 +75,9 @@ export const ANIMATION_CONFIG = {
*/
targetFPS: 60,
/**
*
*
*/
clusterThreshold: 12,
clusterThreshold: 18,
/**
*
*/

19
web/src/views/HandDevice/Home/components/services/animation.service.ts

@ -22,7 +22,7 @@ export class AnimationService {
this.map = map
this.createRippleLayer()
}
/**
/**
*
*/
private createRippleLayer() {
@ -103,10 +103,23 @@ export class AnimationService {
}
addAll(markers: MarkerData[]) {
this.clear()
const source = this.rippleLayer?.getSource()
const features: Feature[] = []
// 为每个标记添加波纹效果
markers.forEach((marker) => {
this.add(marker)
const feature = new Feature({
geometry: new Point(fromLonLat(marker.coordinates)),
markerData: marker
})
// 设置动画开始时间
feature.set('animationStart', Date.now())
feature.set('rippleColor', marker.statusColor)
features.push(feature)
})
source?.addFeatures(features)
}
show() {
@ -153,7 +166,7 @@ export class AnimationService {
if (this.rippleLayer && this.map) {
this.map?.removeLayer(this.rippleLayer)
}
this.rippleLayer = null
this.map = null
}

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

@ -96,7 +96,7 @@ export class MapService {
const webMercatorExtent = transformExtent(extent, 'EPSG:4326', 'EPSG:3857')
this.map.getView().fit(webMercatorExtent, {
padding: [80, 50, 100, 50],
padding: [80, 240, 20, 50],
maxZoom: this.map.getView().getMaxZoom()
})
}

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

@ -14,6 +14,8 @@ import { AnimationService } from './animation.service'
import type { MarkerData, MapProps } from '../types/map.types'
import { createMarkerStyle, getClusterMarkerData, getStatusColor } from '../utils/map.utils'
import { ANIMATION_CONFIG } from '../constants/map.constants'
// 防抖
import { debounce } from 'lodash-es'
export class MarkerService {
private map: Map | null = null
markerLayer: VectorLayer<VectorSource | Cluster> | null = null
@ -38,21 +40,25 @@ export class MarkerService {
/**
*
*/
createMarkerLayer(props: MapProps) {
// console.log('createMarkerLayer')
// this.map?.removeLayer(this.markerLayer)
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 {
this.animationService?.clear()
this.vectorSource.clear()
// 添加标记
const markers = props.markers || []
const features: Feature<Point>[] = []
markers.forEach((marker) => {
const feature = new Feature({
geometry: new Point(fromLonLat(marker.coordinates)),
@ -61,26 +67,84 @@ export class MarkerService {
const statusColor = marker.statusColor || ''
feature.setStyle(createMarkerStyle(statusColor))
this.vectorSource.addFeature(feature)
features.push(feature)
})
this.vectorSource.addFeatures(features)
this.getSinglePointsInView()
}
setClusterDistance= debounce(()=> {
if (!this.map) return
const clusterLayer = this.markerLayer
if (!clusterLayer) return
const clusterSource = clusterLayer.getSource() as Cluster<Feature<Point>>
if (!clusterSource) return
const zoom = this.map.getView().getZoom() || 0
let distance = 2
if (zoom <= 4) {
distance = 100
}else if (zoom <= 6) {
distance = 80
} else if (zoom <= 10) {
distance = 30
} else if (zoom <= 16) {
distance = 30
}else if (zoom <= 17) {
distance = 10
}
console.log('zoom',zoom,'distance',distance)
clusterSource?.setDistance(distance)
},200)
/**
*
* @returns {Array}
*/
getSinglePointsInView = debounce(() => {
const map = this.map
const clusterLayer = this.markerLayer
if (!clusterLayer || !map) return []
const singlePoints = []
// 获取聚合图层的源
const clusterSource = clusterLayer.getSource()
// 获取当前视图范围
const view = map.getView()
const currentExtent = view.calculateExtent(map.getSize())
// 获取视图范围内的所有聚合要素
const featuresInView = clusterSource.getFeaturesInExtent(currentExtent)
// console.log('featuresInView',featuresInView)
featuresInView.forEach((clusterFeature) => {
// 关键:获取聚合要素中包含的所有原始要素
const originalFeatures = clusterFeature.get('features')
// console.log('originalFeatures',originalFeatures);
if (originalFeatures && originalFeatures.length === 1) {
singlePoints.push(originalFeatures[0].get('markerData'))
} else if (!originalFeatures) {
singlePoints.push(clusterFeature.get('markerData'))
}
})
this.animationService?.addAll(singlePoints || [])
return singlePoints
},300)
/**
* props创建markerLayer
*
*/
// : VectorLayer<VectorSource | Cluster>
private createMarkerLayerFromProps(props: MapProps) {
createMarkerLayerFromProps(props: MapProps) {
// console.log('createMarkerLayerFromProps')
this.animationService?.clear()
this.updateData(props)
// this.updateData(props)
// 检查是否应该强制使用单个marker模式
const shouldForceSingleMark = () => {
if (!props.forceSingleMark || !this.map) return false
const currentZoom = this.map.getView().getZoom()
// console.log('currentZoom', currentZoom)
// return currentZoom && currentZoom >= props.forceSingleMark
return currentZoom && currentZoom >= ANIMATION_CONFIG.clusterThreshold
}
@ -89,16 +153,20 @@ export class MarkerService {
if (props.enableCluster && !shouldForceSingleMark()) {
if (this.currentLayerMode === 'cluster') return
this.currentLayerMode = 'cluster'
// this.animationService?.clear()
console.log('聚合图层')
const clusterSource = new Cluster({
source: this.vectorSource,
distance: Math.max(props.clusterDistance || 10, 10)
distance: 20 // 单位是像素
})
newLayer = new VectorLayer({
source: clusterSource,
zIndex: 1,
style: (feature) => {
// 视图变化新视口内所有要素的样式重计算​ (如平移、缩放),所以不能调用this.animationService?.add
const features = feature.get('features')
// 确保features存在且不为空
@ -111,8 +179,6 @@ export class MarkerService {
// 单个marker
const markerData: MarkerData = features[0].get('markerData')
this.animationService?.add(markerData)
return markerData ? createMarkerStyle(markerData.statusColor || '') : new Style()
} else {
// 聚合marker
@ -124,14 +190,10 @@ export class MarkerService {
}
})
} else {
// markers可能变化,必须更新
this.animationService?.addAll(props.markers || [])
if (this.currentLayerMode === 'single') return
this.currentLayerMode = 'single'
// console.log('基础marker')
console.log('基础marker图层')
newLayer = new VectorLayer({
source: this.vectorSource,
@ -169,6 +231,7 @@ export class MarkerService {
this.markerLayer = null
this.animationService?.destroy()
// this.currentProps = null
this.createMarkerLayer.cancel()
this.map = null
}
}

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

@ -81,8 +81,7 @@ export interface MapProps {
fences?: FenceData[]
/** 是否启用聚合功能 */
enableCluster?: boolean
/** 聚合距离(像素) */
clusterDistance?: number
/** 强制单个marker显示的zoom级别阈值 */
forceSingleMark?: number
/** 是否显示轨迹控制面板 */

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

@ -140,6 +140,9 @@ export const createLocationIconSVG = (size: number = 24,color: string='#1296db')
`)}`
}
const styleCache: { [key: string]: Style } = {
};
/**
*
*/
@ -150,9 +153,12 @@ export const createMarkerStyle = (
) => {
if (isCluster && clusterSize) {
// 聚合标记样式
return new Style({
const key=`${color}-${clusterSize}`
if(styleCache[key]) return styleCache[key]
styleCache[key]= new Style({
image: new Circle({
radius: Math.min(20 + clusterSize * 2, 40),
radius: Math.min(20 + clusterSize, 40),
fill: new Fill({
color: color + '80' // 添加透明度
}),
@ -169,9 +175,11 @@ export const createMarkerStyle = (
font: 'bold 14px Arial'
})
})
return styleCache[key]
} else {
// 单个标记样式 - 使用位置图标
return new Style({
if(styleCache[color]) return styleCache[color]
styleCache[color]= new Style({
image: new Icon({
src: createLocationIconSVG(24,color),
scale: 1,
@ -180,6 +188,7 @@ export const createMarkerStyle = (
anchorYUnits: 'fraction'
})
})
return styleCache[color]
}
}

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

@ -113,15 +113,15 @@ import { MarkerData, FenceData } from './components/types/map.types'
import { useHandDetectorStore } from '@/store/modules/handDetector'
import { ElMessage, ElScrollbar } from 'element-plus'
import dayjs from 'dayjs'
import dayjs, { Dayjs } from 'dayjs'
import { getDistance } from 'ol/sphere'
import { shallowRef } from 'vue'
const componentsIsActive = ref(false)
const handDetectorStore = useHandDetectorStore() // store
const getDataTimer = ref<NodeJS.Timeout | null>(null)
const markers = ref<MarkerData[]>([])
const markers = shallowRef<MarkerData[]>([])
const fences = ref<FenceData[]>([])
const mapRef = ref<InstanceType<typeof OpenLayerMap>>()
@ -155,7 +155,7 @@ const filterMarkers = computed(() => {
if (selectStatus.value.length !== 0) {
arr = arr.filter((item) => {
console.log('selectStatus', selectStatus.value,item.statusStr);
// console.log('selectStatus', selectStatus.value,item.statusStr);
if (!item.statusStr) {
return true
}
@ -173,57 +173,62 @@ const filterMarkers = computed(() => {
return arr
})
// const filterMarkers2 = computed(() => {
// var arr: MarkerData[] = []
// for (let i = 0; i < 50000; i++) {
// arr.push({
// id: i + 1,
// sn: '867989072728120',
// battery: 3786,
// value: 4,
// longitude: 118.498279,
// latitude: 38.9647966,
// time: 1762937859776,
// name: '',
// fenceIds: '',
// fenceType: 1,
// gasTypeId: 1,
// unit: 'ppm',
// gasChemical: 'CO',
// batteryAlarmValue: 20,
// accuracy: 1,
// alarmLevel: -1,
// maxAlarmLevel: null,
// firstValue: null,
// maxValue: null,
// gpsType: 0,
// gasStatus: 0,
// alarmId: 200,
// batteryStatus: 0,
// batteryStatusAlarmId: null,
// fenceStatus: 0,
// fenceAlarmId: null,
// onlineStatus: 0,
// enableStatus: 1,
// distance: null,
// maxDistance: null,
// tenantId: null,
// talarmStart: null,
// talarmEnd: null,
// coordinates: [118.498279, 38.9647966],
// timeStr: '2025-11-12 16:57:39',
// statusStr: 'offline',
// statusColor: '#909399',
// statusLabel: '线',
// statusPriority: 7,
// expanded: false
// })
// }
// return arr
// })
const filterMarkers2 = function getFilterMarkers2() {
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
arr.push({
id: i + 1,
sn: '867989072728120',
battery: 3786,
value: 4,
longitude: lon,
latitude: lat,
time: 1762937859776,
name: '侯荣刚',
fenceIds: '',
fenceType: 1,
gasTypeId: 1,
unit: 'ppm',
gasChemical: 'CO',
batteryAlarmValue: 20,
accuracy: 1,
alarmLevel: -1,
maxAlarmLevel: null,
firstValue: null,
maxValue: null,
gpsType: 0,
gasStatus: 0,
alarmId: 200,
batteryStatus: 0,
batteryStatusAlarmId: null,
fenceStatus: 0,
fenceAlarmId: null,
onlineStatus: 0,
enableStatus: 1,
distance: null,
maxDistance: null,
tenantId: null,
talarmStart: null,
talarmEnd: null,
coordinates: [lon, lat],
timeStr: nowTime,
statusStr: 'offline',
statusColor: '#909399',
statusLabel: '离线',
statusPriority: 7,
expanded: false
})
}
markers.value=arr
}
const getMarkers = async () => {
console.log('getMarkers')
return await getLastDetectorData().then((res: HandDetectorData[]) => {
console.time('getLastDetectorData');
res = res.filter((i) => i.enableStatus === 1)
var res2 = res
.map((i) => {
@ -247,6 +252,7 @@ const getMarkers = async () => {
}
})
.sort((a, b) => a.statusPriority - b.statusPriority)
console.timeEnd('getLastDetectorData');
markers.value = res2
})
}
@ -410,11 +416,14 @@ function onClickMarker(markerItem: MarkerData) {
if (findIndex === -1) {
return
}
scrollbarRef.value?.scrollToIndex(findIndex)
setTimeout(() => {
scrollbarRef.value?.scrollToIndex(findIndex)
}, 300)
}
onMounted(() => {
getMarkers()
// filterMarkers2()
getFences()
@ -423,8 +432,9 @@ onMounted(() => {
return
}
getMarkers()
// filterMarkers2()
getFences()
}, 4000)
}, 10000)
})
onActivated(() => {

Loading…
Cancel
Save