You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
224 lines
7.2 KiB
Vue
224 lines
7.2 KiB
Vue
<template>
|
|
<div v-loading="loading" class="app-container">
|
|
<VibrationBoardFilter
|
|
:selection-label="queryForm.selectionLabel"
|
|
:time-range="daterangeRecordTime"
|
|
:sampling-interval="queryForm.samplingInterval"
|
|
:vibration-param="queryForm.vibrationParam"
|
|
show-metric
|
|
@update:time-range="setTimeRange"
|
|
@update:sampling-interval="setSamplingInterval"
|
|
@update:vibration-param="setVibrationParam"
|
|
@query="handleQuery"
|
|
/>
|
|
|
|
<el-row :gutter="16" class="mt-4">
|
|
<el-col :span="6"
|
|
><el-card
|
|
><div class="kpi-label">采样记录数</div>
|
|
<div class="kpi-value">{{ toNumber(overviewData.sampleCount) }}</div></el-card
|
|
></el-col
|
|
>
|
|
<el-col :span="6"
|
|
><el-card
|
|
><div class="kpi-label">设备数量</div>
|
|
<div class="kpi-value">{{ toNumber(overviewData.deviceCount) }}</div></el-card
|
|
></el-col
|
|
>
|
|
<el-col :span="6"
|
|
><el-card
|
|
><div class="kpi-label">主看指标</div>
|
|
<div class="kpi-value">{{ overviewData.metricLabel || '--' }}</div></el-card
|
|
></el-col
|
|
>
|
|
<el-col :span="6"
|
|
><el-card
|
|
><div class="kpi-label">覆盖率</div>
|
|
<div class="kpi-value">{{ formatPercent(toNumber(overviewData.coverageRate)) }}</div></el-card
|
|
></el-col
|
|
>
|
|
</el-row>
|
|
|
|
<el-row :gutter="16" class="mt-4">
|
|
<el-col :span="6" v-for="item in overviewData.metricCards || []" :key="item.field">
|
|
<el-card class="metric-card" :style="{ borderTopColor: metricColorMap[item.field || ''] || '#5b8ff9' }">
|
|
<div class="kpi-label">{{ item.label }}</div>
|
|
<div class="kpi-value">
|
|
{{ formatValue(item.latest) }}<span class="unit">{{ item.unit }}</span>
|
|
</div>
|
|
<div class="metric-foot">均值 {{ formatValue(item.avg) }}{{ item.unit }}</div>
|
|
<div class="metric-foot">峰值 {{ formatValue(item.max) }}{{ item.unit }}</div>
|
|
</el-card>
|
|
</el-col>
|
|
</el-row>
|
|
|
|
<el-row :gutter="16" class="mt-4">
|
|
<el-col :span="6">
|
|
<VibrationBoardDeviceTree :loading="treeLoading" :tree-data="monitorTreeOptions" :tree-props="treeProps" @node-click="handleTreeNodeClick" />
|
|
</el-col>
|
|
<el-col :span="18">
|
|
<el-row :gutter="16">
|
|
<el-col :span="12"
|
|
><el-card><template #header>仪表盘总览</template><Chart ref="gaugeChart" class="chart-lg" /></el-card
|
|
></el-col>
|
|
<el-col :span="12"
|
|
><el-card
|
|
><template #header>{{ hasMultiDevice ? '设备排名对比图' : '主看指标分布图' }}</template
|
|
><Chart ref="rankChart" class="chart-lg" /></el-card
|
|
></el-col>
|
|
</el-row>
|
|
</el-col>
|
|
</el-row>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import Chart from '@/components/Charts/Chart.vue';
|
|
import { getVibrationOverviewData, type VibrationOverviewPageVO } from '@/api/ems/report/vibrationBoard';
|
|
import VibrationBoardFilter from '../components/VibrationBoardFilter.vue';
|
|
import VibrationBoardDeviceTree from '../components/VibrationBoardDeviceTree.vue';
|
|
import { useVibrationBoardQueryState } from '../components/useVibrationBoardQueryState';
|
|
import { vibrationMetricOptions } from '../components/vibrationBoardShared';
|
|
|
|
defineOptions({ name: 'VibrationBoardOverview' });
|
|
|
|
const gaugeChart = ref<any>();
|
|
const rankChart = ref<any>();
|
|
const {
|
|
loading,
|
|
treeLoading,
|
|
monitorTreeOptions,
|
|
treeProps,
|
|
daterangeRecordTime,
|
|
queryForm,
|
|
deviceCount,
|
|
hasMultiDevice,
|
|
loadTree,
|
|
handleTreeNodeClick,
|
|
setTimeRange,
|
|
setSamplingInterval,
|
|
setVibrationParam,
|
|
buildQuery
|
|
} = useVibrationBoardQueryState();
|
|
const overviewData = ref<VibrationOverviewPageVO>({ metricCards: [], gaugeItems: [], deviceRanks: [] });
|
|
const metricColorMap = Object.fromEntries(vibrationMetricOptions.map((item) => [item.field, item.color]));
|
|
|
|
const toNumber = (value: number | string | undefined) => Number(value ?? 0);
|
|
const formatValue = (value: number | string | undefined) => (value === null || value === undefined || value === '' ? '--' : Number(value).toFixed(2));
|
|
const formatPercent = (value: number) => `${(Number(value || 0) * 100).toFixed(1)}%`;
|
|
|
|
const renderCharts = () => {
|
|
const gaugeItems = overviewData.value.gaugeItems || [];
|
|
const deviceRanks = overviewData.value.deviceRanks || [];
|
|
if (gaugeItems.length) {
|
|
gaugeChart.value?.setData({
|
|
series: gaugeItems.map((item, index) => ({
|
|
type: 'gauge',
|
|
min: 0,
|
|
max: toNumber(item.maxValue),
|
|
radius: '78%',
|
|
center: [
|
|
['18%', '55%'],
|
|
['50%', '55%'],
|
|
['82%', '55%']
|
|
][index] || ['50%', '55%'],
|
|
progress: { show: true, width: 12, itemStyle: { color: ['#5b8ff9', '#36cfc9', '#f59e0b'][index] || '#5b8ff9' } },
|
|
axisLine: { lineStyle: { width: 12 } },
|
|
pointer: { show: false },
|
|
axisTick: { show: false },
|
|
splitLine: { length: 10 },
|
|
axisLabel: { distance: 16, fontSize: 10 },
|
|
title: { offsetCenter: [0, '82%'], fontSize: 12 },
|
|
detail: { offsetCenter: [0, '22%'], fontSize: 18, formatter: `${formatValue(item.value)} ${item.unit || ''}` },
|
|
data: [{ value: toNumber(item.value), name: item.name }]
|
|
}))
|
|
});
|
|
} else {
|
|
// 空结果时同步清空仪表盘,避免 KPI 已回到空态但图表仍停留在上一轮查询。
|
|
gaugeChart.value?.setData(null);
|
|
}
|
|
|
|
if (hasMultiDevice.value) {
|
|
if (!deviceRanks.length) {
|
|
rankChart.value?.setData(null);
|
|
return;
|
|
}
|
|
rankChart.value?.setData({
|
|
tooltip: { trigger: 'axis' },
|
|
xAxis: { type: 'value' },
|
|
yAxis: { type: 'category', data: deviceRanks.map((item) => item.monitorName) },
|
|
series: [
|
|
{
|
|
type: 'bar',
|
|
data: deviceRanks.map((item) => toNumber(item.avg)),
|
|
itemStyle: { color: metricColorMap[queryForm.value.vibrationParam] || '#5b8ff9' }
|
|
}
|
|
]
|
|
});
|
|
return;
|
|
}
|
|
|
|
const stats = overviewData.value.primaryMetricStats;
|
|
if (!stats) {
|
|
rankChart.value?.setData(null);
|
|
return;
|
|
}
|
|
rankChart.value?.setData({
|
|
xAxis: { type: 'category', data: ['最小值', '均值', '最大值'] },
|
|
yAxis: { type: 'value', name: overviewData.value.unit || '' },
|
|
series: [
|
|
{
|
|
type: 'bar',
|
|
data: [toNumber(stats.min), toNumber(stats.avg), toNumber(stats.max)],
|
|
itemStyle: { color: metricColorMap[queryForm.value.vibrationParam] || '#5b8ff9' }
|
|
}
|
|
]
|
|
});
|
|
};
|
|
|
|
const handleQuery = async () => {
|
|
loading.value = true;
|
|
try {
|
|
const { data } = await getVibrationOverviewData(buildQuery());
|
|
overviewData.value = data || { metricCards: [], gaugeItems: [], deviceRanks: [] };
|
|
await nextTick();
|
|
renderCharts();
|
|
} finally {
|
|
loading.value = false;
|
|
}
|
|
};
|
|
|
|
onMounted(async () => {
|
|
await loadTree();
|
|
await handleQuery();
|
|
});
|
|
</script>
|
|
|
|
<style scoped>
|
|
.kpi-label {
|
|
color: #5b6573;
|
|
font-size: 13px;
|
|
}
|
|
.kpi-value {
|
|
margin-top: 12px;
|
|
font-size: 26px;
|
|
font-weight: 700;
|
|
color: #1f2329;
|
|
}
|
|
.metric-card {
|
|
border-top: 4px solid #5b8ff9;
|
|
}
|
|
.metric-foot {
|
|
margin-top: 8px;
|
|
font-size: 12px;
|
|
color: #86909c;
|
|
}
|
|
.unit {
|
|
margin-left: 4px;
|
|
font-size: 14px;
|
|
}
|
|
.chart-lg {
|
|
height: 420px;
|
|
}
|
|
</style>
|