🚧 Optimize

This commit is contained in:
2025-09-30 00:28:15 +08:00
parent 4d6a4ff79f
commit cf8bf688bf
36 changed files with 1520 additions and 3896 deletions

View File

@@ -1,18 +1,19 @@
<script setup lang="ts">
import { ref, onMounted, onUnmounted, nextTick, computed, watch } from 'vue';
import { SystemService } from '@/../bindings/voidraft/internal/services';
import type { MemoryStats } from '@/../bindings/voidraft/internal/services';
import { useI18n } from 'vue-i18n';
import { useThemeStore } from '@/stores/themeStore';
import { SystemThemeType } from '@/../bindings/voidraft/internal/models/models';
import {computed, nextTick, onMounted, onUnmounted, ref, watch} from 'vue';
import type {MemoryStats} from '@/../bindings/voidraft/internal/services';
import {SystemService} from '@/../bindings/voidraft/internal/services';
import {useI18n} from 'vue-i18n';
import {useThemeStore} from '@/stores/themeStore';
import {SystemThemeType} from '@/../bindings/voidraft/internal/models/models';
const { t } = useI18n();
const {t} = useI18n();
const themeStore = useThemeStore();
// 响应式状态
const memoryStats = ref<MemoryStats | null>(null);
const formattedMemory = ref('');
const isLoading = ref(true);
const canvasRef = ref<HTMLCanvasElement | null>(null);
let intervalId: ReturnType<typeof setInterval> | null = null;
// 存储历史数据点 (最近60个数据点)
const historyData = ref<number[]>([]);
@@ -21,209 +22,188 @@ const maxDataPoints = 60;
// 动态最大内存值MB初始为200MB会根据实际使用动态调整
const maxMemoryMB = ref(200);
// 使用themeStore获取当前主题
let intervalId: ReturnType<typeof setInterval> | null = null;
// 使用 computed 获取当前主题状态
const isDarkTheme = computed(() => {
const theme = themeStore.currentTheme;
if (theme === SystemThemeType.SystemThemeAuto) {
return window.matchMedia('(prefers-color-scheme: dark)').matches;
}
return theme === SystemThemeType.SystemThemeDark;
const {currentTheme} = themeStore;
return currentTheme === SystemThemeType.SystemThemeDark;
});
// 监听主题变化,重新绘制图表
watch(() => themeStore.currentTheme, () => {
nextTick(() => drawChart());
nextTick(drawChart);
});
// 静默错误处理包装器
const withSilentErrorHandling = async <T>(
operation: () => Promise<T>,
fallback?: T
): Promise<T | undefined> => {
try {
return await operation();
} catch (_error) {
// 静默处理错误,不输出到控制台
return fallback;
}
// 格式化内存显示函数
const formatMemorySize = (heapMB: number): string => {
if (heapMB < 1) return `${(heapMB * 1024).toFixed(0)}K`;
if (heapMB < 100) return `${heapMB.toFixed(1)}M`;
return `${heapMB.toFixed(0)}M`;
};
// 获取内存统计信息
const fetchMemoryStats = async () => {
const stats = await withSilentErrorHandling(() => SystemService.GetMemoryStats());
if (!stats) {
isLoading.value = false;
return;
}
const fetchMemoryStats = async (): Promise<void> => {
try {
const stats = await SystemService.GetMemoryStats();
memoryStats.value = stats;
// 格式化内存显示 - 主要显示堆内存使用量
const heapMB = (stats.heapInUse / 1024 / 1024);
if (heapMB < 1) {
formattedMemory.value = `${(heapMB * 1024).toFixed(0)}K`;
} else if (heapMB < 100) {
formattedMemory.value = `${heapMB.toFixed(1)}M`;
} else {
formattedMemory.value = `${heapMB.toFixed(0)}M`;
}
const heapMB = stats.heapInUse / (1024 * 1024);
formattedMemory.value = formatMemorySize(heapMB);
// 自动调整最大内存值,确保图表能够显示更大范围
if (heapMB > maxMemoryMB.value * 0.8) {
// 如果内存使用超过当前最大值的80%则将最大值调整为当前使用值的2倍
maxMemoryMB.value = Math.ceil(heapMB * 2);
}
// 添加新数据点到历史记录 - 使用动态最大值计算百分比
const memoryUsagePercent = Math.min((heapMB / maxMemoryMB.value) * 100, 100);
historyData.value.push(memoryUsagePercent);
// 保持最大数据点数量
if (historyData.value.length > maxDataPoints) {
historyData.value.shift();
}
historyData.value = [...historyData.value, memoryUsagePercent].slice(-maxDataPoints);
// 更新图表
drawChart();
} catch {
// 静默处理错误
} finally {
isLoading.value = false;
}
};
// 绘制实时曲线图 - 简化版
const drawChart = () => {
if (!canvasRef.value || historyData.value.length === 0) return;
const canvas = canvasRef.value;
const ctx = canvas.getContext('2d');
if (!ctx) return;
// 设置canvas尺寸
const rect = canvas.getBoundingClientRect();
canvas.width = rect.width * window.devicePixelRatio;
canvas.height = rect.height * window.devicePixelRatio;
ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
const width = rect.width;
const height = rect.height;
// 清除画布
ctx.clearRect(0, 0, width, height);
// 根据主题选择合适的颜色 - 更柔和的颜色
const gridColor = isDarkTheme.value ? 'rgba(255, 255, 255, 0.03)' : 'rgba(0, 0, 0, 0.07)';
const lineColor = isDarkTheme.value ? 'rgba(74, 158, 255, 0.6)' : 'rgba(37, 99, 235, 0.6)';
const fillColor = isDarkTheme.value ? 'rgba(74, 158, 255, 0.05)' : 'rgba(37, 99, 235, 0.05)';
const pointColor = isDarkTheme.value ? 'rgba(74, 158, 255, 0.8)' : 'rgba(37, 99, 235, 0.8)';
// 绘制背景网格 - 更加柔和
// 获取主题相关颜色配置
const getThemeColors = () => {
const isDark = isDarkTheme.value;
return {
grid: isDark ? 'rgba(255, 255, 255, 0.03)' : 'rgba(0, 0, 0, 0.07)',
line: isDark ? 'rgba(74, 158, 255, 0.6)' : 'rgba(37, 99, 235, 0.6)',
fill: isDark ? 'rgba(74, 158, 255, 0.05)' : 'rgba(37, 99, 235, 0.05)',
point: isDark ? 'rgba(74, 158, 255, 0.8)' : 'rgba(37, 99, 235, 0.8)'
};
};
// 绘制网格背景
const drawGrid = (ctx: CanvasRenderingContext2D, width: number, height: number, colors: ReturnType<typeof getThemeColors>): void => {
ctx.strokeStyle = colors.grid;
ctx.lineWidth = 0.5;
// 水平网格线
for (let i = 0; i <= 4; i++) {
const y = (height / 4) * i;
ctx.strokeStyle = gridColor;
ctx.lineWidth = 0.5;
ctx.beginPath();
ctx.moveTo(0, y);
ctx.lineTo(width, y);
ctx.stroke();
}
// 垂直网格线
for (let i = 0; i <= 6; i++) {
const x = (width / 6) * i;
ctx.strokeStyle = gridColor;
ctx.lineWidth = 0.5;
ctx.beginPath();
ctx.moveTo(x, 0);
ctx.lineTo(x, height);
ctx.stroke();
}
};
// 绘制平滑曲线路径
const drawSmoothPath = (
ctx: CanvasRenderingContext2D,
data: number[],
startX: number,
stepX: number,
height: number,
fillArea = false
): void => {
if (data.length < 2) return;
const firstY = height - (data[0] / 100) * height;
ctx.beginPath();
if (fillArea) ctx.moveTo(startX, height);
ctx.moveTo(startX, firstY);
// 使用二次贝塞尔曲线绘制平滑路径
for (let i = 1; i < data.length; i++) {
const x = startX + i * stepX;
const y = height - (data[i] / 100) * height;
if (i === 1) {
ctx.lineTo(x, y);
} else {
const prevX = startX + (i - 1) * stepX;
const prevY = height - (data[i - 1] / 100) * height;
const cpX = (prevX + x) / 2;
const cpY = (prevY + y) / 2;
ctx.quadraticCurveTo(cpX, cpY, x, y);
}
}
if (fillArea) {
const lastX = startX + (data.length - 1) * stepX;
ctx.lineTo(lastX, height);
ctx.closePath();
}
};
// 绘制实时曲线图
const drawChart = (): void => {
const canvas = canvasRef.value;
if (!canvas || historyData.value.length === 0) return;
const ctx = canvas.getContext('2d');
if (!ctx) return;
// 设置canvas尺寸
const rect = canvas.getBoundingClientRect();
const dpr = window.devicePixelRatio;
canvas.width = rect.width * dpr;
canvas.height = rect.height * dpr;
ctx.scale(dpr, dpr);
const {width, height} = rect;
// 清除画布
ctx.clearRect(0, 0, width, height);
// 获取主题颜色
const colors = getThemeColors();
// 绘制背景网格
drawGrid(ctx, width, height, colors);
if (historyData.value.length < 2) return;
// 计算数据点位置
const dataLength = historyData.value.length;
const stepX = width / (maxDataPoints - 1);
const startX = width - (dataLength - 1) * stepX;
// 绘制填充区域 - 更柔和的填充
ctx.beginPath();
ctx.moveTo(startX, height);
// 移动到第一个数据点
const firstY = height - (historyData.value[0] / 100) * height;
ctx.lineTo(startX, firstY);
// 绘制数据点路径 - 使用曲线连接点,确保连续性
for (let i = 1; i < dataLength; i++) {
const x = startX + i * stepX;
const y = height - (historyData.value[i] / 100) * height;
// 使用贝塞尔曲线平滑连接
if (i < dataLength - 1) {
const nextX = startX + (i + 1) * stepX;
const nextY = height - (historyData.value[i + 1] / 100) * height;
const cpX1 = x - stepX / 4;
const cpY1 = y;
const cpX2 = x + stepX / 4;
const cpY2 = nextY;
// 使用三次贝塞尔曲线平滑连接点
ctx.bezierCurveTo(cpX1, cpY1, cpX2, cpY2, nextX, nextY);
i++; // 跳过下一个点,因为已经在曲线中处理了
} else {
ctx.lineTo(x, y);
}
}
// 完成填充路径
const lastX = startX + (dataLength - 1) * stepX;
ctx.lineTo(lastX, height);
ctx.closePath();
ctx.fillStyle = fillColor;
// 绘制填充区域
drawSmoothPath(ctx, historyData.value, startX, stepX, height, true);
ctx.fillStyle = colors.fill;
ctx.fill();
// 绘制主曲线 - 平滑连续的曲线
ctx.beginPath();
ctx.moveTo(startX, firstY);
// 重新绘制曲线路径,但这次只绘制线条
for (let i = 1; i < dataLength; i++) {
const x = startX + i * stepX;
const y = height - (historyData.value[i] / 100) * height;
// 使用贝塞尔曲线平滑连接
if (i < dataLength - 1) {
const nextX = startX + (i + 1) * stepX;
const nextY = height - (historyData.value[i + 1] / 100) * height;
const cpX1 = x - stepX / 4;
const cpY1 = y;
const cpX2 = x + stepX / 4;
const cpY2 = nextY;
// 使用三次贝塞尔曲线平滑连接点
ctx.bezierCurveTo(cpX1, cpY1, cpX2, cpY2, nextX, nextY);
i++; // 跳过下一个点,因为已经在曲线中处理了
} else {
ctx.lineTo(x, y);
}
}
ctx.strokeStyle = lineColor;
// 绘制主曲线
drawSmoothPath(ctx, historyData.value, startX, stepX, height);
ctx.strokeStyle = colors.line;
ctx.lineWidth = 1.5;
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
ctx.stroke();
// 绘制当前值的高亮点
const lastX = startX + (dataLength - 1) * stepX;
const lastY = height - (historyData.value[dataLength - 1] / 100) * height;
// 外圈
ctx.fillStyle = pointColor;
ctx.fillStyle = colors.point;
ctx.globalAlpha = 0.4;
ctx.beginPath();
ctx.arc(lastX, lastY, 3, 0, Math.PI * 2);
ctx.fill();
// 内圈
ctx.globalAlpha = 1;
ctx.beginPath();
@@ -232,72 +212,32 @@ const drawChart = () => {
};
// 手动触发GC
const triggerGC = async () => {
const success = await withSilentErrorHandling(() => SystemService.TriggerGC());
if (success) {
// 延迟一下再获取新的统计信息
setTimeout(fetchMemoryStats, 100);
const triggerGC = async (): Promise<void> => {
try {
await SystemService.TriggerGC();
} catch (error) {
console.error("Failed to trigger GC: ", error);
}
};
// 处理窗口大小变化
const handleResize = () => {
if (historyData.value.length > 0) {
nextTick(() => drawChart());
}
};
// 仅监听系统主题变化
const setupSystemThemeListener = () => {
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
const handleSystemThemeChange = () => {
// 仅当设置为auto时才响应系统主题变化
if (themeStore.currentTheme === SystemThemeType.SystemThemeAuto) {
nextTick(() => drawChart());
}
};
// 添加监听器
if (mediaQuery.addEventListener) {
mediaQuery.addEventListener('change', handleSystemThemeChange);
}
// 返回清理函数
return () => {
if (mediaQuery.removeEventListener) {
mediaQuery.removeEventListener('change', handleSystemThemeChange);
}
};
};
onMounted(() => {
fetchMemoryStats();
// 每1秒更新一次内存信息
// 每3秒更新一次内存信息
intervalId = setInterval(fetchMemoryStats, 3000);
// 监听窗口大小变化
window.addEventListener('resize', handleResize);
// 设置系统主题监听器仅用于auto模式
const cleanupThemeListener = setupSystemThemeListener();
// 在卸载时清理
onUnmounted(() => {
if (intervalId) {
clearInterval(intervalId);
}
window.removeEventListener('resize', handleResize);
cleanupThemeListener();
});
});
onUnmounted(() => {
intervalId && clearInterval(intervalId);
});
</script>
<template>
<div class="memory-monitor" @click="triggerGC" :title="`${t('monitor.memory')}: ${formattedMemory} | ${t('monitor.clickToClean')}`">
<div class="memory-monitor" @click="triggerGC"
:title="`${t('monitor.memory')}: ${formattedMemory} | ${t('monitor.clickToClean')}`">
<div class="monitor-info">
<div class="memory-label">
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/>
</svg>
<span>{{ t('monitor.memory') }}</span>
@@ -306,10 +246,10 @@ onMounted(() => {
<div class="memory-loading" v-else>--</div>
</div>
<div class="chart-area">
<canvas
ref="canvasRef"
class="memory-chart"
:class="{ 'loading': isLoading }"
<canvas
ref="canvasRef"
class="memory-chart"
:class="{ 'loading': isLoading }"
></canvas>
</div>
</div>
@@ -323,28 +263,28 @@ onMounted(() => {
cursor: pointer;
transition: all 0.2s ease;
width: 100%;
&:hover {
.monitor-info {
.memory-label {
color: var(--selection-text);
}
.memory-value {
color: var(--toolbar-text);
}
}
.chart-area .memory-chart {
opacity: 1;
}
}
.monitor-info {
display: flex;
align-items: center;
justify-content: space-between;
.memory-label {
display: flex;
align-items: center;
@@ -353,18 +293,18 @@ onMounted(() => {
font-size: 10px;
font-weight: 500;
transition: color 0.2s ease;
svg {
width: 10px;
height: 10px;
opacity: 0.8;
}
span {
user-select: none;
}
}
.memory-value, .memory-loading {
color: var(--toolbar-text-secondary);
font-family: 'JetBrains Mono', 'Courier New', monospace;
@@ -372,26 +312,26 @@ onMounted(() => {
font-weight: 600;
transition: color 0.2s ease;
}
.memory-loading {
opacity: 0.5;
animation: pulse 1.5s ease-in-out infinite;
}
}
.chart-area {
height: 48px;
position: relative;
overflow: hidden;
border-radius: 3px;
.memory-chart {
width: 100%;
height: 100%;
display: block;
opacity: 0.9;
transition: opacity 0.2s ease;
&.loading {
opacity: 0.3;
}
@@ -407,4 +347,4 @@ onMounted(() => {
opacity: 0.8;
}
}
</style>
</style>