Files
mom-web/src/views/iot/devicedata/DeviceDataForm.vue

224 lines
6.1 KiB
Vue
Raw Normal View History

2026-03-05 16:52:12 +08:00
<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>