feat: 适配设备运行状态实时图表与日志列表大数据量展示

- 实时图表数据排序方式由升序改为降序,提升最新数据的展示优先级
- 增加动态优化逻辑,根据数据量自动调整采样、动画和符号显示策略
- 优化时间轴标签显示,避免重叠并提升可读性
- 日志列表从 Ant Design Table 迁移至 VxeTable,提升大数据量下的渲染性能
- 调整日志接口分页参数,确保一次性加载最多9999条数据
- 监听日志数据变化并动态更新表格内容
- 优化 Tab 组件行为,销毁非活跃面板以减少内存占用
- 统一多个模拟组件中的提示文本表述
This commit is contained in:
fhysy 2025-09-23 14:00:34 +08:00
parent b012582f68
commit 019029f3ce
5 changed files with 92 additions and 57 deletions

View File

@ -28,7 +28,7 @@ const renderChart = () => {
if (props.data.length === 0) return;
//
const sortedData = [...props.data].sort((a, b) => a.timestamp - b.timestamp);
const sortedData = [...props.data].reverse();
// ECharts[, ]
const seriesData = sortedData.map((item) => [
@ -36,30 +36,35 @@ const renderChart = () => {
Number.parseFloat(item.value),
]);
//
const dataCount = seriesData.length;
let sampling = 'false';
let showSymbol = true;
let animation = true;
let dataZoomStart = 0;
if (dataCount > 2000) {
sampling = 'lttb';
showSymbol = false;
animation = false;
dataZoomStart = 90; // 10%
} else if (dataCount > 500) {
sampling = 'average';
showSymbol = false;
animation = false;
dataZoomStart = 80;
} else {
sampling = 'false';
showSymbol = true;
animation = true;
dataZoomStart = 0;
}
renderEcharts({
animation,
tooltip: {
//
trigger: 'axis',
formatter(params) {
const item = params[0];
const date = new Date(item.value[0]);
const timeStr = `${date.getFullYear()}-${(date.getMonth() + 1)
.toString()
.padStart(
2,
'0',
)}-${date.getDate().toString().padStart(2, '0')} ${date
.getHours()
.toString()
.padStart(
2,
'0',
)}:${date.getMinutes().toString().padStart(2, '0')}:${date
.getSeconds()
.toString()
.padStart(2, '0')}`;
return `时间: ${timeStr}<br/>值: ${item.value[1]}`;
},
},
grid: {
top: 30,
@ -70,7 +75,11 @@ const renderChart = () => {
name: '时间',
axisLabel: {
rotate: 30,
formatter(value: number) {
hideOverlap: true, //
interval: 'auto', //
align: 'center', //
margin: 32,
formatter(value) {
const date = new Date(value);
return `${date.getFullYear()}-${(date.getMonth() + 1)
.toString()
@ -100,7 +109,7 @@ const renderChart = () => {
show: true, //
xAxisIndex: [0], // x
top: '90%', //
start: 0, // 0%
start: dataZoomStart, // 0%
end: 100, // 100%
left: '17%',
width: '65%',
@ -108,16 +117,18 @@ const renderChart = () => {
{
type: 'inside', //
xAxisIndex: [0], // x
start: 0,
start: dataZoomStart,
end: 100,
},
],
series: [
{
name: '数据系列',
name: '',
type: 'line',
data: seriesData,
// 线
sampling,
showSymbol,
lineStyle: {
color: '#5470C6',
},

View File

@ -1,4 +1,6 @@
<script setup lang="ts">
import type { VxeGridProps } from '#/adapter/vxe-table';
import { computed, onMounted, onUnmounted, ref, watch } from 'vue';
import { EllipsisText } from '@vben/common-ui';
@ -17,11 +19,11 @@ import {
Row,
Select,
Space,
Table,
Tabs,
} from 'ant-design-vue';
import dayjs from 'dayjs';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { deviceLogList } from '#/api/device/device';
import { getWebSocket } from '#/utils/websocket';
@ -33,8 +35,41 @@ interface Props {
deviceInfo: any;
}
interface LogType {
id: number;
name: string;
role: string;
sex: string;
}
const props = defineProps<Props>();
const gridOptions: VxeGridProps<LogType> = {
round: false,
border: false,
toolbarConfig: {
enabled: false,
},
columns: [
{ type: 'seq', width: 70 },
{ field: 'timestamp', title: '时间' },
{ field: 'value', title: '值' },
],
data: [],
minHeight: 300,
maxHeight: 500,
pagerConfig: {
enabled: false,
},
scrollY: {
enabled: true,
gt: 0,
},
showOverflow: true,
};
const [Grid, gridApi] = useVbenVxeGrid({ gridOptions });
const loading = ref(false);
const selectedGroup = ref('all');
const selectedTypes = ref(['R', 'RW']);
@ -157,18 +192,6 @@ const onCalendarChange = (val: any) => {
dates.value = val;
};
const logColumns = [
{ title: '时间', dataIndex: 'timestamp', key: 'timestamp', width: 200 },
{
title: '值',
dataIndex: 'value',
key: 'value',
width: 150,
align: 'center',
},
// { title: '', key: 'action', width: 100 },
];
const openPropertyLog = (property: any) => {
currentProperty.value = property;
logTabActiveKey.value = 'list';
@ -190,6 +213,8 @@ const loadPropertyLog = async (field, startTime, endTime) => {
fields: field,
startTime,
endTime,
current: 1,
size: 9999,
orderType: 2,
metaDataType: 'property',
});
@ -286,6 +311,11 @@ const closeLogModal = () => {
logData.value = [];
};
watch(logData, (data) => {
console.log('日志数据变化', data);
gridApi.setGridOptions({ data });
});
onMounted(() => {
initRuntime();
subscribeRealtimeData();
@ -405,23 +435,13 @@ watch(
</Space>
</div>
<Tabs v-model:active-key="logTabActiveKey" class="log-tabs">
<Tabs
v-model:active-key="logTabActiveKey"
class="log-tabs"
destroy-inactive-tab-pane
>
<Tabs.TabPane key="list" tab="列表">
<Table
:columns="logColumns"
:data-source="logData"
:loading="logLoading"
:pagination="false"
size="small"
row-key="timestamp"
:scroll="{ y: 500 }"
>
<template #bodyCell="{ column }">
<template v-if="column.key === 'action'">
<Button type="link" size="small">查看</Button>
</template>
</template>
</Table>
<Grid class="log-grid" />
</Tabs.TabPane>
<Tabs.TabPane
@ -537,6 +557,10 @@ watch(
}
}
.log-grid :deep(.vxe-grid) {
padding: 0 !important;
}
:deep(.ant-card-body) {
padding: 12px;
}

View File

@ -576,7 +576,7 @@ onMounted(() => {
<div class="parameter-box">
<div class="parameter-header">参数:</div>
<div class="parameter-content">
<pre>{{ parameterContent || '点击触发后显示参数' }}</pre>
<pre>{{ parameterContent || '点击执行后显示参数' }}</pre>
</div>
</div>
</div>

View File

@ -752,7 +752,7 @@ onMounted(() => {
<div class="parameter-box">
<div class="parameter-header">参数:</div>
<div class="parameter-content">
<pre>{{ parameterContent || '点击执行后显示执行参数' }}</pre>
<pre>{{ parameterContent || '点击执行后显示参数' }}</pre>
</div>
</div>
</div>

View File

@ -812,7 +812,7 @@ onMounted(() => {
<div class="parameter-box">
<div class="parameter-header">参数:</div>
<div class="parameter-content">
<pre>{{ parameterContent || '点击执行后显示执行参数' }}</pre>
<pre>{{ parameterContent || '点击执行后显示参数' }}</pre>
</div>
</div>
</div>