Browse Source

优化地图类型、统计面板调整

master
xh 1 week ago
parent
commit
e533bc9250
  1. 208
      web/src/views/HandDevice/Home/components/OpenLayerMap.vue
  2. 20
      web/src/views/HandDevice/Home/components/composables/useMapEvents.ts
  3. 5
      web/src/views/HandDevice/Home/components/composables/useMapServices.ts
  4. 2
      web/src/views/HandDevice/Home/components/constants/map.constants.ts
  5. 6
      web/src/views/HandDevice/Home/components/services/animation.service.ts
  6. 32
      web/src/views/HandDevice/Home/components/services/fence-draw.service.ts
  7. 6
      web/src/views/HandDevice/Home/components/services/fence.service.ts
  8. 10
      web/src/views/HandDevice/Home/components/services/map.service.ts
  9. 21
      web/src/views/HandDevice/Home/components/services/marker.service.ts
  10. 8
      web/src/views/HandDevice/Home/components/services/popup.service.ts
  11. 5
      web/src/views/HandDevice/Home/components/services/trajectory.service.ts
  12. 13
      web/src/views/HandDevice/Home/components/types/map.types.ts
  13. 16
      web/src/views/HandDevice/Home/components/utils/map.utils.ts
  14. 222
      web/src/views/HandDevice/Home/index.vue
  15. 2
      web/src/views/gas/fence/FenceForm.vue
  16. 37
      web/src/views/gas/handalarm/index.vue

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

