♻️ Refactor code
This commit is contained in:
@@ -3,9 +3,11 @@
|
|||||||
|
|
||||||
import * as ConfigService from "./configservice.js";
|
import * as ConfigService from "./configservice.js";
|
||||||
import * as DocumentService from "./documentservice.js";
|
import * as DocumentService from "./documentservice.js";
|
||||||
|
import * as SystemService from "./systemservice.js";
|
||||||
export {
|
export {
|
||||||
ConfigService,
|
ConfigService,
|
||||||
DocumentService
|
DocumentService,
|
||||||
|
SystemService
|
||||||
};
|
};
|
||||||
|
|
||||||
export * from "./models.js";
|
export * from "./models.js";
|
||||||
|
@@ -136,6 +136,73 @@ export enum EditType {
|
|||||||
EditEqual = 2,
|
EditEqual = 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MemoryStats 内存统计信息
|
||||||
|
*/
|
||||||
|
export class MemoryStats {
|
||||||
|
/**
|
||||||
|
* 当前堆内存使用量(字节)
|
||||||
|
*/
|
||||||
|
"heapInUse": number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 堆内存分配总量(字节)
|
||||||
|
*/
|
||||||
|
"heapAlloc": number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统内存使用量(字节)
|
||||||
|
*/
|
||||||
|
"sys": number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GC 次数
|
||||||
|
*/
|
||||||
|
"numGC": number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GC 暂停时间(纳秒)
|
||||||
|
*/
|
||||||
|
"pauseTotalNs": number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Goroutine 数量
|
||||||
|
*/
|
||||||
|
"numGoroutine": number;
|
||||||
|
|
||||||
|
/** Creates a new MemoryStats instance. */
|
||||||
|
constructor($$source: Partial<MemoryStats> = {}) {
|
||||||
|
if (!("heapInUse" in $$source)) {
|
||||||
|
this["heapInUse"] = 0;
|
||||||
|
}
|
||||||
|
if (!("heapAlloc" in $$source)) {
|
||||||
|
this["heapAlloc"] = 0;
|
||||||
|
}
|
||||||
|
if (!("sys" in $$source)) {
|
||||||
|
this["sys"] = 0;
|
||||||
|
}
|
||||||
|
if (!("numGC" in $$source)) {
|
||||||
|
this["numGC"] = 0;
|
||||||
|
}
|
||||||
|
if (!("pauseTotalNs" in $$source)) {
|
||||||
|
this["pauseTotalNs"] = 0;
|
||||||
|
}
|
||||||
|
if (!("numGoroutine" in $$source)) {
|
||||||
|
this["numGoroutine"] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(this, $$source);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new MemoryStats instance from a string or object.
|
||||||
|
*/
|
||||||
|
static createFrom($$source: any = {}): MemoryStats {
|
||||||
|
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||||
|
return new MemoryStats($$parsedSource as Partial<MemoryStats>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Private type creation functions
|
// Private type creation functions
|
||||||
const $$createType0 = Edit.createFrom;
|
const $$createType0 = Edit.createFrom;
|
||||||
const $$createType1 = $Create.Array($$createType0);
|
const $$createType1 = $Create.Array($$createType0);
|
||||||
|
@@ -0,0 +1,46 @@
|
|||||||
|
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||||
|
// This file is automatically generated. DO NOT EDIT
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SystemService 系统监控服务
|
||||||
|
* @module
|
||||||
|
*/
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore: Unused imports
|
||||||
|
import {Call as $Call, Create as $Create} from "@wailsio/runtime";
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore: Unused imports
|
||||||
|
import * as $models from "./models.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FormatBytes 格式化字节数为人类可读的格式
|
||||||
|
*/
|
||||||
|
export function FormatBytes(bytes: number): Promise<string> & { cancel(): void } {
|
||||||
|
let $resultPromise = $Call.ByID(1368998019, bytes) as any;
|
||||||
|
return $resultPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GetMemoryStats 获取当前内存统计信息
|
||||||
|
*/
|
||||||
|
export function GetMemoryStats(): Promise<$models.MemoryStats> & { cancel(): void } {
|
||||||
|
let $resultPromise = $Call.ByID(1678201009) as any;
|
||||||
|
let $typingPromise = $resultPromise.then(($result: any) => {
|
||||||
|
return $$createType0($result);
|
||||||
|
}) as any;
|
||||||
|
$typingPromise.cancel = $resultPromise.cancel.bind($resultPromise);
|
||||||
|
return $typingPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TriggerGC 手动触发垃圾回收
|
||||||
|
*/
|
||||||
|
export function TriggerGC(): Promise<void> & { cancel(): void } {
|
||||||
|
let $resultPromise = $Call.ByID(741882899) as any;
|
||||||
|
return $resultPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Private type creation functions
|
||||||
|
const $$createType0 = $models.MemoryStats.createFrom;
|
1
frontend/components.d.ts
vendored
1
frontend/components.d.ts
vendored
@@ -8,6 +8,7 @@ export {}
|
|||||||
/* prettier-ignore */
|
/* prettier-ignore */
|
||||||
declare module 'vue' {
|
declare module 'vue' {
|
||||||
export interface GlobalComponents {
|
export interface GlobalComponents {
|
||||||
|
MemoryMonitor: typeof import('./src/components/monitor/MemoryMonitor.vue')['default']
|
||||||
RouterLink: typeof import('vue-router')['RouterLink']
|
RouterLink: typeof import('vue-router')['RouterLink']
|
||||||
RouterView: typeof import('vue-router')['RouterView']
|
RouterView: typeof import('vue-router')['RouterView']
|
||||||
Toolbar: typeof import('./src/components/toolbar/Toolbar.vue')['default']
|
Toolbar: typeof import('./src/components/toolbar/Toolbar.vue')['default']
|
||||||
|
717
frontend/package-lock.json
generated
717
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -29,7 +29,7 @@
|
|||||||
"@codemirror/lang-python": "^6.2.1",
|
"@codemirror/lang-python": "^6.2.1",
|
||||||
"@codemirror/lang-rust": "^6.0.1",
|
"@codemirror/lang-rust": "^6.0.1",
|
||||||
"@codemirror/lang-sass": "^6.0.2",
|
"@codemirror/lang-sass": "^6.0.2",
|
||||||
"@codemirror/lang-sql": "^6.8.0",
|
"@codemirror/lang-sql": "^6.9.0",
|
||||||
"@codemirror/lang-vue": "^0.1.3",
|
"@codemirror/lang-vue": "^0.1.3",
|
||||||
"@codemirror/lang-wast": "^6.0.2",
|
"@codemirror/lang-wast": "^6.0.2",
|
||||||
"@codemirror/lang-xml": "^6.1.0",
|
"@codemirror/lang-xml": "^6.1.0",
|
||||||
@@ -40,29 +40,30 @@
|
|||||||
"@codemirror/lint": "^6.8.5",
|
"@codemirror/lint": "^6.8.5",
|
||||||
"@codemirror/search": "^6.5.11",
|
"@codemirror/search": "^6.5.11",
|
||||||
"@codemirror/state": "^6.5.2",
|
"@codemirror/state": "^6.5.2",
|
||||||
"@codemirror/view": "^6.36.8",
|
"@codemirror/view": "^6.37.1",
|
||||||
"@lezer/highlight": "^1.2.1",
|
"@lezer/highlight": "^1.2.1",
|
||||||
"@types/uuid": "^10.0.0",
|
"@types/uuid": "^10.0.0",
|
||||||
"@vueuse/core": "^13.2.0",
|
"@vueuse/core": "^13.3.0",
|
||||||
"codemirror": "^6.0.1",
|
"codemirror": "^6.0.1",
|
||||||
"pinia": "^3.0.2",
|
"pinia": "^3.0.2",
|
||||||
"sass": "^1.89.0",
|
"pinia-plugin-persistedstate": "^4.3.0",
|
||||||
|
"sass": "^1.89.1",
|
||||||
"uuid": "^11.1.0",
|
"uuid": "^11.1.0",
|
||||||
"vue": "^3.5.14",
|
"vue": "^3.5.16",
|
||||||
"vue-i18n": "^11.1.3",
|
"vue-i18n": "^11.1.5",
|
||||||
"vue-router": "^4.5.1"
|
"vue-router": "^4.5.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.27.0",
|
"@eslint/js": "^9.28.0",
|
||||||
"@types/node": "^22.15.18",
|
"@types/node": "^22.15.29",
|
||||||
"@vitejs/plugin-vue": "^5.2.4",
|
"@vitejs/plugin-vue": "^5.2.4",
|
||||||
"@wailsio/runtime": "latest",
|
"@wailsio/runtime": "latest",
|
||||||
"eslint": "^9.27.0",
|
"eslint": "^9.28.0",
|
||||||
"eslint-plugin-vue": "^10.1.0",
|
"eslint-plugin-vue": "^10.1.0",
|
||||||
"globals": "^16.1.0",
|
"globals": "^16.2.0",
|
||||||
"typescript": "^5.8.3",
|
"typescript": "^5.8.3",
|
||||||
"typescript-eslint": "^8.32.1",
|
"typescript-eslint": "^8.33.0",
|
||||||
"unplugin-vue-components": "^28.5.0",
|
"unplugin-vue-components": "^28.7.0",
|
||||||
"vite": "^6.3.5",
|
"vite": "^6.3.5",
|
||||||
"vue-eslint-parser": "^10.1.3",
|
"vue-eslint-parser": "^10.1.3",
|
||||||
"vue-tsc": "^2.2.10"
|
"vue-tsc": "^2.2.10"
|
||||||
|
@@ -1,5 +1,15 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { onMounted } from 'vue';
|
||||||
|
import { useConfigStore } from '@/stores/configStore';
|
||||||
|
|
||||||
|
const configStore = useConfigStore();
|
||||||
|
|
||||||
|
|
||||||
|
// 应用启动时加载配置
|
||||||
|
onMounted(async () => {
|
||||||
|
await configStore.initializeLanguage();
|
||||||
|
await configStore.initConfig();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
349
frontend/src/components/monitor/MemoryMonitor.vue
Normal file
349
frontend/src/components/monitor/MemoryMonitor.vue
Normal 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>
|
@@ -1,32 +1,26 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {useEditorStore} from '@/stores/editorStore';
|
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 {useLogStore} from '@/stores/logStore';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { ref, onMounted, watch } from 'vue';
|
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 { ConfigUtils } from '@/utils/configUtils';
|
||||||
import * as runtime from '@wailsio/runtime';
|
import * as runtime from '@wailsio/runtime';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
const editorStore = useEditorStore();
|
const editorStore = useEditorStore();
|
||||||
const configStore = useConfigStore();
|
const configStore = useConfigStore();
|
||||||
const logStore = useLogStore();
|
const logStore = useLogStore();
|
||||||
const { t, locale } = useI18n();
|
const { t, locale } = useI18n();
|
||||||
|
const router = useRouter();
|
||||||
// 语言下拉菜单
|
// 语言下拉菜单
|
||||||
const showLanguageMenu = ref(false);
|
const showLanguageMenu = ref(false);
|
||||||
|
|
||||||
// 切换语言
|
// 切换语言
|
||||||
const changeLanguage = async (localeCode: SupportedLocaleType) => {
|
const changeLanguage = async (localeCode: SupportedLocaleType) => {
|
||||||
// 使用工具类转换语言类型
|
|
||||||
const backendLanguage = ConfigUtils.frontendLanguageToBackend(localeCode);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 设置后端语言配置
|
// 使用 configStore 的语言设置方法
|
||||||
await configStore.setLanguage(backendLanguage);
|
await configStore.setLocale(localeCode);
|
||||||
// 设置前端语言
|
|
||||||
setLocale(localeCode);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to change language:', error);
|
console.error('Failed to change language:', error);
|
||||||
}
|
}
|
||||||
@@ -44,25 +38,16 @@ const toggleAlwaysOnTop = async () => {
|
|||||||
try {
|
try {
|
||||||
await configStore.toggleAlwaysOnTop();
|
await configStore.toggleAlwaysOnTop();
|
||||||
// 使用Window.SetAlwaysOnTop方法设置窗口置顶状态
|
// 使用Window.SetAlwaysOnTop方法设置窗口置顶状态
|
||||||
runtime.Window.SetAlwaysOnTop(configStore.config.alwaysOnTop);
|
await runtime.Window.SetAlwaysOnTop(configStore.config.alwaysOnTop);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to set window always on top:', error);
|
console.error('Failed to set window always on top:', error);
|
||||||
logStore.error(t('config.alwaysOnTopFailed'));
|
logStore.error(t('config.alwaysOnTopFailed'));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 打开设置窗口
|
// 打开设置页面
|
||||||
const openSettingsWindow = () => {
|
const openSettings = () => {
|
||||||
try {
|
router.push('/settings');
|
||||||
// 直接操作窗口对象
|
|
||||||
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');
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 初始化配置
|
// 初始化配置
|
||||||
@@ -75,17 +60,11 @@ onMounted(async () => {
|
|||||||
// 设置窗口置顶状态
|
// 设置窗口置顶状态
|
||||||
if (configStore.config.alwaysOnTop) {
|
if (configStore.config.alwaysOnTop) {
|
||||||
try {
|
try {
|
||||||
runtime.Window.SetAlwaysOnTop(true);
|
await runtime.Window.SetAlwaysOnTop(true);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to set window always on top:', 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')) }}
|
{{ t('toolbar.tabType.' + (configStore.config.tabType === 'spaces' ? 'spaces' : 'tab')) }}
|
||||||
</span>
|
</span>
|
||||||
<span class="tab-size" title="Tab大小" v-if="configStore.config.tabType === 'spaces'">
|
<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>
|
<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>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
@@ -167,7 +146,7 @@ watch(() => configStore.configLoaded, (isLoaded) => {
|
|||||||
</div>
|
</div>
|
||||||
</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"
|
<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">
|
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<circle cx="12" cy="12" r="3"></circle>
|
<circle cx="12" cy="12" r="3"></circle>
|
||||||
|
@@ -1,219 +0,0 @@
|
|||||||
import { EditorView } from '@codemirror/view';
|
|
||||||
import { Extension, Compartment } from '@codemirror/state';
|
|
||||||
|
|
||||||
// 字体配置接口
|
|
||||||
export interface FontConfig {
|
|
||||||
fontFamily: string;
|
|
||||||
fontSize?: number;
|
|
||||||
lineHeight?: number;
|
|
||||||
fontWeight?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建字体配置compartment
|
|
||||||
export const fontCompartment = new Compartment();
|
|
||||||
|
|
||||||
// 默认鸿蒙字体配置
|
|
||||||
export const HARMONYOS_FONT_CONFIG: FontConfig = {
|
|
||||||
fontFamily: '"HarmonyOS Sans SC", "HarmonyOS Sans", "Microsoft YaHei", "PingFang SC", "Helvetica Neue", Arial, sans-serif',
|
|
||||||
fontSize: 14,
|
|
||||||
lineHeight: 1.5,
|
|
||||||
fontWeight: 'normal'
|
|
||||||
};
|
|
||||||
|
|
||||||
// 从后端配置创建字体配置
|
|
||||||
export function createFontConfigFromBackend(backendConfig: {
|
|
||||||
fontFamily?: string;
|
|
||||||
fontSize?: number;
|
|
||||||
lineHeight?: number;
|
|
||||||
fontWeight?: string;
|
|
||||||
}): FontConfig {
|
|
||||||
return {
|
|
||||||
fontFamily: backendConfig.fontFamily || HARMONYOS_FONT_CONFIG.fontFamily,
|
|
||||||
fontSize: backendConfig.fontSize || HARMONYOS_FONT_CONFIG.fontSize,
|
|
||||||
lineHeight: backendConfig.lineHeight || HARMONYOS_FONT_CONFIG.lineHeight,
|
|
||||||
fontWeight: backendConfig.fontWeight || HARMONYOS_FONT_CONFIG.fontWeight,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建字体样式扩展
|
|
||||||
export function createFontExtension(config: Partial<FontConfig> = {}): Extension {
|
|
||||||
const fontConfig = { ...HARMONYOS_FONT_CONFIG, ...config };
|
|
||||||
|
|
||||||
const styles: Record<string, any> = {
|
|
||||||
'&': {
|
|
||||||
fontFamily: fontConfig.fontFamily,
|
|
||||||
...(fontConfig.fontSize && { fontSize: `${fontConfig.fontSize}px` }),
|
|
||||||
...(fontConfig.lineHeight && { lineHeight: fontConfig.lineHeight.toString() }),
|
|
||||||
...(fontConfig.fontWeight && { fontWeight: fontConfig.fontWeight }),
|
|
||||||
},
|
|
||||||
'.cm-content': {
|
|
||||||
fontFamily: fontConfig.fontFamily,
|
|
||||||
...(fontConfig.fontSize && { fontSize: `${fontConfig.fontSize}px` }),
|
|
||||||
...(fontConfig.lineHeight && { lineHeight: fontConfig.lineHeight.toString() }),
|
|
||||||
...(fontConfig.fontWeight && { fontWeight: fontConfig.fontWeight }),
|
|
||||||
},
|
|
||||||
'.cm-editor': {
|
|
||||||
fontFamily: fontConfig.fontFamily,
|
|
||||||
},
|
|
||||||
'.cm-scroller': {
|
|
||||||
fontFamily: fontConfig.fontFamily,
|
|
||||||
},
|
|
||||||
'.cm-gutters': {
|
|
||||||
fontFamily: fontConfig.fontFamily,
|
|
||||||
...(fontConfig.fontSize && { fontSize: `${fontConfig.fontSize}px` }),
|
|
||||||
},
|
|
||||||
'.cm-lineNumbers': {
|
|
||||||
fontFamily: fontConfig.fontFamily,
|
|
||||||
...(fontConfig.fontSize && { fontSize: `${Math.max(10, fontConfig.fontSize - 1)}px` }),
|
|
||||||
},
|
|
||||||
'.cm-tooltip': {
|
|
||||||
fontFamily: fontConfig.fontFamily,
|
|
||||||
...(fontConfig.fontSize && { fontSize: `${Math.max(12, fontConfig.fontSize - 1)}px` }),
|
|
||||||
},
|
|
||||||
'.cm-completionLabel': {
|
|
||||||
fontFamily: fontConfig.fontFamily,
|
|
||||||
},
|
|
||||||
'.cm-completionDetail': {
|
|
||||||
fontFamily: fontConfig.fontFamily,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return EditorView.theme(styles);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建响应式字体大小扩展
|
|
||||||
export function createResponsiveFontExtension(baseFontSize: number = 14): Extension {
|
|
||||||
return fontCompartment.of(createFontExtension({
|
|
||||||
fontSize: baseFontSize,
|
|
||||||
lineHeight: 1.5
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 从后端配置创建字体扩展
|
|
||||||
export function createFontExtensionFromBackend(backendConfig: {
|
|
||||||
fontFamily?: string;
|
|
||||||
fontSize?: number;
|
|
||||||
lineHeight?: number;
|
|
||||||
fontWeight?: string;
|
|
||||||
}): Extension {
|
|
||||||
const fontConfig = createFontConfigFromBackend(backendConfig);
|
|
||||||
return fontCompartment.of(createFontExtension(fontConfig));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 动态更新字体配置
|
|
||||||
export function updateFontConfig(view: EditorView, config: Partial<FontConfig>): void {
|
|
||||||
const newFontExtension = createFontExtension(config);
|
|
||||||
|
|
||||||
// 使用compartment重新配置字体扩展
|
|
||||||
view.dispatch({
|
|
||||||
effects: fontCompartment.reconfigure(newFontExtension)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 预设字体配置
|
|
||||||
export const FONT_PRESETS = {
|
|
||||||
// 鸿蒙字体系列
|
|
||||||
harmonyos: {
|
|
||||||
name: '鸿蒙字体',
|
|
||||||
fontFamily: '"HarmonyOS Sans SC", "HarmonyOS Sans", "Microsoft YaHei", "PingFang SC", "Helvetica Neue", Arial, sans-serif',
|
|
||||||
fontSize: 14,
|
|
||||||
lineHeight: 1.5,
|
|
||||||
fontWeight: 'normal'
|
|
||||||
},
|
|
||||||
harmonyosCondensed: {
|
|
||||||
name: '鸿蒙紧凑字体',
|
|
||||||
fontFamily: '"HarmonyOS Sans Condensed", "HarmonyOS Sans SC", "HarmonyOS Sans", "Microsoft YaHei", "PingFang SC", "Helvetica Neue", Arial, sans-serif',
|
|
||||||
fontSize: 14,
|
|
||||||
lineHeight: 1.4,
|
|
||||||
fontWeight: 'normal'
|
|
||||||
},
|
|
||||||
|
|
||||||
// 编程专用字体
|
|
||||||
jetbrainsMono: {
|
|
||||||
name: 'JetBrains Mono',
|
|
||||||
fontFamily: '"JetBrains Mono", "Fira Code", "SF Mono", Monaco, Consolas, "Ubuntu Mono", monospace',
|
|
||||||
fontSize: 14,
|
|
||||||
lineHeight: 1.5,
|
|
||||||
fontWeight: 'normal'
|
|
||||||
},
|
|
||||||
firaCode: {
|
|
||||||
name: 'Fira Code',
|
|
||||||
fontFamily: '"Fira Code", "JetBrains Mono", "SF Mono", Monaco, Consolas, "Ubuntu Mono", monospace',
|
|
||||||
fontSize: 14,
|
|
||||||
lineHeight: 1.5,
|
|
||||||
fontWeight: 'normal'
|
|
||||||
},
|
|
||||||
sourceCodePro: {
|
|
||||||
name: 'Source Code Pro',
|
|
||||||
fontFamily: '"Source Code Pro", "SF Mono", Monaco, Consolas, "Ubuntu Mono", monospace',
|
|
||||||
fontSize: 14,
|
|
||||||
lineHeight: 1.5,
|
|
||||||
fontWeight: 'normal'
|
|
||||||
},
|
|
||||||
|
|
||||||
// 系统字体
|
|
||||||
systemMono: {
|
|
||||||
name: '系统等宽字体',
|
|
||||||
fontFamily: '"SF Mono", Monaco, "Cascadia Code", "Roboto Mono", Consolas, "Courier New", monospace',
|
|
||||||
fontSize: 14,
|
|
||||||
lineHeight: 1.5,
|
|
||||||
fontWeight: 'normal'
|
|
||||||
},
|
|
||||||
cascadiaCode: {
|
|
||||||
name: 'Cascadia Code',
|
|
||||||
fontFamily: '"Cascadia Code", "SF Mono", Monaco, Consolas, "Ubuntu Mono", monospace',
|
|
||||||
fontSize: 14,
|
|
||||||
lineHeight: 1.5,
|
|
||||||
fontWeight: 'normal'
|
|
||||||
},
|
|
||||||
|
|
||||||
// 中文友好字体
|
|
||||||
microsoftYaHei: {
|
|
||||||
name: '微软雅黑',
|
|
||||||
fontFamily: '"Microsoft YaHei", "PingFang SC", "Helvetica Neue", Arial, sans-serif',
|
|
||||||
fontSize: 14,
|
|
||||||
lineHeight: 1.5,
|
|
||||||
fontWeight: 'normal'
|
|
||||||
},
|
|
||||||
pingFang: {
|
|
||||||
name: '苹方字体',
|
|
||||||
fontFamily: '"PingFang SC", "Microsoft YaHei", "Helvetica Neue", Arial, sans-serif',
|
|
||||||
fontSize: 14,
|
|
||||||
lineHeight: 1.5,
|
|
||||||
fontWeight: 'normal'
|
|
||||||
},
|
|
||||||
|
|
||||||
// 经典字体
|
|
||||||
arial: {
|
|
||||||
name: 'Arial',
|
|
||||||
fontFamily: 'Arial, "Helvetica Neue", Helvetica, sans-serif',
|
|
||||||
fontSize: 14,
|
|
||||||
lineHeight: 1.5,
|
|
||||||
fontWeight: 'normal'
|
|
||||||
},
|
|
||||||
helvetica: {
|
|
||||||
name: 'Helvetica',
|
|
||||||
fontFamily: '"Helvetica Neue", Helvetica, Arial, sans-serif',
|
|
||||||
fontSize: 14,
|
|
||||||
lineHeight: 1.5,
|
|
||||||
fontWeight: 'normal'
|
|
||||||
}
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
// 字体预设类型
|
|
||||||
export type FontPresetKey = keyof typeof FONT_PRESETS;
|
|
||||||
|
|
||||||
// 获取所有字体预设选项
|
|
||||||
export function getFontPresetOptions() {
|
|
||||||
return Object.entries(FONT_PRESETS).map(([key, preset]) => ({
|
|
||||||
value: key as FontPresetKey,
|
|
||||||
label: preset.name,
|
|
||||||
fontFamily: preset.fontFamily
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 根据预设创建字体扩展
|
|
||||||
export function createPresetFontExtension(preset: keyof typeof FONT_PRESETS, overrides: Partial<FontConfig> = {}): Extension {
|
|
||||||
const config = { ...FONT_PRESETS[preset], ...overrides };
|
|
||||||
return createFontExtension(config);
|
|
||||||
}
|
|
@@ -1,81 +1,17 @@
|
|||||||
import {createI18n} from 'vue-i18n';
|
import {createI18n} from 'vue-i18n';
|
||||||
import messages from './locales';
|
import messages from './locales';
|
||||||
import { ConfigService } from '@/../bindings/voidraft/internal/services';
|
|
||||||
import { LanguageType } from '@/../bindings/voidraft/internal/models';
|
|
||||||
import { ConfigUtils } from '@/utils/configUtils';
|
|
||||||
|
|
||||||
// 定义支持的语言类型
|
|
||||||
export type SupportedLocaleType = 'zh-CN' | 'en-US';
|
|
||||||
|
|
||||||
// 支持的语言列表
|
|
||||||
export const SUPPORTED_LOCALES = [
|
|
||||||
{
|
|
||||||
code: 'zh-CN' as SupportedLocaleType,
|
|
||||||
name: '简体中文'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: 'en-US' as SupportedLocaleType,
|
|
||||||
name: 'English'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
// 获取浏览器的默认语言
|
|
||||||
const getBrowserLanguage = (): SupportedLocaleType => {
|
|
||||||
const browserLang = navigator.language;
|
|
||||||
const langCode = browserLang.split('-')[0];
|
|
||||||
|
|
||||||
// 检查是否支持此语言
|
|
||||||
const supportedLang = SUPPORTED_LOCALES.find(locale =>
|
|
||||||
locale.code.startsWith(langCode) || locale.code.split('-')[0] === langCode
|
|
||||||
);
|
|
||||||
|
|
||||||
return supportedLang?.code || 'zh-CN';
|
|
||||||
};
|
|
||||||
|
|
||||||
// 创建i18n实例
|
// 创建i18n实例
|
||||||
const i18n = createI18n({
|
const i18n = createI18n({
|
||||||
|
compositionOnly: false,
|
||||||
globalInjection: true,
|
globalInjection: true,
|
||||||
|
silentTranslationWarn: true,
|
||||||
locale: 'zh-CN',
|
locale: 'zh-CN',
|
||||||
fallbackLocale: 'zh-CN' as SupportedLocaleType,
|
fallbackLocale: 'zh-CN',
|
||||||
|
silentFallbackWarn: true,
|
||||||
|
missingWarn: true,
|
||||||
|
fallbackWarn: false,
|
||||||
messages
|
messages
|
||||||
});
|
});
|
||||||
|
|
||||||
// 从后端获取语言设置
|
|
||||||
const initializeLanguage = async () => {
|
|
||||||
try {
|
|
||||||
// 使用新的配置服务方法获取语言设置
|
|
||||||
const language = await ConfigService.Get('editor.language') as LanguageType;
|
|
||||||
if (language) {
|
|
||||||
const frontendLocale = ConfigUtils.backendLanguageToFrontend(language);
|
|
||||||
i18n.global.locale = frontendLocale as any;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to get language from backend:', error);
|
|
||||||
// 如果获取失败,使用浏览器语言作为后备
|
|
||||||
const browserLang = getBrowserLanguage();
|
|
||||||
i18n.global.locale = browserLang as any;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 立即初始化语言
|
|
||||||
initializeLanguage();
|
|
||||||
|
|
||||||
// 切换语言的方法
|
|
||||||
export const setLocale = async (locale: SupportedLocaleType) => {
|
|
||||||
if (SUPPORTED_LOCALES.some(l => l.code === locale)) {
|
|
||||||
try {
|
|
||||||
// 转换为后端语言类型
|
|
||||||
const backendLanguage = ConfigUtils.frontendLanguageToBackend(locale);
|
|
||||||
|
|
||||||
// 使用新的配置服务方法设置语言
|
|
||||||
await ConfigService.Set('editor.language', backendLanguage);
|
|
||||||
|
|
||||||
// 更新前端语言
|
|
||||||
i18n.global.locale = locale;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to set language:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default i18n;
|
export default i18n;
|
@@ -48,6 +48,8 @@ export default {
|
|||||||
},
|
},
|
||||||
settings: {
|
settings: {
|
||||||
title: 'Settings',
|
title: 'Settings',
|
||||||
|
backToEditor: 'Back to Editor',
|
||||||
|
systemInfo: 'System Info',
|
||||||
general: 'General',
|
general: 'General',
|
||||||
editing: 'Editor',
|
editing: 'Editor',
|
||||||
appearance: 'Appearance',
|
appearance: 'Appearance',
|
||||||
@@ -56,6 +58,10 @@ export default {
|
|||||||
comingSoon: 'Coming Soon...',
|
comingSoon: 'Coming Soon...',
|
||||||
save: 'Save',
|
save: 'Save',
|
||||||
reset: 'Reset',
|
reset: 'Reset',
|
||||||
|
dangerZone: 'Danger Zone',
|
||||||
|
resetAllSettings: 'Reset All Settings',
|
||||||
|
resetDescription: 'This will restore all settings to their default values. This action cannot be undone.',
|
||||||
|
confirmReset: 'Are you sure you want to reset all settings? This action cannot be undone.',
|
||||||
globalHotkey: 'Global Keyboard Shortcuts',
|
globalHotkey: 'Global Keyboard Shortcuts',
|
||||||
enableGlobalHotkey: 'Enable Global Hotkeys',
|
enableGlobalHotkey: 'Enable Global Hotkeys',
|
||||||
window: 'Window/Application',
|
window: 'Window/Application',
|
||||||
@@ -67,8 +73,8 @@ export default {
|
|||||||
fontSize: 'Font Size',
|
fontSize: 'Font Size',
|
||||||
fontSizeDescription: 'Editor font size',
|
fontSizeDescription: 'Editor font size',
|
||||||
fontSettings: 'Font Settings',
|
fontSettings: 'Font Settings',
|
||||||
fontPreset: 'Font Preset',
|
fontFamily: 'Font Family',
|
||||||
fontPresetDescription: 'Choose a predefined font combination',
|
fontFamilyDescription: 'Choose editor font family',
|
||||||
fontWeight: 'Font Weight',
|
fontWeight: 'Font Weight',
|
||||||
fontWeightDescription: 'Set the thickness of the font',
|
fontWeightDescription: 'Set the thickness of the font',
|
||||||
lineHeight: 'Line Height',
|
lineHeight: 'Line Height',
|
||||||
|
@@ -48,6 +48,8 @@ export default {
|
|||||||
},
|
},
|
||||||
settings: {
|
settings: {
|
||||||
title: '设置',
|
title: '设置',
|
||||||
|
backToEditor: '返回编辑器',
|
||||||
|
systemInfo: '系统信息',
|
||||||
general: '常规',
|
general: '常规',
|
||||||
editing: '编辑器',
|
editing: '编辑器',
|
||||||
appearance: '外观',
|
appearance: '外观',
|
||||||
@@ -56,6 +58,10 @@ export default {
|
|||||||
comingSoon: '即将推出...',
|
comingSoon: '即将推出...',
|
||||||
save: '保存',
|
save: '保存',
|
||||||
reset: '重置',
|
reset: '重置',
|
||||||
|
dangerZone: '危险操作',
|
||||||
|
resetAllSettings: '重置所有设置',
|
||||||
|
resetDescription: '这将恢复所有设置为默认值,此操作无法撤销',
|
||||||
|
confirmReset: '确定要重置所有设置吗?此操作无法撤销。',
|
||||||
globalHotkey: '全局键盘快捷键',
|
globalHotkey: '全局键盘快捷键',
|
||||||
enableGlobalHotkey: '启用全局热键',
|
enableGlobalHotkey: '启用全局热键',
|
||||||
window: '窗口/应用程序',
|
window: '窗口/应用程序',
|
||||||
@@ -67,8 +73,8 @@ export default {
|
|||||||
fontSize: '字体大小',
|
fontSize: '字体大小',
|
||||||
fontSizeDescription: '编辑器字体大小',
|
fontSizeDescription: '编辑器字体大小',
|
||||||
fontSettings: '字体设置',
|
fontSettings: '字体设置',
|
||||||
fontPreset: '字体预设',
|
fontFamily: '字体',
|
||||||
fontPresetDescription: '选择预设的字体组合',
|
fontFamilyDescription: '选择编辑器字体',
|
||||||
fontWeight: '字体粗细',
|
fontWeight: '字体粗细',
|
||||||
fontWeightDescription: '设置字体的粗细程度',
|
fontWeightDescription: '设置字体的粗细程度',
|
||||||
lineHeight: '行高',
|
lineHeight: '行高',
|
||||||
|
@@ -4,9 +4,10 @@ import '@/assets/styles/index.css';
|
|||||||
import {createPinia} from 'pinia';
|
import {createPinia} from 'pinia';
|
||||||
import i18n from './i18n';
|
import i18n from './i18n';
|
||||||
import router from './router';
|
import router from './router';
|
||||||
|
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
|
||||||
|
|
||||||
const pinia = createPinia()
|
const pinia = createPinia()
|
||||||
|
pinia.use(piniaPluginPersistedstate)
|
||||||
const app = createApp(App);
|
const app = createApp(App);
|
||||||
app.use(pinia)
|
app.use(pinia)
|
||||||
app.use(i18n);
|
app.use(i18n);
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
import {createRouter, createWebHashHistory, createWebHistory, RouteRecordRaw} from 'vue-router';
|
import {createRouter, createWebHashHistory, createWebHistory, RouteRecordRaw} from 'vue-router';
|
||||||
import Editor from '@/editor/Editor.vue';
|
import Editor from '@/views/editor/Editor.vue';
|
||||||
import Settings from '@/settings/Settings.vue';
|
import Settings from '@/views/settings/Settings.vue';
|
||||||
import GeneralPage from '@/settings/pages/GeneralPage.vue';
|
import GeneralPage from '@/views/settings/pages/GeneralPage.vue';
|
||||||
import EditingPage from '@/settings/pages/EditingPage.vue';
|
import EditingPage from '@/views/settings/pages/EditingPage.vue';
|
||||||
import AppearancePage from '@/settings/pages/AppearancePage.vue';
|
import AppearancePage from '@/views/settings/pages/AppearancePage.vue';
|
||||||
import KeyBindingsPage from '@/settings/pages/KeyBindingsPage.vue';
|
import KeyBindingsPage from '@/views/settings/pages/KeyBindingsPage.vue';
|
||||||
import UpdatesPage from '@/settings/pages/UpdatesPage.vue';
|
import UpdatesPage from '@/views/settings/pages/UpdatesPage.vue';
|
||||||
|
|
||||||
const routes: RouteRecordRaw[] = [
|
const routes: RouteRecordRaw[] = [
|
||||||
{
|
{
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import {defineStore} from 'pinia';
|
import {defineStore} from 'pinia';
|
||||||
import {ref, computed} from 'vue';
|
import {computed, reactive} from 'vue';
|
||||||
import {
|
import {
|
||||||
ConfigService
|
ConfigService
|
||||||
} from '@/../bindings/voidraft/internal/services';
|
} from '@/../bindings/voidraft/internal/services';
|
||||||
@@ -7,10 +7,34 @@ import {EditorConfig, TabType, LanguageType} from '@/../bindings/voidraft/intern
|
|||||||
import {useLogStore} from './logStore';
|
import {useLogStore} from './logStore';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { ConfigUtils } from '@/utils/configUtils';
|
import { ConfigUtils } from '@/utils/configUtils';
|
||||||
import { FONT_PRESETS, getFontPresetOptions, type FontPresetKey } from '@/editor/extensions/fontExtension';
|
|
||||||
|
|
||||||
// 配置键映射 - 前端字段到后端配置键的映射
|
// 国际化相关导入
|
||||||
const CONFIG_KEY_MAP = {
|
export type SupportedLocaleType = 'zh-CN' | 'en-US';
|
||||||
|
|
||||||
|
// 支持的语言列表
|
||||||
|
export const SUPPORTED_LOCALES = [
|
||||||
|
{
|
||||||
|
code: 'zh-CN' as SupportedLocaleType,
|
||||||
|
name: '简体中文'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'en-US' as SupportedLocaleType,
|
||||||
|
name: 'English'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// 配置键映射和限制的类型定义
|
||||||
|
type ConfigKeyMap = {
|
||||||
|
readonly [K in keyof EditorConfig]: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type NumberConfigKey = 'fontSize' | 'tabSize' | 'lineHeight';
|
||||||
|
type StringConfigKey = 'fontFamily' | 'fontWeight';
|
||||||
|
type BooleanConfigKey = 'enableTabIndent' | 'alwaysOnTop';
|
||||||
|
type EnumConfigKey = 'tabType' | 'language';
|
||||||
|
|
||||||
|
// 配置键映射
|
||||||
|
const CONFIG_KEY_MAP: ConfigKeyMap = {
|
||||||
fontSize: 'editor.font_size',
|
fontSize: 'editor.font_size',
|
||||||
fontFamily: 'editor.font_family',
|
fontFamily: 'editor.font_family',
|
||||||
fontWeight: 'editor.font_weight',
|
fontWeight: 'editor.font_weight',
|
||||||
@@ -26,236 +50,257 @@ const CONFIG_KEY_MAP = {
|
|||||||
const CONFIG_LIMITS = {
|
const CONFIG_LIMITS = {
|
||||||
fontSize: { min: 12, max: 28, default: 13 },
|
fontSize: { min: 12, max: 28, default: 13 },
|
||||||
tabSize: { min: 2, max: 8, default: 4 },
|
tabSize: { min: 2, max: 8, default: 4 },
|
||||||
|
lineHeight: { min: 1.0, max: 3.0, default: 1.5 },
|
||||||
tabType: { values: [TabType.TabTypeSpaces, TabType.TabTypeTab], default: TabType.TabTypeSpaces }
|
tabType: { values: [TabType.TabTypeSpaces, TabType.TabTypeTab], default: TabType.TabTypeSpaces }
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
// 常用字体选项
|
||||||
|
export const FONT_OPTIONS = [
|
||||||
|
{ label: '鸿蒙字体', value: '"HarmonyOS Sans SC", "HarmonyOS Sans", "Microsoft YaHei", "PingFang SC", "Helvetica Neue", Arial, sans-serif' },
|
||||||
|
{ label: '微软雅黑', value: '"Microsoft YaHei", "PingFang SC", "Helvetica Neue", Arial, sans-serif' },
|
||||||
|
{ label: '苹方字体', value: '"PingFang SC", "Microsoft YaHei", "Helvetica Neue", Arial, sans-serif' },
|
||||||
|
{ label: 'JetBrains Mono', value: '"JetBrains Mono", "Fira Code", "SF Mono", Monaco, Consolas, "Ubuntu Mono", monospace' },
|
||||||
|
{ label: 'Fira Code', value: '"Fira Code", "JetBrains Mono", "SF Mono", Monaco, Consolas, "Ubuntu Mono", monospace' },
|
||||||
|
{ label: 'Source Code Pro', value: '"Source Code Pro", "SF Mono", Monaco, Consolas, "Ubuntu Mono", monospace' },
|
||||||
|
{ label: 'Cascadia Code', value: '"Cascadia Code", "SF Mono", Monaco, Consolas, "Ubuntu Mono", monospace' },
|
||||||
|
{ label: '系统等宽字体', value: '"SF Mono", Monaco, "Cascadia Code", "Roboto Mono", Consolas, "Courier New", monospace' }
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
// 获取浏览器的默认语言
|
||||||
|
const getBrowserLanguage = (): SupportedLocaleType => {
|
||||||
|
const browserLang = navigator.language;
|
||||||
|
const langCode = browserLang.split('-')[0];
|
||||||
|
|
||||||
|
// 检查是否支持此语言
|
||||||
|
const supportedLang = SUPPORTED_LOCALES.find(locale =>
|
||||||
|
locale.code.startsWith(langCode) || locale.code.split('-')[0] === langCode
|
||||||
|
);
|
||||||
|
|
||||||
|
return supportedLang?.code || 'zh-CN';
|
||||||
|
};
|
||||||
|
|
||||||
|
// 默认配置
|
||||||
|
const DEFAULT_CONFIG: EditorConfig = {
|
||||||
|
fontSize: CONFIG_LIMITS.fontSize.default,
|
||||||
|
fontFamily: FONT_OPTIONS[0].value,
|
||||||
|
fontWeight: 'normal',
|
||||||
|
lineHeight: CONFIG_LIMITS.lineHeight.default,
|
||||||
|
enableTabIndent: true,
|
||||||
|
tabSize: CONFIG_LIMITS.tabSize.default,
|
||||||
|
tabType: CONFIG_LIMITS.tabType.default,
|
||||||
|
language: LanguageType.LangZhCN,
|
||||||
|
alwaysOnTop: false
|
||||||
|
};
|
||||||
|
|
||||||
export const useConfigStore = defineStore('config', () => {
|
export const useConfigStore = defineStore('config', () => {
|
||||||
// 获取日志store
|
|
||||||
const logStore = useLogStore();
|
const logStore = useLogStore();
|
||||||
const { t } = useI18n();
|
const { t, locale } = useI18n();
|
||||||
|
|
||||||
// 配置状态
|
// 响应式状态
|
||||||
const config = ref<EditorConfig>(new EditorConfig({
|
const state = reactive({
|
||||||
fontSize: CONFIG_LIMITS.fontSize.default,
|
config: { ...DEFAULT_CONFIG } as EditorConfig,
|
||||||
fontFamily: '"HarmonyOS Sans SC", "HarmonyOS Sans", "Microsoft YaHei", "PingFang SC", "Helvetica Neue", Arial, sans-serif',
|
isLoading: false,
|
||||||
fontWeight: 'normal',
|
configLoaded: false
|
||||||
lineHeight: 1.5,
|
});
|
||||||
enableTabIndent: true,
|
|
||||||
tabSize: CONFIG_LIMITS.tabSize.default,
|
|
||||||
tabType: CONFIG_LIMITS.tabType.default,
|
|
||||||
language: LanguageType.LangZhCN,
|
|
||||||
alwaysOnTop: false
|
|
||||||
}));
|
|
||||||
|
|
||||||
// 加载状态
|
// 计算属性 - 使用工厂函数简化
|
||||||
const isLoading = ref(false);
|
const createLimitComputed = (key: NumberConfigKey) => computed(() => CONFIG_LIMITS[key]);
|
||||||
const configLoaded = ref(false);
|
const limits = {
|
||||||
|
fontSize: createLimitComputed('fontSize'),
|
||||||
|
tabSize: createLimitComputed('tabSize'),
|
||||||
|
lineHeight: createLimitComputed('lineHeight')
|
||||||
|
};
|
||||||
|
|
||||||
// 计算属性
|
// 错误处理装饰器
|
||||||
const MIN_FONT_SIZE = computed(() => CONFIG_LIMITS.fontSize.min);
|
const withErrorHandling = <T extends any[], R>(
|
||||||
const MAX_FONT_SIZE = computed(() => CONFIG_LIMITS.fontSize.max);
|
fn: (...args: T) => Promise<R>,
|
||||||
const MIN_TAB_SIZE = computed(() => CONFIG_LIMITS.tabSize.min);
|
errorMsg: string,
|
||||||
const MAX_TAB_SIZE = computed(() => CONFIG_LIMITS.tabSize.max);
|
successMsg?: string
|
||||||
|
) => async (...args: T): Promise<R | null> => {
|
||||||
|
const result = await fn(...args).catch(error => {
|
||||||
|
console.error(errorMsg, error);
|
||||||
|
logStore.error(t(errorMsg));
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
// 从后端加载配置
|
if (result !== null && successMsg) {
|
||||||
async function loadConfig(): Promise<void> {
|
logStore.info(t(successMsg));
|
||||||
if (isLoading.value) return;
|
}
|
||||||
|
|
||||||
isLoading.value = true;
|
return result;
|
||||||
try {
|
};
|
||||||
const appConfig = await ConfigService.GetConfig();
|
|
||||||
|
|
||||||
if (appConfig?.editor) {
|
// 通用配置更新方法
|
||||||
Object.assign(config.value, appConfig.editor);
|
const updateConfig = withErrorHandling(
|
||||||
|
async <K extends keyof EditorConfig>(key: K, value: EditorConfig[K]): Promise<void> => {
|
||||||
|
// 确保配置已加载
|
||||||
|
if (!state.configLoaded && !state.isLoading) {
|
||||||
|
await initConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
configLoaded.value = true;
|
const backendKey = CONFIG_KEY_MAP[key];
|
||||||
logStore.info(t('config.loadSuccess'));
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to load configuration:', error);
|
|
||||||
logStore.error(t('config.loadFailed'));
|
|
||||||
} finally {
|
|
||||||
isLoading.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新配置项的通用方法 - 直接调用后端Set方法
|
|
||||||
async function updateConfig<K extends keyof EditorConfig>(key: K, value: EditorConfig[K]): Promise<void> {
|
|
||||||
if (!configLoaded.value) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const backendKey = CONFIG_KEY_MAP[key as keyof typeof CONFIG_KEY_MAP];
|
|
||||||
if (!backendKey) {
|
if (!backendKey) {
|
||||||
throw new Error(`No backend key mapping found for ${String(key)}`);
|
throw new Error(`No backend key mapping found for ${key.toString()}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
await ConfigService.Set(backendKey, value);
|
await ConfigService.Set(backendKey, value);
|
||||||
|
state.config[key] = value;
|
||||||
|
},
|
||||||
|
'config.saveFailed',
|
||||||
|
'config.saveSuccess'
|
||||||
|
);
|
||||||
|
|
||||||
// 更新本地状态
|
// 加载配置
|
||||||
config.value[key] = value;
|
const initConfig = withErrorHandling(
|
||||||
|
async (): Promise<void> => {
|
||||||
|
if (state.isLoading) return;
|
||||||
|
|
||||||
logStore.info(t('config.saveSuccess'));
|
state.isLoading = true;
|
||||||
} catch (error) {
|
const appConfig = await ConfigService.GetConfig();
|
||||||
console.error(`Failed to update config ${String(key)}:`, error);
|
|
||||||
logStore.error(t('config.saveFailed'));
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 字体大小操作
|
if (appConfig?.editor) {
|
||||||
async function adjustFontSize(delta: number): Promise<void> {
|
Object.assign(state.config, appConfig.editor);
|
||||||
const newSize = ConfigUtils.clamp(config.value.fontSize + delta, CONFIG_LIMITS.fontSize.min, CONFIG_LIMITS.fontSize.max);
|
}
|
||||||
await updateConfig('fontSize', newSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tab大小操作
|
state.configLoaded = true;
|
||||||
async function adjustTabSize(delta: number): Promise<void> {
|
state.isLoading = false;
|
||||||
const newSize = ConfigUtils.clamp(config.value.tabSize + delta, CONFIG_LIMITS.tabSize.min, CONFIG_LIMITS.tabSize.max);
|
},
|
||||||
await updateConfig('tabSize', newSize);
|
'config.loadFailed',
|
||||||
}
|
'config.loadSuccess'
|
||||||
|
);
|
||||||
|
|
||||||
// 切换操作
|
// 数值调整工厂函数
|
||||||
async function toggleTabIndent(): Promise<void> {
|
const createNumberAdjuster = (key: NumberConfigKey) => {
|
||||||
await updateConfig('enableTabIndent', !config.value.enableTabIndent);
|
const limit = CONFIG_LIMITS[key];
|
||||||
}
|
return {
|
||||||
|
increase: () => updateConfig(key, ConfigUtils.clamp(state.config[key] + 1, limit.min, limit.max)),
|
||||||
|
decrease: () => updateConfig(key, ConfigUtils.clamp(state.config[key] - 1, limit.min, limit.max)),
|
||||||
|
set: (value: number) => updateConfig(key, ConfigUtils.clamp(value, limit.min, limit.max)),
|
||||||
|
reset: () => updateConfig(key, limit.default)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
async function toggleTabType(): Promise<void> {
|
// 布尔值切换工厂函数
|
||||||
const newTabType = config.value.tabType === TabType.TabTypeSpaces ? TabType.TabTypeTab : TabType.TabTypeSpaces;
|
const createToggler = (key: BooleanConfigKey) =>
|
||||||
await updateConfig('tabType', newTabType);
|
() => updateConfig(key, !state.config[key]);
|
||||||
}
|
|
||||||
|
|
||||||
async function toggleAlwaysOnTop(): Promise<void> {
|
// 枚举值切换工厂函数
|
||||||
await updateConfig('alwaysOnTop', !config.value.alwaysOnTop);
|
const createEnumToggler = <T extends TabType>(key: 'tabType', values: readonly T[]) =>
|
||||||
}
|
() => {
|
||||||
|
const currentIndex = values.indexOf(state.config[key] as T);
|
||||||
// 语言设置
|
const nextIndex = (currentIndex + 1) % values.length;
|
||||||
async function setLanguage(language: LanguageType): Promise<void> {
|
return updateConfig(key, values[nextIndex]);
|
||||||
try {
|
};
|
||||||
await ConfigService.Set(CONFIG_KEY_MAP.language, language);
|
|
||||||
config.value.language = language;
|
|
||||||
logStore.info(t('config.languageChanged'));
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to set language:', error);
|
|
||||||
logStore.error(t('config.languageChangeFailed'));
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 重置配置
|
// 重置配置
|
||||||
async function resetConfig(): Promise<void> {
|
const resetConfig = withErrorHandling(
|
||||||
if (isLoading.value) return;
|
async (): Promise<void> => {
|
||||||
|
if (state.isLoading) return;
|
||||||
|
|
||||||
try {
|
state.isLoading = true;
|
||||||
isLoading.value = true;
|
|
||||||
await ConfigService.ResetConfig();
|
await ConfigService.ResetConfig();
|
||||||
await loadConfig();
|
await initConfig();
|
||||||
logStore.info(t('config.resetSuccess'));
|
state.isLoading = false;
|
||||||
} catch (error) {
|
},
|
||||||
console.error('Failed to reset configuration:', error);
|
'config.resetFailed',
|
||||||
logStore.error(t('config.resetFailed'));
|
'config.resetSuccess'
|
||||||
} finally {
|
);
|
||||||
isLoading.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置字体大小
|
// 语言设置方法
|
||||||
async function setFontSize(size: number): Promise<void> {
|
const setLanguage = withErrorHandling(
|
||||||
await updateConfig('fontSize', size);
|
async (language: LanguageType): Promise<void> => {
|
||||||
}
|
await ConfigService.Set(CONFIG_KEY_MAP.language, language);
|
||||||
|
state.config.language = language;
|
||||||
|
|
||||||
// 设置Tab大小
|
// 同步更新前端语言
|
||||||
async function setTabSize(size: number): Promise<void> {
|
const frontendLocale = ConfigUtils.backendLanguageToFrontend(language);
|
||||||
await updateConfig('tabSize', size);
|
locale.value = frontendLocale as any;
|
||||||
}
|
},
|
||||||
|
'config.languageChangeFailed',
|
||||||
|
'config.languageChanged'
|
||||||
|
);
|
||||||
|
|
||||||
// 字体预设相关方法
|
// 通过前端语言代码设置语言
|
||||||
async function setFontPreset(presetKey: FontPresetKey): Promise<void> {
|
const setLocale = async (localeCode: SupportedLocaleType): Promise<void> => {
|
||||||
const preset = FONT_PRESETS[presetKey];
|
const backendLanguage = ConfigUtils.frontendLanguageToBackend(localeCode);
|
||||||
if (!preset) {
|
await setLanguage(backendLanguage);
|
||||||
throw new Error(`Unknown font preset: ${presetKey}`);
|
};
|
||||||
}
|
|
||||||
|
|
||||||
|
// 初始化语言设置
|
||||||
|
const initializeLanguage = async (): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
// 批量更新字体相关配置
|
// 如果配置未加载,先加载配置
|
||||||
await updateConfig('fontFamily', preset.fontFamily);
|
if (!state.configLoaded) {
|
||||||
await updateConfig('fontWeight', preset.fontWeight);
|
await initConfig();
|
||||||
await updateConfig('lineHeight', preset.lineHeight);
|
|
||||||
// 可选择是否同时更新字体大小
|
|
||||||
// await updateConfig('fontSize', preset.fontSize);
|
|
||||||
|
|
||||||
logStore.info(`字体预设已切换为: ${preset.name}`);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to set font preset:', error);
|
|
||||||
logStore.error('字体预设设置失败');
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取当前字体预设(如果匹配的话)
|
|
||||||
function getCurrentFontPreset(): FontPresetKey | null {
|
|
||||||
const currentFamily = config.value.fontFamily;
|
|
||||||
for (const [key, preset] of Object.entries(FONT_PRESETS)) {
|
|
||||||
if (preset.fontFamily === currentFamily) {
|
|
||||||
return key as FontPresetKey;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 同步前端语言设置
|
||||||
|
const frontendLocale = ConfigUtils.backendLanguageToFrontend(state.config.language);
|
||||||
|
locale.value = frontendLocale as any;
|
||||||
|
} catch (error) {
|
||||||
|
const browserLang = getBrowserLanguage();
|
||||||
|
locale.value = browserLang as any;
|
||||||
}
|
}
|
||||||
return null;
|
};
|
||||||
}
|
|
||||||
|
|
||||||
// 设置字体族
|
// 创建数值调整器
|
||||||
async function setFontFamily(fontFamily: string): Promise<void> {
|
const fontSize = createNumberAdjuster('fontSize');
|
||||||
await updateConfig('fontFamily', fontFamily);
|
const tabSize = createNumberAdjuster('tabSize');
|
||||||
}
|
const lineHeight = createNumberAdjuster('lineHeight');
|
||||||
|
|
||||||
// 设置字体粗细
|
// 创建切换器
|
||||||
async function setFontWeight(fontWeight: string): Promise<void> {
|
const toggleTabIndent = createToggler('enableTabIndent');
|
||||||
await updateConfig('fontWeight', fontWeight);
|
const toggleAlwaysOnTop = createToggler('alwaysOnTop');
|
||||||
}
|
const toggleTabType = createEnumToggler('tabType', CONFIG_LIMITS.tabType.values);
|
||||||
|
|
||||||
// 设置行高
|
// 字符串配置设置器
|
||||||
async function setLineHeight(lineHeight: number): Promise<void> {
|
const setFontFamily = (value: string) => updateConfig('fontFamily', value);
|
||||||
await updateConfig('lineHeight', lineHeight);
|
const setFontWeight = (value: string) => updateConfig('fontWeight', value);
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
// 状态
|
// 状态
|
||||||
config,
|
config: computed(() => state.config),
|
||||||
configLoaded,
|
configLoaded: computed(() => state.configLoaded),
|
||||||
isLoading,
|
isLoading: computed(() => state.isLoading),
|
||||||
|
|
||||||
// 计算属性
|
// 限制常量
|
||||||
MIN_FONT_SIZE,
|
...limits,
|
||||||
MAX_FONT_SIZE,
|
|
||||||
MIN_TAB_SIZE,
|
|
||||||
MAX_TAB_SIZE,
|
|
||||||
|
|
||||||
// 核心方法
|
// 核心方法
|
||||||
loadConfig,
|
initConfig,
|
||||||
resetConfig,
|
resetConfig,
|
||||||
setLanguage,
|
|
||||||
updateConfig,
|
updateConfig,
|
||||||
|
|
||||||
|
// 语言相关方法
|
||||||
|
setLanguage,
|
||||||
|
setLocale,
|
||||||
|
initializeLanguage,
|
||||||
|
|
||||||
// 字体大小操作
|
// 字体大小操作
|
||||||
increaseFontSize: () => adjustFontSize(1),
|
...fontSize,
|
||||||
decreaseFontSize: () => adjustFontSize(-1),
|
increaseFontSize: fontSize.increase,
|
||||||
resetFontSize: () => setFontSize(CONFIG_LIMITS.fontSize.default),
|
decreaseFontSize: fontSize.decrease,
|
||||||
setFontSize,
|
resetFontSize: fontSize.reset,
|
||||||
|
setFontSize: fontSize.set,
|
||||||
|
|
||||||
// Tab操作
|
// Tab操作
|
||||||
toggleTabIndent,
|
toggleTabIndent,
|
||||||
increaseTabSize: () => adjustTabSize(1),
|
...tabSize,
|
||||||
decreaseTabSize: () => adjustTabSize(-1),
|
increaseTabSize: tabSize.increase,
|
||||||
|
decreaseTabSize: tabSize.decrease,
|
||||||
|
setTabSize: tabSize.set,
|
||||||
toggleTabType,
|
toggleTabType,
|
||||||
setTabSize,
|
|
||||||
|
// 行高操作
|
||||||
|
setLineHeight: lineHeight.set,
|
||||||
|
|
||||||
// 窗口操作
|
// 窗口操作
|
||||||
toggleAlwaysOnTop,
|
toggleAlwaysOnTop,
|
||||||
|
|
||||||
// 字体预设相关方法
|
// 字体操作
|
||||||
setFontPreset,
|
|
||||||
getCurrentFontPreset,
|
|
||||||
setFontFamily,
|
setFontFamily,
|
||||||
setFontWeight,
|
setFontWeight,
|
||||||
setLineHeight
|
|
||||||
};
|
};
|
||||||
|
},{
|
||||||
|
persist: true,
|
||||||
});
|
});
|
@@ -1,5 +1,5 @@
|
|||||||
import { LanguageType } from '@/../bindings/voidraft/internal/models/models';
|
import { LanguageType } from '@/../bindings/voidraft/internal/models/models';
|
||||||
import { SupportedLocaleType } from '@/i18n';
|
import type { SupportedLocaleType } from '@/stores/configStore';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 配置工具类
|
* 配置工具类
|
||||||
|
@@ -19,7 +19,7 @@ import {
|
|||||||
updateFontConfig,
|
updateFontConfig,
|
||||||
} from './extensions';
|
} from './extensions';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { DocumentService } from '@/../bindings/voidraft/internal/services';
|
import { DocumentService } from '../../../bindings/voidraft/internal/services';
|
||||||
import Toolbar from '@/components/toolbar/Toolbar.vue';
|
import Toolbar from '@/components/toolbar/Toolbar.vue';
|
||||||
|
|
||||||
const editorStore = useEditorStore();
|
const editorStore = useEditorStore();
|
@@ -1,5 +1,5 @@
|
|||||||
import { EditorView, ViewPlugin, ViewUpdate } from '@codemirror/view';
|
import { EditorView, ViewPlugin, ViewUpdate } from '@codemirror/view';
|
||||||
import { DocumentService } from '@/../bindings/voidraft/internal/services';
|
import { DocumentService } from '../../../../bindings/voidraft/internal/services';
|
||||||
import { useDebounceFn } from '@vueuse/core';
|
import { useDebounceFn } from '@vueuse/core';
|
||||||
|
|
||||||
// 定义自动保存配置选项
|
// 定义自动保存配置选项
|
@@ -23,7 +23,7 @@ import {defaultKeymap, history, historyKeymap,} from '@codemirror/commands';
|
|||||||
import {highlightSelectionMatches, searchKeymap} from '@codemirror/search';
|
import {highlightSelectionMatches, searchKeymap} from '@codemirror/search';
|
||||||
import {autocompletion, closeBrackets, closeBracketsKeymap, completionKeymap} from '@codemirror/autocomplete';
|
import {autocompletion, closeBrackets, closeBracketsKeymap, completionKeymap} from '@codemirror/autocomplete';
|
||||||
import {lintKeymap} from '@codemirror/lint';
|
import {lintKeymap} from '@codemirror/lint';
|
||||||
import {customHighlightActiveLine, defaultDark} from '@/editor/theme/default-dark';
|
import {customHighlightActiveLine, defaultDark} from '@/views/editor/theme/default-dark';
|
||||||
|
|
||||||
// 基本编辑器设置,包含常用扩展
|
// 基本编辑器设置,包含常用扩展
|
||||||
export const createBasicSetup = (): Extension[] => {
|
export const createBasicSetup = (): Extension[] => {
|
111
frontend/src/views/editor/extensions/fontExtension.ts
Normal file
111
frontend/src/views/editor/extensions/fontExtension.ts
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
import { EditorView } from '@codemirror/view';
|
||||||
|
import { Extension, Compartment } from '@codemirror/state';
|
||||||
|
|
||||||
|
// 字体配置接口
|
||||||
|
export interface FontConfig {
|
||||||
|
fontFamily: string;
|
||||||
|
fontSize?: number;
|
||||||
|
lineHeight?: number;
|
||||||
|
fontWeight?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建字体配置compartment
|
||||||
|
export const fontCompartment = new Compartment();
|
||||||
|
|
||||||
|
// 默认字体配置
|
||||||
|
export const DEFAULT_FONT_CONFIG: FontConfig = {
|
||||||
|
fontFamily: '"HarmonyOS Sans SC", "HarmonyOS Sans", "Microsoft YaHei", "PingFang SC", "Helvetica Neue", Arial, sans-serif',
|
||||||
|
fontSize: 14,
|
||||||
|
lineHeight: 1.5,
|
||||||
|
fontWeight: 'normal'
|
||||||
|
};
|
||||||
|
|
||||||
|
// 从后端配置创建字体配置
|
||||||
|
export function createFontConfigFromBackend(backendConfig: {
|
||||||
|
fontFamily?: string;
|
||||||
|
fontSize?: number;
|
||||||
|
lineHeight?: number;
|
||||||
|
fontWeight?: string;
|
||||||
|
}): FontConfig {
|
||||||
|
return {
|
||||||
|
fontFamily: backendConfig.fontFamily || DEFAULT_FONT_CONFIG.fontFamily,
|
||||||
|
fontSize: backendConfig.fontSize || DEFAULT_FONT_CONFIG.fontSize,
|
||||||
|
lineHeight: backendConfig.lineHeight || DEFAULT_FONT_CONFIG.lineHeight,
|
||||||
|
fontWeight: backendConfig.fontWeight || DEFAULT_FONT_CONFIG.fontWeight,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建字体样式扩展
|
||||||
|
export function createFontExtension(config: Partial<FontConfig> = {}): Extension {
|
||||||
|
const fontConfig = { ...DEFAULT_FONT_CONFIG, ...config };
|
||||||
|
|
||||||
|
const styles: Record<string, any> = {
|
||||||
|
'&': {
|
||||||
|
fontFamily: fontConfig.fontFamily,
|
||||||
|
...(fontConfig.fontSize && { fontSize: `${fontConfig.fontSize}px` }),
|
||||||
|
...(fontConfig.lineHeight && { lineHeight: fontConfig.lineHeight.toString() }),
|
||||||
|
...(fontConfig.fontWeight && { fontWeight: fontConfig.fontWeight }),
|
||||||
|
},
|
||||||
|
'.cm-content': {
|
||||||
|
fontFamily: fontConfig.fontFamily,
|
||||||
|
...(fontConfig.fontSize && { fontSize: `${fontConfig.fontSize}px` }),
|
||||||
|
...(fontConfig.lineHeight && { lineHeight: fontConfig.lineHeight.toString() }),
|
||||||
|
...(fontConfig.fontWeight && { fontWeight: fontConfig.fontWeight }),
|
||||||
|
},
|
||||||
|
'.cm-editor': {
|
||||||
|
fontFamily: fontConfig.fontFamily,
|
||||||
|
},
|
||||||
|
'.cm-scroller': {
|
||||||
|
fontFamily: fontConfig.fontFamily,
|
||||||
|
},
|
||||||
|
'.cm-gutters': {
|
||||||
|
fontFamily: fontConfig.fontFamily,
|
||||||
|
...(fontConfig.fontSize && { fontSize: `${fontConfig.fontSize}px` }),
|
||||||
|
},
|
||||||
|
'.cm-lineNumbers': {
|
||||||
|
fontFamily: fontConfig.fontFamily,
|
||||||
|
...(fontConfig.fontSize && { fontSize: `${Math.max(10, fontConfig.fontSize - 1)}px` }),
|
||||||
|
},
|
||||||
|
'.cm-tooltip': {
|
||||||
|
fontFamily: fontConfig.fontFamily,
|
||||||
|
...(fontConfig.fontSize && { fontSize: `${Math.max(12, fontConfig.fontSize - 1)}px` }),
|
||||||
|
},
|
||||||
|
'.cm-completionLabel': {
|
||||||
|
fontFamily: fontConfig.fontFamily,
|
||||||
|
},
|
||||||
|
'.cm-completionDetail': {
|
||||||
|
fontFamily: fontConfig.fontFamily,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return EditorView.theme(styles);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建响应式字体大小扩展
|
||||||
|
export function createResponsiveFontExtension(baseFontSize: number = 14): Extension {
|
||||||
|
return fontCompartment.of(createFontExtension({
|
||||||
|
fontSize: baseFontSize,
|
||||||
|
lineHeight: 1.5
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从后端配置创建字体扩展
|
||||||
|
export function createFontExtensionFromBackend(backendConfig: {
|
||||||
|
fontFamily?: string;
|
||||||
|
fontSize?: number;
|
||||||
|
lineHeight?: number;
|
||||||
|
fontWeight?: string;
|
||||||
|
}): Extension {
|
||||||
|
const fontConfig = createFontConfigFromBackend(backendConfig);
|
||||||
|
return fontCompartment.of(createFontExtension(fontConfig));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 动态更新字体配置
|
||||||
|
export function updateFontConfig(view: EditorView, config: Partial<FontConfig>): void {
|
||||||
|
const newFontExtension = createFontExtension(config);
|
||||||
|
|
||||||
|
// 使用compartment重新配置字体扩展
|
||||||
|
view.dispatch({
|
||||||
|
effects: fontCompartment.reconfigure(newFontExtension)
|
||||||
|
});
|
||||||
|
}
|
@@ -2,7 +2,7 @@ import {Compartment, Extension} from '@codemirror/state';
|
|||||||
import {EditorView, keymap} from '@codemirror/view';
|
import {EditorView, keymap} from '@codemirror/view';
|
||||||
import {indentSelection} from '@codemirror/commands';
|
import {indentSelection} from '@codemirror/commands';
|
||||||
import {indentUnit} from '@codemirror/language';
|
import {indentUnit} from '@codemirror/language';
|
||||||
import {TabType} from '@/../bindings/voidraft/internal/models/models';
|
import {TabType} from '../../../../bindings/voidraft/internal/models/models';
|
||||||
|
|
||||||
// Tab设置相关的compartment
|
// Tab设置相关的compartment
|
||||||
export const tabSizeCompartment = new Compartment();
|
export const tabSizeCompartment = new Compartment();
|
@@ -3,6 +3,7 @@ import { useConfigStore } from '@/stores/configStore';
|
|||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { ref, watch } from 'vue';
|
import { ref, watch } from 'vue';
|
||||||
import { useRouter, useRoute } from 'vue-router';
|
import { useRouter, useRoute } from 'vue-router';
|
||||||
|
import MemoryMonitor from '@/components/monitor/MemoryMonitor.vue';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const configStore = useConfigStore();
|
const configStore = useConfigStore();
|
||||||
@@ -26,9 +27,9 @@ const handleNavClick = (item: typeof navItems[0]) => {
|
|||||||
router.push(item.route);
|
router.push(item.route);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 重置设置
|
// 返回编辑器
|
||||||
const resetSettings = async () => {
|
const goBackToEditor = () => {
|
||||||
await configStore.resetConfig();
|
router.push('/');
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -36,7 +37,14 @@ const resetSettings = async () => {
|
|||||||
<div class="settings-container">
|
<div class="settings-container">
|
||||||
<div class="settings-sidebar">
|
<div class="settings-sidebar">
|
||||||
<div class="settings-header">
|
<div class="settings-header">
|
||||||
<h1>{{ t('settings.title') }}</h1>
|
<div class="header-content">
|
||||||
|
<button class="back-button" @click="goBackToEditor" :title="t('settings.backToEditor')">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path d="M19 12H5M12 19l-7-7 7-7"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<h1>{{ t('settings.title') }}</h1>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="settings-nav">
|
<div class="settings-nav">
|
||||||
<div
|
<div
|
||||||
@@ -50,10 +58,11 @@ const resetSettings = async () => {
|
|||||||
<span class="nav-text">{{ t(`settings.${item.id}`) }}</span>
|
<span class="nav-text">{{ t(`settings.${item.id}`) }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="settings-actions">
|
<div class="settings-footer">
|
||||||
<button class="reset-button" @click="resetSettings">
|
<div class="memory-info-section">
|
||||||
{{ t('settings.reset') }}
|
<div class="section-title">{{ t('settings.systemInfo') }}</div>
|
||||||
</button>
|
<MemoryMonitor />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="settings-content">
|
<div class="settings-content">
|
||||||
@@ -88,11 +97,40 @@ const resetSettings = async () => {
|
|||||||
border-bottom: 1px solid #444444;
|
border-bottom: 1px solid #444444;
|
||||||
background-color: #2d2d2d;
|
background-color: #2d2d2d;
|
||||||
|
|
||||||
h1 {
|
.header-content {
|
||||||
font-size: 18px;
|
display: flex;
|
||||||
font-weight: 600;
|
align-items: center;
|
||||||
margin: 0;
|
gap: 12px;
|
||||||
color: #ffffff;
|
|
||||||
|
.back-button {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: #a0a0a0;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #e0e0e0;
|
||||||
|
background-color: #3a3a3a;
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 0;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,29 +182,23 @@ const resetSettings = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-actions {
|
.settings-footer {
|
||||||
padding: 16px;
|
padding: 12px 16px 16px 16px;
|
||||||
border-top: 1px solid #444444;
|
border-top: 1px solid #444444;
|
||||||
background-color: #2d2d2d;
|
background-color: #2d2d2d;
|
||||||
|
|
||||||
.reset-button {
|
.memory-info-section {
|
||||||
width: 100%;
|
display: flex;
|
||||||
padding: 8px 12px;
|
flex-direction: column;
|
||||||
background-color: #3a3a3a;
|
gap: 8px;
|
||||||
border: 1px solid #555555;
|
|
||||||
color: #e0e0e0;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
font-size: 14px;
|
|
||||||
|
|
||||||
&:hover {
|
.section-title {
|
||||||
background-color: #444444;
|
font-size: 10px;
|
||||||
border-color: #666666;
|
color: #777777;
|
||||||
}
|
font-weight: 500;
|
||||||
|
margin-bottom: 0;
|
||||||
&:active {
|
text-transform: uppercase;
|
||||||
transform: translateY(1px);
|
letter-spacing: 0.5px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -4,9 +4,7 @@ import { useI18n } from 'vue-i18n';
|
|||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import SettingSection from '../components/SettingSection.vue';
|
import SettingSection from '../components/SettingSection.vue';
|
||||||
import SettingItem from '../components/SettingItem.vue';
|
import SettingItem from '../components/SettingItem.vue';
|
||||||
import { LanguageType } from '@/../bindings/voidraft/internal/models/models';
|
import { LanguageType } from '../../../../bindings/voidraft/internal/models/models';
|
||||||
import { setLocale, SupportedLocaleType } from '@/i18n';
|
|
||||||
import { ConfigUtils } from '@/utils/configUtils';
|
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const configStore = useConfigStore();
|
const configStore = useConfigStore();
|
||||||
@@ -23,12 +21,8 @@ const updateLanguage = async (event: Event) => {
|
|||||||
const selectedLanguage = select.value as LanguageType;
|
const selectedLanguage = select.value as LanguageType;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 设置后端语言配置
|
// 使用 configStore 的语言设置方法
|
||||||
await configStore.setLanguage(selectedLanguage);
|
await configStore.setLanguage(selectedLanguage);
|
||||||
|
|
||||||
// 同步前端语言设置
|
|
||||||
const frontendLocale = ConfigUtils.backendLanguageToFrontend(selectedLanguage);
|
|
||||||
setLocale(frontendLocale);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to update language:', error);
|
console.error('Failed to update language:', error);
|
||||||
}
|
}
|
@@ -1,26 +1,37 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useConfigStore } from '@/stores/configStore';
|
import { useConfigStore } from '@/stores/configStore';
|
||||||
|
import { FONT_OPTIONS } from '@/stores/configStore';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { ref, computed } from 'vue';
|
import { ref, computed, onMounted } from 'vue';
|
||||||
import SettingSection from '../components/SettingSection.vue';
|
import SettingSection from '../components/SettingSection.vue';
|
||||||
import SettingItem from '../components/SettingItem.vue';
|
import SettingItem from '../components/SettingItem.vue';
|
||||||
import ToggleSwitch from '../components/ToggleSwitch.vue';
|
import ToggleSwitch from '../components/ToggleSwitch.vue';
|
||||||
import { TabType } from '@/../bindings/voidraft/internal/models/models';
|
import { TabType } from '../../../../bindings/voidraft/internal/models/models';
|
||||||
import { getFontPresetOptions, type FontPresetKey } from '@/editor/extensions/fontExtension';
|
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const configStore = useConfigStore();
|
const configStore = useConfigStore();
|
||||||
|
|
||||||
// 字体预设选项
|
// 确保配置已加载
|
||||||
const fontPresetOptions = getFontPresetOptions();
|
onMounted(async () => {
|
||||||
const currentFontPreset = computed(() => configStore.getCurrentFontPreset());
|
if (!configStore.configLoaded) {
|
||||||
|
await configStore.loadConfig();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// 字体预设选择
|
// 字体选择选项
|
||||||
const handleFontPresetChange = (event: Event) => {
|
const fontFamilyOptions = FONT_OPTIONS;
|
||||||
|
const currentFontFamily = computed(() => configStore.config.fontFamily);
|
||||||
|
|
||||||
|
// 字体选择
|
||||||
|
const handleFontFamilyChange = async (event: Event) => {
|
||||||
const target = event.target as HTMLSelectElement;
|
const target = event.target as HTMLSelectElement;
|
||||||
const presetKey = target.value;
|
const fontFamily = target.value;
|
||||||
if (presetKey && presetKey !== 'custom') {
|
if (fontFamily) {
|
||||||
configStore.setFontPreset(presetKey as FontPresetKey);
|
try {
|
||||||
|
await configStore.setFontFamily(fontFamily);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to set font family:', error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -38,29 +49,49 @@ const fontWeightOptions = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
// 字体粗细选择
|
// 字体粗细选择
|
||||||
const handleFontWeightChange = (event: Event) => {
|
const handleFontWeightChange = async (event: Event) => {
|
||||||
const target = event.target as HTMLSelectElement;
|
const target = event.target as HTMLSelectElement;
|
||||||
configStore.setFontWeight(target.value);
|
try {
|
||||||
|
await configStore.setFontWeight(target.value);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to set font weight:', error);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 行高控制
|
// 行高控制
|
||||||
const increaseLineHeight = () => {
|
const increaseLineHeight = async () => {
|
||||||
const newLineHeight = Math.min(3.0, configStore.config.lineHeight + 0.1);
|
const newLineHeight = Math.min(3.0, configStore.config.lineHeight + 0.1);
|
||||||
configStore.setLineHeight(Math.round(newLineHeight * 10) / 10);
|
try {
|
||||||
|
await configStore.setLineHeight(Math.round(newLineHeight * 10) / 10);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to increase line height:', error);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const decreaseLineHeight = () => {
|
const decreaseLineHeight = async () => {
|
||||||
const newLineHeight = Math.max(1.0, configStore.config.lineHeight - 0.1);
|
const newLineHeight = Math.max(1.0, configStore.config.lineHeight - 0.1);
|
||||||
configStore.setLineHeight(Math.round(newLineHeight * 10) / 10);
|
try {
|
||||||
|
await configStore.setLineHeight(Math.round(newLineHeight * 10) / 10);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to decrease line height:', error);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 字体大小控制
|
// 字体大小控制
|
||||||
const increaseFontSize = () => {
|
const increaseFontSize = async () => {
|
||||||
configStore.increaseFontSize();
|
try {
|
||||||
|
await configStore.increaseFontSize();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to increase font size:', error);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const decreaseFontSize = () => {
|
const decreaseFontSize = async () => {
|
||||||
configStore.decreaseFontSize();
|
try {
|
||||||
|
await configStore.decreaseFontSize();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to decrease font size:', error);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Tab类型切换
|
// Tab类型切换
|
||||||
@@ -71,12 +102,37 @@ const tabTypeText = computed(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Tab大小增减
|
// Tab大小增减
|
||||||
const increaseTabSize = () => {
|
const increaseTabSize = async () => {
|
||||||
configStore.increaseTabSize();
|
try {
|
||||||
|
await configStore.increaseTabSize();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to increase tab size:', error);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const decreaseTabSize = () => {
|
const decreaseTabSize = async () => {
|
||||||
configStore.decreaseTabSize();
|
try {
|
||||||
|
await configStore.decreaseTabSize();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to decrease tab size:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Tab相关操作
|
||||||
|
const handleToggleTabIndent = async () => {
|
||||||
|
try {
|
||||||
|
await configStore.toggleTabIndent();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to toggle tab indent:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleToggleTabType = async () => {
|
||||||
|
try {
|
||||||
|
await configStore.toggleTabType();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to toggle tab type:', error);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -84,17 +140,16 @@ const decreaseTabSize = () => {
|
|||||||
<div class="settings-page">
|
<div class="settings-page">
|
||||||
<SettingSection :title="t('settings.fontSettings')">
|
<SettingSection :title="t('settings.fontSettings')">
|
||||||
<SettingItem
|
<SettingItem
|
||||||
:title="t('settings.fontPreset')"
|
:title="t('settings.fontFamily')"
|
||||||
:description="t('settings.fontPresetDescription')"
|
:description="t('settings.fontFamilyDescription')"
|
||||||
>
|
>
|
||||||
<select
|
<select
|
||||||
class="font-preset-select"
|
class="font-family-select"
|
||||||
:value="currentFontPreset || 'custom'"
|
:value="currentFontFamily"
|
||||||
@change="handleFontPresetChange"
|
@change="handleFontFamilyChange"
|
||||||
>
|
>
|
||||||
<option value="custom">自定义</option>
|
|
||||||
<option
|
<option
|
||||||
v-for="option in fontPresetOptions"
|
v-for="option in fontFamilyOptions"
|
||||||
:key="option.value"
|
:key="option.value"
|
||||||
:value="option.value"
|
:value="option.value"
|
||||||
>
|
>
|
||||||
@@ -163,14 +218,14 @@ const decreaseTabSize = () => {
|
|||||||
<SettingSection :title="t('settings.tabSettings')">
|
<SettingSection :title="t('settings.tabSettings')">
|
||||||
<SettingItem :title="t('settings.tabSize')">
|
<SettingItem :title="t('settings.tabSize')">
|
||||||
<div class="number-control">
|
<div class="number-control">
|
||||||
<button @click="decreaseTabSize" class="control-button" :disabled="configStore.config.tabSize <= configStore.MIN_TAB_SIZE">-</button>
|
<button @click="decreaseTabSize" class="control-button" :disabled="configStore.config.tabSize <= configStore.tabSize.min">-</button>
|
||||||
<span>{{ configStore.config.tabSize }}</span>
|
<span>{{ configStore.config.tabSize }}</span>
|
||||||
<button @click="increaseTabSize" class="control-button" :disabled="configStore.config.tabSize >= configStore.MAX_TAB_SIZE">+</button>
|
<button @click="increaseTabSize" class="control-button" :disabled="configStore.config.tabSize >= configStore.tabSize.max">+</button>
|
||||||
</div>
|
</div>
|
||||||
</SettingItem>
|
</SettingItem>
|
||||||
|
|
||||||
<SettingItem :title="t('settings.tabType')">
|
<SettingItem :title="t('settings.tabType')">
|
||||||
<button class="tab-type-toggle" @click="configStore.toggleTabType">
|
<button class="tab-type-toggle" @click="handleToggleTabType">
|
||||||
{{ tabTypeText }}
|
{{ tabTypeText }}
|
||||||
</button>
|
</button>
|
||||||
</SettingItem>
|
</SettingItem>
|
||||||
@@ -178,7 +233,7 @@ const decreaseTabSize = () => {
|
|||||||
<SettingItem :title="t('settings.enableTabIndent')">
|
<SettingItem :title="t('settings.enableTabIndent')">
|
||||||
<ToggleSwitch
|
<ToggleSwitch
|
||||||
v-model="configStore.config.enableTabIndent"
|
v-model="configStore.config.enableTabIndent"
|
||||||
@update:modelValue="configStore.toggleTabIndent"
|
@update:modelValue="handleToggleTabIndent"
|
||||||
/>
|
/>
|
||||||
</SettingItem>
|
</SettingItem>
|
||||||
</SettingSection>
|
</SettingSection>
|
||||||
@@ -325,7 +380,7 @@ const decreaseTabSize = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.font-preset-select,
|
.font-family-select,
|
||||||
.font-weight-select {
|
.font-weight-select {
|
||||||
min-width: 180px;
|
min-width: 180px;
|
||||||
padding: 8px 12px;
|
padding: 8px 12px;
|
@@ -39,6 +39,13 @@ const updateSelectedKey = (event: Event) => {
|
|||||||
const toggleModifier = (key: keyof typeof selectedModifiers.value) => {
|
const toggleModifier = (key: keyof typeof selectedModifiers.value) => {
|
||||||
selectedModifiers.value[key] = !selectedModifiers.value[key];
|
selectedModifiers.value[key] = !selectedModifiers.value[key];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 重置设置
|
||||||
|
const resetSettings = async () => {
|
||||||
|
if (confirm(t('settings.confirmReset'))) {
|
||||||
|
await configStore.resetConfig();
|
||||||
|
}
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -96,6 +103,20 @@ const toggleModifier = (key: keyof typeof selectedModifiers.value) => {
|
|||||||
<button class="select-button">{{ t('settings.selectDirectory') }}</button>
|
<button class="select-button">{{ t('settings.selectDirectory') }}</button>
|
||||||
</div>
|
</div>
|
||||||
</SettingSection>
|
</SettingSection>
|
||||||
|
|
||||||
|
<SettingSection :title="t('settings.dangerZone')">
|
||||||
|
<div class="danger-zone">
|
||||||
|
<div class="reset-section">
|
||||||
|
<div class="reset-info">
|
||||||
|
<h4>{{ t('settings.resetAllSettings') }}</h4>
|
||||||
|
<p>{{ t('settings.resetDescription') }}</p>
|
||||||
|
</div>
|
||||||
|
<button class="reset-button" @click="resetSettings">
|
||||||
|
{{ t('settings.reset') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</SettingSection>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -210,4 +231,59 @@ const toggleModifier = (key: keyof typeof selectedModifiers.value) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.danger-zone {
|
||||||
|
padding: 20px;
|
||||||
|
background-color: rgba(220, 53, 69, 0.05);
|
||||||
|
border: 1px solid rgba(220, 53, 69, 0.2);
|
||||||
|
border-radius: 6px;
|
||||||
|
margin-top: 10px;
|
||||||
|
|
||||||
|
.reset-section {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 20px;
|
||||||
|
|
||||||
|
.reset-info {
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
margin: 0 0 6px 0;
|
||||||
|
color: #ff6b6b;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
color: #b0b0b0;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.reset-button {
|
||||||
|
padding: 8px 16px;
|
||||||
|
background-color: #dc3545;
|
||||||
|
border: 1px solid #c82333;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: #ffffff;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #c82333;
|
||||||
|
border-color: #bd2130;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: translateY(1px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
6
go.mod
6
go.mod
@@ -13,12 +13,12 @@ require (
|
|||||||
require (
|
require (
|
||||||
dario.cat/mergo v1.0.2 // indirect
|
dario.cat/mergo v1.0.2 // indirect
|
||||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||||
github.com/ProtonMail/go-crypto v1.2.0 // indirect
|
github.com/ProtonMail/go-crypto v1.3.0 // indirect
|
||||||
github.com/adrg/xdg v0.5.3 // indirect
|
github.com/adrg/xdg v0.5.3 // indirect
|
||||||
github.com/bep/debounce v1.2.1 // indirect
|
github.com/bep/debounce v1.2.1 // indirect
|
||||||
github.com/cloudflare/circl v1.6.1 // indirect
|
github.com/cloudflare/circl v1.6.1 // indirect
|
||||||
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
|
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
|
||||||
github.com/ebitengine/purego v0.8.3 // indirect
|
github.com/ebitengine/purego v0.8.4 // indirect
|
||||||
github.com/emirpasic/gods v1.18.1 // indirect
|
github.com/emirpasic/gods v1.18.1 // indirect
|
||||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||||
github.com/go-git/go-billy/v5 v5.6.2 // indirect
|
github.com/go-git/go-billy/v5 v5.6.2 // indirect
|
||||||
@@ -33,7 +33,7 @@ require (
|
|||||||
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
||||||
github.com/leaanthony/go-ansi-parser v1.6.1 // indirect
|
github.com/leaanthony/go-ansi-parser v1.6.1 // indirect
|
||||||
github.com/leaanthony/u v1.1.1 // indirect
|
github.com/leaanthony/u v1.1.1 // indirect
|
||||||
github.com/lmittmann/tint v1.0.7 // indirect
|
github.com/lmittmann/tint v1.1.1 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||||
|
12
go.sum
12
go.sum
@@ -3,8 +3,8 @@ dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
|
|||||||
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
||||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||||
github.com/ProtonMail/go-crypto v1.2.0 h1:+PhXXn4SPGd+qk76TlEePBfOfivE0zkWFenhGhFLzWs=
|
github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw=
|
||||||
github.com/ProtonMail/go-crypto v1.2.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE=
|
github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE=
|
||||||
github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78=
|
github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78=
|
||||||
github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ=
|
github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ=
|
||||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
||||||
@@ -20,8 +20,8 @@ github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGL
|
|||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/ebitengine/purego v0.8.3 h1:K+0AjQp63JEZTEMZiwsI9g0+hAMNohwUOtY0RPGexmc=
|
github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw=
|
||||||
github.com/ebitengine/purego v0.8.3/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||||
github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
|
github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
|
||||||
github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
|
github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
|
||||||
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||||
@@ -69,8 +69,8 @@ github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed
|
|||||||
github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU=
|
github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU=
|
||||||
github.com/leaanthony/u v1.1.1 h1:TUFjwDGlNX+WuwVEzDqQwC2lOv0P4uhTQw7CMFdiK7M=
|
github.com/leaanthony/u v1.1.1 h1:TUFjwDGlNX+WuwVEzDqQwC2lOv0P4uhTQw7CMFdiK7M=
|
||||||
github.com/leaanthony/u v1.1.1/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI=
|
github.com/leaanthony/u v1.1.1/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI=
|
||||||
github.com/lmittmann/tint v1.0.7 h1:D/0OqWZ0YOGZ6AyC+5Y2kD8PBEzBk6rFHVSfOqCkF9Y=
|
github.com/lmittmann/tint v1.1.1 h1:xmmGuinUsCSxWdwH1OqMUQ4tzQsq3BdjJLAAmVKJ9Dw=
|
||||||
github.com/lmittmann/tint v1.0.7/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE=
|
github.com/lmittmann/tint v1.1.1/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE=
|
||||||
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
|
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
|
||||||
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
|
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
|
||||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||||
|
@@ -8,7 +8,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// RegisterTrayEvents 注册与系统托盘相关的所有事件
|
// RegisterTrayEvents 注册与系统托盘相关的所有事件
|
||||||
func RegisterTrayEvents(app *application.App, systray *application.SystemTray, mainWindow *application.WebviewWindow, settingsWindow *application.WebviewWindow) {
|
func RegisterTrayEvents(app *application.App, systray *application.SystemTray, mainWindow *application.WebviewWindow) {
|
||||||
// 不附加窗口到系统托盘,避免失去焦点自动缩小
|
// 不附加窗口到系统托盘,避免失去焦点自动缩小
|
||||||
// systray.AttachWindow(mainWindow)
|
// systray.AttachWindow(mainWindow)
|
||||||
|
|
||||||
@@ -28,30 +28,14 @@ func RegisterTrayEvents(app *application.App, systray *application.SystemTray, m
|
|||||||
mainWindow.Hide()
|
mainWindow.Hide()
|
||||||
})
|
})
|
||||||
|
|
||||||
// 设置窗口关闭事件处理
|
|
||||||
settingsWindow.RegisterHook(wailsevents.Common.WindowClosing, func(event *application.WindowEvent) {
|
|
||||||
// 取消默认关闭行为
|
|
||||||
event.Cancel()
|
|
||||||
// 隐藏窗口
|
|
||||||
settingsWindow.Hide()
|
|
||||||
})
|
|
||||||
|
|
||||||
// 注册事件监听器,用于处理前端发送的显示设置窗口事件
|
|
||||||
app.OnEvent("show_settings_window", func(event *application.CustomEvent) {
|
|
||||||
settingsWindow.Show()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterTrayMenuEvents 注册系统托盘菜单事件
|
// RegisterTrayMenuEvents 注册系统托盘菜单事件
|
||||||
func RegisterTrayMenuEvents(app *application.App, menu *application.Menu, mainWindow *application.WebviewWindow, settingsWindow *application.WebviewWindow) {
|
func RegisterTrayMenuEvents(app *application.App, menu *application.Menu, mainWindow *application.WebviewWindow) {
|
||||||
menu.Add("主窗口").OnClick(func(data *application.Context) {
|
menu.Add("主窗口").OnClick(func(data *application.Context) {
|
||||||
mainWindow.Show()
|
mainWindow.Show()
|
||||||
})
|
})
|
||||||
|
|
||||||
menu.Add("设置").OnClick(func(data *application.Context) {
|
|
||||||
settingsWindow.Show()
|
|
||||||
})
|
|
||||||
|
|
||||||
menu.AddSeparator()
|
menu.AddSeparator()
|
||||||
|
|
||||||
menu.Add("退出").OnClick(func(data *application.Context) {
|
menu.Add("退出").OnClick(func(data *application.Context) {
|
||||||
|
@@ -9,6 +9,7 @@ import (
|
|||||||
type ServiceManager struct {
|
type ServiceManager struct {
|
||||||
configService *ConfigService
|
configService *ConfigService
|
||||||
documentService *DocumentService
|
documentService *DocumentService
|
||||||
|
systemService *SystemService
|
||||||
logger *log.LoggerService
|
logger *log.LoggerService
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,6 +24,9 @@ func NewServiceManager() *ServiceManager {
|
|||||||
// 初始化文档服务
|
// 初始化文档服务
|
||||||
documentService := NewDocumentService(configService, logger)
|
documentService := NewDocumentService(configService, logger)
|
||||||
|
|
||||||
|
// 初始化系统服务
|
||||||
|
systemService := NewSystemService(logger)
|
||||||
|
|
||||||
// 初始化文档服务
|
// 初始化文档服务
|
||||||
err := documentService.Initialize()
|
err := documentService.Initialize()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -33,6 +37,7 @@ func NewServiceManager() *ServiceManager {
|
|||||||
return &ServiceManager{
|
return &ServiceManager{
|
||||||
configService: configService,
|
configService: configService,
|
||||||
documentService: documentService,
|
documentService: documentService,
|
||||||
|
systemService: systemService,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -42,5 +47,6 @@ func (sm *ServiceManager) GetServices() []application.Service {
|
|||||||
return []application.Service{
|
return []application.Service{
|
||||||
application.NewService(sm.configService),
|
application.NewService(sm.configService),
|
||||||
application.NewService(sm.documentService),
|
application.NewService(sm.documentService),
|
||||||
|
application.NewService(sm.systemService),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
83
internal/services/system_service.go
Normal file
83
internal/services/system_service.go
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/wailsapp/wails/v3/pkg/services/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SystemService 系统监控服务
|
||||||
|
type SystemService struct {
|
||||||
|
logger *log.LoggerService
|
||||||
|
}
|
||||||
|
|
||||||
|
// MemoryStats 内存统计信息
|
||||||
|
type MemoryStats struct {
|
||||||
|
// 当前堆内存使用量(字节)
|
||||||
|
HeapInUse uint64 `json:"heapInUse"`
|
||||||
|
// 堆内存分配总量(字节)
|
||||||
|
HeapAlloc uint64 `json:"heapAlloc"`
|
||||||
|
// 系统内存使用量(字节)
|
||||||
|
Sys uint64 `json:"sys"`
|
||||||
|
// GC 次数
|
||||||
|
NumGC uint32 `json:"numGC"`
|
||||||
|
// GC 暂停时间(纳秒)
|
||||||
|
PauseTotalNs uint64 `json:"pauseTotalNs"`
|
||||||
|
// Goroutine 数量
|
||||||
|
NumGoroutine int `json:"numGoroutine"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSystemService 创建新的系统服务实例
|
||||||
|
func NewSystemService(logger *log.LoggerService) *SystemService {
|
||||||
|
return &SystemService{
|
||||||
|
logger: logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMemoryStats 获取当前内存统计信息
|
||||||
|
func (ss *SystemService) GetMemoryStats() MemoryStats {
|
||||||
|
var m runtime.MemStats
|
||||||
|
runtime.ReadMemStats(&m)
|
||||||
|
|
||||||
|
return MemoryStats{
|
||||||
|
HeapInUse: m.HeapInuse,
|
||||||
|
HeapAlloc: m.HeapAlloc,
|
||||||
|
Sys: m.Sys,
|
||||||
|
NumGC: m.NumGC,
|
||||||
|
PauseTotalNs: m.PauseTotalNs,
|
||||||
|
NumGoroutine: runtime.NumGoroutine(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FormatBytes 格式化字节数为人类可读的格式
|
||||||
|
func (ss *SystemService) FormatBytes(bytes uint64) string {
|
||||||
|
const unit = 1024
|
||||||
|
if bytes < unit {
|
||||||
|
return "< 1 KB"
|
||||||
|
}
|
||||||
|
div, exp := uint64(unit), 0
|
||||||
|
for n := bytes / unit; n >= unit; n /= unit {
|
||||||
|
div *= unit
|
||||||
|
exp++
|
||||||
|
}
|
||||||
|
|
||||||
|
switch exp {
|
||||||
|
case 0:
|
||||||
|
return "< 1 KB"
|
||||||
|
case 1:
|
||||||
|
return fmt.Sprintf("%.1f KB", float64(bytes)/float64(div))
|
||||||
|
case 2:
|
||||||
|
return fmt.Sprintf("%.1f MB", float64(bytes)/float64(div))
|
||||||
|
case 3:
|
||||||
|
return fmt.Sprintf("%.1f GB", float64(bytes)/float64(div))
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("%.1f TB", float64(bytes)/float64(div))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TriggerGC 手动触发垃圾回收
|
||||||
|
func (ss *SystemService) TriggerGC() {
|
||||||
|
runtime.GC()
|
||||||
|
ss.logger.Info("Manual GC triggered")
|
||||||
|
}
|
@@ -7,7 +7,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// SetupSystemTray 设置系统托盘及其功能
|
// SetupSystemTray 设置系统托盘及其功能
|
||||||
func SetupSystemTray(app *application.App, mainWindow *application.WebviewWindow, settingsWindow *application.WebviewWindow, assets embed.FS) {
|
func SetupSystemTray(app *application.App, mainWindow *application.WebviewWindow, assets embed.FS) {
|
||||||
// 创建系统托盘
|
// 创建系统托盘
|
||||||
systray := app.NewSystemTray()
|
systray := app.NewSystemTray()
|
||||||
|
|
||||||
@@ -22,10 +22,10 @@ func SetupSystemTray(app *application.App, mainWindow *application.WebviewWindow
|
|||||||
menu := app.NewMenu()
|
menu := app.NewMenu()
|
||||||
|
|
||||||
// 注册托盘菜单事件
|
// 注册托盘菜单事件
|
||||||
events.RegisterTrayMenuEvents(app, menu, mainWindow, settingsWindow)
|
events.RegisterTrayMenuEvents(app, menu, mainWindow)
|
||||||
|
|
||||||
systray.SetMenu(menu)
|
systray.SetMenu(menu)
|
||||||
|
|
||||||
// 注册托盘相关事件
|
// 注册托盘相关事件
|
||||||
events.RegisterTrayEvents(app, systray, mainWindow, settingsWindow)
|
events.RegisterTrayEvents(app, systray, mainWindow)
|
||||||
}
|
}
|
||||||
|
21
main.go
21
main.go
@@ -88,28 +88,9 @@ func main() {
|
|||||||
URL: "/#/",
|
URL: "/#/",
|
||||||
})
|
})
|
||||||
mainWindow.Center()
|
mainWindow.Center()
|
||||||
settingsWindow := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
|
|
||||||
Title: "voidraft设置",
|
|
||||||
Width: 700,
|
|
||||||
Height: 800,
|
|
||||||
Hidden: true,
|
|
||||||
AlwaysOnTop: true,
|
|
||||||
DisableResize: true,
|
|
||||||
MinimiseButtonState: application.ButtonHidden,
|
|
||||||
MaximiseButtonState: application.ButtonHidden,
|
|
||||||
Mac: application.MacWindow{
|
|
||||||
Backdrop: application.MacBackdropTranslucent,
|
|
||||||
TitleBar: application.MacTitleBarHiddenInset,
|
|
||||||
InvisibleTitleBarHeight: 50,
|
|
||||||
},
|
|
||||||
Windows: application.WindowsWindow{},
|
|
||||||
BackgroundColour: application.NewRGB(27, 38, 54),
|
|
||||||
URL: "/#/settings",
|
|
||||||
})
|
|
||||||
settingsWindow.Center()
|
|
||||||
|
|
||||||
// 设置系统托盘
|
// 设置系统托盘
|
||||||
systray.SetupSystemTray(app, mainWindow, settingsWindow, assets)
|
systray.SetupSystemTray(app, mainWindow, assets)
|
||||||
|
|
||||||
// Create a goroutine that emits an event containing the current time every second.
|
// Create a goroutine that emits an event containing the current time every second.
|
||||||
// The frontend can listen to this event and update the UI accordingly.
|
// The frontend can listen to this event and update the UI accordingly.
|
||||||
|
Reference in New Issue
Block a user