♻️ Refactor code

This commit is contained in:
2025-06-02 13:34:54 +08:00
parent 44f7baad10
commit a516b8973e
53 changed files with 1513 additions and 1094 deletions

View File

@@ -0,0 +1,349 @@
<script setup lang="ts">
import { ref, onMounted, onUnmounted, nextTick } from 'vue';
import { SystemService } from '@/../bindings/voidraft/internal/services';
import type { MemoryStats } from '@/../bindings/voidraft/internal/services';
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个数据点每3秒一个点总共3分钟历史)
const historyData = ref<number[]>([]);
const maxDataPoints = 60;
// 获取内存统计信息
const fetchMemoryStats = async () => {
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 memoryUsagePercent = Math.min((stats.heapInUse / (100 * 1024 * 1024)) * 100, 100);
historyData.value.push(memoryUsagePercent);
// 保持最大数据点数量
if (historyData.value.length > maxDataPoints) {
historyData.value.shift();
}
// 更新图表
drawChart();
isLoading.value = false;
} catch (error) {
console.error('Failed to fetch memory stats:', error);
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);
// 绘制背景网格 - 朦胧的网格,从上到下逐渐清晰
for (let i = 0; i <= 6; i++) {
const y = (height / 6) * i;
const opacity = 0.01 + (i / 6) * 0.03; // 从上到下逐渐清晰
ctx.strokeStyle = `rgba(255, 255, 255, ${opacity})`;
ctx.lineWidth = 0.5;
ctx.beginPath();
ctx.moveTo(0, y);
ctx.lineTo(width, y);
ctx.stroke();
}
// 垂直网格线
for (let i = 0; i <= 8; i++) {
const x = (width / 8) * i;
ctx.strokeStyle = 'rgba(255, 255, 255, 0.02)';
ctx.lineWidth = 0.5;
ctx.beginPath();
ctx.moveTo(x, 0);
ctx.lineTo(x, height);
ctx.stroke();
}
if (historyData.value.length < 2) return;
// 计算数据点位置
const dataLength = historyData.value.length;
const stepX = width / (maxDataPoints - 1);
const startX = width - (dataLength - 1) * stepX;
// 绘制填充区域 - 从上朦胧到下清晰的渐变
const gradient = ctx.createLinearGradient(0, 0, 0, height);
gradient.addColorStop(0, 'rgba(74, 158, 255, 0.1)'); // 顶部很淡
gradient.addColorStop(0.3, 'rgba(74, 158, 255, 0.15)');
gradient.addColorStop(0.7, 'rgba(74, 158, 255, 0.25)');
gradient.addColorStop(1, 'rgba(74, 158, 255, 0.4)'); // 底部较浓
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 controlX = x + stepX / 2;
const controlY = y;
ctx.quadraticCurveTo(controlX, controlY, (x + nextX) / 2, (y + nextY) / 2);
} else {
ctx.lineTo(x, y);
}
}
// 完成填充路径
const lastX = startX + (dataLength - 1) * stepX;
ctx.lineTo(lastX, height);
ctx.closePath();
ctx.fillStyle = gradient;
ctx.fill();
// 绘制主曲线 - 从上到下逐渐清晰
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
// 分段绘制曲线,每段有不同的透明度
const segments = 10;
for (let seg = 0; seg < segments; seg++) {
const segmentStart = seg / segments;
const segmentEnd = (seg + 1) / segments;
const opacity = 0.3 + (seg / segments) * 0.7; // 从上0.3到下1.0
ctx.strokeStyle = `rgba(74, 158, 255, ${opacity})`;
ctx.lineWidth = 1.5 + (seg / segments) * 0.8; // 线条也从细到粗
ctx.beginPath();
let segmentStarted = false;
for (let i = 0; i < dataLength; i++) {
const x = startX + i * stepX;
const y = height - (historyData.value[i] / 100) * height;
const yPercent = 1 - (y / height);
if (yPercent >= segmentStart && yPercent <= segmentEnd) {
if (!segmentStarted) {
ctx.moveTo(x, y);
segmentStarted = true;
} else {
if (i < dataLength - 1) {
const nextX = startX + (i + 1) * stepX;
const nextY = height - (historyData.value[i + 1] / 100) * height;
const controlX = x + stepX / 2;
const controlY = y;
ctx.quadraticCurveTo(controlX, controlY, (x + nextX) / 2, (y + nextY) / 2);
} else {
ctx.lineTo(x, y);
}
}
}
}
if (segmentStarted) {
ctx.stroke();
}
}
// 绘制当前值的高亮点 - 根据位置调整透明度
const lastY = height - (historyData.value[dataLength - 1] / 100) * height;
const pointOpacity = 0.4 + (1 - lastY / height) * 0.6;
// 外圈
ctx.fillStyle = `rgba(74, 158, 255, ${pointOpacity * 0.3})`;
ctx.beginPath();
ctx.arc(lastX, lastY, 4, 0, Math.PI * 2);
ctx.fill();
// 内圈
ctx.fillStyle = `rgba(74, 158, 255, ${pointOpacity})`;
ctx.beginPath();
ctx.arc(lastX, lastY, 2, 0, Math.PI * 2);
ctx.fill();
};
// 手动触发GC
const triggerGC = async () => {
try {
await SystemService.TriggerGC();
// 延迟一下再获取新的统计信息
setTimeout(fetchMemoryStats, 100);
} catch (error) {
console.error('Failed to trigger GC:', error);
}
};
// 处理窗口大小变化
const handleResize = () => {
if (historyData.value.length > 0) {
nextTick(() => drawChart());
}
};
onMounted(() => {
fetchMemoryStats();
// 每3秒更新一次内存信息
intervalId = setInterval(fetchMemoryStats, 3000);
// 监听窗口大小变化
window.addEventListener('resize', handleResize);
});
onUnmounted(() => {
if (intervalId) {
clearInterval(intervalId);
}
window.removeEventListener('resize', handleResize);
});
</script>
<template>
<div class="memory-monitor" @click="triggerGC" :title="`内存: ${formattedMemory} | 点击清理内存`">
<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">
<path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/>
</svg>
<span>内存</span>
</div>
<div class="memory-value" v-if="!isLoading">{{ formattedMemory }}</div>
<div class="memory-loading" v-else>--</div>
</div>
<div class="chart-area">
<canvas
ref="canvasRef"
class="memory-chart"
:class="{ 'loading': isLoading }"
></canvas>
</div>
</div>
</template>
<style scoped lang="scss">
.memory-monitor {
display: flex;
flex-direction: column;
gap: 6px;
cursor: pointer;
transition: all 0.2s ease;
width: 100%;
&:hover {
.monitor-info {
.memory-label {
color: #4a9eff;
}
.memory-value {
color: #ffffff;
}
}
.chart-area .memory-chart {
opacity: 1;
}
}
.monitor-info {
display: flex;
align-items: center;
justify-content: space-between;
.memory-label {
display: flex;
align-items: center;
gap: 4px;
color: #a0a0a0;
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: #e0e0e0;
font-family: 'JetBrains Mono', 'Courier New', monospace;
font-size: 9px;
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;
}
}
}
}
@keyframes pulse {
0%, 100% {
opacity: 0.5;
}
50% {
opacity: 0.8;
}
}
</style>

