|
|
|
|
@ -7,14 +7,14 @@
|
|
|
|
|
<div class="brand-icon">
|
|
|
|
|
<Icon icon="ep:cpu" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="brand-text">IOT 实时监控中心副标题</div>
|
|
|
|
|
<div class="brand-text">{{ t('Header.subTitle') }}</div>
|
|
|
|
|
</div>
|
|
|
|
|
<h1>设备运行总览大屏标题</h1>
|
|
|
|
|
<h1>{{ t('Header.title') }}</h1>
|
|
|
|
|
<div class="header-meta">
|
|
|
|
|
<div><span>当前时间:</span>{{ currentTime }}</div>
|
|
|
|
|
<div><span>最后更新:</span>{{ lastUpdateTime || '-' }}</div>
|
|
|
|
|
<div><span>{{ t('Header.currentTime') }}</span>{{ currentTime }}</div>
|
|
|
|
|
<div><span>{{ t('Header.lastUpdate') }}</span>{{ lastUpdateTime || '-' }}</div>
|
|
|
|
|
<div class="refresh-row">
|
|
|
|
|
<span>刷新频率:</span>15 秒刷新
|
|
|
|
|
<span>{{ t('Header.refreshRate') }}</span>{{ t('Header.refreshRateValue', { seconds: 15 }) }}
|
|
|
|
|
<button type="button" class="screen-btn" @click="toggleFullscreen">
|
|
|
|
|
<Icon :icon="isFullscreen ? 'zmdi:fullscreen-exit' : 'zmdi:fullscreen'" />
|
|
|
|
|
</button>
|
|
|
|
|
@ -42,34 +42,34 @@
|
|
|
|
|
|
|
|
|
|
<div class="alarm-board panel">
|
|
|
|
|
<div class="alarm-head">
|
|
|
|
|
<span>当前报警设备</span>
|
|
|
|
|
<span>今日累计报警时长</span>
|
|
|
|
|
<span>最后上报时间</span>
|
|
|
|
|
<span>{{ t('AlarmBoard.currentDevice') }}</span>
|
|
|
|
|
<span>{{ t('AlarmBoard.todayAlarmDuration') }}</span>
|
|
|
|
|
<span>{{ t('AlarmBoard.lastReportTime') }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
<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>报警 {{ formatMinutes(item.alarmMinutes) }}</span>
|
|
|
|
|
<span>{{ t('AlarmBoard.alarmPrefix') }} {{ formatMinutes(item.alarmMinutes) }}</span>
|
|
|
|
|
<span>{{ item.lastReportTime }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div v-else class="empty-line">暂无报警设备</div>
|
|
|
|
|
<div v-else class="empty-line">{{ t('AlarmBoard.empty') }}</div>
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
<section class="panel detail-panel">
|
|
|
|
|
<div class="panel-title">
|
|
|
|
|
<h2>设备运行明细</h2>
|
|
|
|
|
<span>{{ detailPageText }} · 自动轮播</span>
|
|
|
|
|
<h2>{{ t('Detail.title') }}</h2>
|
|
|
|
|
<span>{{ t('Detail.carouselText', { page: detailPageText }) }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="detail-table">
|
|
|
|
|
<div class="table-header table-row">
|
|
|
|
|
<span>设备名称</span>
|
|
|
|
|
<span>当前状态</span>
|
|
|
|
|
<span>运行时间(今日)</span>
|
|
|
|
|
<span>空闲时间(今日)</span>
|
|
|
|
|
<span>报警时间(今日)</span>
|
|
|
|
|
<span>运行效率(今日)</span>
|
|
|
|
|
<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>
|
|
|
|
|
</div>
|
|
|
|
|
<div v-if="pagedRows.length" class="table-body">
|
|
|
|
|
<div
|
|
|
|
|
@ -88,22 +88,22 @@
|
|
|
|
|
<span class="rate">{{ formatPercent(row.utilizationRate) }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div v-else class="empty-table">暂无设备运行数据</div>
|
|
|
|
|
<div v-else class="empty-table">{{ t('Detail.empty') }}</div>
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
<section class="chart-row">
|
|
|
|
|
<div class="panel chart-panel">
|
|
|
|
|
<div class="panel-title">
|
|
|
|
|
<h2>近 24 小时运行率趋势</h2>
|
|
|
|
|
<span>平均运行率:{{ formatPercent(todayAverageRate) }}</span>
|
|
|
|
|
<h2>{{ t('Charts.last24Hours') }}</h2>
|
|
|
|
|
<span>{{ t('Charts.averageRate', { value: formatPercent(todayAverageRate) }) }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div ref="hourChartRef" class="chart"></div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="panel chart-panel">
|
|
|
|
|
<div class="panel-title">
|
|
|
|
|
<h2>近 7 天运行率趋势</h2>
|
|
|
|
|
<span>平均运行率:{{ formatPercent(sevenDayAverageRate) }}</span>
|
|
|
|
|
<h2>{{ t('Charts.last7Days') }}</h2>
|
|
|
|
|
<span>{{ t('Charts.averageRate', { value: formatPercent(sevenDayAverageRate) }) }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div ref="weekChartRef" class="chart"></div>
|
|
|
|
|
</div>
|
|
|
|
|
@ -123,6 +123,8 @@ import { OrganizationApi } from '@/api/mes/organization'
|
|
|
|
|
|
|
|
|
|
defineOptions({ name: 'IotReportDashboard3' })
|
|
|
|
|
|
|
|
|
|
const { t } = useI18n('Dashboard3')
|
|
|
|
|
|
|
|
|
|
type RunStatus = 'running' | 'standby' | 'fault' | 'offline'
|
|
|
|
|
|
|
|
|
|
interface OrganizationFilterItem {
|
|
|
|
|
@ -168,12 +170,12 @@ let clockTimer: number | undefined
|
|
|
|
|
let refreshTimer: number | undefined
|
|
|
|
|
let carouselTimer: number | undefined
|
|
|
|
|
|
|
|
|
|
const statusText: Record<RunStatus, string> = {
|
|
|
|
|
running: '运行',
|
|
|
|
|
standby: '空闲',
|
|
|
|
|
fault: '报警',
|
|
|
|
|
offline: '离线'
|
|
|
|
|
}
|
|
|
|
|
const statusText = computed<Record<RunStatus, string>>(() => ({
|
|
|
|
|
running: t('Status.running'),
|
|
|
|
|
standby: t('Status.standby'),
|
|
|
|
|
fault: t('Status.fault'),
|
|
|
|
|
offline: t('Status.offline')
|
|
|
|
|
}))
|
|
|
|
|
|
|
|
|
|
const metricValueMap = computed(() => {
|
|
|
|
|
return overviewData.value.metrics.reduce<Record<string, number>>((acc, item) => {
|
|
|
|
|
@ -197,7 +199,7 @@ const summaryMap = computed(() => {
|
|
|
|
|
const statusCards = computed(() => [
|
|
|
|
|
{
|
|
|
|
|
key: 'total',
|
|
|
|
|
label: '设备总数',
|
|
|
|
|
label: t('Metrics.total'),
|
|
|
|
|
icon: 'ep:connection',
|
|
|
|
|
value:
|
|
|
|
|
overviewData.value.totalDevices ||
|
|
|
|
|
@ -207,7 +209,7 @@ const statusCards = computed(() => [
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'online',
|
|
|
|
|
label: '在线',
|
|
|
|
|
label: t('Metrics.online'),
|
|
|
|
|
icon: 'ep:cpu',
|
|
|
|
|
value: Math.max(0, totalDeviceCount.value - offlineDeviceCount.value),
|
|
|
|
|
percent: 100 - (summaryMap.value.offline || 0),
|
|
|
|
|
@ -215,7 +217,7 @@ const statusCards = computed(() => [
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'offline',
|
|
|
|
|
label: '离线',
|
|
|
|
|
label: t('Metrics.offline'),
|
|
|
|
|
icon: 'ep:link',
|
|
|
|
|
value: offlineDeviceCount.value,
|
|
|
|
|
percent: summaryMap.value.offline || 0,
|
|
|
|
|
@ -223,7 +225,7 @@ const statusCards = computed(() => [
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'running',
|
|
|
|
|
label: '运行',
|
|
|
|
|
label: t('Metrics.running'),
|
|
|
|
|
icon: 'ep:video-play',
|
|
|
|
|
value: getMetricValue(['running', 'run', 'runningDevices']) ?? getRowsByStatus('running').length,
|
|
|
|
|
percent: summaryMap.value.running || 0,
|
|
|
|
|
@ -231,7 +233,7 @@ const statusCards = computed(() => [
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'standby',
|
|
|
|
|
label: '空闲',
|
|
|
|
|
label: t('Metrics.standby'),
|
|
|
|
|
icon: 'ep:video-pause',
|
|
|
|
|
value: getMetricValue(['standby', 'idle', 'standbyDevices']) ?? getRowsByStatus('standby').length,
|
|
|
|
|
percent: summaryMap.value.standby || 0,
|
|
|
|
|
@ -239,7 +241,7 @@ const statusCards = computed(() => [
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'fault',
|
|
|
|
|
label: '报警',
|
|
|
|
|
label: t('Metrics.fault'),
|
|
|
|
|
icon: 'ep:bell',
|
|
|
|
|
value: getMetricValue(['fault', 'alarm', 'faultDevices']) ?? getRowsByStatus('fault').length,
|
|
|
|
|
percent: summaryMap.value.fault || 0,
|
|
|
|
|
@ -303,9 +305,9 @@ 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 `${rest}分`
|
|
|
|
|
if (!rest) return `${hours}小时`
|
|
|
|
|
return `${hours}小时${rest}分`
|
|
|
|
|
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 average = (values: number[]) => {
|
|
|
|
|
|