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

master
黄伟杰 2 months ago
parent cc9449226a
commit 4793d8ab23

@ -69,6 +69,19 @@ export interface HistoryRecordParams {
attributeCodes?: string[] 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 { export interface DeviceContactModelVO {
id: number id: number
attributeCode?: string attributeCode?: string
@ -158,6 +171,12 @@ export const DeviceApi = {
return await request.get({ url: `/iot/device/device-run-status-stats` }) 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 () => { devicePointList: async () => {
return await request.get({ url: `/iot/device/devicePointList` }) return await request.get({ url: `/iot/device/devicePointList` })
}, },

@ -3,7 +3,7 @@
<div id="device-map" class="map-instance"></div> <div id="device-map" class="map-instance"></div>
<!-- Custom HTML Markers for Map --> <!-- Custom HTML Markers for Map -->
<div style="display: none;"> <div style="display: none">
<div <div
v-for="item in markersData" v-for="item in markersData"
:key="item.name" :key="item.name"
@ -46,8 +46,10 @@ const map = ref<Map | null>(null)
const overlays = ref<Overlay[]>([]) const overlays = ref<Overlay[]>([])
const getMarkerStatus = (item: any) => { 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.alarm > 0) return 'status-alarm'
if (item.online === 0) return 'status-offline' if (total > 0 && offline >= total) return 'status-offline'
return 'status-normal' return 'status-normal'
} }
@ -83,11 +85,11 @@ const renderMarkers = () => {
if (!map.value) return if (!map.value) return
// Clear old overlays // Clear old overlays
overlays.value.forEach(overlay => map.value?.removeOverlay(overlay)) overlays.value.forEach((overlay) => map.value?.removeOverlay(overlay))
overlays.value = [] overlays.value = []
// Add new overlays // Add new overlays
props.markersData.forEach(item => { props.markersData.forEach((item) => {
const el = document.getElementById(`marker-${item.name}`) const el = document.getElementById(`marker-${item.name}`)
if (el) { if (el) {
const overlay = new Overlay({ const overlay = new Overlay({
@ -120,15 +122,22 @@ const updateView = () => {
} }
} }
watch(() => props.viewType, () => { watch(
() => props.viewType,
() => {
updateView() updateView()
}) }
)
watch(() => props.markersData, () => { watch(
() => props.markersData,
() => {
nextTick(() => { nextTick(() => {
renderMarkers() renderMarkers()
}) })
}, { deep: true }) },
{ deep: true }
)
onMounted(() => { onMounted(() => {
initMap() initMap()
@ -168,7 +177,8 @@ onUnmounted(() => {
z-index: 10; z-index: 10;
transition: transform 0.2s; transition: transform 0.2s;
&:hover, &.is-active { &:hover,
&.is-active {
transform: scale(1.15); transform: scale(1.15);
z-index: 20; z-index: 20;
} }
@ -226,16 +236,25 @@ onUnmounted(() => {
/* Status Colors */ /* Status Colors */
&.status-normal { &.status-normal {
color: #10b981; color: #10b981;
.marker-core { background: #10b981; } .marker-core {
background: #10b981;
}
} }
&.status-alarm { &.status-alarm {
color: #f56c6c; color: #f56c6c;
.marker-core { background: #f56c6c; } .marker-core {
background: #f56c6c;
}
} }
&.status-offline { &.status-offline {
color: #909399; color: #909399;
.marker-core { background: #909399; } .marker-core {
.ripple { animation: none; border-color: rgba(144, 147, 153, 0.4); } background: #909399;
}
.ripple {
animation: none;
border-color: rgba(144, 147, 153, 0.4);
}
} }
} }

@ -48,7 +48,7 @@
<div class="map-body"> <div class="map-body">
<DeviceMap <DeviceMap
:view-type="currentView" :view-type="currentView"
:markers-data="mapData" :markers-data="markerData"
:selected-name="selectedCountry?.name" :selected-name="selectedCountry?.name"
@select="handleMarkerSelect" @select="handleMarkerSelect"
/> />
@ -124,10 +124,14 @@
<div <div
class="dist-item" class="dist-item"
v-for="item in mapData" v-for="item in mapData"
:key="item.name" :key="item.customerId"
@click="handleMarkerSelect(item)" @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> <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="primary" class="dist-tag">{{ item.total }}</el-tag>
<el-tag size="small" type="success" class="dist-tag" v-if="item.online > 0" <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 { ref, computed, onMounted } from 'vue'
import DeviceMap from './components/DeviceMap.vue' import DeviceMap from './components/DeviceMap.vue'
import { DeviceApi } from '@/api/iot/device' import { DeviceApi } from '@/api/iot/device'
import type { DeviceStatusCountByCustomerRespVO } from '@/api/iot/device'
defineOptions({ name: 'IoTDeviceMap' }) defineOptions({ name: 'IoTDeviceMap' })
const currentView = ref<'global' | 'china'>('global') 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({ const runStatusStats = ref({
totalDeviceCount: 0, totalDeviceCount: 0,
offlineCount: 0, offlineCount: 0,
@ -160,89 +178,17 @@ const runStatusStats = ref({
alarmRunningCount: 0 alarmRunningCount: 0
}) })
// Mock Data const mapData = ref<MapItem[]>([])
const mapData = ref([
{ const markerData = computed(() =>
name: '中国', mapData.value.filter(
longitude: 104.1954, (item) =>
latitude: 35.8617, Number.isFinite(item.longitude) &&
total: 89, Number.isFinite(item.latitude) &&
online: 82, item.longitude !== 0 &&
alarm: 3, item.latitude !== 0
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 topStats = computed(() => [ const topStats = computed(() => [
{ {
@ -274,16 +220,22 @@ const topStats = computed(() => [
value: runStatusStats.value.faultStandbyCount, value: runStatusStats.value.faultStandbyCount,
icon: 'ep:warning-filled', icon: 'ep:warning-filled',
type: 'alarm' 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 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 () => { onMounted(async () => {
await loadRunStatusStats() await Promise.all([loadRunStatusStats(), loadMapData()])
}) })
</script> </script>
@ -601,6 +580,29 @@ onMounted(async () => {
border-color: #86efac; 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 { .dist-name {
font-size: 14px; font-size: 14px;
font-weight: 500; font-weight: 500;

Loading…
Cancel
Save