View File

@@ -1,32 +1,26 @@
<script setup lang="ts">
import {useEditorStore} from '@/stores/editorStore';
import {useConfigStore} from '@/stores/configStore';
import {useConfigStore, SUPPORTED_LOCALES, type SupportedLocaleType} from '@/stores/configStore';
import {useLogStore} from '@/stores/logStore';
import { useI18n } from 'vue-i18n';
import { ref, onMounted, watch } from 'vue';
import {SUPPORTED_LOCALES, setLocale, SupportedLocaleType} from '@/i18n';
import {LanguageType} from '@/../bindings/voidraft/internal/models/models';
import { ConfigUtils } from '@/utils/configUtils';
import * as runtime from '@wailsio/runtime';
import { useRouter } from 'vue-router';
const editorStore = useEditorStore();
const configStore = useConfigStore();
const logStore = useLogStore();
const { t, locale } = useI18n();
const router = useRouter();
// 语言下拉菜单
const showLanguageMenu = ref(false);
// 切换语言
const changeLanguage = async (localeCode: SupportedLocaleType) => {
// 使用工具类转换语言类型
const backendLanguage = ConfigUtils.frontendLanguageToBackend(localeCode);
try {
// 设置后端语言配置
await configStore.setLanguage(backendLanguage);
// 设置前端语言
setLocale(localeCode);
// 使用 configStore 的语言设置方法
await configStore.setLocale(localeCode);
} catch (error) {
console.error('Failed to change language:', error);
}
@@ -44,25 +38,16 @@ const toggleAlwaysOnTop = async () => {
try {
await configStore.toggleAlwaysOnTop();
// 使用Window.SetAlwaysOnTop方法设置窗口置顶状态
runtime.Window.SetAlwaysOnTop(configStore.config.alwaysOnTop);
await runtime.Window.SetAlwaysOnTop(configStore.config.alwaysOnTop);
} catch (error) {
console.error('Failed to set window always on top:', error);
logStore.error(t('config.alwaysOnTopFailed'));
}
};
// 打开设置窗口
const openSettingsWindow = () => {
try {
// 直接操作窗口对象
runtime.Events.Emit({
name: "show_settings_window",
data: {},
});
} catch (error) {
console.error('Failed to open settings window:', error);
logStore.error('Failed to open settings window');
}
// 打开设置页面
const openSettings = () => {
router.push('/settings');
};
// 初始化配置
@@ -75,17 +60,11 @@ onMounted(async () => {
// 设置窗口置顶状态
if (configStore.config.alwaysOnTop) {
try {
runtime.Window.SetAlwaysOnTop(true);
await runtime.Window.SetAlwaysOnTop(true);
} catch (error) {
console.error('Failed to set window always on top:', error);
}
}
// 同步前端语言设置
const frontendLocale = ConfigUtils.backendLanguageToFrontend(configStore.config.language);
if (locale.value !== frontendLocale) {
setLocale(frontendLocale);
}
});
// 监听配置加载完成
@@ -130,9 +109,9 @@ watch(() => configStore.configLoaded, (isLoaded) => {
{{ t('toolbar.tabType.' + (configStore.config.tabType === 'spaces' ? 'spaces' : 'tab')) }}
</span>
<span class="tab-size" title="Tab大小" v-if="configStore.config.tabType === 'spaces'">
<button class="tab-btn" @click="() => configStore.decreaseTabSize()" :disabled="configStore.config.tabSize <= configStore.MIN_TAB_SIZE">-</button>
<button class="tab-btn" @click="() => configStore.decreaseTabSize()" :disabled="configStore.config.tabSize <= configStore.tabSize.min">-</button>
<span>{{ configStore.config.tabSize }}</span>
<button class="tab-btn" @click="() => configStore.increaseTabSize()" :disabled="configStore.config.tabSize >= configStore.MAX_TAB_SIZE">+</button>
<button class="tab-btn" @click="() => configStore.increaseTabSize()" :disabled="configStore.config.tabSize >= configStore.tabSize.max">+</button>
</span>
</span>
@@ -167,7 +146,7 @@ watch(() => configStore.configLoaded, (isLoaded) => {
</div>
</div>
<button class="settings-btn" :title="t('toolbar.settings')" @click="openSettingsWindow">
<button class="settings-btn" :title="t('toolbar.settings')" @click="openSettings">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="3"></circle>