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.

205 lines
9.0 KiB
Vue

<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 :xs="24" :sm="12">
<el-card shadow="never">
<template #header>
<div class="card-header">
<span class="font-bold">入库延迟分布</span>
<HelpButton title="入库延迟分布" :content="QUALITY_HELP" />
</div>
</template>
<div ref="delayDistChartRef" class="chart-container" />
</el-card>
</el-col>
<el-col :xs="24" :sm="12">
<el-card shadow="never">
<template #header>
<div class="card-header">
<span class="font-bold">数据完整率</span>
<HelpButton title="数据完整率" :content="QUALITY_HELP" />
</div>
</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="card-header">
<span class="font-bold">时间逆序可疑数据recodeTime &lt; collectTime</span>
<div class="flex items-center gap-2">
<el-tag type="danger" size="small">{{ timeReversalList.length }} </el-tag>
<HelpButton title="时间逆序可疑数据" :content="QUALITY_HELP" />
</div>
</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 :xs="24" :sm="12">
<el-card shadow="never">
<template #header>
<div class="card-header">
<span class="font-bold">采样间隔异常</span>
<HelpButton title="采样间隔异常" :content="QUALITY_HELP" />
</div>
</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 :xs="24" :sm="12">
<el-card shadow="never">
<template #header>
<div class="card-header">
<span class="font-bold">测点活跃度</span>
<HelpButton title="测点活跃度" :content="QUALITY_HELP" />
</div>
</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'
import HelpButton from '../components/HelpButton.vue'
import { QUALITY_HELP } from '../components/helpContent'
import { useChartResize } from '../components/useChartResize'
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[]>([])
const { getChart } = useChartResize(delayDistChartRef)
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()
const chart = getChart(delayDistChartRef)
if (chart && ddRes.data.length) {
chart.setOption({
tooltip: {
trigger: 'axis',
axisPointer: { type: 'shadow' },
formatter: (params: any) => `${params[0].name}<br/>样本数: <b>${params[0].value}</b>`
},
grid: { left: 60, right: 30, top: 30, bottom: 30 },
xAxis: { type: 'category', data: ddRes.data.map(d => d.delayBucket), axisLabel: { color: '#666' } },
yAxis: { type: 'value', name: '样本数', axisLabel: { color: '#666' }, splitLine: { lineStyle: { type: 'dashed' } } },
series: [{
type: 'bar',
data: ddRes.data.map(d => d.sampleCount),
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: '#e6a23c' },
{ offset: 1, color: '#f5deb3' }
]),
borderRadius: [4, 4, 0, 0]
},
label: { show: true, position: 'top', fontSize: 10, color: '#666' },
barMaxWidth: 30
}]
})
}
} finally { loading.value = false }
}
onMounted(() => { initTimeRange(); handleQuery() })
</script>
<style scoped>
.card-header {
display: flex;
align-items: center;
justify-content: space-between;
}
.chart-container {
width: 100%;
min-height: 300px;
}
</style>