feat:设备地图页面对接接口

master
黄伟杰 1 month ago
parent cc9449226a
commit 4793d8ab23

@ -69,6 +69,19 @@ export interface HistoryRecordParams {
attributeCodes?: string[]
}
export interface DeviceStatusCountByCustomerRespVO {
customerId: number
customerName: string
totalDeviceCount: number
offlineCount: number
runningCount: number
standbyCount: number
faultStandbyCount: number
alarmRunningCount: number
longitude: number
latitude: number
}
export interface DeviceContactModelVO {
id: number
attributeCode?: string
@ -158,6 +171,12 @@ export const DeviceApi = {
return await request.get({ url: `/iot/device/device-run-status-stats` })
},
getStatusCountByCustomer: async () => {
return await request.get<DeviceStatusCountByCustomerRespVO[]>({
url: `/iot/device/status-count-by-customer`
})
},
devicePointList: async () => {
return await request.get({ url: `/iot/device/devicePointList` })
},

@ -1,13 +1,13 @@
<template>
<div class="map-container">
<div id="device-map" class="map-instance"></div>
<!-- Custom HTML Markers for Map -->
<div style="display: none;">
<div
v-for="item in markersData"
:key="item.name"
:id="`marker-${item.name}`"
<div style="display: none">
<div
v-for="item in markersData"
:key="item.name"
:id="`marker-${item.name}`"
class="custom-marker"
:class="[getMarkerStatus(item), { 'is-active': selectedName === item.name }]"
@click="onMarkerClick(item)"
@ -46,8 +46,10 @@ const map = ref<Map | null>(null)
const overlays = ref<Overlay[]>([])
const getMarkerStatus = (item: any) => {
const total = Number(item.total || 0)
const offline = Number(item.offline || 0)
if (item.alarm > 0) return 'status-alarm'
if (item.online === 0) return 'status-offline'
if (total > 0 && offline >= total) return 'status-offline'
return 'status-normal'
}
@ -81,13 +83,13 @@ const initMap = () => {
const renderMarkers = () => {
if (!map.value) return
// Clear old overlays
overlays.value.forEach(overlay => map.value?.removeOverlay(overlay))
overlays.value.forEach((overlay) => map.value?.removeOverlay(overlay))
overlays.value = []
// Add new overlays
props.markersData.forEach(item => {
props.markersData.forEach((item) => {
const el = document.getElementById(`marker-${item.name}`)
if (el) {
const overlay = new Overlay({
@ -120,15 +122,22 @@ const updateView = () => {
}
}
watch(() => props.viewType, () => {
updateView()
})
watch(
() => props.viewType,
() => {
updateView()
}
)
watch(() => props.markersData, () => {
nextTick(() => {
renderMarkers()
})
}, { deep: true })
watch(
() => props.markersData,
() => {
nextTick(() => {
renderMarkers()
})
},
{ deep: true }
)
onMounted(() => {
initMap()
@ -168,7 +177,8 @@ onUnmounted(() => {
z-index: 10;
transition: transform 0.2s;
&:hover, &.is-active {
&:hover,
&.is-active {
transform: scale(1.15);
z-index: 20;
}
@ -186,7 +196,7 @@ onUnmounted(() => {
font-size: 12px;
font-weight: bold;
color: #fff;
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
.marker-label {
@ -194,7 +204,7 @@ onUnmounted(() => {
right: -40px;
top: 50%;
transform: translateY(-50%);
background: rgba(255,255,255,0.9);
background: rgba(255, 255, 255, 0.9);
padding: 2px 6px;
border-radius: 4px;
font-size: 12px;
@ -202,7 +212,7 @@ onUnmounted(() => {
white-space: nowrap;
pointer-events: none;
font-weight: bold;
box-shadow: 0 1px 2px rgba(0,0,0,0.1);
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
}
.ripple {
@ -218,7 +228,7 @@ onUnmounted(() => {
z-index: 1;
border: 1px solid currentColor;
}
.ripple-2 {
animation-delay: 1s;
}
@ -226,16 +236,25 @@ onUnmounted(() => {
/* Status Colors */
&.status-normal {
color: #10b981;
.marker-core { background: #10b981; }
.marker-core {
background: #10b981;
}
}
&.status-alarm {
color: #f56c6c;
.marker-core { background: #f56c6c; }
.marker-core {
background: #f56c6c;
}
}
&.status-offline {
color: #909399;
.marker-core { background: #909399; }
.ripple { animation: none; border-color: rgba(144, 147, 153, 0.4); }
.marker-core {
background: #909399;
}
.ripple {
animation: none;
border-color: rgba(144, 147, 153, 0.4);
}
}
}

@ -48,7 +48,7 @@
<div class="map-body">
<DeviceMap
:view-type="currentView"
:markers-data="mapData"
:markers-data="markerData"
:selected-name="selectedCountry?.name"
@select="handleMarkerSelect"
/>
@ -124,10 +124,14 @@
<div
class="dist-item"
v-for="item in mapData"
:key="item.name"
:key="item.customerId"
@click="handleMarkerSelect(item)"
:class="{ 'is-active': selectedCountry?.name === item.name }"
:class="[
`status-${getItemStatus(item)}`,
{ 'is-active': selectedCountry?.name === item.name }
]"
>
<span class="dist-status-dot"></span>
<span class="dist-name">{{ item.name }}</span>
<el-tag size="small" type="primary" class="dist-tag">{{ item.total }}</el-tag>
<el-tag size="small" type="success" class="dist-tag" v-if="item.online > 0"
@ -146,11 +150,25 @@
import { ref, computed, onMounted } from 'vue'
import DeviceMap from './components/DeviceMap.vue'
import { DeviceApi } from '@/api/iot/device'
import type { DeviceStatusCountByCustomerRespVO } from '@/api/iot/device'
defineOptions({ name: 'IoTDeviceMap' })
const currentView = ref<'global' | 'china'>('global')
const selectedCountry = ref<any>(null)
type MapItem = {
customerId: number
name: string
longitude: number
latitude: number
total: number
online: number
alarm: number
running: number
offline: number
standby: number
}
const selectedCountry = ref<MapItem | null>(null)
const runStatusStats = ref({
totalDeviceCount: 0,
offlineCount: 0,
@ -160,89 +178,17 @@ const runStatusStats = ref({
alarmRunningCount: 0
})
// Mock Data
const mapData = ref([
{
name: '中国',
longitude: 104.1954,
latitude: 35.8617,
total: 89,
online: 82,
alarm: 3,
running: 79,
offline: 7
},
{
name: '美国',
longitude: -95.7129,
latitude: 37.0902,
total: 25,
online: 23,
alarm: 1,
running: 18,
offline: 2
},
{
name: '日本',
longitude: 138.2529,
latitude: 36.2048,
total: 12,
online: 11,
alarm: 0,
running: 11,
offline: 1
},
{
name: '德国',
longitude: 10.4515,
latitude: 51.1657,
total: 15,
online: 14,
alarm: 0,
running: 14,
offline: 1
},
{
name: '印度',
longitude: 78.9629,
latitude: 20.5937,
total: 4,
online: 3,
alarm: 0,
running: 3,
offline: 1
},
{
name: '巴西',
longitude: -51.9253,
latitude: -14.235,
total: 2,
online: 2,
alarm: 0,
running: 2,
offline: 0
},
{
name: '韩国',
longitude: 127.7669,
latitude: 35.9078,
total: 8,
online: 7,
alarm: 1,
running: 6,
offline: 1
},
{
name: '墨西哥',
longitude: -102.5528,
latitude: 23.6345,
total: 1,
online: 0,
alarm: 0,
running: 0,
offline: 1
}
])
const mapData = ref<MapItem[]>([])
const markerData = computed(() =>
mapData.value.filter(
(item) =>
Number.isFinite(item.longitude) &&
Number.isFinite(item.latitude) &&
item.longitude !== 0 &&
item.latitude !== 0
)
)
const topStats = computed(() => [
{
@ -274,16 +220,22 @@ const topStats = computed(() => [
value: runStatusStats.value.faultStandbyCount,
icon: 'ep:warning-filled',
type: 'alarm'
},
{
label: '报警运行',
value: runStatusStats.value.alarmRunningCount,
icon: 'ep:bell',
type: 'alarm'
}
// {
// label: '',
// value: runStatusStats.value.alarmRunningCount,
// icon: 'ep:bell',
// type: 'alarm'
// }
])
const handleMarkerSelect = (item: any) => {
const getItemStatus = (item: MapItem) => {
if (item.alarm > 0) return 'alarm'
if (item.total > 0 && item.offline >= item.total) return 'offline'
return 'normal'
}
const handleMarkerSelect = (item: MapItem) => {
selectedCountry.value = item
}
@ -299,8 +251,35 @@ const loadRunStatusStats = async () => {
}
}
const loadMapData = async () => {
const data = await DeviceApi.getStatusCountByCustomer()
const transformedData = (data || []).map((item: DeviceStatusCountByCustomerRespVO) => {
const total = Number(item.totalDeviceCount || 0)
const offline = Number(item.offlineCount || 0)
const alarm = Number(item.faultStandbyCount || 0) + Number(item.alarmRunningCount || 0)
const online = Math.max(total - offline, 0)
return {
customerId: Number(item.customerId),
name: item.customerName || '-',
longitude: Number(item.longitude || 0),
latitude: Number(item.latitude || 0),
total,
online,
alarm,
running: Number(item.runningCount || 0),
offline,
standby: Number(item.standbyCount || 0)
}
})
mapData.value = transformedData
if (selectedCountry.value) {
selectedCountry.value =
transformedData.find((item) => item.customerId === selectedCountry.value?.customerId) || null
}
}
onMounted(async () => {
await loadRunStatusStats()
await Promise.all([loadRunStatusStats(), loadMapData()])
})
</script>
@ -601,6 +580,29 @@ onMounted(async () => {
border-color: #86efac;
}
&.status-normal {
.dist-status-dot {
background-color: #10b981;
}
}
&.status-alarm {
.dist-status-dot {
background-color: #ef4444;
}
}
&.status-offline {
.dist-status-dot {
background-color: #9ca3af;
}
}
.dist-status-dot {
width: 8px;
height: 8px;
border-radius: 50%;
flex-shrink: 0;
}
.dist-name {
font-size: 14px;
font-weight: 500;

Loading…
Cancel
Save