尝试完善图表更新 bug 图表无法显示

This commit is contained in:
zwb 2024-07-30 18:15:24 +08:00
parent 30ffdd1782
commit 0d801118cc
3 changed files with 76 additions and 148 deletions

View File

@ -52,48 +52,32 @@ public class ServerController(
[FromQuery] List<string?>? dataTypes = null, [FromQuery] int? startIndex = 0) [FromQuery] List<string?>? dataTypes = null, [FromQuery] int? startIndex = 0)
{ {
var userId = HttpContext.User.Claims.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier)!.Value; var userId = HttpContext.User.Claims.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier)!.Value;
//创建一个缓存
Dictionary<string, ServerMonitoringData?> temp = new();
//创建一个时间缓存
//创建查询 流式 //创建查询 流式
var query = dbContext.ServerMonitoringData var query = dbContext.ServerMonitoringData
.AsNoTracking() // 不追踪实体状态 .AsNoTracking() // 不追踪实体状态
.Where(x => x.ServerId == serverId) // 筛选服务器ID .Where(x => x.ServerId == serverId) // 筛选服务器ID
.Where(x => dataTypes == null || dataTypes.Contains(x.DataType)) // 如果dataTypes不为空则进一步筛选 .Where(x => dataTypes == null || dataTypes.Contains(x.DataType)) // 如果dataTypes不为空则进一步筛选
.OrderBy(x => x.Time)
.Skip(Math.Max(startIndex ?? 0, 0)) // 跳过指定数量的记录 .Skip(Math.Max(startIndex ?? 0, 0)) // 跳过指定数量的记录
.Take(1000) .Take(1000)
.AsAsyncEnumerable(); // 启用流式查询 .AsAsyncEnumerable(); // 启用流式查询
Dictionary<string, List<List<object>>> temp = new();
await foreach (var data in query) await foreach (var data in query)
{ {
//计算两者时间差 按秒分割 if (!temp.TryGetValue(data.DataType, out var value))
if (!temp.TryGetValue(data.DataType, out var tempData)) tempData = data;
temp[data.DataType] = data;
var time = tempData?.Time;
if (time == null || data.Time == null) continue;
var timeDifferenceInSeconds = (data.Time - time)?.TotalSeconds;
if (timeDifferenceInSeconds <= 0) continue;
var steps = (int)timeDifferenceInSeconds!;
var value = double.Parse(data.Data ?? "0");
var lastValue = double.Parse(tempData?.Data ?? "0");
//计算中间值 贝塞尔曲线
var controlPoint = (value + lastValue) / 2;
List<List<object>> values = [];
for (var i = 0; i < steps; i++)
{ {
var t = (double)i / (steps - 1); value = new List<List<object>>();
var bezierValue = (1 - t) * (1 - t) * value + 2 * (1 - t) * t * controlPoint + t * t * lastValue; temp[data.DataType] = value;
var bezierTime = time.Value.AddSeconds(i);
var utcTime = bezierTime.ToUniversalTime();
var unixTimestamp = ((DateTimeOffset)utcTime).ToUnixTimeMilliseconds();
List<object> innerList =
[
unixTimestamp,
((int)bezierValue).ToString(CultureInfo.InvariantCulture)
];
values.Add(innerList);
} }
await hubContext.Clients.Groups(userId).SendAsync("ReceiveDataHistory", data.DataType, values); var unixTimestamp = ((DateTimeOffset)data.Time?.ToUniversalTime()!).ToUnixTimeMilliseconds();
value.Add([unixTimestamp!, data.Data!]);
}
foreach (var entry in temp)
{
await hubContext.Clients.Groups(userId).SendAsync("ReceiveDataHistory", entry.Key, entry.Value, true);
} }
return Ok(); return Ok();

View File

