Files
mom-web/src/views/iot/devicedata/DeviceDataForm.vue
2026-03-05 16:52:12 +08:00

224 lines
6.1 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<el-dialog
v-model="historyDialogVisible"
:title="dialogTitle"
width="900px"
destroy-on-close
>
<div class="history-toolbar">
<el-form inline label-width="0">
<el-form-item>
<el-date-picker
v-model="historyRange"
type="datetimerange"
range-separator=""
start-placeholder="开始时间"
end-placeholder="结束时间"
value-format="YYYY-MM-DD HH:mm:ss"
class="history-range-picker"
@change="onHistorySearch"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="fetchHistoryData">查询</el-button>
</el-form-item>
</el-form>
</div>
<div class="history-chart" v-loading="historyLoading">
<template v-if="historyData.length">
<Echart :key="echartKey" :options="historyChartOption" height="320px" />
</template>
<el-empty v-else description="暂无历史数据" />
</div>
</el-dialog>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue'
import dayjs from 'dayjs'
import { ElMessage } from 'element-plus'
import type { EChartsOption } from 'echarts'
import { Echart } from '@/components/Echart'
import { DeviceApi } from '@/api/iot/device/device/index'
interface LatestData {
collectedAt?: number | string | null
temperatureC?: number | string | null
analog5?: number | string | null
deviceType?: number | string | null
}
interface ProductWithLatest {
id: number
name?: string
latestData?: LatestData | null
}
const historyDialogVisible = ref(false)
const selectedDevice = ref<ProductWithLatest | null>(null)
const historyLoading = ref(false)
const historyData = ref<any[]>([])
const historyRange = ref<[string, string]>(['', ''])
const echartKey = ref(0) // 强制 Echart 重新渲染,避免二次打开不显示
const dialogTitle = computed(() =>
selectedDevice.value ? `${selectedDevice.value.name || '设备'}历史数据` : '设备历史数据'
)
const formatTimestamp = (value: number | string | null | undefined) => {
if (value === null || value === undefined) return '-'
const num = typeof value === 'number' ? value : Number(value)
const ts = Number.isNaN(num) ? Date.parse(String(value)) : num < 1e12 ? num * 1000 : num
if (!Number.isFinite(ts)) return '-'
const date = dayjs(ts)
return date.isValid() ? date.format('YYYY-MM-DD HH:mm:ss') : '-'
}
const toNumeric = (value: number | string | null | undefined) => {
if (value === null || value === undefined || value === '') return null
const num = typeof value === 'number' ? value : Number(value)
return Number.isNaN(num) ? null : Number(num.toFixed(2))
}
const toTimestamp = (value: number | string | null | undefined) => {
if (value === null || value === undefined || value === '') return null
const num = typeof value === 'number' ? value : Number(value)
const ts = Number.isNaN(num) ? Date.parse(String(value)) : num < 1e12 ? num * 1000 : num
return Number.isFinite(ts) ? ts : null
}
// 按时间升序展示,让最新数据在图表右侧
const sortedHistoryList = computed(() => {
const list = historyData.value || []
return [...list].sort((a, b) => {
const ta = toTimestamp(a?.collectedAt) || 0
const tb = toTimestamp(b?.collectedAt) || 0
return ta - tb
})
})
const historyChartOption = computed<EChartsOption>(() => {
const list = sortedHistoryList.value
const times = list.map((item) => formatTimestamp(item.collectedAt))
const temperatures = list.map((item) => toNumeric(item.temperatureC))
const humidities = list.map((item) => toNumeric(item.analog5))
return {
tooltip: {
trigger: 'axis'
},
legend: {
data: ['温度(℃)', '湿度(RH)']
},
grid: {
left: 40,
right: 40,
top: 40,
bottom: 40
},
xAxis: {
type: 'category',
boundaryGap: false,
data: times
},
yAxis: [
{
type: 'value',
name: '温度(℃)'
},
{
type: 'value',
name: '湿度(RH)'
}
],
series: [
{
name: '温度(℃)',
type: 'line',
smooth: true,
data: temperatures,
showSymbol: false
},
{
name: '湿度(RH)',
type: 'line',
smooth: true,
yAxisIndex: 1,
data: humidities,
showSymbol: false
}
]
}
})
const resetHistoryRange = () => {
const end = dayjs()
const start = end.subtract(1, 'day')
historyRange.value = [start.format('YYYY-MM-DD 00:00:00'), end.format('YYYY-MM-DD 23:59:59')]
}
const fetchHistoryData = async () => {
if (!selectedDevice.value) return
if (!historyRange.value?.[0] || !historyRange.value?.[1]) {
ElMessage.warning('请选择时间范围')
return
}
historyLoading.value = true
try {
const params = {
deviceId: selectedDevice.value.id,
startTime: historyRange.value[0],
endTime: historyRange.value[1]
}
const res = await DeviceApi.getOeeBaseDataList(params)
const data = (res as any)?.data ?? (res as any) ?? []
historyData.value = Array.isArray(data) ? data : []
echartKey.value += 1
} catch (error) {
historyData.value = []
ElMessage.error('获取历史数据失败')
} finally {
historyLoading.value = false
}
}
const onHistorySearch = () => {
// 将结束时间调整为当天的 23:59:59日期选择器默认是 00:00:00
if (historyRange.value?.[1]) {
const end = dayjs(historyRange.value[1])
// 如果结束时间是 00:00:00说明是日期选择器默认值需要调整为当天的 23:59:59
if (end.format('HH:mm:ss') === '00:00:00') {
historyRange.value[1] = end.format('YYYY-MM-DD') + ' 23:59:59'
}
}
fetchHistoryData()
}
const openHistoryDialog = (device: ProductWithLatest) => {
selectedDevice.value = device
resetHistoryRange()
historyDialogVisible.value = true
fetchHistoryData()
}
defineExpose({
openHistoryDialog
})
</script>
<style scoped>
.history-toolbar {
margin-bottom: 12px;
}
.history-range-picker {
width: 360px;
}
.history-chart {
min-height: 320px;
}
</style>