@ -24,37 +24,7 @@
@time-change="setTrajectoryTime"
@time-range-change="setTrajectoryTimeRange"
/>
<div class="top-panel" v-show="!appStore.mobile && !props.hideTopPanel">
<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">
<div class="data_item__title">手持设备</div>
<div class="data_item__value"
>{{ handDetectorCount }}<span class="data_item__unit"></span></div
>
</div>
<div class="data_item">
<div class="data_item__title">在线数量</div>
<div class="data_item__value"
>{{ onlineCount }}<span class="data_item__unit"></span></div
>
</div>
<!-- <div class="data_item">
<div class="data_item__title">用户数量</div>
<div class="data_item__value">200<span class="data_item__unit"></span></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 v-if="panelVisible" class="info-panel">
<div class="info-panel__header">
<span class="info-panel__title">设备详情</span>
@ -74,8 +44,7 @@
</div>
</template>
<script lang="ts" setup>
import { useAppStore } from '@/store/modules/app'
import { useHandDetectorStore } from '@/store/modules/handDetector'
import { ref, onMounted, watch } from 'vue'
import { ElMessage } from 'element-plus'
//
@ -101,13 +70,13 @@ const props = withDefaults(defineProps<MapProps>(), {
minZoom: MAP_DEFAULTS.minZoom,
markers: () => DEFAULT_MARKERS,
fences: () => DEFAULT_FENCES,
forceSingleMark: 13,
enableCluster: MAP_DEFAULTS.enableCluster,
clusterDistance: MAP_DEFAULTS.clusterDistance,
showTrajectories: true,
showMarkers: true,
showFences: true,
showDrawFences: true,
hideTopPanel: false
showDrawFences: true
})
const emit = defineEmits<{
@ -120,24 +89,20 @@ const showTrajectories = ref(false)
const showFences = ref(props.showFences)
const showDrawFences = ref(false)
const mapContainerRef = ref<HTMLElement | null>(null)
const handDetectorStore = useHandDetectorStore()
//
const appStore = useAppStore()
const panelVisible = ref(false)
const selectedMarker = ref<MarkerData | null>(null)
const search = ref('')
/**
* gasStatus 气体报警状态
gasStatus 气体报警状态
batteryStatus 电池报警状态
fenceStatus 电子围栏报警状态
onlineStatus 1在线 0离线
enableStatus 1启用 0未启用
*/
//
const handDetectorCount = computed(() => props.markers.length)
const onlineCount = computed(() => props.markers.filter((item) => item.onlineStatus === 1).length)
// 使
const {
services,
@ -196,6 +161,7 @@ const toggleDrawFences = () => {
}
import { MapService } from './services/map.service'
let mapService: MapService | null = null
let isMapInitialized = false
/**
@ -233,6 +199,11 @@ const initMap = () => {
selectedMarker.value = marker
panelVisible.value = true
},
onZoomEnd: (zoom: number) => {
// console.log('onZoomEnd', zoom)
// services.markerService?.updateMarkers(props.markers)
},
markerLayer: layerRefs.value?.markerLayer,
refreshMarkerStyles
}
@ -322,157 +293,6 @@ defineExpose({ refreshFences })
}
}
/* 顶部面板样式 */
.top-panel {
position: absolute;
top: 12px;
left: 12px;
right: 12px;
z-index: 1000;
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 10px 12px;
flex-wrap: wrap;
height: 100px;
box-sizing: border-box;
.top-panel__left {
flex: 0 0 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%;
display: flex;
align-items: center;
.search-group {
display: flex;
align-items: center;
gap: 8px;
padding: 8px;
.search-type {
flex: 0 0 120px;
}
.search-input {
width: 220px;
}
}
}
.top-panel__center {
flex: 1 1 auto;
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 12px;
align-items: center;
.data_item {
background: rgba(255, 255, 255, 0.85);
border: 1px solid rgba(0, 0, 0, 0.06);
border-radius: 8px;
padding: 8px 12px;
min-height: 56px;
display: flex;
flex-direction: column;
justify-content: center;
.data_item__title {
font-size: 12px;
color: #909399;
}
.data_item__value {
font-size: 18px;
font-weight: 600;
color: #303133;
margin-top: 4px;
.data_item__unit {
font-size: 12px;
color: #909399;
margin-left: 6px;
}
}
}
}
.top-panel__right {
display: flex;
align-items: center;
gap: 8px;
flex: 0 0 auto;
background: rgba(255, 255, 255, 0.7);
border: 1px solid rgba(0, 0, 0, 0.06);
padding: 8px 12px;
border-radius: 8px;
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.12);
white-space: nowrap;
height: 100%;
.legend-title {
color: #606266;
}
.normal-legend {
padding: 4px 8px;
border-radius: 999px;
color: #fff;
font-size: 12px;
line-height: 1;
background: #67c23a;
}
.alarm1-legend {
padding: 4px 8px;
border-radius: 999px;
color: #fff;
font-size: 12px;
line-height: 1;
background: #e6a23c;
}
.alarm2-legend {
padding: 4px 8px;
border-radius: 999px;
color: #fff;
font-size: 12px;
line-height: 1;
background: #f56c6c;
}
}
}
@media (max-width: 992px) {
.top-panel {
.top-panel__left {
flex: 1 1 100%;
}
.top-panel__center {
flex: 1 1 100%;
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.top-panel__right {
flex: 1 1 100%;
justify-content: flex-start;
}
}
}
@media (max-width: 600px) {
.top-panel {
.top-panel__center {
grid-template-columns: 1fr;
}
}
}
.info-panel {
position: absolute;
top: 100px;
@ -548,6 +368,4 @@ defineExpose({ refreshFences })
}
}
}
</style>

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

