♻️ Refactor code
This commit is contained in:
1 parent
44f7baad10
commit
a516b8973e
53 files changed
+1513
-1094
No files matched your search
@@ -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;
|
||||||
Vendored
+1
@@ -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']
|
||||||
|
|||||||
Generated
+292
-425
File diff suppressed because it is too large.
Load diff
+13
-12
@@ -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>
|
||||||
|
|||||||
@@ -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,11 +4,12 @@ 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);
|
||||||
app.use(router);
|
app.use(router);
|
||||||
app.mount('#app');
|
app.mount('#app');
|
||||||
@@ -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[] = [
|
||||||
{
|
{
|
||||||
|
|||||||
+233
-188
@@ -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 => {
|
||||||
async function loadConfig(): Promise<void> {
|
console.error(errorMsg, error);
|
||||||
if (isLoading.value) return;
|
logStore.error(t(errorMsg));
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
isLoading.value = true;
|
if (result !== null && successMsg) {
|
||||||
try {
|
logStore.info(t(successMsg));
|
||||||
const appConfig = await ConfigService.GetConfig();
|
}
|
||||||
|
|
||||||
if (appConfig?.editor) {
|
return result;
|
||||||
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'
|
||||||
|
);
|
||||||
|
|
||||||
|
// 加载配置
|
||||||
|
const initConfig = withErrorHandling(
|
||||||
|
async (): Promise<void> => {
|
||||||
|
if (state.isLoading) return;
|
||||||
|
|
||||||
// 更新本地状态
|
state.isLoading = true;
|
||||||
config.value[key] = value;
|
const appConfig = await ConfigService.GetConfig();
|
||||||
|
|
||||||
logStore.info(t('config.saveSuccess'));
|
if (appConfig?.editor) {
|
||||||
} catch (error) {
|
Object.assign(state.config, appConfig.editor);
|
||||||
console.error(`Failed to update config ${String(key)}:`, error);
|
}
|
||||||
logStore.error(t('config.saveFailed'));
|
|
||||||
throw error;
|
state.configLoaded = true;
|
||||||
}
|
state.isLoading = false;
|
||||||
}
|
},
|
||||||
|
'config.loadFailed',
|
||||||
|
'config.loadSuccess'
|
||||||
|
);
|
||||||
|
|
||||||
// 字体大小操作
|
// 数值调整工厂函数
|
||||||
async function adjustFontSize(delta: number): Promise<void> {
|
const createNumberAdjuster = (key: NumberConfigKey) => {
|
||||||
const newSize = ConfigUtils.clamp(config.value.fontSize + delta, CONFIG_LIMITS.fontSize.min, CONFIG_LIMITS.fontSize.max);
|
const limit = CONFIG_LIMITS[key];
|
||||||
await updateConfig('fontSize', newSize);
|
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)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
// Tab大小操作
|
// 布尔值切换工厂函数
|
||||||
async function adjustTabSize(delta: number): Promise<void> {
|
const createToggler = (key: BooleanConfigKey) =>
|
||||||
const newSize = ConfigUtils.clamp(config.value.tabSize + delta, CONFIG_LIMITS.tabSize.min, CONFIG_LIMITS.tabSize.max);
|
() => updateConfig(key, !state.config[key]);
|
||||||
await updateConfig('tabSize', newSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 切换操作
|
// 枚举值切换工厂函数
|
||||||
async function toggleTabIndent(): Promise<void> {
|
const createEnumToggler = <T extends TabType>(key: 'tabType', values: readonly T[]) =>
|
||||||
await updateConfig('enableTabIndent', !config.value.enableTabIndent);
|
() => {
|
||||||
}
|
const currentIndex = values.indexOf(state.config[key] as T);
|
||||||
|
const nextIndex = (currentIndex + 1) % values.length;
|
||||||
async function toggleTabType(): Promise<void> {
|
return updateConfig(key, values[nextIndex]);
|
||||||
const newTabType = config.value.tabType === TabType.TabTypeSpaces ? TabType.TabTypeTab : TabType.TabTypeSpaces;
|
};
|
||||||
await updateConfig('tabType', newTabType);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function toggleAlwaysOnTop(): Promise<void> {
|
|
||||||
await updateConfig('alwaysOnTop', !config.value.alwaysOnTop);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 语言设置
|
|
||||||
async function setLanguage(language: LanguageType): Promise<void> {
|
|
||||||
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 {
|
|
||||||
isLoading.value = true;
|
|
||||||
await ConfigService.ResetConfig();
|
|
||||||
await loadConfig();
|
|
||||||
logStore.info(t('config.resetSuccess'));
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to reset configuration:', error);
|
|
||||||
logStore.error(t('config.resetFailed'));
|
|
||||||
} finally {
|
|
||||||
isLoading.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置字体大小
|
|
||||||
async function setFontSize(size: number): Promise<void> {
|
|
||||||
await updateConfig('fontSize', size);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置Tab大小
|
|
||||||
async function setTabSize(size: number): Promise<void> {
|
|
||||||
await updateConfig('tabSize', size);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 字体预设相关方法
|
|
||||||
async function setFontPreset(presetKey: FontPresetKey): Promise<void> {
|
|
||||||
const preset = FONT_PRESETS[presetKey];
|
|
||||||
if (!preset) {
|
|
||||||
throw new Error(`Unknown font preset: ${presetKey}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 批量更新字体相关配置
|
|
||||||
await updateConfig('fontFamily', preset.fontFamily);
|
|
||||||
await updateConfig('fontWeight', preset.fontWeight);
|
|
||||||
await updateConfig('lineHeight', preset.lineHeight);
|
|
||||||
// 可选择是否同时更新字体大小
|
|
||||||
// await updateConfig('fontSize', preset.fontSize);
|
|
||||||
|
|
||||||
logStore.info(`字体预设已切换为: ${preset.name}`);
|
state.isLoading = true;
|
||||||
} catch (error) {
|
await ConfigService.ResetConfig();
|
||||||
console.error('Failed to set font preset:', error);
|
await initConfig();
|
||||||
logStore.error('字体预设设置失败');
|
state.isLoading = false;
|
||||||
throw error;
|
},
|
||||||
}
|
'config.resetFailed',
|
||||||
}
|
'config.resetSuccess'
|
||||||
|
);
|
||||||
|
|
||||||
// 获取当前字体预设(如果匹配的话)
|
// 语言设置方法
|
||||||
function getCurrentFontPreset(): FontPresetKey | null {
|
const setLanguage = withErrorHandling(
|
||||||
const currentFamily = config.value.fontFamily;
|
async (language: LanguageType): Promise<void> => {
|
||||||
for (const [key, preset] of Object.entries(FONT_PRESETS)) {
|
await ConfigService.Set(CONFIG_KEY_MAP.language, language);
|
||||||
if (preset.fontFamily === currentFamily) {
|
state.config.language = language;
|
||||||
return key as FontPresetKey;
|
|
||||||
|
// 同步更新前端语言
|
||||||
|
const frontendLocale = ConfigUtils.backendLanguageToFrontend(language);
|
||||||
|
locale.value = frontendLocale as any;
|
||||||
|
},
|
||||||
|
'config.languageChangeFailed',
|
||||||
|
'config.languageChanged'
|
||||||
|
);
|
||||||
|
|
||||||
|
// 通过前端语言代码设置语言
|
||||||
|
const setLocale = async (localeCode: SupportedLocaleType): Promise<void> => {
|
||||||
|
const backendLanguage = ConfigUtils.frontendLanguageToBackend(localeCode);
|
||||||
|
await setLanguage(backendLanguage);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 初始化语言设置
|
||||||
|
const initializeLanguage = async (): Promise<void> => {
|
||||||
|
try {
|
||||||
|
// 如果配置未加载,先加载配置
|
||||||
|
if (!state.configLoaded) {
|
||||||
|
await initConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 同步前端语言设置
|
||||||
|
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);
|
||||||
|
|
||||||
|
// 字符串配置设置器
|
||||||
|
const setFontFamily = (value: string) => updateConfig('fontFamily', value);
|
||||||
|
const setFontWeight = (value: string) => updateConfig('fontWeight', value);
|
||||||
|
|
||||||
// 设置行高
|
|
||||||
async function setLineHeight(lineHeight: number): Promise<void> {
|
|
||||||
await updateConfig('lineHeight', lineHeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
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
-1
@@ -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';
|
||||||
|
|
||||||
// 定义自动保存配置选项
|
// 定义自动保存配置选项
|
||||||
+1
-1
@@ -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[] => {
|
||||||
@@ -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)
|
||||||
|
});
|
||||||
|
}
|
||||||
File renamed without changes.
File renamed without changes.
+1
-1
@@ -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();
|
||||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
+2
-8
@@ -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);
|
||||||
}
|
}
|
||||||
+92
-37
@@ -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;
|
||||||
+76
@@ -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>
|
||||||
File renamed without changes.
File renamed without changes.
@@ -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
|
||||||
|
|||||||
@@ -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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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)
|
||||||
}
|
}
|
||||||
@@ -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