Browse Source

优化

master
xh 1 week ago
parent
commit
587d3c9a2d
  1. 4
      web/src/api/gas/index.ts
  2. 27
      web/src/views/HandDevice/Home/components/OpenLayerMap.vue
  3. 104
      web/src/views/HandDevice/Home/components/composables/useMapServices.ts
  4. 17
      web/src/views/HandDevice/Home/components/services/animation.service.ts
  5. 12
      web/src/views/HandDevice/Home/components/services/fence-draw.service.ts
  6. 11
      web/src/views/HandDevice/Home/components/services/fence.service.ts
  7. 74
      web/src/views/HandDevice/Home/components/services/map.service.ts
  8. 148
      web/src/views/HandDevice/Home/components/services/marker.service.ts
  9. 9
      web/src/views/HandDevice/Home/components/services/trajectory.service.ts
  10. 2
      web/src/views/HandDevice/Home/components/types/map.types.ts
  11. 2
      web/src/views/HandDevice/Home/components/utils/map.utils.ts
  12. 14
      web/src/views/HandDevice/Home/index.vue

4
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 }

27
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 })
</script>
@ -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;

104
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<LayerRefs | null>(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,

17
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<VectorSource> {
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

12
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({

11
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<VectorSource> {
this.map = map
createFenceLayer(fences: FenceData[]): VectorLayer<VectorSource> {
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))

74
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<XYZ | OSM> | null = null
private popupOverlay: Overlay | null = null
map: Map | null = null
tileLayer: TileLayer<XYZ | OSM> | 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 {

148
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<VectorSource | Cluster> | null = null
private currentProps: MapProps | null = null
private map: Map | null = null
private markerLayer: VectorLayer<VectorSource | Cluster> | 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<VectorSource | Cluster> {
// 保存地图实例
if (map) {
this.map = map
createMarkerLayer(props: MapProps): VectorLayer<VectorSource | Cluster> {
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<VectorSource | Cluster> {
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
}
}

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

@ -29,11 +29,14 @@ export class TrajectoryService {
// 按时间排序的所有轨迹点
private sortedTrajectoryPoints: Array<TrajectoryPoint> = []
constructor(map: Map) {
this.map = map
}
/**
*
*/
createTrajectoryLayer(map: Map): VectorLayer<VectorSource> {
this.map = map
createTrajectoryLayer(): VectorLayer<VectorSource> {
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
}

2
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[]
/** 轨迹颜色 */

2
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'
})

14
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 @@
</template>
<script lang="ts" setup>
import OpenLayerMap from './components/OpenLayerMap.vue'
import { getLastestDetectorData } from '@/api/gas'
import { getLastDetectorData } from '@/api/gas'
import { HandDetectorData } from '@/api/gas/handdetector'
import { MarkerData, FenceData } from './components/types/map.types'
import { useAppStore } from '@/store/modules/app'
@ -65,9 +65,11 @@ const onlineCount = computed(() => markers.value.filter((item) => item.onlineSta
const getMarkers = async () => {
console.log('getMarkers')
return await getLastestDetectorData().then((res: HandDetectorData[]) => {
return await getLastDetectorData().then((res: HandDetectorData[]) => {
res = res.filter((i) => i.enableStatus === 1)
var res2 = res.map((i) => {
console.log([i.longitude, i.latitude]);
return {
...i,
coordinates: [i.longitude, i.latitude],
@ -123,7 +125,7 @@ onUnmounted(() => {
top: 12px;
left: 50px;
right: 10px;
z-index: 1000;
z-index: 999;
display: flex;
align-items: center;
justify-content: space-between;
@ -178,7 +180,7 @@ onUnmounted(() => {
.data_item__title {
font-size: 14px;
color: #909399;
color: #3f3f3f;
vertical-align: middle;
}
@ -192,7 +194,7 @@ onUnmounted(() => {
}
.data_item__unit {
font-size: 14px;
color: #909399;
color: #3f3f3f;
vertical-align: middle;
}
}

Loading…
Cancel
Save