@ -1,6 +1,8 @@
/**
* composable
*/
import type { Map } from 'ol'
import type Overlay from 'ol/Overlay'
import dayjs from 'dayjs'
import { fromLonLat } from 'ol/proj'
import { TrajectoryService } from '../services/trajectory.service'
@ -53,8 +55,7 @@ export const useMapEvents = () => {
handleFence: (feature: any): string => {
const 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)
return `
@ -78,13 +79,14 @@ export const useMapEvents = () => {
*
*/
const setupMapEventListeners = (
map: any,
popupOverlay: any,
map: Map,
popupOverlay: Overlay | null,
trajectoryService: TrajectoryService | null,
popupService: PopupService | null,
opts?: {
isDrawing?: () => boolean
onMarkerClick?: (markerData: any) => void
onZoomEnd?: (zoom: number) => void
markerLayer?: any
refreshMarkerStyles?: () => void
}
@ -98,6 +100,7 @@ export const useMapEvents = () => {
hidePopup(popupOverlay)
return
}
//
const feature = map.forEachFeatureAtPixel(event.pixel, (feature: any) => feature)
if (feature) {
@ -123,16 +126,23 @@ export const useMapEvents = () => {
// 地图移动结束事件(包括放缩)
const handleMoveEnd = () => {
// console.log('handleMoveEnd');
// OpenLayers的Cluster会自动重新计算聚合,只需要刷新样式
if (opts?.markerLayer) {
opts.markerLayer.changed()
}
if (opts?.onZoomEnd) {
const zoom = map.getView().getZoom() || 0
opts.onZoomEnd(zoom)
}
}
map.on('pointermove', handlePointerMove)
map.on('click', handleClick)
map.on('moveend', handleMoveEnd)
return {
destroy: () => {
map.un('pointermove', handlePointerMove)
@ -148,7 +158,7 @@ export const useMapEvents = () => {
const showPopup = (
event: any,
feature: any,
popupOverlay: any,
popupOverlay: Overlay | null,
popupGenerator: PopupContentGenerator
) => {
if (!popupOverlay) return

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

@ -2,7 +2,7 @@
* composable
*/
import { ref, onUnmounted, reactive } from 'vue'
import type { MapProps } 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'
@ -66,7 +66,7 @@ export const useMapServices = () => {
*
*/
const initializeMapAndLayers = (
mapInstance: { map: any; popupOverlay: any },
mapInstance: MapInstance,
props: MapProps
) => {
// 初始化地图
@ -205,6 +205,7 @@ export const useMapServices = () => {
// 从地图中移除旧的marker layer
map.removeLayer(layerRefs.value.markerLayer)
map.removeLayer(layerRefs.value.rippleLayer)
console.log('updateMarkers', markers);
// 更新marker service(这会创建新的layer)
services.markerService.updateMarkers(markers)

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

@ -52,7 +52,7 @@ export const MAP_DEFAULTS = {
maxZoom: 18,
minZoom: 0,
enableCluster: true,
clusterDistance: 0
clusterDistance: 1
}
// 动画配置

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

@ -1,6 +1,8 @@
/**
*
*/
import type { Map } from 'ol'
import { Vector as VectorLayer } from 'ol/layer'
import { Vector as VectorSource } from 'ol/source'
import { Feature } from 'ol'
@ -14,7 +16,7 @@ import { ANIMATION_CONFIG } from '../constants/map.constants'
export class AnimationService {
private rippleLayer: VectorLayer<VectorSource> | null = null
private animationTimer: number | null = null
private map: any = null
private map: Map | null = null
private enableCluster: boolean = true
/**
@ -22,7 +24,7 @@ export class AnimationService {
*/
createRippleLayer(
markers: MarkerData[],
map: any,
map: Map,
enableCluster: boolean = true
): VectorLayer<VectorSource> {
this.map = map

32
web/src/views/HandDevice/Home/components/services/fence-draw.service.ts

@ -1,6 +1,7 @@
/**
*
*/
import type { Map } from 'ol'
import { Vector as VectorLayer } from 'ol/layer'
import { Vector as VectorSource } from 'ol/source'
import { Draw, Modify, Snap } from 'ol/interaction'
@ -11,7 +12,7 @@ import { toLonLat, fromLonLat } from 'ol/proj'
import type { FenceData } from '../types/map.types'
export class FenceDrawService {
private map: any = null
private map: Map | null = null
private drawLayer: VectorLayer<VectorSource> | null = null
private drawInteraction: Draw | null = null
private modifyInteraction: Modify | null = null
@ -24,7 +25,7 @@ export class FenceDrawService {
/**
*
*/
init(map: any): void {
init(map: Map): void {
this.map = map
this.createDrawLayer()
}
@ -60,7 +61,7 @@ export class FenceDrawService {
zIndex: 10 // 确保绘制图层在最上层
})
this.map.addLayer(this.drawLayer)
this.map && this.map.addLayer(this.drawLayer)
}
/**
@ -143,13 +144,14 @@ export class FenceDrawService {
source: this.drawLayer!.getSource()!
})
// 添加交互到地图
this.map.addInteraction(this.drawInteraction)
this.map.addInteraction(this.modifyInteraction)
this.map.addInteraction(this.snapInteraction)
// 改变鼠标样式
this.map.getViewport().style.cursor = 'crosshair'
if (this.map) {
// 添加交互到地图
this.map.addInteraction(this.drawInteraction)
this.map.addInteraction(this.modifyInteraction)
this.map.addInteraction(this.snapInteraction)
this.map.getViewport().style.cursor = 'crosshair'
}
}
/**
@ -162,20 +164,22 @@ export class FenceDrawService {
// 移除交互
if (this.drawInteraction) {
this.map.removeInteraction(this.drawInteraction)
this.map && this.map.removeInteraction(this.drawInteraction)
this.drawInteraction = null
}
if (this.modifyInteraction) {
this.map.removeInteraction(this.modifyInteraction)
this.map && this.map.removeInteraction(this.modifyInteraction)
this.modifyInteraction = null
}
if (this.snapInteraction) {
this.map.removeInteraction(this.snapInteraction)
this.map && this.map.removeInteraction(this.snapInteraction)
this.snapInteraction = null
}
// 恢复鼠标样式
this.map.getViewport().style.cursor = ''
if (this.map) {
this.map.getViewport().style.cursor = ''
}
// 清空绘制图层
if (this.drawLayer) {
@ -220,7 +224,7 @@ export class FenceDrawService {
showEditFence(fence: FenceData): void {
this.clearDrawLayer()
if (this.drawLayer && fence.fenceRange.length > 0) {
if (this.drawLayer && fence.fenceRange && fence.fenceRange.length > 0) {
// 将围栏坐标转换为地图坐标并创建多边形
const coordinates = fence.fenceRange.map((coord) => [coord[0], coord[1]])

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

@ -1,6 +1,8 @@
/**
*
*/
import type { Map } from 'ol'
import { Vector as VectorLayer } from 'ol/layer'
import { Vector as VectorSource } from 'ol/source'
import { Feature } from 'ol'
@ -13,13 +15,13 @@ import { FENCE_STATUS, FENCE_TYPE } from '../types/map.types'
export class FenceService {
private fenceLayer: VectorLayer<VectorSource> | null = null
private fenceData: FenceData[] = []
private map: any = null
private map: Map | null = null
private isVisible: boolean = true
/**
*
*/
createFenceLayer(fences: FenceData[], map: any): VectorLayer<VectorSource> {
createFenceLayer(fences: FenceData[], map: Map): VectorLayer<VectorSource> {
this.map = map
this.fenceData = fences
const source = new VectorSource()

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

@ -48,8 +48,8 @@ export class MapService {
this.popupOverlay = new Overlay({
element: popupElement,
positioning: 'bottom-center',
stopEvent: false,
positioning: 'bottom-center',// 弹窗位置相对于标记的位置
stopEvent: false,// 是否阻止事件冒泡
offset: [0, -10]
})
@ -67,8 +67,10 @@ export class MapService {
this.map = new Map({
target: container,
layers: [this.tileLayer],
overlays: [popup],
layers: [this.tileLayer],//图层数组
overlays: [popup],//弹窗数组
// 定义地图的显示范围、投影、缩放级别和旋转角度
view: new View({
center: center,
zoom: props.zoom,

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

@ -1,6 +1,7 @@
/**
*
*/
import type { Map } from 'ol'
import { Vector as VectorLayer } from 'ol/layer'
import { Vector as VectorSource, Cluster } from 'ol/source'
import { Feature } from 'ol'
@ -13,19 +14,19 @@ import { createMarkerStyle, getClusterMarkerData } from '../utils/map.utils'
export class MarkerService {
private markerLayer: VectorLayer<VectorSource | Cluster> | null = null
private currentProps: MapProps | null = null
private map: any = null
private map: Map | null = null
/**
*
*/
setMap(map: any): void {
setMap(map: Map): void {
this.map = map
}
/**
*
*/
createMarkerLayer(props: MapProps, map?: any): VectorLayer<VectorSource | Cluster> {
createMarkerLayer(props: MapProps, map?: Map): VectorLayer<VectorSource | Cluster> {
// 保存地图实例
if (map) {
this.map = map
@ -50,8 +51,9 @@ export class MarkerService {
const shouldForceSingleMark = () => {
if (!props.forceSingleMark || !this.map) return false
const currentZoom = this.map.getView().getZoom()
return currentZoom >= props.forceSingleMark
return currentZoom && currentZoom >= props.forceSingleMark
}
console.log('shouldForceSingleMark', shouldForceSingleMark())
// 如果启用聚合且不强制使用单个marker模式
if (props.enableCluster && !shouldForceSingleMark()) {
@ -86,7 +88,9 @@ export class MarkerService {
source: source
})
}
// this.markerLayer = new VectorLayer({
// source: source
// })
return this.markerLayer
}
@ -102,10 +106,13 @@ 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)
@ -138,9 +145,9 @@ export class MarkerService {
const shouldForceSingleMark = () => {
if (!props.forceSingleMark || !this.map) return false
const currentZoom = this.map.getView().getZoom()
return currentZoom >= props.forceSingleMark
return currentZoom && currentZoom >= props.forceSingleMark
}
console.log('createMarkerLayerFromProps shouldForceSingleMark', shouldForceSingleMark())
// 如果启用聚合且不强制使用单个marker模式
if (props.enableCluster && !shouldForceSingleMark()) {
const clusterSource = new Cluster({

8
web/src/views/HandDevice/Home/components/services/popup.service.ts

@ -44,6 +44,14 @@ export class PopupService {
<div style="color: ${getStatusColor(status)};">
状态: ${getStatusLabel(status)}
</div>
<div>gasStatus: ${markerData.gasStatus}</div>
<div>onlineStatus: ${markerData.onlineStatus}</div>
<div>enableStatus: ${markerData.enableStatus}</div>
<div>fenceStatus: ${markerData.fenceStatus}</div>
<div>batteryStatus: ${markerData.batteryStatus}</div>
<div>${markerData.gasChemical}</div>
<div>${markerData.value} ${markerData.unit ? markerData.unit : ''}</div>
<div>${markerData.time ? markerData.time : '-'} </div>

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

@ -7,6 +7,7 @@ import { Feature } from 'ol'
import { LineString, Point } from 'ol/geom'
import { Style, Stroke, Circle, Fill, Text, Icon } from 'ol/style'
import { fromLonLat } from 'ol/proj'
import type { Map } from 'ol'
import type {
TrajectoryData,
TrajectoryPoint,
@ -19,7 +20,7 @@ import dayjs from 'dayjs'
export class TrajectoryService {
private trajectoryLayer: VectorLayer<VectorSource> | null = null
private trajectoryData: TrajectoryData[] = []
private map: any = null
private map: Map | null = null
private animationTimer: number | null = null
// 当前移动的 marker 图层
@ -31,7 +32,7 @@ export class TrajectoryService {
/**
*
*/
createTrajectoryLayer(map: any): VectorLayer<VectorSource> {
createTrajectoryLayer(map: Map): VectorLayer<VectorSource> {
this.map = map
const source = new VectorSource()

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

@ -1,3 +1,7 @@
import type { Map } from 'ol'
import type { Tile as TileLayer } from 'ol/layer'
import type { OSM, XYZ } from 'ol/source'
import type Overlay from 'ol/Overlay'
/**
*
*/
@ -84,8 +88,7 @@ export interface MapProps {
showFences?: boolean
/** 是否显示绘制围栏 */
showDrawFences?: boolean
/** 是否隐藏顶部面板 */
hideTopPanel?: boolean
}
// 围栏数据接口
@ -168,10 +171,10 @@ export interface TrajectoryPlayState {
// 地图实例接口
export interface MapInstance {
map: any
tileLayer: any
map: Map
tileLayer: TileLayer<XYZ | OSM>
markerLayer: any
rippleLayer: any
popupOverlay: any
popupOverlay: Overlay|null
trajectoryLayer?: any
}

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

@ -37,13 +37,21 @@ export const getHighestPriorityStatus = (markerData: MarkerData): keyof typeof S
const onlineStatus = String(markerData.onlineStatus) === '0' ? 'offline' : null
// 收集非正常状态
if (gasStatus && markerData.gasStatus !== 0)
if (gasStatus && markerData.gasStatus !== 0) {
statuses.push(gasStatus as keyof typeof STATUS_PRIORITY)
if (batteryStatus && markerData.batteryStatus !== 0)
}
if (batteryStatus && markerData.batteryStatus !== 0) {
statuses.push(batteryStatus as keyof typeof STATUS_PRIORITY)
if (fenceStatus && markerData.fenceStatus !== 0)
}
if (fenceStatus && markerData.fenceStatus !== 0) {
statuses.push(fenceStatus as keyof typeof STATUS_PRIORITY)
if (onlineStatus) statuses.push(onlineStatus)
}
if (onlineStatus) {
statuses.push(onlineStatus)
}
// 如果没有报警状态,则为正常
if (statuses.length === 0) return 'normal'

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

@ -1,18 +1,68 @@
<template>
<OpenLayerMap class="w-full map-container" v-if="inited" :markers="markers" :fences="fences" />
<OpenLayerMap
class="w-full map-container"
v-if="inited"
:showDrawFences="false"
:showTrajectories="false"
:markers="markers"
: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>
</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 OpenLayerMap from './components/OpenLayerMap.vue'
import { getLastestDetectorData } from '@/api/gas'
import { HandDetectorData } from '@/api/gas/handdetector'
import { MarkerData,FenceData } from './components/types/map.types'
import { Fence } from '@/api/gas/fence'
import { FenceApi } from '@/api/gas/fence'
import { MarkerData, FenceData } from './components/types/map.types'
import { useAppStore } from '@/store/modules/app'
import { useHandDetectorStore } from '@/store/modules/handDetector'
import dayjs from 'dayjs'
const appStore = useAppStore()
const handDetectorStore = useHandDetectorStore() // store
const getDataTimer = ref<NodeJS.Timeout | null>(null)
const markers = ref<MarkerData[]>([])
const fences = ref<FenceData[]>([])
const inited = ref(false)
const search = ref('')
//
const handDetectorCount = computed(() => markers.value.length)
const onlineCount = computed(() => markers.value.filter((item) => item.onlineStatus === 1).length)
const getMarkers = async () => {
console.log('getMarkers')
return await getLastestDetectorData().then((res: HandDetectorData[]) => {
@ -37,14 +87,9 @@ const getMarkers = async () => {
})
}
const getFences = async () => {
console.log('getFences')
return await FenceApi.getFencePage({
pageNo: 1,
pageSize: 100
}).then((res) => {
return await handDetectorStore.getAllFences().then((res) => {
console.log('getFences', res)
let fencesData = res.list as Fence[]
fencesData = fencesData.map((i) => {
let fencesData = res.map((i) => {
return {
...i,
fenceRange: JSON.parse(i.fenceRange)
@ -67,9 +112,162 @@ onUnmounted(() => {
clearInterval(getDataTimer.value as NodeJS.Timeout)
})
</script>
<style scoped>
<style scoped lang="scss">
.map-container {
width: 100%;
height: calc(100vh - 140px);
}
/* 顶部面板样式 */
.top-panel {
position: absolute;
top: 12px;
left: 50px;
right: 10px;
z-index: 1000;
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;
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%;
display: flex;
align-items: center;
gap: 8px;
padding: 10px;
.search-group {
display: flex;
align-items: center;
gap: 8px;
// .search-type {
// flex: 0 0 120px;
// }
.search-input {
width: 220px;
}
}
}
.top-panel__center {
flex: 1 1 auto;
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 12px;
align-items: center;
.data_item {
background: rgba(255, 255, 255, 0.9);
border: 1px solid rgba(0, 0, 0, 0.06);
border-radius: 8px;
padding: 12px 12px;
// min-height: 56px;
// display: flex;
// flex-direction: column;
// justify-content: center;
.data_item__title {
font-size: 14px;
color: #909399;
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: #909399;
vertical-align: middle;
}
}
}
.top-panel__right {
display: flex;
align-items: center;
gap: 8px;
flex: 0 0 auto;
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 {
padding: 4px 8px;
border-radius: 999px;
color: #fff;
font-size: 12px;
line-height: 1;
background: #67c23a;
}
.alarm1-legend {
padding: 4px 8px;
border-radius: 999px;
color: #fff;
font-size: 12px;
line-height: 1;
background: #e6a23c;
}
.alarm2-legend {
padding: 4px 8px;
border-radius: 999px;
color: #fff;
font-size: 12px;
line-height: 1;
background: #f56c6c;
}
}
}
@media (max-width: 992px) {
.top-panel {
.top-panel__left {
flex: 1 1 100%;
}
.top-panel__center {
flex: 1 1 100%;
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.top-panel__right {
flex: 1 1 100%;
justify-content: flex-start;
}
}
}
@media (max-width: 600px) {
.top-panel {
.top-panel__center {
grid-template-columns: 1fr;
}
}
}
</style>

2
web/src/views/gas/fence/FenceForm.vue

@ -16,7 +16,7 @@
ref="mapRef"
:show-markers="false"
:show-trajectories="false"
hide-top-panel
:show-fences="true"
:show-draw-fences="true"
@fence-draw-complete="handleFenceDrawComplete"

37
web/src/views/gas/handalarm/index.vue

@ -8,9 +8,9 @@
:inline="true"
label-width="120px"
>
<el-form-item label="持有人" prop="name">
<el-form-item label="持有人" prop="detectorId">
<el-select
v-model="queryParams.name"
v-model="queryParams.detectorId"
placeholder="请选择持有人"
clearable
filterable
@ -25,6 +25,24 @@
/>
</el-select>
</el-form-item>
<el-form-item label="持有人name" prop="name">
<el-select
v-model="queryParams.name"
placeholder="请选择持有人"
clearable
filterable
@keyup.enter="handleQuery"
class="!w-240px"
>
<el-option
v-for="item in handDetectorStore.getHandDetectorList"
:key="item.id"
:label="item.name"
:value="String(item.name)"
/>
</el-select>
</el-form-item>
<el-form-item label="设备编号" prop="sn">
<el-input
v-model="queryParams.sn"
@ -268,6 +286,7 @@ const total = ref(0) // 列表的总页数
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
detectorId: undefined,
name: undefined,
sn: undefined,
@ -318,19 +337,7 @@ const openForm = (type: string, id?: number) => {
formRef.value.open(type, id)
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
//
await message.delConfirm()
//
await HandAlarmApi.deleteHandAlarm(id)
message.success(t('common.delSuccess'))
//
await getList()
} catch {}
}
/** 批量删除GAS手持探测器警报 */
const handleDeleteBatch = async () => {
try {

Loading…
Cancel
Save