feat: 适配设备运行状态实时图表与日志列表大数据量展示
- 实时图表数据排序方式由升序改为降序,提升最新数据的展示优先级 - 增加动态优化逻辑,根据数据量自动调整采样、动画和符号显示策略 - 优化时间轴标签显示,避免重叠并提升可读性 - 日志列表从 Ant Design Table 迁移至 VxeTable,提升大数据量下的渲染性能 - 调整日志接口分页参数,确保一次性加载最多9999条数据 - 监听日志数据变化并动态更新表格内容 - 优化 Tab 组件行为,销毁非活跃面板以减少内存占用 - 统一多个模拟组件中的提示文本表述
This commit is contained in:
parent
b012582f68
commit
019029f3ce
|
@ -28,7 +28,7 @@ const renderChart = () => {
|
||||||
if (props.data.length === 0) return;
|
if (props.data.length === 0) return;
|
||||||
|
|
||||||
// 按时间升序排序
|
// 按时间升序排序
|
||||||
const sortedData = [...props.data].sort((a, b) => a.timestamp - b.timestamp);
|
const sortedData = [...props.data].reverse();
|
||||||
|
|
||||||
// ECharts时间序列数据格式:[时间, 数值]
|
// ECharts时间序列数据格式:[时间, 数值]
|
||||||
const seriesData = sortedData.map((item) => [
|
const seriesData = sortedData.map((item) => [
|
||||||
|
@ -36,30 +36,35 @@ const renderChart = () => {
|
||||||
Number.parseFloat(item.value),
|
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({
|
renderEcharts({
|
||||||
|
animation,
|
||||||
tooltip: {
|
tooltip: {
|
||||||
// 提示框组件
|
// 提示框组件
|
||||||
trigger: 'axis',
|
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: {
|
grid: {
|
||||||
top: 30,
|
top: 30,
|
||||||
|
@ -70,7 +75,11 @@ const renderChart = () => {
|
||||||
name: '时间',
|
name: '时间',
|
||||||
axisLabel: {
|
axisLabel: {
|
||||||
rotate: 30,
|
rotate: 30,
|
||||||
formatter(value: number) {
|
hideOverlap: true, // 自动隐藏重叠标签
|
||||||
|
interval: 'auto', // 自动间隔显示
|
||||||
|
align: 'center', // 让多行内容居中
|
||||||
|
margin: 32,
|
||||||
|
formatter(value) {
|
||||||
const date = new Date(value);
|
const date = new Date(value);
|
||||||
return `${date.getFullYear()}-${(date.getMonth() + 1)
|
return `${date.getFullYear()}-${(date.getMonth() + 1)
|
||||||
.toString()
|
.toString()
|
||||||
|
@ -100,7 +109,7 @@ const renderChart = () => {
|
||||||
show: true, // 显示组件
|
show: true, // 显示组件
|
||||||
xAxisIndex: [0], // 控制第一个x轴
|
xAxisIndex: [0], // 控制第一个x轴
|
||||||
top: '90%', // 放置在底部
|
top: '90%', // 放置在底部
|
||||||
start: 0, // 初始起始范围比例为0%
|
start: dataZoomStart, // 初始起始范围比例为0%
|
||||||
end: 100, // 初始结束范围比例为100%
|
end: 100, // 初始结束范围比例为100%
|
||||||
left: '17%',
|
left: '17%',
|
||||||
width: '65%',
|
width: '65%',
|
||||||
|
@ -108,16 +117,18 @@ const renderChart = () => {
|
||||||
{
|
{
|
||||||
type: 'inside', // 内置型
|
type: 'inside', // 内置型
|
||||||
xAxisIndex: [0], // 控制第一个x轴
|
xAxisIndex: [0], // 控制第一个x轴
|
||||||
start: 0,
|
start: dataZoomStart,
|
||||||
end: 100,
|
end: 100,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
series: [
|
series: [
|
||||||
{
|
{
|
||||||
name: '数据系列',
|
name: '值',
|
||||||
type: 'line',
|
type: 'line',
|
||||||
data: seriesData,
|
data: seriesData,
|
||||||
// 可选:配置线条样式
|
// 可选:配置线条样式
|
||||||
|
sampling,
|
||||||
|
showSymbol,
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
color: '#5470C6',
|
color: '#5470C6',
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import type { VxeGridProps } from '#/adapter/vxe-table';
|
||||||
|
|
||||||
import { computed, onMounted, onUnmounted, ref, watch } from 'vue';
|
import { computed, onMounted, onUnmounted, ref, watch } from 'vue';
|
||||||
|
|
||||||
import { EllipsisText } from '@vben/common-ui';
|
import { EllipsisText } from '@vben/common-ui';
|
||||||
|
@ -17,11 +19,11 @@ import {
|
||||||
Row,
|
Row,
|
||||||
Select,
|
Select,
|
||||||
Space,
|
Space,
|
||||||
Table,
|
|
||||||
Tabs,
|
Tabs,
|
||||||
} from 'ant-design-vue';
|
} from 'ant-design-vue';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
|
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
import { deviceLogList } from '#/api/device/device';
|
import { deviceLogList } from '#/api/device/device';
|
||||||
import { getWebSocket } from '#/utils/websocket';
|
import { getWebSocket } from '#/utils/websocket';
|
||||||
|
|
||||||
|
@ -33,8 +35,41 @@ interface Props {
|
||||||
deviceInfo: any;
|
deviceInfo: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface LogType {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
role: string;
|
||||||
|
sex: string;
|
||||||
|
}
|
||||||
|
|
||||||
const props = defineProps<Props>();
|
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 loading = ref(false);
|
||||||
const selectedGroup = ref('all');
|
const selectedGroup = ref('all');
|
||||||
const selectedTypes = ref(['R', 'RW']);
|
const selectedTypes = ref(['R', 'RW']);
|
||||||
|
@ -157,18 +192,6 @@ const onCalendarChange = (val: any) => {
|
||||||
dates.value = val;
|
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) => {
|
const openPropertyLog = (property: any) => {
|
||||||
currentProperty.value = property;
|
currentProperty.value = property;
|
||||||
logTabActiveKey.value = 'list';
|
logTabActiveKey.value = 'list';
|
||||||
|
@ -190,6 +213,8 @@ const loadPropertyLog = async (field, startTime, endTime) => {
|
||||||
fields: field,
|
fields: field,
|
||||||
startTime,
|
startTime,
|
||||||
endTime,
|
endTime,
|
||||||
|
current: 1,
|
||||||
|
size: 9999,
|
||||||
orderType: 2,
|
orderType: 2,
|
||||||
metaDataType: 'property',
|
metaDataType: 'property',
|
||||||
});
|
});
|
||||||
|
@ -286,6 +311,11 @@ const closeLogModal = () => {
|
||||||
logData.value = [];
|
logData.value = [];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
watch(logData, (data) => {
|
||||||
|
console.log('日志数据变化', data);
|
||||||
|
gridApi.setGridOptions({ data });
|
||||||
|
});
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
initRuntime();
|
initRuntime();
|
||||||
subscribeRealtimeData();
|
subscribeRealtimeData();
|
||||||
|
@ -405,23 +435,13 @@ watch(
|
||||||
</Space>
|
</Space>
|
||||||
</div>
|
</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="列表">
|
<Tabs.TabPane key="list" tab="列表">
|
||||||
<Table
|
<Grid class="log-grid" />
|
||||||
: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>
|
|
||||||
</Tabs.TabPane>
|
</Tabs.TabPane>
|
||||||
|
|
||||||
<Tabs.TabPane
|
<Tabs.TabPane
|
||||||
|
@ -537,6 +557,10 @@ watch(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.log-grid :deep(.vxe-grid) {
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
:deep(.ant-card-body) {
|
:deep(.ant-card-body) {
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -576,7 +576,7 @@ onMounted(() => {
|
||||||
<div class="parameter-box">
|
<div class="parameter-box">
|
||||||
<div class="parameter-header">参数:</div>
|
<div class="parameter-header">参数:</div>
|
||||||
<div class="parameter-content">
|
<div class="parameter-content">
|
||||||
<pre>{{ parameterContent || '点击触发后显示参数' }}</pre>
|
<pre>{{ parameterContent || '点击执行后显示参数' }}</pre>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -752,7 +752,7 @@ onMounted(() => {
|
||||||
<div class="parameter-box">
|
<div class="parameter-box">
|
||||||
<div class="parameter-header">参数:</div>
|
<div class="parameter-header">参数:</div>
|
||||||
<div class="parameter-content">
|
<div class="parameter-content">
|
||||||
<pre>{{ parameterContent || '点击执行后显示执行参数' }}</pre>
|
<pre>{{ parameterContent || '点击执行后显示参数' }}</pre>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -812,7 +812,7 @@ onMounted(() => {
|
||||||
<div class="parameter-box">
|
<div class="parameter-box">
|
||||||
<div class="parameter-header">参数:</div>
|
<div class="parameter-header">参数:</div>
|
||||||
<div class="parameter-content">
|
<div class="parameter-content">
|
||||||
<pre>{{ parameterContent || '点击执行后显示执行参数' }}</pre>
|
<pre>{{ parameterContent || '点击执行后显示参数' }}</pre>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in New Issue