|
|
|
|
@ -119,7 +119,12 @@ import { useFullscreen } from '@vueuse/core'
|
|
|
|
|
import * as echarts from 'echarts'
|
|
|
|
|
import type { EChartsOption } from 'echarts'
|
|
|
|
|
import { DeviceOperationOverviewApi } from '@/api/iot/deviceOperationOverview'
|
|
|
|
|
import type { DeviceOperationOverviewRespVO, DeviceOperationOverviewTimelineRowVO } from '@/api/iot/deviceOperationOverview'
|
|
|
|
|
import type {
|
|
|
|
|
DeviceOperationOverviewHourlyStatusVO,
|
|
|
|
|
DeviceOperationOverviewRespVO,
|
|
|
|
|
DeviceOperationOverviewTimelineRowVO
|
|
|
|
|
} from '@/api/iot/deviceOperationOverview'
|
|
|
|
|
import { DeviceApi, type DeviceVO } from '@/api/iot/device'
|
|
|
|
|
import { OrganizationApi } from '@/api/mes/organization'
|
|
|
|
|
|
|
|
|
|
defineOptions({ name: 'IotReportDashboard3' })
|
|
|
|
|
@ -128,6 +133,14 @@ const { t } = useI18n('Dashboard3')
|
|
|
|
|
|
|
|
|
|
type RunStatus = 'running' | 'standby' | 'fault' | 'offline'
|
|
|
|
|
|
|
|
|
|
interface DeviceStatusStatistics {
|
|
|
|
|
total: number
|
|
|
|
|
running: number
|
|
|
|
|
standby: number
|
|
|
|
|
fault: number
|
|
|
|
|
offline: number
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface OrganizationFilterItem {
|
|
|
|
|
id: string
|
|
|
|
|
parentId?: string | null
|
|
|
|
|
@ -159,6 +172,15 @@ const overviewData = ref<DeviceOperationOverviewRespVO>({
|
|
|
|
|
timelineRows: [],
|
|
|
|
|
totalDevices: 0
|
|
|
|
|
})
|
|
|
|
|
const last24HourlyStatus = ref<DeviceOperationOverviewHourlyStatusVO[]>([])
|
|
|
|
|
const deviceStatusStatistics = ref<DeviceStatusStatistics>({
|
|
|
|
|
total: 0,
|
|
|
|
|
running: 0,
|
|
|
|
|
standby: 0,
|
|
|
|
|
fault: 0,
|
|
|
|
|
offline: 0
|
|
|
|
|
})
|
|
|
|
|
const deviceOperatingStatusMap = ref<Record<string, RunStatus>>({})
|
|
|
|
|
const weekTrend = ref<{ date: string; rate: number }[]>([])
|
|
|
|
|
const detailPage = ref(1)
|
|
|
|
|
const detailPageSize = 8
|
|
|
|
|
@ -178,18 +200,6 @@ const statusText = computed<Record<RunStatus, string>>(() => ({
|
|
|
|
|
offline: t('Status.offline')
|
|
|
|
|
}))
|
|
|
|
|
|
|
|
|
|
const metricValueMap = computed(() => {
|
|
|
|
|
return overviewData.value.metrics.reduce<Record<string, number>>((acc, item) => {
|
|
|
|
|
acc[item.key] = Number(item.value) || 0
|
|
|
|
|
return acc
|
|
|
|
|
}, {})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const getMetricValue = (keys: string[]) => {
|
|
|
|
|
const foundKey = keys.find((key) => metricValueMap.value[key] !== undefined)
|
|
|
|
|
return foundKey ? metricValueMap.value[foundKey] : undefined
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const calcDeviceRate = (count: number, total: number) => {
|
|
|
|
|
if (!total) return 0
|
|
|
|
|
return Number(((count / total) * 100).toFixed(1))
|
|
|
|
|
@ -248,7 +258,7 @@ const statusCards = computed(() => [
|
|
|
|
|
const detailRows = computed<DetailRow[]>(() => {
|
|
|
|
|
return overviewData.value.timelineRows.map((row) => {
|
|
|
|
|
const minutes = calcMinutes(row)
|
|
|
|
|
const status = getCurrentStatus(row)
|
|
|
|
|
const status = deviceOperatingStatusMap.value[String(row.id)] || getCurrentStatus(row)
|
|
|
|
|
return {
|
|
|
|
|
id: String(row.id),
|
|
|
|
|
name: row.name || '-',
|
|
|
|
|
@ -263,22 +273,14 @@ const detailRows = computed<DetailRow[]>(() => {
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const totalDetailPages = computed(() => Math.max(1, Math.ceil(detailRows.value.length / detailPageSize)))
|
|
|
|
|
const runningDeviceCount = computed(
|
|
|
|
|
() => getMetricValue(['running', 'run', 'runningDevices']) ?? getRowsByStatus('running').length
|
|
|
|
|
)
|
|
|
|
|
const standbyDeviceCount = computed(
|
|
|
|
|
() => getMetricValue(['standby', 'idle', 'standbyDevices']) ?? getRowsByStatus('standby').length
|
|
|
|
|
)
|
|
|
|
|
const faultDeviceCount = computed(
|
|
|
|
|
() => getMetricValue(['fault', 'alarm', 'faultDevices']) ?? getRowsByStatus('fault').length
|
|
|
|
|
)
|
|
|
|
|
const offlineDeviceCount = computed(
|
|
|
|
|
() => getMetricValue(['offline', 'offlineDevices']) ?? getRowsByStatus('offline').length
|
|
|
|
|
)
|
|
|
|
|
const runningDeviceCount = computed(() => deviceStatusStatistics.value.running)
|
|
|
|
|
const standbyDeviceCount = computed(() => deviceStatusStatistics.value.standby)
|
|
|
|
|
const faultDeviceCount = computed(() => deviceStatusStatistics.value.fault)
|
|
|
|
|
const offlineDeviceCount = computed(() => deviceStatusStatistics.value.offline)
|
|
|
|
|
const onlineDeviceCount = computed(
|
|
|
|
|
() => runningDeviceCount.value + standbyDeviceCount.value + faultDeviceCount.value
|
|
|
|
|
)
|
|
|
|
|
const totalDeviceCount = computed(() => onlineDeviceCount.value + offlineDeviceCount.value)
|
|
|
|
|
const totalDeviceCount = computed(() => deviceStatusStatistics.value.total)
|
|
|
|
|
const pagedRows = computed(() => {
|
|
|
|
|
const start = (detailPage.value - 1) * detailPageSize
|
|
|
|
|
return detailRows.value.slice(start, start + detailPageSize)
|
|
|
|
|
@ -290,15 +292,22 @@ const alarmDevices = computed(() => {
|
|
|
|
|
.sort((a, b) => b.alarmMinutes - a.alarmMinutes)
|
|
|
|
|
.slice(0, 2)
|
|
|
|
|
})
|
|
|
|
|
const hourlyRates = computed(() =>
|
|
|
|
|
overviewData.value.hourlyStatus.map((item) => {
|
|
|
|
|
const total = item.running + item.standby + item.fault + item.offline
|
|
|
|
|
return {
|
|
|
|
|
hour: item.hour,
|
|
|
|
|
rate: total > 0 ? Number(((item.running / total) * 100).toFixed(1)) : 0
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
)
|
|
|
|
|
const hourlyRates = computed(() => {
|
|
|
|
|
const rateMap = last24HourlyStatus.value.reduce<Record<string, number>>((acc, item) => {
|
|
|
|
|
const rate = getHourlyRunningRate(item)
|
|
|
|
|
getHourlyKeyCandidates(item.hour).forEach((key) => {
|
|
|
|
|
acc[key] = rate
|
|
|
|
|
})
|
|
|
|
|
return acc
|
|
|
|
|
}, {})
|
|
|
|
|
|
|
|
|
|
return getLast24HourBuckets().map((time) => ({
|
|
|
|
|
hour: time.format('MM-DD HH:00'),
|
|
|
|
|
rate: getHourlyKeyCandidates(time.format('YYYY-MM-DD HH:00:00'))
|
|
|
|
|
.map((key) => rateMap[key])
|
|
|
|
|
.find((rate) => rate !== undefined) ?? 0
|
|
|
|
|
}))
|
|
|
|
|
})
|
|
|
|
|
const todayAverageRate = computed(() => average(hourlyRates.value.map((item) => item.rate)))
|
|
|
|
|
const sevenDayAverageRate = computed(() => average(weekTrend.value.map((item) => item.rate)))
|
|
|
|
|
|
|
|
|
|
@ -319,7 +328,92 @@ const average = (values: number[]) => {
|
|
|
|
|
return Number((valid.reduce((sum, item) => sum + item, 0) / valid.length).toFixed(1))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const getRowsByStatus = (status: RunStatus) => detailRows.value.filter((row) => row.status === status)
|
|
|
|
|
const getHourlyRunningRate = (item: DeviceOperationOverviewHourlyStatusVO) => {
|
|
|
|
|
const total = item.running + item.standby + item.fault + item.offline
|
|
|
|
|
return total > 0 ? Number(((item.running / total) * 100).toFixed(1)) : 0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const getHourlyKeyCandidates = (hour: string) => {
|
|
|
|
|
const text = String(hour || '').trim()
|
|
|
|
|
const candidates = new Set<string>()
|
|
|
|
|
if (text) candidates.add(text)
|
|
|
|
|
|
|
|
|
|
const parsed = dayjs(text)
|
|
|
|
|
if (parsed.isValid()) {
|
|
|
|
|
candidates.add(parsed.format('YYYY-MM-DD HH:00:00'))
|
|
|
|
|
candidates.add(parsed.format('YYYY-MM-DD HH:00'))
|
|
|
|
|
candidates.add(parsed.format('MM-DD HH:00'))
|
|
|
|
|
candidates.add(parsed.format('HH:00'))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const hourMatch = text.match(/(\d{1,2}):00/)
|
|
|
|
|
if (hourMatch) {
|
|
|
|
|
candidates.add(`${hourMatch[1].padStart(2, '0')}:00`)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Array.from(candidates)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const normalizeDeviceOperatingStatus = (value: string | number | undefined): RunStatus => {
|
|
|
|
|
const text = String(value ?? '').trim().toLowerCase()
|
|
|
|
|
if (['运行', '运行中', 'running', 'run', '1'].includes(text)) return 'running'
|
|
|
|
|
if (['待机', '待机中', 'standby', 'idle', '2'].includes(text)) return 'standby'
|
|
|
|
|
if (['故障', '故障中', 'fault', 'error', '3'].includes(text)) return 'fault'
|
|
|
|
|
return 'offline'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const setDeviceStatusStatistics = (rows: DeviceVO[] = []) => {
|
|
|
|
|
const nextStats: DeviceStatusStatistics = {
|
|
|
|
|
total: rows.length,
|
|
|
|
|
running: 0,
|
|
|
|
|
standby: 0,
|
|
|
|
|
fault: 0,
|
|
|
|
|
offline: 0
|
|
|
|
|
}
|
|
|
|
|
const nextStatusMap: Record<string, RunStatus> = {}
|
|
|
|
|
rows.forEach((row) => {
|
|
|
|
|
const status = normalizeDeviceOperatingStatus(row.operatingStatus)
|
|
|
|
|
nextStats[status] += 1
|
|
|
|
|
if (row.id != null) {
|
|
|
|
|
nextStatusMap[String(row.id)] = status
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
deviceStatusStatistics.value = nextStats
|
|
|
|
|
deviceOperatingStatusMap.value = nextStatusMap
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const getDevicePageRows = (data: any): DeviceVO[] => {
|
|
|
|
|
return Array.isArray(data) ? data : Array.isArray(data?.list) ? data.list : []
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const refreshDeviceStatusStatistics = async () => {
|
|
|
|
|
const ids = getDeviceIds().join(',')
|
|
|
|
|
const baseParams = {
|
|
|
|
|
...(ids ? { ids } : {})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const firstPage = await DeviceApi.getDevicePage({
|
|
|
|
|
...baseParams,
|
|
|
|
|
pageNo: 1,
|
|
|
|
|
pageSize: 1
|
|
|
|
|
})
|
|
|
|
|
const firstRows = getDevicePageRows(firstPage)
|
|
|
|
|
const total = Number((firstPage as any)?.total) || firstRows.length
|
|
|
|
|
const pageSize = Math.max(total, firstRows.length, 1)
|
|
|
|
|
const data = pageSize === firstRows.length
|
|
|
|
|
? firstPage
|
|
|
|
|
: await DeviceApi.getDevicePage({
|
|
|
|
|
...baseParams,
|
|
|
|
|
pageNo: 1,
|
|
|
|
|
pageSize
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
setDeviceStatusStatistics(getDevicePageRows(data))
|
|
|
|
|
} catch {
|
|
|
|
|
setDeviceStatusStatistics([])
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const getCurrentStatus = (row: DeviceOperationOverviewTimelineRowVO): RunStatus => {
|
|
|
|
|
const currentHour = dayjs().hour() + dayjs().minute() / 60
|
|
|
|
|
@ -387,6 +481,24 @@ const buildParams = (startTime: string, endTime: string, pageSize = 1000) => ({
|
|
|
|
|
timelinePageSize: pageSize
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const getDayRange = (date = dayjs()) => ({
|
|
|
|
|
start: date.startOf('day').format('YYYY-MM-DD HH:mm:ss'),
|
|
|
|
|
end: date.add(1, 'day').startOf('day').format('YYYY-MM-DD HH:mm:ss')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const getLast24HoursRange = () => {
|
|
|
|
|
const end = dayjs().startOf('hour')
|
|
|
|
|
return {
|
|
|
|
|
start: end.subtract(24, 'hour').format('YYYY-MM-DD HH:mm:ss'),
|
|
|
|
|
end: end.format('YYYY-MM-DD HH:mm:ss')
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const getLast24HourBuckets = () => {
|
|
|
|
|
const end = dayjs().startOf('hour')
|
|
|
|
|
return Array.from({ length: 24 }, (_, index) => end.subtract(23 - index, 'hour'))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const loadOrganizationList = async () => {
|
|
|
|
|
const data = await OrganizationApi.getOrganizationList()
|
|
|
|
|
organizationList.value = Array.isArray(data)
|
|
|
|
|
@ -400,8 +512,7 @@ const loadOrganizationList = async () => {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const refreshTodayData = async () => {
|
|
|
|
|
const start = dayjs().startOf('day').format('YYYY-MM-DD HH:mm:ss')
|
|
|
|
|
const end = dayjs().endOf('day').format('YYYY-MM-DD HH:mm:ss')
|
|
|
|
|
const { start, end } = getDayRange()
|
|
|
|
|
const response = await DeviceOperationOverviewApi.getRunOverview(buildParams(start, end))
|
|
|
|
|
overviewData.value = {
|
|
|
|
|
metrics: response?.metrics || [],
|
|
|
|
|
@ -414,11 +525,18 @@ const refreshTodayData = async () => {
|
|
|
|
|
lastUpdateTime.value = dayjs().format('YYYY/M/D HH:mm:ss')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const refreshLast24HourTrend = async () => {
|
|
|
|
|
const { start, end } = getLast24HoursRange()
|
|
|
|
|
const response = await DeviceOperationOverviewApi.getRunOverview(buildParams(start, end))
|
|
|
|
|
last24HourlyStatus.value = response?.hourlyStatus || []
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const refreshWeekTrend = async () => {
|
|
|
|
|
const requests = Array.from({ length: 7 }).map((_, index) => {
|
|
|
|
|
const date = dayjs().subtract(6 - index, 'day')
|
|
|
|
|
const { start, end } = getDayRange(date)
|
|
|
|
|
return DeviceOperationOverviewApi.getRunOverview(
|
|
|
|
|
buildParams(date.startOf('day').format('YYYY-MM-DD HH:mm:ss'), date.endOf('day').format('YYYY-MM-DD HH:mm:ss'), 1)
|
|
|
|
|
buildParams(start, end, 1)
|
|
|
|
|
).then((res) => {
|
|
|
|
|
const running = res?.summary?.find((item) => item.status === 'running')?.percent || 0
|
|
|
|
|
return {
|
|
|
|
|
@ -431,7 +549,7 @@ const refreshWeekTrend = async () => {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const refreshData = async () => {
|
|
|
|
|
await refreshTodayData()
|
|
|
|
|
await Promise.all([refreshTodayData(), refreshDeviceStatusStatistics(), refreshLast24HourTrend()])
|
|
|
|
|
await refreshWeekTrend()
|
|
|
|
|
renderCharts()
|
|
|
|
|
}
|
|
|
|
|
|