|
|
|
@ -12,16 +12,107 @@ |
|
|
|
@on-click-marker="onClickMarker" |
|
|
|
/> |
|
|
|
<TopPanel |
|
|
|
v-model="search" |
|
|
|
v-model:search="search" |
|
|
|
v-model:selectStatus="selectStatus" |
|
|
|
:handDetectorCount="handDetectorCount" |
|
|
|
:onlineCount="onlineCount" |
|
|
|
/> |
|
|
|
</div> |
|
|
|
<div class="markerList"> |
|
|
|
<div style="height: 100%"> |
|
|
|
<!-- <RecycleScroller |
|
|
|
ref="scrollbarRef" |
|
|
|
style="height: 100%" |
|
|
|
:items="filterMarkers" |
|
|
|
:item-size="48" |
|
|
|
key-field="id" |
|
|
|
v-slot="{ item }" |
|
|
|
@scroll="onScroll" |
|
|
|
> |
|
|
|
<div class="marker-item" :data-id="item.id"> |
|
|
|
<div class="flex-1 text-left font-400 text-13px"> {{ item.name }}</div> |
|
|
|
<div class="text-gray-500 font-400 text-12px pr-1" :style="{ color: item.statusColor }"> |
|
|
|
{{ item.statusLabel }} |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</RecycleScroller> --> |
|
|
|
|
|
|
|
<DynamicScroller |
|
|
|
ref="scrollbarRef" |
|
|
|
class="scroller" |
|
|
|
:items="filterMarkers" |
|
|
|
:min-item-size="48" |
|
|
|
key-field="id" |
|
|
|
v-slot="{ item, active }" |
|
|
|
style="height: 100%" |
|
|
|
@scroll="onScroll" |
|
|
|
> |
|
|
|
<DynamicScrollerItem :item="item" :active="active" :size-dependencies="[item.expanded]"> |
|
|
|
<div class="marker-panel"> |
|
|
|
<div class="marker-item" :data-id="item.id" @click="togglePanel(item)"> |
|
|
|
<div class="flex-1 text-left font-400 text-13px"> {{ item.name }}</div> |
|
|
|
<div |
|
|
|
class="text-gray-500 font-400 text-12px pr-1" |
|
|
|
:style="{ color: item.statusColor }" |
|
|
|
> |
|
|
|
{{ item.statusLabel }} |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
<div v-show="item.expanded" class="markerList-content"> |
|
|
|
<div><span>SN:</span>{{ item.sn }}</div> |
|
|
|
<div><span>类型:</span>{{ item.gasChemical }}</div> |
|
|
|
|
|
|
|
<div |
|
|
|
><span>气体状态:</span |
|
|
|
>{{ getLabelWithTypeValue('gasStatus', item.gasStatus) }}</div |
|
|
|
> |
|
|
|
<div |
|
|
|
><span>围栏状态:</span |
|
|
|
>{{ getLabelWithTypeValue('fenceStatus', item.fenceStatus) }}</div |
|
|
|
> |
|
|
|
<div |
|
|
|
><span>电池状态:</span |
|
|
|
>{{ getLabelWithTypeValue('batteryStatus', item.batteryStatus) }}</div |
|
|
|
> |
|
|
|
|
|
|
|
<div><span>电量:</span>{{ item.battery }}</div> |
|
|
|
<div><span>数值:</span>{{ item.value }} {{ item.unit }}</div> |
|
|
|
<div><span>时间:</span>{{ item.timeStr }}</div> |
|
|
|
|
|
|
|
<div style="margin-top: 10px"> |
|
|
|
<el-button |
|
|
|
plain |
|
|
|
size="small" |
|
|
|
v-if="item.latitude && item.longitude" |
|
|
|
@click="setCenter(item)" |
|
|
|
>定位</el-button |
|
|
|
> |
|
|
|
<el-button |
|
|
|
v-hasPermi="['gas:hand-td:HistoricalSn']" |
|
|
|
plain |
|
|
|
size="small" |
|
|
|
@click="onClickTrajectory(item)" |
|
|
|
>轨迹</el-button |
|
|
|
> |
|
|
|
<el-button |
|
|
|
v-hasPermi="['gas:hand-td:HistoricalSn']" |
|
|
|
plain |
|
|
|
size="small" |
|
|
|
@click="onClickHistoricalCurve(item)" |
|
|
|
>历史曲线</el-button |
|
|
|
> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</DynamicScrollerItem> |
|
|
|
</DynamicScroller> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
<!--marker列表 --> |
|
|
|
<!-- |
|
|
|
<el-scrollbar height="100%" ref="scrollbarRef" @scroll="onScroll"> |
|
|
|
<!--marker列表 --> |
|
|
|
<el-collapse accordion v-model="activeName"> |
|
|
|
<el-collapse-item :name="item.id" v-for="item in filterMarkers" :key="item.id"> |
|
|
|
<el-collapse accordion v-model="activeId"> |
|
|
|
<el-collapse-item :name="item.id" v-for="item in filterMarkers2" :key="item.id"> |
|
|
|
<template #title> |
|
|
|
<div class="flex flex-row w-100%"> |
|
|
|
<div class="flex-1 text-left font-500"> |
|
|
|
@ -35,7 +126,7 @@ |
|
|
|
<div class="markerList-content"> |
|
|
|
<div><span>SN:</span>{{ item.sn }}</div> |
|
|
|
<div><span>类型:</span>{{ item.gasChemical }}</div> |
|
|
|
<!-- <hr /> --> |
|
|
|
|
|
|
|
<div |
|
|
|
><span>气体状态:</span |
|
|
|
>{{ getLabelWithTypeValue('gasStatus', item.gasStatus) }}</div |
|
|
|
@ -78,13 +169,25 @@ |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</el-collapse-item> |
|
|
|
</el-collapse> |
|
|
|
</el-collapse> |
|
|
|
</el-scrollbar> |
|
|
|
</div> |
|
|
|
--> |
|
|
|
|
|
|
|
<el-popover |
|
|
|
ref="popoverRef" |
|
|
|
:virtual-ref="virtualRef" |
|
|
|
trigger="click" |
|
|
|
title="With title" |
|
|
|
virtual-triggering |
|
|
|
> |
|
|
|
<span> Some content </span> |
|
|
|
</el-popover> |
|
|
|
<HistoricalCurve ref="historicalCurveRef" /> |
|
|
|
</div> |
|
|
|
</template> |
|
|
|
<script lang="ts" setup> |
|
|
|
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css' |
|
|
|
import { RecycleScroller, DynamicScroller, DynamicScrollerItem } from 'vue-virtual-scroller' |
|
|
|
import OpenLayerMap from './components/OpenLayerMap.vue' |
|
|
|
import TopPanel from './components/TopPanel.vue' |
|
|
|
import HistoricalCurve from './components/HistoricalCurve.vue' |
|
|
|
@ -107,6 +210,7 @@ import { useHandDetectorStore } from '@/store/modules/handDetector' |
|
|
|
import { ElMessage, ElScrollbar } from 'element-plus' |
|
|
|
import dayjs from 'dayjs' |
|
|
|
import { getDistance } from 'ol/sphere' |
|
|
|
|
|
|
|
const componentsIsActive = ref(false) |
|
|
|
|
|
|
|
const handDetectorStore = useHandDetectorStore() // 手持探测器 store |
|
|
|
@ -117,6 +221,7 @@ const fences = ref<FenceData[]>([]) |
|
|
|
|
|
|
|
const mapRef = ref<InstanceType<typeof OpenLayerMap>>() |
|
|
|
const search = ref('') |
|
|
|
const selectStatus = ref(['normal', 'offline', 'fence', 'alarm']) |
|
|
|
watch( |
|
|
|
() => search.value, |
|
|
|
(newSearch, oldSearch) => { |
|
|
|
@ -131,8 +236,9 @@ watch( |
|
|
|
const handDetectorCount = computed(() => markers.value.length) |
|
|
|
const onlineCount = computed(() => markers.value.filter((item) => item.onlineStatus === 1).length) |
|
|
|
const filterMarkers = computed(() => { |
|
|
|
var arr = markers.value |
|
|
|
if (search.value) { |
|
|
|
return markers.value.filter((item) => { |
|
|
|
arr = 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) |
|
|
|
@ -140,9 +246,70 @@ const filterMarkers = computed(() => { |
|
|
|
return isName || isSn || isGasChemical |
|
|
|
}) |
|
|
|
} |
|
|
|
return markers.value |
|
|
|
if (selectStatus.value.length !== 0) { |
|
|
|
arr = arr.filter((item) => { |
|
|
|
if (!item.statusStr) { |
|
|
|
return true |
|
|
|
} |
|
|
|
if (item.statusStr == 'gasStatus_2' || item.statusStr == 'gasStatus_1') { |
|
|
|
return selectStatus.value.includes('alarm') |
|
|
|
} |
|
|
|
return selectStatus.value.includes(item.statusStr) |
|
|
|
}) |
|
|
|
} |
|
|
|
// console.log('markers.value', markers.value) |
|
|
|
|
|
|
|
return arr |
|
|
|
}) |
|
|
|
|
|
|
|
// const filterMarkers2 = computed(() => { |
|
|
|
// var arr: MarkerData[] = [] |
|
|
|
// for (let i = 0; i < 20000; 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 getMarkers = async () => { |
|
|
|
console.log('getMarkers') |
|
|
|
return await getLastDetectorData().then((res: HandDetectorData[]) => { |
|
|
|
@ -165,7 +332,8 @@ const getMarkers = async () => { |
|
|
|
statusStr: statusStr, //状态字符串 |
|
|
|
statusColor: getStatusColor(statusStr), //状态颜色 |
|
|
|
statusLabel: getStatusLabel(statusStr), //状态标签 |
|
|
|
statusPriority: getStatusPriority(statusStr) //状态优先级, |
|
|
|
statusPriority: getStatusPriority(statusStr), //状态优先级, |
|
|
|
expanded: activeId.value === i.id |
|
|
|
} |
|
|
|
}) |
|
|
|
.sort((a, b) => a.statusPriority - b.statusPriority) |
|
|
|
@ -322,27 +490,23 @@ function onClickTrajectory(item: MarkerData) { |
|
|
|
trajectoryTimeRange.value = [dayjs().subtract(1, 'hour').valueOf(), dayjs().valueOf()] |
|
|
|
showTrajectory(item) |
|
|
|
} |
|
|
|
const scrollbarRef = ref<InstanceType<typeof ElScrollbar>>() |
|
|
|
const activeName = ref<number>() |
|
|
|
const scrollbarRef = ref() |
|
|
|
const virtualRef = ref<HTMLDivElement>() |
|
|
|
const activeId = ref<number>() |
|
|
|
// 点击手持设备 |
|
|
|
function onClickMarker(item: MarkerData) { |
|
|
|
console.log('onClickMarker', item) |
|
|
|
activeName.value = item.id |
|
|
|
var findIndex = markers.value.findIndex((item) => item.id === activeName.value) |
|
|
|
function onClickMarker(markerItem: MarkerData) { |
|
|
|
console.log('onClickMarker', markerItem) |
|
|
|
|
|
|
|
var findIndex = markers.value.findIndex((item) => item.id === markerItem.id) |
|
|
|
if (findIndex === -1) { |
|
|
|
return |
|
|
|
} |
|
|
|
setTimeout(() => { |
|
|
|
let top = findIndex * 48 |
|
|
|
// scrollbarRef.value?.setScrollTop(top) |
|
|
|
// 取消之前的动画 |
|
|
|
cancelAnimationFrame(AnimationId.value as number) |
|
|
|
scrollTo(scrollbarScrollTop.value, top, scrollbarScrollTop.value) |
|
|
|
}, 500) |
|
|
|
showPannel(markerItem, findIndex) |
|
|
|
} |
|
|
|
const scrollbarScrollTop = ref(0) |
|
|
|
function onScroll({ scrollTop }) { |
|
|
|
scrollbarScrollTop.value = scrollTop |
|
|
|
function onScroll(e) { |
|
|
|
// console.log('onScroll', e) |
|
|
|
scrollbarScrollTop.value = e.target.scrollTop |
|
|
|
} |
|
|
|
|
|
|
|
// 滚动到指定位置 |
|
|
|
@ -363,12 +527,47 @@ function scrollTo(from: number, to: number, current: number) { |
|
|
|
} |
|
|
|
} |
|
|
|
current = current + speed |
|
|
|
|
|
|
|
scrollbarRef.value?.setScrollTop(current) |
|
|
|
console.log('scrollbarRef.value', scrollbarRef.value) |
|
|
|
// DynamicScroller内部用的RecycleScroller组件,所以滚动到指定位置用RecycleScroller方法 |
|
|
|
scrollbarRef.value?.$refs?.scroller?.scrollToPosition(current) |
|
|
|
AnimationId.value = requestAnimationFrame(() => { |
|
|
|
scrollTo(from, to, current) |
|
|
|
}) |
|
|
|
} |
|
|
|
const showPannel = (item, index) => { |
|
|
|
if (activeId.value && activeId.value !== item.id) { |
|
|
|
let last = markers.value.find((lastItem) => lastItem.id === activeId.value) |
|
|
|
if (last) { |
|
|
|
last.expanded = false |
|
|
|
} |
|
|
|
} |
|
|
|
item.expanded = true |
|
|
|
activeId.value = item.id |
|
|
|
|
|
|
|
let top = index * 48 |
|
|
|
// scrollbarRef.value?.scrollToItem(index) |
|
|
|
|
|
|
|
// 取消之前的动画 |
|
|
|
cancelAnimationFrame(AnimationId.value as number) |
|
|
|
scrollTo(scrollbarScrollTop.value, top, scrollbarScrollTop.value) |
|
|
|
} |
|
|
|
const togglePanel = (item) => { |
|
|
|
if (activeId.value && activeId.value !== item.id) { |
|
|
|
let last = markers.value.find((lastItem) => lastItem.id === activeId.value) |
|
|
|
if (last) { |
|
|
|
last.expanded = false |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (item.expanded) { |
|
|
|
activeId.value = undefined |
|
|
|
item.expanded = false |
|
|
|
} else { |
|
|
|
activeId.value = item.id |
|
|
|
item.expanded = true |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
onMounted(() => { |
|
|
|
getMarkers() |
|
|
|
|
|
|
|
@ -410,12 +609,25 @@ onUnmounted(() => { |
|
|
|
overflow: hidden; |
|
|
|
background-color: rgba(255, 255, 255, 0.8); |
|
|
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2); |
|
|
|
padding: 0 10px; |
|
|
|
padding: 5px 0 10px 10px; |
|
|
|
margin-left: 10px; |
|
|
|
.marker-panel { |
|
|
|
box-sizing: border-box; |
|
|
|
border-bottom: 1px solid rgba(0, 0, 0, 0.1); |
|
|
|
} |
|
|
|
.marker-item { |
|
|
|
display: flex; |
|
|
|
flex-direction: row; |
|
|
|
height: 48px; |
|
|
|
line-height: 48px; |
|
|
|
|
|
|
|
font-family: var(--el-font-family); |
|
|
|
} |
|
|
|
.markerList-content { |
|
|
|
border-top: 1px solid rgba(0, 0, 0, 0.1); |
|
|
|
padding: 10px; |
|
|
|
font-size: 12px; |
|
|
|
font-weight: 400; |
|
|
|
|
|
|
|
span { |
|
|
|
display: inline-block; |
|
|
|
@ -433,4 +645,19 @@ onUnmounted(() => { |
|
|
|
.el-collapse-item__wrap { |
|
|
|
background-color: rgba(255, 255, 255, 0.8); |
|
|
|
} |
|
|
|
::-webkit-scrollbar-thumb { |
|
|
|
background: rgba(0, 0, 0, 0.1) !important; /* 滑块颜色 */ |
|
|
|
border-radius: 4px; |
|
|
|
} |
|
|
|
|
|
|
|
::-webkit-scrollbar { |
|
|
|
width: 8px; |
|
|
|
background-color: #f5f5f5; |
|
|
|
} |
|
|
|
|
|
|
|
/* |
|
|
|
::-webkit-scrollbar-thumb { |
|
|
|
border-radius: 6px; |
|
|
|
background-color: #555; |
|
|
|
} */ |
|
|
|
</style> |
|
|
|
|