|
|
|
|
@ -50,7 +50,7 @@
|
|
|
|
|
<div v-if="alarmDevices.length" class="alarm-list">
|
|
|
|
|
<div v-for="item in alarmDevices" :key="item.id" class="alarm-item">
|
|
|
|
|
<strong :title="item.name">{{ item.name }}</strong>
|
|
|
|
|
<span>{{ t('AlarmBoard.alarmPrefix') }} {{ formatMinutes(item.alarmMinutes) }}</span>
|
|
|
|
|
<span>{{ t('AlarmBoard.alarmPrefix') }} {{ formatHours(item.alarmTime) }}</span>
|
|
|
|
|
<span>{{ item.lastReportTime }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
@ -67,10 +67,10 @@
|
|
|
|
|
<div class="table-header table-row">
|
|
|
|
|
<span>{{ t('Detail.columns.deviceName') }}</span>
|
|
|
|
|
<span>{{ t('Detail.columns.currentStatus') }}</span>
|
|
|
|
|
<span>{{ t('Detail.columns.runningTime') }}</span>
|
|
|
|
|
<span>{{ t('Detail.columns.standbyTime') }}</span>
|
|
|
|
|
<span>{{ t('Detail.columns.alarmTime') }}</span>
|
|
|
|
|
<span>{{ t('Detail.columns.efficiency') }}</span>
|
|
|
|
|
<span>运行时间(小时)</span>
|
|
|
|
|
<span>待机时间(小时)</span>
|
|
|
|
|
<span>故障时间(小时)</span>
|
|
|
|
|
<span>稼动率</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div v-if="pagedRows.length" class="table-body">
|
|
|
|
|
<div
|
|
|
|
|
@ -83,10 +83,10 @@
|
|
|
|
|
<span>
|
|
|
|
|
<em class="status-pill" :class="`status-pill--${row.status}`">{{ statusText[row.status] }}</em>
|
|
|
|
|
</span>
|
|
|
|
|
<span>{{ formatMinutes(row.runningMinutes) }}</span>
|
|
|
|
|
<span>{{ formatMinutes(row.standbyMinutes) }}</span>
|
|
|
|
|
<span>{{ formatMinutes(row.alarmMinutes) }}</span>
|
|
|
|
|
<span class="rate">{{ formatPercent(row.utilizationRate) }}</span>
|
|
|
|
|
<span>{{ formatHours(row.runningTime) }}</span>
|
|
|
|
|
<span>{{ formatHours(row.standbyTime) }}</span>
|
|
|
|
|
<span>{{ formatHours(row.alarmTime) }}</span>
|
|
|
|
|
<span class="rate">{{ formatPercentText(row.utilizationRate) }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div v-else class="empty-table">{{ t('Detail.empty') }}</div>
|
|
|
|
|
@ -124,6 +124,10 @@ import type {
|
|
|
|
|
DeviceOperationOverviewRespVO,
|
|
|
|
|
DeviceOperationOverviewTimelineRowVO
|
|
|
|
|
} from '@/api/iot/deviceOperationOverview'
|
|
|
|
|
import {
|
|
|
|
|
DeviceOperationRecordApi,
|
|
|
|
|
type DeviceOperationRecordVO
|
|
|
|
|
} from '@/api/iot/deviceOperationRecord'
|
|
|
|
|
import { DeviceApi, type DeviceVO } from '@/api/iot/device'
|
|
|
|
|
import { OrganizationApi } from '@/api/mes/organization'
|
|
|
|
|
|
|
|
|
|
@ -152,10 +156,10 @@ interface DetailRow {
|
|
|
|
|
id: string
|
|
|
|
|
name: string
|
|
|
|
|
status: RunStatus
|
|
|
|
|
runningMinutes: number
|
|
|
|
|
standbyMinutes: number
|
|
|
|
|
alarmMinutes: number
|
|
|
|
|
utilizationRate: number
|
|
|
|
|
runningTime: number
|
|
|
|
|
standbyTime: number
|
|
|
|
|
alarmTime: number
|
|
|
|
|
utilizationRate: string
|
|
|
|
|
lastReportTime: string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -172,6 +176,7 @@ const overviewData = ref<DeviceOperationOverviewRespVO>({
|
|
|
|
|
timelineRows: [],
|
|
|
|
|
totalDevices: 0
|
|
|
|
|
})
|
|
|
|
|
const operationRecordRows = ref<DeviceOperationRecordVO[]>([])
|
|
|
|
|
const last24HourlyStatus = ref<DeviceOperationOverviewHourlyStatusVO[]>([])
|
|
|
|
|
const deviceStatusStatistics = ref<DeviceStatusStatistics>({
|
|
|
|
|
total: 0,
|
|
|
|
|
@ -256,18 +261,21 @@ const statusCards = computed(() => [
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
const detailRows = computed<DetailRow[]>(() => {
|
|
|
|
|
return overviewData.value.timelineRows.map((row) => {
|
|
|
|
|
const minutes = calcMinutes(row)
|
|
|
|
|
const status = deviceOperatingStatusMap.value[String(row.id)] || getCurrentStatus(row)
|
|
|
|
|
return operationRecordRows.value.map((row) => {
|
|
|
|
|
const id = String(row.deviceId ?? row.id)
|
|
|
|
|
const status =
|
|
|
|
|
deviceOperatingStatusMap.value[id] ||
|
|
|
|
|
overviewStatusMap.value[id] ||
|
|
|
|
|
'offline'
|
|
|
|
|
return {
|
|
|
|
|
id: String(row.id),
|
|
|
|
|
name: row.name || '-',
|
|
|
|
|
id,
|
|
|
|
|
name: row.deviceName || '-',
|
|
|
|
|
status,
|
|
|
|
|
runningMinutes: minutes.running,
|
|
|
|
|
standbyMinutes: minutes.standby,
|
|
|
|
|
alarmMinutes: minutes.fault,
|
|
|
|
|
utilizationRate: Number(row.utilizationRate) || 0,
|
|
|
|
|
lastReportTime: status === 'fault' || minutes.fault > 0 ? lastUpdateTime.value || '-' : '-'
|
|
|
|
|
runningTime: Number(row.totalRunningTime) || 0,
|
|
|
|
|
standbyTime: Number(row.totalStandbyTime) || 0,
|
|
|
|
|
alarmTime: Number(row.totalFaultTime) || 0,
|
|
|
|
|
utilizationRate: row.utilizationRate || '0%',
|
|
|
|
|
lastReportTime: status === 'fault' || Number(row.totalFaultTime) > 0 ? lastUpdateTime.value || '-' : '-'
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
@ -288,8 +296,8 @@ const pagedRows = computed(() => {
|
|
|
|
|
const detailPageText = computed(() => `${detailPage.value} / ${totalDetailPages.value}`)
|
|
|
|
|
const alarmDevices = computed(() => {
|
|
|
|
|
return detailRows.value
|
|
|
|
|
.filter((item) => item.status === 'fault' || item.alarmMinutes > 0)
|
|
|
|
|
.sort((a, b) => b.alarmMinutes - a.alarmMinutes)
|
|
|
|
|
.filter((item) => item.status === 'fault' || item.alarmTime > 0)
|
|
|
|
|
.sort((a, b) => b.alarmTime - a.alarmTime)
|
|
|
|
|
.slice(0, 2)
|
|
|
|
|
})
|
|
|
|
|
const hourlyRates = computed(() => {
|
|
|
|
|
@ -313,13 +321,15 @@ const sevenDayAverageRate = computed(() => average(weekTrend.value.map((item) =>
|
|
|
|
|
|
|
|
|
|
const formatPercent = (value?: number) => `${Number(value || 0).toFixed(1)}%`
|
|
|
|
|
|
|
|
|
|
const formatMinutes = (minutes: number) => {
|
|
|
|
|
const safeMinutes = Math.max(0, Math.round(minutes || 0))
|
|
|
|
|
const hours = Math.floor(safeMinutes / 60)
|
|
|
|
|
const rest = safeMinutes % 60
|
|
|
|
|
if (!hours) return t('Time.minute', { minute: rest })
|
|
|
|
|
if (!rest) return t('Time.hour', { hour: hours })
|
|
|
|
|
return t('Time.hourMinute', { hour: hours, minute: rest })
|
|
|
|
|
const formatPercentText = (value?: string | number) => {
|
|
|
|
|
const text = String(value ?? '').trim()
|
|
|
|
|
if (!text) return '0%'
|
|
|
|
|
return text.includes('%') ? text : `${Number(text || 0).toFixed(1)}%`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const formatHours = (hours: number) => {
|
|
|
|
|
const value = Number(hours || 0)
|
|
|
|
|
return `${Number(value.toFixed(2))}${t('Time.hour', { hour: '' }).trim()}`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const average = (values: number[]) => {
|
|
|
|
|
@ -421,6 +431,13 @@ const getCurrentStatus = (row: DeviceOperationOverviewTimelineRowVO): RunStatus
|
|
|
|
|
return currentSegment?.status || row.segments?.[row.segments.length - 1]?.status || 'offline'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const overviewStatusMap = computed(() => {
|
|
|
|
|
return overviewData.value.timelineRows.reduce<Record<string, RunStatus>>((acc, row) => {
|
|
|
|
|
acc[String(row.id)] = getCurrentStatus(row)
|
|
|
|
|
return acc
|
|
|
|
|
}, {})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const calcMinutes = (row: DeviceOperationOverviewTimelineRowVO) => {
|
|
|
|
|
return (row.segments || []).reduce<Record<RunStatus, number>>(
|
|
|
|
|
(acc, segment) => {
|
|
|
|
|
@ -473,7 +490,7 @@ const getDeviceIds = () => {
|
|
|
|
|
return collectDeviceIdsByOrg(route.query.orgId ? String(route.query.orgId) : undefined)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const buildParams = (startTime: string, endTime: string, pageSize = 1000) => ({
|
|
|
|
|
const buildParams = (startTime: string, endTime: string, pageSize = 100) => ({
|
|
|
|
|
ids: getDeviceIds().join(','),
|
|
|
|
|
startTime,
|
|
|
|
|
endTime,
|
|
|
|
|
@ -525,6 +542,22 @@ const refreshTodayData = async () => {
|
|
|
|
|
lastUpdateTime.value = dayjs().format('YYYY/M/D HH:mm:ss')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const getOperationRecordRows = (data: any): DeviceOperationRecordVO[] => {
|
|
|
|
|
return Array.isArray(data) ? data : Array.isArray(data?.list) ? data.list : []
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const refreshOperationRecordRows = async () => {
|
|
|
|
|
const { start, end } = getDayRange()
|
|
|
|
|
const response = await DeviceOperationRecordApi.getDeviceOperationRecordPage({
|
|
|
|
|
pageNo: 1,
|
|
|
|
|
pageSize: 100,
|
|
|
|
|
ids: getDeviceIds().join(','),
|
|
|
|
|
startTime: start,
|
|
|
|
|
endTime: end
|
|
|
|
|
})
|
|
|
|
|
operationRecordRows.value = getOperationRecordRows(response)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const refreshLast24HourTrend = async () => {
|
|
|
|
|
const { start, end } = getLast24HoursRange()
|
|
|
|
|
const response = await DeviceOperationOverviewApi.getRunOverview(buildParams(start, end))
|
|
|
|
|
@ -549,7 +582,12 @@ const refreshWeekTrend = async () => {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const refreshData = async () => {
|
|
|
|
|
await Promise.all([refreshTodayData(), refreshDeviceStatusStatistics(), refreshLast24HourTrend()])
|
|
|
|
|
await Promise.all([
|
|
|
|
|
refreshTodayData(),
|
|
|
|
|
refreshOperationRecordRows(),
|
|
|
|
|
refreshDeviceStatusStatistics(),
|
|
|
|
|
refreshLast24HourTrend()
|
|
|
|
|
])
|
|
|
|
|
await refreshWeekTrend()
|
|
|
|
|
renderCharts()
|
|
|
|
|
}
|
|
|
|
|
@ -619,7 +657,7 @@ const resizeCharts = () => {
|
|
|
|
|
|
|
|
|
|
onMounted(async () => {
|
|
|
|
|
updateClock()
|
|
|
|
|
clockTimer = window.setInterval(updateClock, 1000)
|
|
|
|
|
clockTimer = window.setInterval(updateClock, 100)
|
|
|
|
|
await loadOrganizationList()
|
|
|
|
|
await refreshData()
|
|
|
|
|
refreshTimer = window.setInterval(() => {
|
|
|
|
|
|