|
|
|
|
|
<template>
|
|
|
|
|
|
<div class="app-container">
|
|
|
|
|
|
<el-card shadow="never">
|
|
|
|
|
|
<el-form :inline="true" :model="queryForm" size="small">
|
|
|
|
|
|
<el-form-item label="时间范围">
|
|
|
|
|
|
<el-date-picker v-model="timeRange" type="datetimerange" value-format="YYYY-MM-DD HH:mm:ss"
|
|
|
|
|
|
range-separator="至" start-placeholder="开始时间" end-placeholder="结束时间" style="width: 380px" />
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
<el-form-item label="采样间隔阈值">
|
|
|
|
|
|
<el-input-number v-model="queryForm.gapThresholdSeconds" :min="60" :max="86400" :step="60" style="width: 140px" />
|
|
|
|
|
|
<span class="ml-1 text-xs text-gray-400">秒</span>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
<el-form-item label="预期采样数">
|
|
|
|
|
|
<el-input-number v-model="queryForm.expectedSampleCount" :min="1" :max="100000" style="width: 140px" />
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
<el-form-item>
|
|
|
|
|
|
<el-button type="primary" icon="Search" @click="handleQuery">查询</el-button>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
</el-form>
|
|
|
|
|
|
</el-card>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 入库延迟分布 -->
|
|
|
|
|
|
<el-row :gutter="16" class="mt-4">
|
|
|
|
|
|
<el-col :span="12">
|
|
|
|
|
|
<el-card shadow="never">
|
|
|
|
|
|
<template #header><span class="font-bold">入库延迟分布</span></template>
|
|
|
|
|
|
<div ref="delayDistChartRef" style="height: 300px" />
|
|
|
|
|
|
</el-card>
|
|
|
|
|
|
</el-col>
|
|
|
|
|
|
<el-col :span="12">
|
|
|
|
|
|
<el-card shadow="never">
|
|
|
|
|
|
<template #header><span class="font-bold">数据完整率</span></template>
|
|
|
|
|
|
<el-table :data="completenessList" stripe size="small" max-height="300">
|
|
|
|
|
|
<el-table-column prop="monitorName" label="测点" show-overflow-tooltip />
|
|
|
|
|
|
<el-table-column prop="actualCount" label="实际数" width="80" align="right" />
|
|
|
|
|
|
<el-table-column prop="completenessRate" label="完整率" width="120" align="right">
|
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
|
<el-progress :percentage="Math.round((row.completenessRate ?? 0) * 100)" :stroke-width="12"
|
|
|
|
|
|
:color="row.completenessRate >= 0.9 ? '#67c23a' : row.completenessRate >= 0.7 ? '#e6a23c' : '#f56c6c'" />
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
</el-table>
|
|
|
|
|
|
</el-card>
|
|
|
|
|
|
</el-col>
|
|
|
|
|
|
</el-row>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 时间逆序可疑数据 -->
|
|
|
|
|
|
<el-card shadow="never" class="mt-4">
|
|
|
|
|
|
<template #header>
|
|
|
|
|
|
<div class="flex items-center justify-between">
|
|
|
|
|
|
<span class="font-bold">时间逆序可疑数据(recodeTime < collectTime)</span>
|
|
|
|
|
|
<el-tag type="danger" size="small">{{ timeReversalList.length }} 条</el-tag>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<el-table :data="timeReversalList" stripe size="small" max-height="300">
|
|
|
|
|
|
<el-table-column prop="monitorId" label="测点ID" width="150" />
|
|
|
|
|
|
<el-table-column prop="monitorName" label="测点名称" width="180" />
|
|
|
|
|
|
<el-table-column prop="temperature" label="温度" width="80" align="right">
|
|
|
|
|
|
<template #default="{ row }">{{ row.temperature?.toFixed(2) }}℃</template>
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
<el-table-column prop="collectTime" label="采集时间" width="200" />
|
|
|
|
|
|
<el-table-column prop="recodeTime" label="入库时间" width="200" />
|
|
|
|
|
|
<el-table-column prop="delaySeconds" label="延迟(秒)" width="100" align="right">
|
|
|
|
|
|
<template #default="{ row }"><el-tag type="danger" size="small">{{ row.delaySeconds }}s</el-tag></template>
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
</el-table>
|
|
|
|
|
|
</el-card>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 采样间隔异常 + 测点活跃度 -->
|
|
|
|
|
|
<el-row :gutter="16" class="mt-4">
|
|
|
|
|
|
<el-col :span="12">
|
|
|
|
|
|
<el-card shadow="never">
|
|
|
|
|
|
<template #header><span class="font-bold">采样间隔异常</span></template>
|
|
|
|
|
|
<el-table :data="samplingGapList" stripe size="small" max-height="300">
|
|
|
|
|
|
<el-table-column prop="monitorName" label="测点" show-overflow-tooltip />
|
|
|
|
|
|
<el-table-column prop="gapSeconds" label="间隔(秒)" width="100" align="right">
|
|
|
|
|
|
<template #default="{ row }"><el-tag type="warning" size="small">{{ row.gapSeconds }}s</el-tag></template>
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
<el-table-column prop="prevTime" label="前次时间" width="160" />
|
|
|
|
|
|
<el-table-column prop="collectTime" label="本次时间" width="160" />
|
|
|
|
|
|
</el-table>
|
|
|
|
|
|
</el-card>
|
|
|
|
|
|
</el-col>
|
|
|
|
|
|
<el-col :span="12">
|
|
|
|
|
|
<el-card shadow="never">
|
|
|
|
|
|
<template #header><span class="font-bold">测点活跃度</span></template>
|
|
|
|
|
|
<el-table :data="activityList" stripe size="small" max-height="300">
|
|
|
|
|
|
<el-table-column prop="monitorName" label="测点" show-overflow-tooltip />
|
|
|
|
|
|
<el-table-column prop="actualCount" label="样本数" width="80" align="right" />
|
|
|
|
|
|
<el-table-column prop="firstTime" label="首次" width="160" />
|
|
|
|
|
|
<el-table-column prop="lastTime" label="末次" width="160" />
|
|
|
|
|
|
</el-table>
|
|
|
|
|
|
</el-card>
|
|
|
|
|
|
</el-col>
|
|
|
|
|
|
</el-row>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
|
import { ref, reactive, onMounted, nextTick } from 'vue'
|
|
|
|
|
|
import * as echarts from 'echarts'
|
|
|
|
|
|
import { getDelayDistribution, getTimeReversal, getSamplingGapAnomalies, getCompletenessRate, getMonitorActivity } from '@/api/ems/report/tempBoard'
|
|
|
|
|
|
import type { TempBoardQuery, TempBoardQualityVO } from '@/api/ems/report/tempBoard'
|
|
|
|
|
|
|
|
|
|
|
|
defineOptions({ name: 'TempBoardQuality' })
|
|
|
|
|
|
|
|
|
|
|
|
const timeRange = ref<[string, string]>(['', ''])
|
|
|
|
|
|
const queryForm = reactive<TempBoardQuery>({ gapThresholdSeconds: 300, expectedSampleCount: 1440 })
|
|
|
|
|
|
const loading = ref(false)
|
|
|
|
|
|
const delayDistChartRef = ref<HTMLElement>()
|
|
|
|
|
|
const timeReversalList = ref<TempBoardQualityVO[]>([])
|
|
|
|
|
|
const samplingGapList = ref<TempBoardQualityVO[]>([])
|
|
|
|
|
|
const completenessList = ref<TempBoardQualityVO[]>([])
|
|
|
|
|
|
const activityList = ref<TempBoardQualityVO[]>([])
|
|
|
|
|
|
|
|
|
|
|
|
function initTimeRange() {
|
|
|
|
|
|
const end = new Date(); const start = new Date(end.getTime() - 24 * 3600 * 1000)
|
|
|
|
|
|
const p = (n: number) => n.toString().padStart(2, '0')
|
|
|
|
|
|
const fmt = (d: Date) => `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`
|
|
|
|
|
|
timeRange.value = [fmt(start), fmt(end)]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function handleQuery() {
|
|
|
|
|
|
if (timeRange.value?.[0]) { queryForm.startTime = timeRange.value[0]; queryForm.endTime = timeRange.value[1] }
|
|
|
|
|
|
loading.value = true
|
|
|
|
|
|
try {
|
|
|
|
|
|
const [ddRes, trRes, sgRes, crRes, actRes] = await Promise.all([
|
|
|
|
|
|
getDelayDistribution(queryForm), getTimeReversal(queryForm),
|
|
|
|
|
|
getSamplingGapAnomalies(queryForm), getCompletenessRate(queryForm), getMonitorActivity(queryForm)
|
|
|
|
|
|
])
|
|
|
|
|
|
timeReversalList.value = trRes.data; samplingGapList.value = sgRes.data
|
|
|
|
|
|
completenessList.value = crRes.data; activityList.value = actRes.data
|
|
|
|
|
|
await nextTick()
|
|
|
|
|
|
// 渲染延迟分布柱图
|
|
|
|
|
|
if (delayDistChartRef.value && ddRes.data.length) {
|
|
|
|
|
|
const chart = echarts.init(delayDistChartRef.value)
|
|
|
|
|
|
chart.setOption({
|
|
|
|
|
|
tooltip: { trigger: 'axis' },
|
|
|
|
|
|
grid: { left: 60, right: 30, top: 30, bottom: 30 },
|
|
|
|
|
|
xAxis: { type: 'category', data: ddRes.data.map(d => d.delayBucket) },
|
|
|
|
|
|
yAxis: { type: 'value', name: '样本数' },
|
|
|
|
|
|
series: [{ type: 'bar', data: ddRes.data.map(d => d.sampleCount), itemStyle: { color: '#e6a23c' } }]
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
} finally { loading.value = false }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
onMounted(() => { initTimeRange(); handleQuery() })
|
|
|
|
|
|
</script>
|