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.

208 lines
9.2 KiB
Vue

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<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)
/** 初始化默认时间范围昨天8:30 ~ 今天8:30 */
function initTimeRange() {
const now = new Date()
const end = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 8, 30, 0)
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>