@ -95,60 +95,46 @@ const items = [
<template> <template>
<div :id="`card_${id}`" ref="cardRef" class="card-layout "> <div :id="`card_${id}`" ref="cardRef" class="card-layout ">
<div :id="'item' + id" class="card-title vue-draggable-handle"> <div :id="'item' + id" class="card-title vue-draggable-handle">
<div></div>
<h3>{{ title ?? "默认标题" }}</h3> <h3>{{ title ?? "默认标题" }}</h3>
</div> </div>
<div class="card-content">
<component :is="charts.find(x => x.id === chart)?.component" v-if="targetIsVisible&&!isSwitched" <component :is="charts.find(x => x.id === chart)?.component" v-if="targetIsVisible&&!isSwitched"
:rangeNum="rangeNum" :rangeNum="rangeNum"
:valueIds="valueIds" :valueIds="valueIds"
:valueNames="valueNames"/> :valueNames="valueNames"/>
</div> </div>
</div>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "base"; @import "base";
.card-layout { .card-layout {
width: 100%;
height: 100%; height: 100%;
width: 100%;
background: $light-bg-color; background: $light-bg-color;
border-radius: $radius; display: grid;
box-shadow: 0 10px 30px 0 rgba(17, 38, 146, 0.05); grid-template-rows: 40px 1fr;
padding: $padding*1.5;
position: relative;
display: flex;
flex-direction: column;
gap: $gap;
will-change: scroll-position, contents;
border: $border;
.dark-mode & { .dark-mode & {
background: $dark-bg-color; background: $dark-bg-color;
} }
} }
.card-title { .card-title {
display: flex; display: flex;
justify-content: space-between;
align-items: center; align-items: center;
gap: $gap; padding: 0 20px;
color: $light-text-color;
position: absolute;
z-index: 10;
&,
h3:hover {
cursor: move;
}
.dark-mode & { h3 {
color: $dark-text-color; font-size: 14px;
line-height: 24px;
text-align: left;
color: #333;
} }
div {
width: 6px;
height: 20px;
border-radius: $radius;
background: $primary-color;
} }
.card-content{
padding: 0 20px 20px;
} }
</style> </style>

View File

@ -4,7 +4,6 @@ import {useMainLayoutStore} from "~/strores/UseMainLayoutStore";
import type {PropType} from "vue"; import type {PropType} from "vue";
import {ref} from 'vue'; import {ref} from 'vue';
import dayjs from "dayjs";
import VChart from 'vue-echarts'; import VChart from 'vue-echarts';
import _ from "lodash"; import _ from "lodash";
import {useSessionSignalRStore} from "~/strores/HubStore"; import {useSessionSignalRStore} from "~/strores/HubStore";
@ -12,9 +11,6 @@ import {useSessionSignalRStore} from "~/strores/HubStore";
const dataStore = useDataStore() const dataStore = useDataStore()
const hubStore = useSessionSignalRStore() const hubStore = useSessionSignalRStore()
const mainLayoutStore = useMainLayoutStore() const mainLayoutStore = useMainLayoutStore()
type ArbitraryKeyValuePairs = {
[key: string]: (number | string)[];
};
const chartRef = ref<any>(null) const chartRef = ref<any>(null)
const isLoading = ref<boolean>(true) const isLoading = ref<boolean>(true)
const props = defineProps({ const props = defineProps({
@ -46,15 +42,16 @@ const option = computed(() => {
} }
}, },
grid: { grid: {
left: '2%', left: '0',
right: '50', right: '50',
bottom: '10%', bottom: '38',
top: '15',
containLabel: true containLabel: true
}, },
xAxis: [ xAxis: [
{ {
type: 'time', type: 'time',
// boundaryGap: false boundaryGap: false,
axisLabel: { axisLabel: {
showMinLabel: true, showMinLabel: true,
showMaxLabel: true, showMaxLabel: true,
@ -66,15 +63,6 @@ const option = computed(() => {
type: 'value', type: 'value',
} }
], ],
toolbox: {
feature: {
dataZoom: {
yAxisIndex: 'none'
},
saveAsImage: {}
}
},
dataZoom: [ dataZoom: [
{ {
type: 'inside', type: 'inside',
@ -122,18 +110,33 @@ let interval: NodeJS.Timeout;
onUnmounted(() => { onUnmounted(() => {
clearInterval(interval); clearInterval(interval);
}) })
let maxValue = 100;
onMounted(() => { onMounted(() => {
setTimeout(()=>{ if (!chartRef.value) {
hubStore.connection?.on("ReceiveDataHistory",(type:string,values:string[][])=>{ console.error('ECharts instance is not initialized');
isLoading.value = true; return;
}
hubStore.connection?.on("ReceiveDataHistory", (type: string, values: [number,string][], done: boolean) => {
chartRef.value.appendData({ chartRef.value.appendData({
seriesIndex: props.valueIds?.indexOf(type), seriesIndex: props.valueIds?.indexOf(type),
data: values data: values
}) })
if (done) {
//y140%
// const currentOption = chartRef.value.getOption();
// const maxData = Math.max(...values.map(item =>item[1]));
// if (maxData > maxValue) {
// maxValue = maxData
// currentOption.yAxis[0].max = maxData * 1.4;
// chartRef.value.setOption(currentOption);
// }
}
isLoading.value = false; isLoading.value = false;
}) })
},1000)
setTimeout(() => { setTimeout(() => {
$fetch('/Api/Server/GetServerHistoryDate', { $fetch('/Api/Server/GetServerHistoryDate', {
method: 'GET', method: 'GET',
@ -147,53 +150,8 @@ onMounted(() => {
}, },
baseURL: useRuntimeConfig().public.baseUrl, baseURL: useRuntimeConfig().public.baseUrl,
}) })
setTimeout(()=>{ }, 2000)
interval = setInterval(() => {
const data = dataStore.data
const time=Date.now()
props.valueIds?.forEach((id, index) => {
const newData = data[id] ?? 0
chartRef.value.appendData({
seriesIndex: index,
data: [[time,newData]]
})
const currentOption = chartRef.value.getOption();
chartRef.value.setOption(currentOption)
})},1000)
})
},1000)
// let history = dataStore.dataHistory
// setTimeout(() => {
// isLoading.value = false;
// }, 5000)
// nextTick(() => {
// props.valueIds?.forEach((id, index) => {
// chartRet.value.appendData({
// seriesIndex: index,
// data: history.data[id]
// })
// })
// const currentOption = chartRet.value.getOption();
// currentOption.xAxis[0].data = history.times
// chartRet.value.setOption(currentOption)
// isLoading.value = false
// interval = setInterval(() => {
// const data = dataStore.data
// props.valueIds?.forEach((id, index) => {
// const newData = data[id] ?? 0
// if (!values.value[id]) {
// values.value[id] = []
// }
// chartRet.value.appendData({
// seriesIndex: index,
// data: [newData]
// })
// })
// const currentOption = chartRet.value.getOption();
// currentOption.xAxis[0].data.push(dayjs().format('MM-DD HH:mm:ss'))
// chartRet.value.setOption(currentOption)
// }, 4000 + 1000 * Math.random())
// })
}) })
// let endIndex = dataStore.dataHistory.times ? dataStore.dataHistory.times.length : 0 // let endIndex = dataStore.dataHistory.times ? dataStore.dataHistory.times.length : 0
// let startTemp = 0; // let startTemp = 0;